View Javadoc
1   /*
2    * Copyright (C) 2005-2015 Schlichtherle IT Services.
3    * All rights reserved. Use is subject to license terms.
4    */
5   package net.java.truevfs.comp.zip;
6   
7   import java.util.Formatter;
8   import javax.annotation.CheckForNull;
9   import javax.annotation.Nullable;
10  import javax.annotation.concurrent.NotThreadSafe;
11  import static net.java.truevfs.comp.zip.Constants.EMPTY;
12  import static net.java.truevfs.comp.zip.Constants.FORCE_ZIP64_EXT;
13  import static net.java.truevfs.comp.zip.ExtraField.WINZIP_AES_ID;
14  import static net.java.truevfs.comp.zip.ExtraField.ZIP64_HEADER_ID;
15  import static net.java.truevfs.comp.zip.LittleEndian.readLong;
16  import static net.java.truevfs.comp.zip.LittleEndian.writeLong;
17  
18  /**
19   * Replacement for {@link java.util.zip.ZipEntry java.util.zip.ZipEntry}.
20   * For every numeric property of this class, the default value is
21   * {@code UNKNOWN} in order to indicate an unknown state and it's
22   * permitted to set this value explicitly in order to reset the property.
23   * <p>
24   * Note that a {@code ZipEntry} object can be used with only one
25   * {@link ZipFile} or {@link ZipOutputStream} instance.
26   * Reusing the same {@code ZipEntry} object with a second object of these
27   * classes is an error and may result in unpredictable behaviour.
28   * <p>
29   * In general, this class is <em>not</em> thread-safe.
30   * However, it is safe to call only the getters of this class from multiple
31   * threads concurrently.
32   *
33   * @author  Christian Schlichtherle
34   */
35  // TODO: Consider implementing net.java.truevfs.kernel.entry.Entry.
36  @NotThreadSafe
37  public class ZipEntry implements Cloneable {
38  
39      // Bit masks for initialized fields.
40      private static final int PLATFORM = 1, METHOD = 1 << 1,
41                               CRC = 1 << 2, DTIME = 1 << 6,
42                               EATTR = 1 << 7;
43  
44      /** The unknown value for numeric properties. */
45      public static final byte UNKNOWN = -1;
46  
47      /** Windows platform. */
48      public static final short PLATFORM_FAT  = 0;
49  
50      /** Unix platform. */
51      public static final short PLATFORM_UNIX = 3;
52  
53      /**
54       * Method for <em>Stored</em> (uncompressed) entries.
55       *
56       * @see   #setMethod(int)
57       */
58      public static final int STORED = 0;
59  
60      /**
61       * Method for <em>Deflated</em> compressed entries.
62       *
63       * @see   #setMethod(int)
64       */
65      public static final int DEFLATED = 8;
66  
67      /**
68       * Method for <em>BZIP2</em> compressed entries.
69       *
70       * @see   #setMethod(int)
71       */
72      public static final int BZIP2 = 12;
73  
74      /**
75       * Pseudo compression method for WinZip AES encrypted entries.
76       */
77      static final int WINZIP_AES = 99;
78  
79      /** General Purpose Bit Flag mask for encrypted data. */
80      static final int GPBF_ENCRYPTED = 1;
81  
82      static final int GPBF_DATA_DESCRIPTOR = 1 << 3;
83      static final int GPBF_UTF8 = 1 << 11;
84  
85      /**
86       * Smallest supported DOS date/time value in a ZIP file,
87       * which is January 1<sup>st</sup>, 1980 AD 00:00:00 local time.
88       */
89      public static final long MIN_DOS_TIME = DateTimeConverter.MIN_DOS_TIME;
90  
91      /**
92       * Largest supported DOS date/time value in a ZIP file,
93       * which is December 31<sup>st</sup>, 2107 AD 23:59:58 local time.
94       */
95      public static final long MAX_DOS_TIME = DateTimeConverter.MAX_DOS_TIME;
96  
97      private byte init;                  // bit flags for init state
98      private String name;
99      private byte platform;              // 1 byte unsigned int (UByte)
100     private short general;              // 2 bytes unsigned int (UShort)
101     private short method;               // 2 bytes unsigned int (UShort)
102     private int dtime;                  // 4 bytes unsigned int (UInt)
103     private int crc;                    // 4 bytes unsigned int (UInt)
104     private long csize = UNKNOWN;       // 63 bits unsigned integer (ULong)
105     private long size = UNKNOWN;        // 63 bits unsigned integer (Ulong)
106     private int eattr;                  // 4 bytes unsigned int (Uint)
107 
108     /** Relative Offset Of Local File Header. */
109     private long offset = UNKNOWN;     // 63 bits unsigned integer (ULong)
110 
111     /**
112      * The map of extra fields.
113      * Maps from Header ID [Integer] to extra field [ExtraField].
114      * Should be {@code null} or may be empty if no extra fields are used.
115      */
116     private @CheckForNull ExtraFields fields;
117 
118     /** Comment field. */
119     private @CheckForNull String comment;
120 
121     /** Constructs a new ZIP entry with the given name. */
122     public ZipEntry(final String name) {
123         UShort.check(name.length());
124         this.name = name;
125     }
126 
127     /**
128      * Constructs a new ZIP entry with the given name and all other properties
129      * copied from the given template.
130      */
131     @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
132     protected ZipEntry(final String name, final ZipEntry template) {
133         UShort.check(name.length());
134         this.init = template.init;
135         this.name = name;
136         this.platform = template.platform;
137         this.general = template.general;
138         this.method = template.method;
139         this.dtime = template.dtime;
140         this.crc = template.crc;
141         this.csize = template.csize;
142         this.size = template.size;
143         this.eattr = template.eattr;
144         this.offset = template.offset;
145         final ExtraFields templateFields = template.fields;
146         this.fields = templateFields == null ? null : templateFields.clone();
147         this.comment = template.comment;
148     }
149 
150     @Override
151     @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
152     public ZipEntry clone() {
153         final ZipEntry entry;
154         try {
155             entry = (ZipEntry) super.clone();
156         } catch (CloneNotSupportedException ex) {
157             throw new AssertionError(ex);
158         }
159         final ExtraFields fields = this.fields;
160         entry.fields = fields == null ? null : fields.clone();
161         return entry;
162     }
163 
164     private boolean isInit(final int mask) { return 0 != (init & mask); }
165 
166     private void setInit(final int mask, final boolean init) {
167         if (init)
168             this.init |=  mask;
169         else
170             this.init &= ~mask;
171     }
172 
173     /** Returns the ZIP entry name. */
174     public final String getName() { return name; }
175 
176     /**
177      * Returns true if and only if this ZIP entry represents a directory entry
178      * (i.e. end with {@code '/'}).
179      */
180     public final boolean isDirectory() { return name.endsWith("/"); }
181 
182     public final int getPlatform() {
183         return isInit(PLATFORM) ? platform & UByte.MAX_VALUE : UNKNOWN;
184     }
185 
186     public final void setPlatform(final int platform) {
187         final boolean known = UNKNOWN != platform;
188         if (known) {
189             UByte.check(platform, name, "Platform out of range");
190             this.platform = (byte) platform;
191         } else {
192             this.platform = 0;
193         }
194         setInit(PLATFORM, known);
195     }
196 
197     final int getRawPlatform() { return platform & UByte.MAX_VALUE; }
198 
199     final void setRawPlatform(final int platform) {
200         assert UByte.check(platform);
201         this.platform = (byte) platform;
202         setInit(PLATFORM, true);
203     }
204 
205     final int getRawVersionNeededToExtract() {
206         final int method = getRawMethod();
207         return BZIP2 == method
208                 ? 46
209                 : isZip64ExtensionsRequired()
210                     ? 45
211                     : DEFLATED == method || isDirectory()
212                         ? 20
213                         : 10;
214     }
215 
216     /** Returns the General Purpose Bit Flags. */
217     final int getGeneralPurposeBitFlags() {
218         return general & UShort.MAX_VALUE;
219     }
220 
221     /** Sets the General Purpose Bit Flags. */
222     final void setGeneralPurposeBitFlags(final int general) {
223         assert UShort.check(general);
224         this.general = (short) general;
225     }
226 
227     /** Returns the indexed General Purpose Bit Flag. */
228     final boolean getGeneralPurposeBitFlag(final int mask) {
229         return 0 != (general & mask);
230     }
231 
232     /** Sets the indexed General Purpose Bit Flag. */
233     final void setGeneralPurposeBitFlag(final int mask, final boolean bit) {
234         if (bit)
235             general |=  mask;
236         else
237             general &= ~mask;
238     }
239 
240     /**
241      * Returns {@code true} if and only if this ZIP entry is encrypted.
242      * Note that only WinZip AES encryption is currently supported.
243      *
244      * @return {@code true} if and only if this ZIP entry is encrypted.
245      */
246     public final boolean isEncrypted() {
247         return getGeneralPurposeBitFlag(GPBF_ENCRYPTED);
248     }
249 
250     /**
251      * Sets the encryption flag for this ZIP entry.
252      * If you set this to {@code true}, you will also need to provide
253      * {@link ZipOutputStream#setCryptoParameters(ZipCryptoParameters) crypto parameters}.
254      * <p>
255      * Note that only {@link WinZipAesParameters WinZip AES encryption} is
256      * currently supported.
257      *
258      * @param encrypted whether or not this ZIP entry should get encrypted.
259      */
260     public final void setEncrypted(boolean encrypted) {
261         setGeneralPurposeBitFlag(GPBF_ENCRYPTED, encrypted);
262     }
263 
264     /**
265      * Sets the encryption property to {@code false} and removes any other
266      * encryption artifacts, e.g. a WinZip AES extra field.
267      * @see   <a href="http://java.net/jira/browse/TRUEZIP-176">#TRUEZIP-176</a>
268      */
269     public final void clearEncryption() {
270         setEncrypted(false);
271         final WinZipAesExtraField field
272                 = (WinZipAesExtraField) removeExtraField(WINZIP_AES_ID);
273         if (WINZIP_AES == getRawMethod())
274             setRawMethod(null == field ? UNKNOWN : field.getMethod());
275     }
276 
277     /**
278      * Returns the compression method for this entry.
279      *
280      * @see #setMethod(int)
281      * @see ZipOutputStream#getMethod()
282      */
283     public final int getMethod() {
284         return isInit(METHOD) ? method & UShort.MAX_VALUE : UNKNOWN;
285     }
286 
287     /**
288      * Sets the compression method for this entry.
289      *
290      * @see    #getMethod()
291      * @see    ZipOutputStream#setMethod(int)
292      * @throws IllegalArgumentException If {@code method} is not
293      *         {@link #STORED}, {@link #DEFLATED}, {@link #BZIP2} or
294      *         {@link #UNKNOWN}.
295      */
296     public final void setMethod(final int method) {
297         switch (method) {
298             case WINZIP_AES:
299                 // This is only present to support manual copying of properties.
300                 // It should never be set by applications.
301             case STORED:
302             case DEFLATED:
303             case BZIP2:
304                 this.method = (short) method;
305                 setInit(METHOD, true);
306                 break;
307             case UNKNOWN:
308                 this.method = 0;
309                 setInit(METHOD, false);
310                 break;
311             default:
312                 throw new IllegalArgumentException(
313                         name + " (unsupported compression method " + method + ")");
314         }
315     }
316 
317     final int getRawMethod() { return method & UShort.MAX_VALUE; }
318 
319     final void setRawMethod(final int method) {
320         assert UShort.check(method);
321         this.method = (short) method;
322         setInit(METHOD, true);
323     }
324 
325     public final long getTime() {
326         if (!isInit(DTIME)) return UNKNOWN;
327         return getDateTimeConverter().toJavaTime(dtime & UInt.MAX_VALUE);
328     }
329 
330     public final void setTime(final long jtime) {
331         final boolean known = UNKNOWN != jtime;
332         if (known) {
333             this.dtime = (int) getDateTimeConverter().toDosTime(jtime);
334         } else {
335             this.dtime = 0;
336         }
337         setInit(DTIME, known);
338     }
339 
340     final long getRawTime() { return dtime & UInt.MAX_VALUE; }
341 
342     final void setRawTime(final long dtime) {
343         assert UInt.check(dtime);
344         this.dtime = (int) dtime;
345         setInit(DTIME, true);
346     }
347 
348     /**
349      * Returns a {@link DateTimeConverter} for the conversion of Java time
350      * to DOS date/time fields and vice versa.
351      * <p>
352      * The implementation in the class {@link ZipEntry} returns
353      * {@link DateTimeConverter#JAR}.
354      *
355      * @return A {@link DateTimeConverter} for the conversion of Java time
356      *         to DOS date/time fields and vice versa.
357      */
358     protected DateTimeConverter getDateTimeConverter() {
359         return DateTimeConverter.JAR;
360     }
361 
362     public final long getCrc() {
363         return isInit(CRC) ? crc & UInt.MAX_VALUE : UNKNOWN;
364     }
365 
366     public final void setCrc(final long crc) {
367         final boolean known = UNKNOWN != crc;
368         if (known) {
369             UInt.check(crc, name, "CRC-32 out of range");
370             this.crc = (int) crc;
371         } else {
372             this.crc = 0;
373         }
374         setInit(CRC, known);
375     }
376 
377     final long getRawCrc() { return crc & UInt.MAX_VALUE; }
378 
379     final void setRawCrc(final long crc) {
380         assert UInt.check(crc);
381         this.crc = (int) crc;
382         setInit(CRC, true);
383     }
384 
385     /**
386      * Returns the compressed size of this entry.
387      *
388      * @see #setCompressedSize
389      */
390     public final long getCompressedSize() { return csize; }
391 
392     /**
393      * Sets the compressed size of this entry.
394      *
395      * @param csize The Compressed Size.
396      * @throws IllegalArgumentException If {@code csize} is not in the
397      *         range from {@code 0} to {@link ULong#MAX_VALUE}
398      *         ({@value net.java.truevfs.comp.zip.ULong#MAX_VALUE}).
399      * @see #getCompressedSize
400      */
401     public final void setCompressedSize(final long csize) {
402         if (UNKNOWN != csize)
403             ULong.check(csize, name, "Compressed Size out of range");
404         this.csize = csize;
405     }
406 
407     final long getRawCompressedSize() {
408         final long csize = this.csize;
409         if (UNKNOWN == csize) return 0;
410         return FORCE_ZIP64_EXT || UInt.MAX_VALUE <= csize
411                 ? UInt.MAX_VALUE
412                 : csize;
413     }
414 
415     final void setRawCompressedSize(final long csize) {
416         assert ULong.check(csize);
417         this.csize = csize;
418     }
419 
420     /**
421      * Returns the uncompressed size of this entry.
422      *
423      * @see #setCompressedSize
424      */
425     public final long getSize() { return size; }
426 
427     /**
428      * Sets the uncompressed size of this entry.
429      *
430      * @param size The (Uncompressed) Size.
431      * @throws IllegalArgumentException If {@code size} is not in the
432      *         range from {@code 0} to {@link ULong#MAX_VALUE}
433      *         ({@value net.java.truevfs.comp.zip.ULong#MAX_VALUE}).
434      * @see #getCompressedSize
435      */
436     public final void setSize(final long size) {
437         if (UNKNOWN != size)
438             ULong.check(size, name, "Uncompressed Size out of range");
439         this.size = size;
440     }
441 
442     final long getRawSize() {
443         final long size = this.size;
444         if (UNKNOWN == size) return 0;
445         return FORCE_ZIP64_EXT || UInt.MAX_VALUE <= size
446                 ? UInt.MAX_VALUE
447                 : size;
448     }
449 
450     final void setRawSize(final long size) {
451         assert ULong.check(size);
452         this.size = size;
453     }
454 
455     /**
456      * Returns the external file attributes.
457      *
458      * @return The external file attributes.
459      */
460     public final long getExternalAttributes() {
461         return isInit(EATTR) ? eattr & UInt.MAX_VALUE : UNKNOWN;
462     }
463 
464     /**
465      * Sets the external file attributes.
466      *
467      * @param eattr the external file attributes.
468      */
469     public final void setExternalAttributes(final long eattr) {
470         final boolean known = UNKNOWN != eattr;
471         if (known) {
472             UInt.check(eattr, name, "external file attributes out of range");
473             this.eattr = (int) eattr;
474         } else {
475             this.eattr = 0;
476         }
477         setInit(EATTR, known);
478     }
479 
480     final long getRawExternalAttributes() {
481         if (!isInit(EATTR)) return isDirectory() ? 0x10 : 0;
482         return eattr & UInt.MAX_VALUE;
483     }
484 
485     final void setRawExternalAttributes(final long eattr) {
486         assert UInt.check(eattr);
487         this.eattr = (int) eattr;
488         setInit(EATTR, true);
489     }
490 
491     final long getOffset() { return offset; }
492 
493     final long getRawOffset() {
494         final long offset = this.offset;
495         if (UNKNOWN == offset) return 0;
496         return FORCE_ZIP64_EXT || UInt.MAX_VALUE <= offset
497                 ? UInt.MAX_VALUE
498                 : offset;
499     }
500 
501     final void setRawOffset(final long offset) {
502         assert ULong.check(offset);
503         this.offset = offset;
504     }
505 
506     final @Nullable ExtraField getExtraField(int headerId) {
507         final ExtraFields fields = this.fields;
508         return fields == null ? null : fields.get(headerId);
509     }
510 
511     final @Nullable ExtraField addExtraField(final ExtraField field) {
512         assert null != field;
513         ExtraFields fields = this.fields;
514         if (null == fields) this.fields = fields = new ExtraFields();
515         return fields.add(field);
516     }
517 
518     final @Nullable ExtraField removeExtraField(final int headerId) {
519         final ExtraFields fields = this.fields;
520         return null != fields ? fields.remove(headerId) : null;
521     }
522 
523     /**
524      * Returns a protective copy of the serialized extra fields.
525      * Note that unlike its template {@link java.util.zip.ZipEntry#getExtra()},
526      * this method never returns {@code null}.
527      *
528      * @return A new byte array holding the serialized extra fields.
529      *         {@code null} is never returned.
530      */
531     public final byte[] getExtra() { return getExtraFields(false); }
532 
533     /**
534      * Sets the serialized extra fields by making a protective copy.
535      * Note that this method parses the serialized extra fields according to
536      * the ZIP File Format Specification and limits its size to 64 KB.
537      * Therefore, this property cannot not be used to hold arbitrary
538      * (application) data.
539      * Consider storing such data in a separate entry instead.
540      *
541      * @param  buf The byte array holding the serialized extra fields.
542      * @throws IllegalArgumentException if the serialized extra fields exceed
543      *         64 KB or do not conform to the ZIP File Format Specification.
544      */
545     public final void setExtra(final @CheckForNull byte[] buf)
546     throws IllegalArgumentException {
547         if (null != buf) {
548             UShort.check(buf.length, "extra fields too large", null);
549             setExtraFields(buf, false);
550         } else {
551             this.fields = null;
552         }
553     }
554 
555     /**
556      * Returns a protective copy of the serialized extra fields.
557      *
558      * @return A new byte array holding the serialized extra fields.
559      *         {@code null} is never returned.
560      * @see    #getRawExtraFields()
561      */
562     final byte[] getRawExtraFields() { return getExtraFields(true); }
563 
564     /**
565      * Sets extra fields and parses ZIP64 extra field.
566      * This method <em>must not</em> get called before the uncompressed size,
567      * compressed size and offset have been initialized!
568      *
569      * @throws IllegalArgumentException if the serialized extra fields do not
570      *         conform to the ZIP File Format Specification.
571      */
572     final void setRawExtraFields(final byte[] buf)
573     throws IllegalArgumentException {
574         setExtraFields(buf, true);
575     }
576 
577     private byte[] getExtraFields(final boolean zip64) {
578         ExtraFields fields = this.fields;
579         if (zip64) {
580             final ExtraField field = composeZip64ExtraField();
581             if (null != field) {
582                 fields = null != fields ? fields.clone() : new ExtraFields();
583                 fields.add(field);
584             }
585         } else {
586             assert null == fields || null == fields.get(ZIP64_HEADER_ID);
587         }
588         return null == fields ? EMPTY : fields.getDataBlock();
589     }
590 
591     /**
592      * @throws IllegalArgumentException if the serialized extra fields do not
593      *         conform to the ZIP File Format Specification.
594      */
595     private void setExtraFields(final byte[] buf, final boolean zip64)
596     throws IllegalArgumentException {
597         assert UShort.check(buf.length);
598         if (0 < buf.length) {
599             final ExtraFields fields = new ExtraFields();
600             try {
601                 fields.readFrom(buf, 0, buf.length);
602                 if (zip64) parseZip64ExtraField(fields);
603             } catch (final IndexOutOfBoundsException ex) {
604                 throw new IllegalArgumentException(ex);
605             }
606             fields.remove(ZIP64_HEADER_ID);
607             this.fields = 0 < fields.size() ? fields : null;
608         } else {
609             this.fields = null;
610         }
611     }
612 
613     /**
614      * Composes a ZIP64 Extended Information extra field from the properties
615      * of this entry.
616      * If no ZIP64 Extended Information extra field is required it is removed
617      * from the collection of extra fields.
618      */
619     private @CheckForNull ExtraField composeZip64ExtraField() {
620         final byte[] data = new byte[3 * 8]; // maximum size
621         int off = 0;
622         // Write out Uncompressed Size.
623         final long size = getSize();
624         if (FORCE_ZIP64_EXT && UNKNOWN != size || UInt.MAX_VALUE <= size) {
625             writeLong(size, data, off);
626             off += 8;
627         }
628         // Write out Compressed Size.
629         final long csize = getCompressedSize();
630         if (FORCE_ZIP64_EXT && UNKNOWN != csize || UInt.MAX_VALUE <= csize) {
631             writeLong(csize, data, off);
632             off += 8;
633         }
634         // Write out Relative Header Offset.
635         final long offset = getOffset();
636         if (FORCE_ZIP64_EXT && UNKNOWN != offset || UInt.MAX_VALUE <= offset) {
637             writeLong(offset, data, off);
638             off += 8;
639         }
640         // Create ZIP64 Extended Information extra field from serialized data.
641         final ExtraField field;
642         if (off > 0) {
643             field = new DefaultExtraField(ZIP64_HEADER_ID);
644             field.readFrom(data, 0, off);
645         } else {
646             field = null;
647         }
648         return field;
649     }
650 
651     /**
652      * Parses the properties of this entry from the ZIP64 Extended Information
653      * extra field, if present.
654      * The ZIP64 Extended Information extra field is <em>not</em> removed.
655      */
656     private void parseZip64ExtraField(final ExtraFields fields)
657     throws IndexOutOfBoundsException {
658         final ExtraField ef = fields.get(ZIP64_HEADER_ID);
659         if (null == ef) return;
660         final byte[] data = ef.getDataBlock();
661         int off = 0;
662         // Read in Uncompressed Size.
663         final long size = getRawSize();
664         if (UInt.MAX_VALUE <= size) {
665             assert UInt.MAX_VALUE == size;
666             setRawSize(readLong(data, off));
667             off += 8;
668         }
669         // Read in Compressed Size.
670         final long csize = getRawCompressedSize();
671         if (UInt.MAX_VALUE <= csize) {
672             assert UInt.MAX_VALUE == csize;
673             setRawCompressedSize(readLong(data, off));
674             off += 8;
675         }
676         // Read in Relative Header Offset.
677         final long offset = getRawOffset();
678         if (UInt.MAX_VALUE <= offset) {
679             assert UInt.MAX_VALUE == offset;
680             setRawOffset(readLong(data, off));
681             //off += 8;
682         }
683     }
684 
685     public final @CheckForNull String getComment() { return comment; }
686 
687     /**
688      * Sets the entry comment.
689      * Note that this method limits the comment size to 64 KB.
690      * Therefore, this property should not be used to hold arbitrary
691      * (application) data.
692      * Consider storing such data in a separate entry instead.
693      *
694      * @param  comment The entry comment.
695      * @throws RuntimeException if the entry comment exceeds 64 KB.
696      */
697     public final void setComment(final @CheckForNull String comment) {
698         if (null != comment)
699             UShort.check(comment.length(), name, "Comment too long");
700         this.comment = comment;
701     }
702 
703     final String getRawComment() {
704         final String comment = this.comment;
705         return null != comment ? comment : "";
706     }
707 
708     final void setRawComment(final String comment) {
709         assert UShort.check(comment.length());
710         this.comment = comment;
711     }
712 
713     final boolean isDataDescriptorRequired() {
714         return UNKNOWN == (getCrc() | getCompressedSize() | getSize());
715     }
716 
717     final boolean isZip64ExtensionsRequired() {
718         // Offset MUST be considered in decision about ZIP64 format - see
719         // description of Data Descriptor in ZIP File Format Specification!
720         if (FORCE_ZIP64_EXT) return true;
721         return UInt.MAX_VALUE <= getCompressedSize()
722                 || UInt.MAX_VALUE <= getSize()
723                 || UInt.MAX_VALUE <= getOffset();
724     }
725 
726     /**
727      * Returns a string representation of this object for debugging and logging
728      * purposes.
729      */
730     @Override
731     public String toString() {
732         final StringBuilder s = new StringBuilder(256);
733         final Formatter f = new Formatter(s)
734                 .format("%s[name=%s", getClass().getName(), getName());
735         long value;
736         if (UNKNOWN != (value = getGeneralPurposeBitFlags()))
737             f.format(", gpbf=0x%04X", value);
738         if (UNKNOWN != (value = getMethod()))
739             f.format(", method=%d", value);
740         if (UNKNOWN != (value = getTime()))
741             f.format(", time=%tc", value);
742         if (UNKNOWN != (value = getCrc()))
743             f.format(", crc=0x%08X", value);
744         if (UNKNOWN != (value = getCompressedSize()))
745             f.format(", compressedSize=%d", value);
746         if (UNKNOWN != (value = getSize()))
747             f.format(", size=%d", value);
748         if (UNKNOWN != (value = getExternalAttributes()))
749             f.format(", ea=0x%08X", value);
750         {
751             final String comment = getComment();
752             if (null != comment)
753                 f.format(", comment=\"%s\"", comment);
754         }
755         return s.append("]").toString();
756     }
757 }