/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.tools.zip;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.zip.ZipException;

Extension that adds better handling of extra fields and provides access to the internal and external file attributes.

The extra data is expected to follow the recommendation of APPNOTE.txt:

  • the extra byte array consists of a sequence of extra fields
  • each extra fields starts by a two byte header id followed by a two byte sequence holding the length of the remainder of data.

Any extra data that cannot be parsed by the rules above will be consumed as "unparseable" extra data and treated differently by the methods of this class. Older versions would have thrown an exception if any attempt was made to read or write extra data not conforming to the recommendation.

/** * Extension that adds better handling of extra fields and provides * access to the internal and external file attributes. * * <p>The extra data is expected to follow the recommendation of * <a href="https://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.txt</a>:</p> * <ul> * <li>the extra byte array consists of a sequence of extra fields</li> * <li>each extra fields starts by a two byte header id followed by * a two byte sequence holding the length of the remainder of * data.</li> * </ul> * * <p>Any extra data that cannot be parsed by the rules above will be * consumed as "unparseable" extra data and treated differently by the * methods of this class. Older versions would have thrown an * exception if any attempt was made to read or write extra data not * conforming to the recommendation.</p> * */
public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { public static final int PLATFORM_UNIX = 3; public static final int PLATFORM_FAT = 0; public static final int CRC_UNKNOWN = -1; private static final int SHORT_MASK = 0xFFFF; private static final int SHORT_SHIFT = 16; private static final byte[] EMPTY = new byte[0];
The ZipEntry base class only supports the compression methods STORED and DEFLATED. We override the field so that any compression methods can be used.

The default value -1 means that the method has not been specified.

/** * The {@link java.util.zip.ZipEntry} base class only supports * the compression methods STORED and DEFLATED. We override the * field so that any compression methods can be used. * <p>The default value -1 means that the method has not been specified.</p> */
private int method = -1;
The ZipEntry.setSize method in the base class throws an IllegalArgumentException if the size is bigger than 2GB for Java versions < 7. Need to keep our own size information for Zip64 support.
/** * The {@link java.util.zip.ZipEntry#setSize} method in the base * class throws an IllegalArgumentException if the size is bigger * than 2GB for Java versions < 7. Need to keep our own size * information for Zip64 support. */
private long size = -1; private int internalAttributes = 0; private int platform = PLATFORM_FAT; private long externalAttributes = 0; private ZipExtraField[] extraFields; private UnparseableExtraFieldData unparseableExtra = null; private String name = null; private byte[] rawName = null; private GeneralPurposeBit gpb = new GeneralPurposeBit(); private static final ZipExtraField[] noExtraFields = new ZipExtraField[0];
Creates a new zip entry with the specified name.

Assumes the entry represents a directory if and only if the name ends with a forward slash "/".

Params:
  • name – the name of the entry
Since:1.1
/** * Creates a new zip entry with the specified name. * * <p>Assumes the entry represents a directory if and only if the * name ends with a forward slash "/".</p> * * @param name the name of the entry * @since 1.1 */
public ZipEntry(final String name) { super(name); setName(name); }
Creates a new zip entry with fields taken from the specified zip entry.

Assumes the entry represents a directory if and only if the name ends with a forward slash "/".

Params:
  • entry – the entry to get fields from
Throws:
Since:1.1
/** * Creates a new zip entry with fields taken from the specified zip entry. * * <p>Assumes the entry represents a directory if and only if the * name ends with a forward slash "/".</p> * * @param entry the entry to get fields from * @since 1.1 * @throws ZipException on error */
public ZipEntry(final java.util.zip.ZipEntry entry) throws ZipException { super(entry); setName(entry.getName()); final byte[] extra = entry.getExtra(); if (extra != null) { setExtraFields(ExtraFieldUtils.parse(extra, true, ExtraFieldUtils.UnparseableExtraField.READ)); } else { // initializes extra data to an empty byte array setExtra(); } setMethod(entry.getMethod()); this.size = entry.getSize(); }
Creates a new zip entry with fields taken from the specified zip entry.

Assumes the entry represents a directory if and only if the name ends with a forward slash "/".

Params:
  • entry – the entry to get fields from
Throws:
Since:1.1
/** * Creates a new zip entry with fields taken from the specified zip entry. * * <p>Assumes the entry represents a directory if and only if the * name ends with a forward slash "/".</p> * * @param entry the entry to get fields from * @throws ZipException on error * @since 1.1 */
public ZipEntry(final ZipEntry entry) throws ZipException { this((java.util.zip.ZipEntry) entry); setInternalAttributes(entry.getInternalAttributes()); setExternalAttributes(entry.getExternalAttributes()); setExtraFields(getAllExtraFieldsNoCopy()); setPlatform(entry.getPlatform()); GeneralPurposeBit other = entry.getGeneralPurposeBit(); setGeneralPurposeBit(other == null ? null : (GeneralPurposeBit) other.clone()); }
Since:1.9
/** * @since 1.9 */
protected ZipEntry() { this(""); }
Creates a new zip entry taking some information from the given file and using the provided name.

The name will be adjusted to end with a forward slash "/" if the file is a directory. If the file is not a directory a potential trailing forward slash will be stripped from the entry name.

Params:
  • inputFile – File
  • entryName – String
/** * Creates a new zip entry taking some information from the given * file and using the provided name. * * <p>The name will be adjusted to end with a forward slash "/" if * the file is a directory. If the file is not a directory a * potential trailing forward slash will be stripped from the * entry name.</p> * * @param inputFile File * @param entryName String */
public ZipEntry(final File inputFile, final String entryName) { this(inputFile.isDirectory() && !entryName.endsWith("/") ? entryName + "/" : entryName); if (inputFile.isFile()) { setSize(inputFile.length()); } setTime(inputFile.lastModified()); // TODO are there any other fields we can set here? }
Overwrite clone.
Returns:a cloned copy of this ZipEntry
Since:1.1
/** * Overwrite clone. * * @return a cloned copy of this ZipEntry * @since 1.1 */
@Override public Object clone() { final ZipEntry e = (ZipEntry) super.clone(); e.setInternalAttributes(getInternalAttributes()); e.setExternalAttributes(getExternalAttributes()); e.setExtraFields(getAllExtraFieldsNoCopy()); return e; }
Returns the compression method of this entry, or -1 if the compression method has not been specified.
Returns:compression method
/** * Returns the compression method of this entry, or -1 if the * compression method has not been specified. * * @return compression method */
@Override public int getMethod() { return method; }
Sets the compression method of this entry.
Params:
  • method – compression method
/** * Sets the compression method of this entry. * * @param method compression method */
@Override public void setMethod(final int method) { if (method < 0) { throw new IllegalArgumentException("ZIP compression method can not be negative: " + method); } this.method = method; }
Retrieves the internal file attributes.
Returns:the internal file attributes
Since:1.1
/** * Retrieves the internal file attributes. * * @return the internal file attributes * @since 1.1 */
public int getInternalAttributes() { return internalAttributes; }
Sets the internal file attributes.
Params:
  • value – an int value
Since:1.1
/** * Sets the internal file attributes. * * @param value an <code>int</code> value * @since 1.1 */
public void setInternalAttributes(final int value) { internalAttributes = value; }
Retrieves the external file attributes.
Returns:the external file attributes
Since:1.1
/** * Retrieves the external file attributes. * * @return the external file attributes * @since 1.1 */
public long getExternalAttributes() { return externalAttributes; }
Sets the external file attributes.
Params:
  • value – an long value
Since:1.1
/** * Sets the external file attributes. * * @param value an <code>long</code> value * @since 1.1 */
public void setExternalAttributes(final long value) { externalAttributes = value; }
Sets Unix permissions in a way that is understood by Info-Zip's unzip command.
Params:
  • mode – an int value
Since:Ant 1.5.2
/** * Sets Unix permissions in a way that is understood by Info-Zip's * unzip command. * * @param mode an <code>int</code> value * @since Ant 1.5.2 */
public void setUnixMode(final int mode) { // CheckStyle:MagicNumberCheck OFF - no point setExternalAttributes((mode << SHORT_SHIFT) // MS-DOS read-only attribute | ((mode & 0200) == 0 ? 1 : 0) // MS-DOS directory flag | (isDirectory() ? 0x10 : 0)); // CheckStyle:MagicNumberCheck ON platform = PLATFORM_UNIX; }
Unix permission.
Returns:the unix permissions
Since:Ant 1.6
/** * Unix permission. * * @return the unix permissions * @since Ant 1.6 */
public int getUnixMode() { return platform != PLATFORM_UNIX ? 0 : (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK); }
Platform specification to put into the "version made by" part of the central file header.
Returns:PLATFORM_FAT unless setUnixMode has been called, in which case PLATFORM_UNIX will be returned.
Since:Ant 1.5.2
/** * Platform specification to put into the &quot;version made * by&quot; part of the central file header. * * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} * has been called, in which case PLATFORM_UNIX will be returned. * * @since Ant 1.5.2 */
public int getPlatform() { return platform; }
Set the platform (UNIX or FAT).
Params:
  • platform – an int value - 0 is FAT, 3 is UNIX
Since:1.9
/** * Set the platform (UNIX or FAT). * * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX * @since 1.9 */
protected void setPlatform(final int platform) { this.platform = platform; }
Replaces all currently attached extra fields with the new array.
Params:
  • fields – an array of extra fields
Since:1.1
/** * Replaces all currently attached extra fields with the new array. * * @param fields an array of extra fields * @since 1.1 */
public void setExtraFields(final ZipExtraField[] fields) { List<ZipExtraField> newFields = new ArrayList<>(); for (ZipExtraField field : fields) { if (field instanceof UnparseableExtraFieldData) { unparseableExtra = (UnparseableExtraFieldData) field; } else { newFields.add(field); } } extraFields = newFields.toArray(new ZipExtraField[newFields.size()]); setExtra(); }
Retrieves all extra fields that have been parsed successfully.
Returns:an array of the extra fields
/** * Retrieves all extra fields that have been parsed successfully. * * @return an array of the extra fields */
public ZipExtraField[] getExtraFields() { return getParseableExtraFields(); }
Retrieves extra fields.
Params:
Returns:an array of the extra fields
Since:1.1
/** * Retrieves extra fields. * * @param includeUnparseable whether to also return unparseable * extra fields as {@link UnparseableExtraFieldData} if such data * exists. * @return an array of the extra fields * @since 1.1 */
public ZipExtraField[] getExtraFields(final boolean includeUnparseable) { return includeUnparseable ? getAllExtraFields() : getParseableExtraFields(); } private ZipExtraField[] getParseableExtraFieldsNoCopy() { if (extraFields == null) { return noExtraFields; } return extraFields; } private ZipExtraField[] getParseableExtraFields() { final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy(); return (parseableExtraFields == extraFields) ? copyOf(parseableExtraFields) : parseableExtraFields; } private ZipExtraField[] copyOf(ZipExtraField[] src) { return copyOf(src, src.length); } private ZipExtraField[] copyOf(ZipExtraField[] src, int length) { ZipExtraField[] cpy = new ZipExtraField[length]; System.arraycopy(src, 0, cpy, 0, Math.min(src.length, length)); return cpy; } private ZipExtraField[] getMergedFields() { final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); zipExtraFields[extraFields.length] = unparseableExtra; return zipExtraFields; } private ZipExtraField[] getUnparseableOnly() { return unparseableExtra == null ? noExtraFields : new ZipExtraField[] {unparseableExtra}; } private ZipExtraField[] getAllExtraFields() { final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy(); return (allExtraFieldsNoCopy == extraFields) ? copyOf(allExtraFieldsNoCopy) : allExtraFieldsNoCopy; }
Get all extra fields, including unparseable ones.
Returns:An array of all extra fields. Not necessarily a copy of internal data structures, hence private method
/** * Get all extra fields, including unparseable ones. * * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method */
private ZipExtraField[] getAllExtraFieldsNoCopy() { if (extraFields == null) { return getUnparseableOnly(); } return unparseableExtra != null ? getMergedFields() : extraFields; }
Adds an extra field - replacing an already present extra field of the same type.

If no extra field of the same type exists, the field will be added as last field.

Params:
  • ze – an extra field
Since:1.1
/** * Adds an extra field - replacing an already present extra field * of the same type. * * <p>If no extra field of the same type exists, the field will be * added as last field.</p> * * @param ze an extra field * @since 1.1 */
public void addExtraField(final ZipExtraField ze) { if (ze instanceof UnparseableExtraFieldData) { unparseableExtra = (UnparseableExtraFieldData) ze; } else { if (extraFields == null) { extraFields = new ZipExtraField[] {ze}; } else { if (getExtraField(ze.getHeaderId()) != null) { removeExtraField(ze.getHeaderId()); } final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); zipExtraFields[extraFields.length] = ze; extraFields = zipExtraFields; } } setExtra(); }
Adds an extra field - replacing an already present extra field of the same type.

The new extra field will be the first one.

Params:
  • ze – an extra field
Since:1.1
/** * Adds an extra field - replacing an already present extra field * of the same type. * * <p>The new extra field will be the first one.</p> * * @param ze an extra field * @since 1.1 */
public void addAsFirstExtraField(final ZipExtraField ze) { if (ze instanceof UnparseableExtraFieldData) { unparseableExtra = (UnparseableExtraFieldData) ze; } else { if (getExtraField(ze.getHeaderId()) != null) { removeExtraField(ze.getHeaderId()); } ZipExtraField[] copy = extraFields; int newLen = extraFields != null ? extraFields.length + 1 : 1; extraFields = new ZipExtraField[newLen]; extraFields[0] = ze; if (copy != null) { System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1); } } setExtra(); }
Remove an extra field.
Params:
  • type – the type of extra field to remove
Since:1.1
/** * Remove an extra field. * * @param type the type of extra field to remove * @since 1.1 */
public void removeExtraField(final ZipShort type) { if (extraFields == null) { throw new NoSuchElementException(); } List<ZipExtraField> newResult = new ArrayList<>(); for (ZipExtraField extraField : extraFields) { if (!type.equals(extraField.getHeaderId())) { newResult.add(extraField); } } if (extraFields.length == newResult.size()) { throw new NoSuchElementException(); } extraFields = newResult.toArray(new ZipExtraField[newResult.size()]); setExtra(); }
Removes unparseable extra field data.
/** * Removes unparseable extra field data. */
public void removeUnparseableExtraFieldData() { if (unparseableExtra == null) { throw new NoSuchElementException(); } unparseableExtra = null; setExtra(); }
Looks up an extra field by its header id.
Params:
  • type – ZipShort
Returns:null if no such field exists.
/** * Looks up an extra field by its header id. * * @param type ZipShort * @return null if no such field exists. */
public ZipExtraField getExtraField(final ZipShort type) { if (extraFields != null) { for (ZipExtraField extraField : extraFields) { if (type.equals(extraField.getHeaderId())) { return extraField; } } } return null; }
Looks up extra field data that couldn't be parsed correctly.
Returns:null if no such field exists.
/** * Looks up extra field data that couldn't be parsed correctly. * * @return null if no such field exists. */
public UnparseableExtraFieldData getUnparseableExtraFieldData() { return unparseableExtra; }
Parses the given bytes as extra field data and consumes any unparseable data as an UnparseableExtraFieldData instance.
Params:
  • extra – an array of bytes to be parsed into extra fields
Throws:
Since:1.1
/** * Parses the given bytes as extra field data and consumes any * unparseable data as an {@link UnparseableExtraFieldData} * instance. * * @param extra an array of bytes to be parsed into extra fields * @throws RuntimeException if the bytes cannot be parsed * @throws RuntimeException on error * @since 1.1 */
@Override public void setExtra(final byte[] extra) throws RuntimeException { try { final ZipExtraField[] local = ExtraFieldUtils.parse(extra, true, ExtraFieldUtils.UnparseableExtraField.READ); mergeExtraFields(local, true); } catch (final ZipException e) { // actually this is not be possible as of Ant 1.8.1 throw new RuntimeException("Error parsing extra fields for entry: " //NOSONAR + getName() + " - " + e.getMessage(), e); } }
Unfortunately java.util.zip.ZipOutputStream seems to access the extra data directly, so overriding getExtra doesn't help - we need to modify super's data directly.
Since:1.1
/** * Unfortunately {@link java.util.zip.ZipOutputStream * java.util.zip.ZipOutputStream} seems to access the extra data * directly, so overriding getExtra doesn't help - we need to * modify super's data directly. * * @since 1.1 */
protected void setExtra() { super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true))); }
Sets the central directory part of extra fields.
Params:
  • b – boolean
/** * Sets the central directory part of extra fields. * * @param b boolean */
public void setCentralDirectoryExtra(final byte[] b) { try { final ZipExtraField[] central = ExtraFieldUtils.parse(b, false, ExtraFieldUtils.UnparseableExtraField.READ); mergeExtraFields(central, false); } catch (final ZipException e) { throw new RuntimeException(e.getMessage(), e); //NOSONAR } }
Retrieves the extra data for the local file data.
Returns:the extra data for local file
Since:1.1
/** * Retrieves the extra data for the local file data. * * @return the extra data for local file * @since 1.1 */
public byte[] getLocalFileDataExtra() { final byte[] extra = getExtra(); return extra != null ? extra : EMPTY; }
Retrieves the extra data for the central directory.
Returns:the central directory extra data
Since:1.1
/** * Retrieves the extra data for the central directory. * * @return the central directory extra data * @since 1.1 */
public byte[] getCentralDirectoryExtra() { return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true)); }
Make this class work in JDK 1.1 like a 1.2 class.

This either stores the size for later usage or invokes setCompressedSize via reflection.

Params:
  • size – the size to use
Deprecated:since 1.7. Use setCompressedSize directly.
Since:1.2
/** * Make this class work in JDK 1.1 like a 1.2 class. * * <p>This either stores the size for later usage or invokes * setCompressedSize via reflection.</p> * * @param size the size to use * @deprecated since 1.7. * Use setCompressedSize directly. * @since 1.2 */
@Deprecated public void setComprSize(final long size) { setCompressedSize(size); }
Get the name of the entry.
Returns:the entry name
Since:1.9
/** * Get the name of the entry. * * @return the entry name * @since 1.9 */
@Override public String getName() { return name == null ? super.getName() : name; }
Is this entry a directory?
Returns:true if the entry is a directory
Since:1.10
/** * Is this entry a directory? * * @return true if the entry is a directory * @since 1.10 */
@Override public boolean isDirectory() { return getName().endsWith("/"); }
Set the name of the entry.
Params:
  • name – the name to use
/** * Set the name of the entry. * * @param name the name to use */
protected void setName(String name) { if (name != null && getPlatform() == PLATFORM_FAT && !name.contains("/")) { name = name.replace('\\', '/'); } this.name = name; }
Gets the uncompressed size of the entry data.
Returns:the entry size
/** * Gets the uncompressed size of the entry data. * * @return the entry size */
@Override public long getSize() { return size; }
Sets the uncompressed size of the entry data.
Params:
  • size – the uncompressed size in bytes
Throws:
/** * Sets the uncompressed size of the entry data. * * @param size the uncompressed size in bytes * @exception IllegalArgumentException if the specified size is less * than 0 */
@Override public void setSize(final long size) { if (size < 0) { throw new IllegalArgumentException("invalid entry size"); } this.size = size; }
Sets the name using the raw bytes and the string created from it by guessing or using the configured encoding.
Params:
  • name – the name to use created from the raw bytes using the guessed or configured encoding
  • rawName – the bytes originally read as name from the archive
/** * Sets the name using the raw bytes and the string created from * it by guessing or using the configured encoding. * * @param name the name to use created from the raw bytes using * the guessed or configured encoding * @param rawName the bytes originally read as name from the * archive */
protected void setName(final String name, final byte[] rawName) { setName(name); this.rawName = rawName; }
Returns the raw bytes that made up the name before it has been converted using the configured or guessed encoding.

This method will return null if this instance has not been read from an archive.

Returns:byte[]
/** * Returns the raw bytes that made up the name before it has been * converted using the configured or guessed encoding. * * <p>This method will return null if this instance has not been * read from an archive.</p> * * @return byte[] */
public byte[] getRawName() { if (rawName != null) { final byte[] b = new byte[rawName.length]; System.arraycopy(rawName, 0, b, 0, rawName.length); return b; } return null; }
Get the hashCode of the entry. This uses the name as the hashcode.
Returns:a hashcode.
Since:Ant 1.7
/** * Get the hashCode of the entry. * This uses the name as the hashcode. * * @return a hashcode. * @since Ant 1.7 */
@Override public int hashCode() { // this method has severe consequences on performance. We cannot rely // on the super.hashCode() method since super.getName() always return // the empty string in the current implementation (there's no setter) // so it is basically draining the performance of a hashmap lookup return getName().hashCode(); }
The "general purpose bit" field.
Returns:GeneralPurposeBit
/** * The "general purpose bit" field. * * @return GeneralPurposeBit */
public GeneralPurposeBit getGeneralPurposeBit() { return gpb; }
The "general purpose bit" field.
Params:
  • b – GeneralPurposeBit
/** * The "general purpose bit" field. * * @param b GeneralPurposeBit */
public void setGeneralPurposeBit(final GeneralPurposeBit b) { gpb = b; }
If there are no extra fields, use the given fields as new extra data - otherwise merge the fields assuming the existing fields and the new fields stem from different locations inside the archive.
Params:
  • f – the extra fields to merge
  • local – whether the new fields originate from local data
/** * If there are no extra fields, use the given fields as new extra * data - otherwise merge the fields assuming the existing fields * and the new fields stem from different locations inside the * archive. * * @param f the extra fields to merge * @param local whether the new fields originate from local data */
private void mergeExtraFields(final ZipExtraField[] f, final boolean local) throws ZipException { if (extraFields == null) { setExtraFields(f); } else { for (final ZipExtraField element : f) { ZipExtraField existing; if (element instanceof UnparseableExtraFieldData) { existing = unparseableExtra; } else { existing = getExtraField(element.getHeaderId()); } if (existing == null) { addExtraField(element); } else { if (local || !(existing instanceof CentralDirectoryParsingZipExtraField)) { final byte[] b = element.getLocalFileDataData(); existing.parseFromLocalFileData(b, 0, b.length); } else { final byte[] b = element.getCentralDirectoryData(); ((CentralDirectoryParsingZipExtraField) existing) .parseFromCentralDirectoryData(b, 0, b.length); } } } setExtra(); } }
{@inheritDoc}
/** {@inheritDoc} */
public Date getLastModifiedDate() { return new Date(getTime()); } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } final ZipEntry other = (ZipEntry) obj; final String myName = getName(); final String otherName = other.getName(); if (myName == null) { if (otherName != null) { return false; } } else if (!myName.equals(otherName)) { return false; } String myComment = getComment(); String otherComment = other.getComment(); if (myComment == null) { myComment = ""; } if (otherComment == null) { otherComment = ""; } return getTime() == other.getTime() && myComment.equals(otherComment) && getInternalAttributes() == other.getInternalAttributes() && getPlatform() == other.getPlatform() && getExternalAttributes() == other.getExternalAttributes() && getMethod() == other.getMethod() && getSize() == other.getSize() && getCrc() == other.getCrc() && getCompressedSize() == other.getCompressedSize() && Arrays.equals(getCentralDirectoryExtra(), other.getCentralDirectoryExtra()) && Arrays.equals(getLocalFileDataExtra(), other.getLocalFileDataExtra()) && gpb.equals(other.gpb); } }