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    }