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 int KNOWN_SIZE   = 1;
054      private static final int KNOWN_CSIZE  = 2;
055      private static final int KNOWN_CRC    = 4;
056      private static final int KNOWN_TIME   = 8;
057      private static final int KNOWN_EXTRA  = 16;
058    
059      private static Calendar cal;
060    
061      private String name;
062      private int size;
063      private long compressedSize = -1;
064      private int crc;
065      private int dostime;
066      private short known = 0;
067      private short method = -1;
068      private byte[] extra = null;
069      private String comment = null;
070    
071      int flags;              /* used by ZipOutputStream */
072      int offset;             /* used by ZipFile and ZipOutputStream */
073    
074      /**
075       * Compression method.  This method doesn't compress at all.
076       */
077      public static final int STORED = 0;
078      /**
079       * Compression method.  This method uses the Deflater.
080       */
081      public static final int DEFLATED = 8;
082    
083      /**
084       * Creates a zip entry with the given name.
085       * @param name the name. May include directory components separated
086       * by '/'.
087       *
088       * @exception NullPointerException when name is null.
089       * @exception IllegalArgumentException when name is bigger then 65535 chars.
090       */
091      public ZipEntry(String name)
092      {
093        int length = name.length();
094        if (length > 65535)
095          throw new IllegalArgumentException("name length is " + length);
096        this.name = name;
097      }
098    
099      /**
100       * Creates a copy of the given zip entry.
101       * @param e the entry to copy.
102       */
103      public ZipEntry(ZipEntry e)
104      {
105        this(e, e.name);
106      }
107    
108      ZipEntry(ZipEntry e, String name)
109      {
110        this.name = name;
111        known = e.known;
112        size = e.size;
113        compressedSize = e.compressedSize;
114        crc = e.crc;
115        dostime = e.dostime;
116        method = e.method;
117        extra = e.extra;
118        comment = e.comment;
119      }
120    
121      final void setDOSTime(int dostime)
122      {
123        this.dostime = dostime;
124        known |= KNOWN_TIME;
125      }
126    
127      final int getDOSTime()
128      {
129        if ((known & KNOWN_TIME) == 0)
130          return 0;
131        else
132          return dostime;
133      }
134    
135      /**
136       * Creates a copy of this zip entry.
137       */
138      /**
139       * Clones the entry.
140       */
141      public Object clone()
142      {
143        try
144          {
145            // The JCL says that the `extra' field is also copied.
146            ZipEntry clone = (ZipEntry) super.clone();
147            if (extra != null)
148              clone.extra = (byte[]) extra.clone();
149            return clone;
150          }
151        catch (CloneNotSupportedException ex)
152          {
153            throw new InternalError();
154          }
155      }
156    
157      /**
158       * Returns the entry name.  The path components in the entry are
159       * always separated by slashes ('/').  
160       */
161      public String getName()
162      {
163        return name;
164      }
165    
166      /**
167       * Sets the time of last modification of the entry.
168       * @time the time of last modification of the entry.
169       */
170      public void setTime(long time)
171      {
172        Calendar cal = getCalendar();
173        synchronized (cal)
174          {
175            cal.setTimeInMillis(time);
176            dostime = (cal.get(Calendar.YEAR) - 1980 & 0x7f) << 25
177              | (cal.get(Calendar.MONTH) + 1) << 21
178              | (cal.get(Calendar.DAY_OF_MONTH)) << 16
179              | (cal.get(Calendar.HOUR_OF_DAY)) << 11
180              | (cal.get(Calendar.MINUTE)) << 5
181              | (cal.get(Calendar.SECOND)) >> 1;
182          }
183        this.known |= KNOWN_TIME;
184      }
185    
186      /**
187       * Gets the time of last modification of the entry.
188       * @return the time of last modification of the entry, or -1 if unknown.
189       */
190      public long getTime()
191      {
192        // The extra bytes might contain the time (posix/unix extension)
193        parseExtra();
194    
195        if ((known & KNOWN_TIME) == 0)
196          return -1;
197    
198        int sec = 2 * (dostime & 0x1f);
199        int min = (dostime >> 5) & 0x3f;
200        int hrs = (dostime >> 11) & 0x1f;
201        int day = (dostime >> 16) & 0x1f;
202        int mon = ((dostime >> 21) & 0xf) - 1;
203        int year = ((dostime >> 25) & 0x7f) + 1980; /* since 1900 */
204       
205        try
206          {
207            cal = getCalendar();
208            synchronized (cal)
209              {
210                cal.set(year, mon, day, hrs, min, sec);
211                return cal.getTimeInMillis();
212              }
213          }
214        catch (RuntimeException ex)
215          {
216            /* Ignore illegal time stamp */
217            known &= ~KNOWN_TIME;
218            return -1;
219          }
220      }
221    
222      private static synchronized Calendar getCalendar()
223      {
224        if (cal == null)
225          cal = Calendar.getInstance();
226    
227        return cal;
228      }
229    
230      /**
231       * Sets the size of the uncompressed data.
232       * @exception IllegalArgumentException if size is not in 0..0xffffffffL
233       */
234      public void setSize(long size)
235      {
236        if ((size & 0xffffffff00000000L) != 0)
237            throw new IllegalArgumentException();
238        this.size = (int) size;
239        this.known |= KNOWN_SIZE;
240      }
241    
242      /**
243       * Gets the size of the uncompressed data.
244       * @return the size or -1 if unknown.
245       */
246      public long getSize()
247      {
248        return (known & KNOWN_SIZE) != 0 ? size & 0xffffffffL : -1L;
249      }
250    
251      /**
252       * Sets the size of the compressed data.
253       */
254      public void setCompressedSize(long csize)
255      {
256        this.compressedSize = csize;
257      }
258    
259      /**
260       * Gets the size of the compressed data.
261       * @return the size or -1 if unknown.
262       */
263      public long getCompressedSize()
264      {
265        return compressedSize;
266      }
267    
268      /**
269       * Sets the crc of the uncompressed data.
270       * @exception IllegalArgumentException if crc is not in 0..0xffffffffL
271       */
272      public void setCrc(long crc)
273      {
274        if ((crc & 0xffffffff00000000L) != 0)
275            throw new IllegalArgumentException();
276        this.crc = (int) crc;
277        this.known |= KNOWN_CRC;
278      }
279    
280      /**
281       * Gets the crc of the uncompressed data.
282       * @return the crc or -1 if unknown.
283       */
284      public long getCrc()
285      {
286        return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L;
287      }
288    
289      /**
290       * Sets the compression method.  Only DEFLATED and STORED are
291       * supported.
292       * @exception IllegalArgumentException if method is not supported.
293       * @see ZipOutputStream#DEFLATED
294       * @see ZipOutputStream#STORED 
295       */
296      public void setMethod(int method)
297      {
298        if (method != ZipOutputStream.STORED
299            && method != ZipOutputStream.DEFLATED)
300            throw new IllegalArgumentException();
301        this.method = (short) method;
302      }
303    
304      /**
305       * Gets the compression method.  
306       * @return the compression method or -1 if unknown.
307       */
308      public int getMethod()
309      {
310        return method;
311      }
312    
313      /**
314       * Sets the extra data.
315       * @exception IllegalArgumentException if extra is longer than 0xffff bytes.
316       */
317      public void setExtra(byte[] extra)
318      {
319        if (extra == null) 
320          {
321            this.extra = null;
322            return;
323          }
324        if (extra.length > 0xffff)
325          throw new IllegalArgumentException();
326        this.extra = extra;
327      }
328    
329      private void parseExtra()
330      {
331        // Already parsed?
332        if ((known & KNOWN_EXTRA) != 0)
333          return;
334    
335        if (extra == null)
336          {
337            known |= KNOWN_EXTRA;
338            return;
339          }
340    
341        try
342          {
343            int pos = 0;
344            while (pos < extra.length) 
345              {
346                int sig = (extra[pos++] & 0xff)
347                  | (extra[pos++] & 0xff) << 8;
348                int len = (extra[pos++] & 0xff)
349                  | (extra[pos++] & 0xff) << 8;
350                if (sig == 0x5455) 
351                  {
352                    /* extended time stamp */
353                    int flags = extra[pos];
354                    if ((flags & 1) != 0)
355                      {
356                        long time = ((extra[pos+1] & 0xff)
357                                | (extra[pos+2] & 0xff) << 8
358                                | (extra[pos+3] & 0xff) << 16
359                                | (extra[pos+4] & 0xff) << 24);
360                        setTime(time);
361                      }
362                  }
363                pos += len;
364              }
365          }
366        catch (ArrayIndexOutOfBoundsException ex)
367          {
368            /* be lenient */
369          }
370    
371        known |= KNOWN_EXTRA;
372        return;
373      }
374    
375      /**
376       * Gets the extra data.
377       * @return the extra data or null if not set.
378       */
379      public byte[] getExtra()
380      {
381        return extra;
382      }
383    
384      /**
385       * Sets the entry comment.
386       * @exception IllegalArgumentException if comment is longer than 0xffff.
387       */
388      public void setComment(String comment)
389      {
390        if (comment != null && comment.length() > 0xffff)
391          throw new IllegalArgumentException();
392        this.comment = comment;
393      }
394    
395      /**
396       * Gets the comment.
397       * @return the comment or null if not set.
398       */
399      public String getComment()
400      {
401        return comment;
402      }
403    
404      /**
405       * Gets true, if the entry is a directory.  This is solely
406       * determined by the name, a trailing slash '/' marks a directory.  
407       */
408      public boolean isDirectory()
409      {
410        int nlen = name.length();
411        return nlen > 0 && name.charAt(nlen - 1) == '/';
412      }
413    
414      /**
415       * Gets the string representation of this ZipEntry.  This is just
416       * the name as returned by getName().
417       */
418      public String toString()
419      {
420        return name;
421      }
422    
423      /**
424       * Gets the hashCode of this ZipEntry.  This is just the hashCode
425       * of the name.  Note that the equals method isn't changed, though.
426       */
427      public int hashCode()
428      {
429        return name.hashCode();
430      }
431    }