001/* ZipEntry.java --
002   Copyright (C) 2001, 2002, 2004, 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.util.zip;
040
041import 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 */
051public 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}