001 /* ZipEntry.java --
002 Copyright (C) 2001, 2002, 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package java.util.zip;
040
041 import java.util.Calendar;
042
043 /**
044 * This class represents a member of a zip archive. ZipFile and
045 * ZipInputStream will give you instances of this class as information
046 * about the members in an archive. On the other hand ZipOutputStream
047 * needs an instance of this class to create a new member.
048 *
049 * @author Jochen Hoenicke
050 */
051 public class ZipEntry implements ZipConstants, Cloneable
052 {
053 private static final byte KNOWN_SIZE = 1;
054 private static final byte KNOWN_CSIZE = 2;
055 private static final byte KNOWN_CRC = 4;
056 private static final byte KNOWN_TIME = 8;
057 private static final byte KNOWN_DOSTIME = 16;
058 private static final byte KNOWN_EXTRA = 32;
059
060 /** Immutable name of the entry */
061 private final String name;
062 /** Uncompressed size */
063 private int size;
064 /** Compressed size */
065 private long compressedSize = -1;
066 /** CRC of uncompressed data */
067 private int crc;
068 /** Comment or null if none */
069 private String comment = null;
070 /** The compression method. Either DEFLATED or STORED, by default -1. */
071 private byte method = -1;
072 /** Flags specifying what we know about this entry */
073 private byte known = 0;
074 /**
075 * The 32bit DOS encoded format for the time of this entry. Only valid if
076 * KNOWN_DOSTIME is set in known.
077 */
078 private int dostime;
079 /**
080 * The 64bit Java encoded millisecond time since the beginning of the epoch.
081 * Only valid if KNOWN_TIME is set in known.
082 */
083 private long time;
084 /** Extra data */
085 private byte[] extra = null;
086
087 int flags; /* used by ZipOutputStream */
088 int offset; /* used by ZipFile and ZipOutputStream */
089
090 /**
091 * Compression method. This method doesn't compress at all.
092 */
093 public static final int STORED = 0;
094 /**
095 * Compression method. This method uses the Deflater.
096 */
097 public static final int DEFLATED = 8;
098
099 /**
100 * Creates a zip entry with the given name.
101 * @param name the name. May include directory components separated
102 * by '/'.
103 *
104 * @exception NullPointerException when name is null.
105 * @exception IllegalArgumentException when name is bigger then 65535 chars.
106 */
107 public ZipEntry(String name)
108 {
109 int length = name.length();
110 if (length > 65535)
111 throw new IllegalArgumentException("name length is " + length);
112 this.name = name;
113 }
114
115 /**
116 * Creates a copy of the given zip entry.
117 * @param e the entry to copy.
118 */
119 public ZipEntry(ZipEntry e)
120 {
121 this(e, e.name);
122 }
123
124 ZipEntry(ZipEntry e, String name)
125 {
126 this.name = name;
127 known = e.known;
128 size = e.size;
129 compressedSize = e.compressedSize;
130 crc = e.crc;
131 dostime = e.dostime;
132 time = e.time;
133 method = e.method;
134 extra = e.extra;
135 comment = e.comment;
136 }
137
138 final void setDOSTime(int dostime)
139 {
140 this.dostime = dostime;
141 known |= KNOWN_DOSTIME;
142 known &= ~KNOWN_TIME;
143 }
144
145 final int getDOSTime()
146 {
147 if ((known & KNOWN_DOSTIME) != 0)
148 return dostime;
149 else if ((known & KNOWN_TIME) != 0)
150 {
151 Calendar cal = Calendar.getInstance();
152 cal.setTimeInMillis(time);
153 dostime = (cal.get(Calendar.YEAR) - 1980 & 0x7f) << 25
154 | (cal.get(Calendar.MONTH) + 1) << 21
155 | (cal.get(Calendar.DAY_OF_MONTH)) << 16
156 | (cal.get(Calendar.HOUR_OF_DAY)) << 11
157 | (cal.get(Calendar.MINUTE)) << 5
158 | (cal.get(Calendar.SECOND)) >> 1;
159 known |= KNOWN_DOSTIME;
160 return dostime;
161 }
162 else
163 return 0;
164 }
165
166 /**
167 * Creates a copy of this zip entry.
168 */
169 public Object clone()
170 {
171 // JCL defines this as being the same as the copy constructor above,
172 // except that value of the "extra" field is also copied. Take care
173 // that in the case of a subclass we use clone() rather than the copy
174 // constructor.
175 ZipEntry clone;
176 if (this.getClass() == ZipEntry.class)
177 clone = new ZipEntry(this);
178 else
179 {
180 try
181 {
182 clone = (ZipEntry) super.clone();
183 }
184 catch (CloneNotSupportedException e)
185 {
186 throw new InternalError();
187 }
188 }
189 if (extra != null)
190 {
191 clone.extra = new byte[extra.length];
192 System.arraycopy(extra, 0, clone.extra, 0, extra.length);
193 }
194 return clone;
195 }
196
197 /**
198 * Returns the entry name. The path components in the entry are
199 * always separated by slashes ('/').
200 */
201 public String getName()
202 {
203 return name;
204 }
205
206 /**
207 * Sets the time of last modification of the entry.
208 * @time the time of last modification of the entry.
209 */
210 public void setTime(long time)
211 {
212 this.time = time;
213 this.known |= KNOWN_TIME;
214 this.known &= ~KNOWN_DOSTIME;
215 }
216
217 /**
218 * Gets the time of last modification of the entry.
219 * @return the time of last modification of the entry, or -1 if unknown.
220 */
221 public long getTime()
222 {
223 // The extra bytes might contain the time (posix/unix extension)
224 parseExtra();
225
226 if ((known & KNOWN_TIME) != 0)
227 return time;
228 else if ((known & KNOWN_DOSTIME) != 0)
229 {
230 int sec = 2 * (dostime & 0x1f);
231 int min = (dostime >> 5) & 0x3f;
232 int hrs = (dostime >> 11) & 0x1f;
233 int day = (dostime >> 16) & 0x1f;
234 int mon = ((dostime >> 21) & 0xf) - 1;
235 int year = ((dostime >> 25) & 0x7f) + 1980; /* since 1900 */
236
237 try
238 {
239 Calendar cal = Calendar.getInstance();
240 cal.set(year, mon, day, hrs, min, sec);
241 time = cal.getTimeInMillis();
242 known |= KNOWN_TIME;
243 return time;
244 }
245 catch (RuntimeException ex)
246 {
247 /* Ignore illegal time stamp */
248 known &= ~KNOWN_TIME;
249 return -1;
250 }
251 }
252 else
253 return -1;
254 }
255
256 /**
257 * Sets the size of the uncompressed data.
258 * @exception IllegalArgumentException if size is not in 0..0xffffffffL
259 */
260 public void setSize(long size)
261 {
262 if ((size & 0xffffffff00000000L) != 0)
263 throw new IllegalArgumentException();
264 this.size = (int) size;
265 this.known |= KNOWN_SIZE;
266 }
267
268 /**
269 * Gets the size of the uncompressed data.
270 * @return the size or -1 if unknown.
271 */
272 public long getSize()
273 {
274 return (known & KNOWN_SIZE) != 0 ? size & 0xffffffffL : -1L;
275 }
276
277 /**
278 * Sets the size of the compressed data.
279 */
280 public void setCompressedSize(long csize)
281 {
282 this.compressedSize = csize;
283 }
284
285 /**
286 * Gets the size of the compressed data.
287 * @return the size or -1 if unknown.
288 */
289 public long getCompressedSize()
290 {
291 return compressedSize;
292 }
293
294 /**
295 * Sets the crc of the uncompressed data.
296 * @exception IllegalArgumentException if crc is not in 0..0xffffffffL
297 */
298 public void setCrc(long crc)
299 {
300 if ((crc & 0xffffffff00000000L) != 0)
301 throw new IllegalArgumentException();
302 this.crc = (int) crc;
303 this.known |= KNOWN_CRC;
304 }
305
306 /**
307 * Gets the crc of the uncompressed data.
308 * @return the crc or -1 if unknown.
309 */
310 public long getCrc()
311 {
312 return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L;
313 }
314
315 /**
316 * Sets the compression method. Only DEFLATED and STORED are
317 * supported.
318 * @exception IllegalArgumentException if method is not supported.
319 * @see ZipOutputStream#DEFLATED
320 * @see ZipOutputStream#STORED
321 */
322 public void setMethod(int method)
323 {
324 if (method != ZipOutputStream.STORED
325 && method != ZipOutputStream.DEFLATED)
326 throw new IllegalArgumentException();
327 this.method = (byte) method;
328 }
329
330 /**
331 * Gets the compression method.
332 * @return the compression method or -1 if unknown.
333 */
334 public int getMethod()
335 {
336 return method;
337 }
338
339 /**
340 * Sets the extra data.
341 * @exception IllegalArgumentException if extra is longer than 0xffff bytes.
342 */
343 public void setExtra(byte[] extra)
344 {
345 if (extra == null)
346 {
347 this.extra = null;
348 return;
349 }
350 if (extra.length > 0xffff)
351 throw new IllegalArgumentException();
352 this.extra = extra;
353 }
354
355 private void parseExtra()
356 {
357 // Already parsed?
358 if ((known & KNOWN_EXTRA) != 0)
359 return;
360
361 if (extra == null)
362 {
363 known |= KNOWN_EXTRA;
364 return;
365 }
366
367 try
368 {
369 int pos = 0;
370 while (pos < extra.length)
371 {
372 int sig = (extra[pos++] & 0xff)
373 | (extra[pos++] & 0xff) << 8;
374 int len = (extra[pos++] & 0xff)
375 | (extra[pos++] & 0xff) << 8;
376 if (sig == 0x5455)
377 {
378 /* extended time stamp */
379 int flags = extra[pos];
380 if ((flags & 1) != 0)
381 {
382 long time = ((extra[pos+1] & 0xff)
383 | (extra[pos+2] & 0xff) << 8
384 | (extra[pos+3] & 0xff) << 16
385 | (extra[pos+4] & 0xff) << 24);
386 setTime(time*1000);
387 }
388 }
389 pos += len;
390 }
391 }
392 catch (ArrayIndexOutOfBoundsException ex)
393 {
394 /* be lenient */
395 }
396
397 known |= KNOWN_EXTRA;
398 return;
399 }
400
401 /**
402 * Gets the extra data.
403 * @return the extra data or null if not set.
404 */
405 public byte[] getExtra()
406 {
407 return extra;
408 }
409
410 /**
411 * Sets the entry comment.
412 * @exception IllegalArgumentException if comment is longer than 0xffff.
413 */
414 public void setComment(String comment)
415 {
416 if (comment != null && comment.length() > 0xffff)
417 throw new IllegalArgumentException();
418 this.comment = comment;
419 }
420
421 /**
422 * Gets the comment.
423 * @return the comment or null if not set.
424 */
425 public String getComment()
426 {
427 return comment;
428 }
429
430 /**
431 * Gets true, if the entry is a directory. This is solely
432 * determined by the name, a trailing slash '/' marks a directory.
433 */
434 public boolean isDirectory()
435 {
436 int nlen = name.length();
437 return nlen > 0 && name.charAt(nlen - 1) == '/';
438 }
439
440 /**
441 * Gets the string representation of this ZipEntry. This is just
442 * the name as returned by getName().
443 */
444 public String toString()
445 {
446 return name;
447 }
448
449 /**
450 * Gets the hashCode of this ZipEntry. This is just the hashCode
451 * of the name. Note that the equals method isn't changed, though.
452 */
453 public int hashCode()
454 {
455 return name.hashCode();
456 }
457 }