    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 * http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.archivers.zip;
    020
    021import java.io.Serializable;
    022import java.nio.file.attribute.FileTime;
    023import java.util.Arrays;
    024import java.util.Date;
    025import java.util.Objects;
    026import java.util.zip.ZipException;
    027
    028import org.apache.commons.compress.utils.TimeUtils;
    029import org.apache.commons.io.file.attribute.FileTimes;
    030
    031/**
    032 * <p>
    033 * An extra field that stores additional file and directory timestamp data for ZIP entries. Each ZIP entry can include up to three timestamps (modify, access,
    034 * create*). The timestamps are stored as 32 bit signed integers representing seconds since UNIX epoch (Jan 1st, 1970, UTC). This field improves on ZIP's
    035 * default timestamp granularity, since it allows one to store additional timestamps, and, in addition, the timestamps are stored using per-second granularity
    036 * (zip's default behavior can only store timestamps to the nearest <em>even</em> second).
    037 * </p>
    038 * <p>
    039 * Unfortunately, 32 (signed) bits can only store dates up to the year 2037, and so this extra field will eventually be obsolete. Enjoy it while it lasts!
    040 * </p>
    041 * <ul>
    042 * <li><b>modifyTime:</b> most recent time of file/directory modification (or file/dir creation if the entry has not been modified since it was created).</li>
    043 * <li><b>accessTime:</b> most recent time file/directory was opened (e.g., read from disk). Many people disable their operating systems from updating this
    044 * value using the NOATIME mount option to optimize disk behavior, and thus it's not always reliable. In those cases it's always equal to modifyTime.</li>
    045 * <li><b>*createTime:</b> modern Linux file systems (e.g., ext2 and newer) do not appear to store a value like this, and so it's usually omitted altogether in
    046 * the ZIP extra field. Perhaps other UNIX systems track this.</li>
    047 * </ul>
    048 * <p>
    049 * We're using the field definition given in Info-Zip's source archive: zip-3.0.tar.gz/proginfo/extrafld.txt
    050 * </p>
    051 *
    052 * <pre>
    053 * Value         Size        Description
    054 * -----         ----        -----------
    055 * 0x5455        Short       tag for this extra block type ("UT")
    056 * TSize         Short       total data size for this block
    057 * Flags         Byte        info bits
    058 * (ModTime)     Long        time of last modification (UTC/GMT)
    059 * (AcTime)      Long        time of last access (UTC/GMT)
    060 * (CrTime)      Long        time of original creation (UTC/GMT)
    061 *
    062 * Central-header version:
    063 *
    064 * Value         Size        Description
    065 * -----         ----        -----------
    066 * 0x5455        Short       tag for this extra block type ("UT")
    067 * TSize         Short       total data size for this block
    068 * Flags         Byte        info bits (refers to local header!)
    069 * (ModTime)     Long        time of last modification (UTC/GMT)
    070 * </pre>
    071 *
    072 * @since 1.5
    073 */
    074public class X5455_ExtendedTimestamp implements ZipExtraField, Cloneable, Serializable {
    075    private static final long serialVersionUID = 1L;
    076
    077    /**
    078     * The header ID for this extra field.
    079     *
    080     * @since 1.23
    081     */
    082    public static final ZipShort HEADER_ID = new ZipShort(0x5455);
    083
    084    /**
    085     * The bit set inside the flags by when the last modification time is present in this extra field.
    086     */
    087    public static final byte MODIFY_TIME_BIT = 1;
    088    /**
    089     * The bit set inside the flags by when the lasr access time is present in this extra field.
    090     */
    091    public static final byte ACCESS_TIME_BIT = 2;
    092    /**
    093     * The bit set inside the flags by when the original creation time is present in this extra field.
    094     */
    095    public static final byte CREATE_TIME_BIT = 4;
    096
    097    /**
    098     * Utility method converts java.util.Date (milliseconds since epoch) into a ZipLong (seconds since epoch).
    099     * <p/>
    100     * Also makes sure the converted ZipLong is not too big to fit in 32 unsigned bits.
    101     *
    102     * @param d java.util.Date to convert to ZipLong
    103     * @return ZipLong
    104     */
    105    private static ZipLong dateToZipLong(final Date d) {
    106        if (d == null) {
    107            return null;
    108        }
    109        return unixTimeToZipLong(d.getTime() / 1000);
    110    }
    111
    112    /**
    113     * Utility method converts {@link FileTime} into a ZipLong (seconds since epoch).
    114     * <p/>
    115     * Also makes sure the converted ZipLong is not too big to fit in 32 unsigned bits.
    116     *
    117     * @param time {@link FileTime} to convert to ZipLong
    118     * @return ZipLong
    119     */
    120    private static ZipLong fileTimeToZipLong(final FileTime time) {
    121        return time == null ? null : unixTimeToZipLong(TimeUtils.toUnixTime(time));
    122    }
    123
    124    private static FileTime unixTimeToFileTime(final ZipLong unixTime) {
    125        return unixTime != null ? FileTimes.fromUnixTime(unixTime.getIntValue()) : null;
    126    }
    127    // The 3 boolean fields (below) come from this flag's byte. The remaining 5 bits
    128    // are ignored according to the current version of the spec (December 2012).
    129
    130    private static ZipLong unixTimeToZipLong(final long unixTime) {
    131        if (!FileTimes.isUnixTime(unixTime)) {
    132            throw new IllegalArgumentException("X5455 timestamps must fit in a signed 32 bit integer: " + unixTime);
    133        }
    134        return new ZipLong(unixTime);
    135    }
    136
    137    private static Date zipLongToDate(final ZipLong unixTime) {
    138        return unixTime != null ? new Date(unixTime.getIntValue() * 1000L) : null;
    139    }
    140
    141    private byte flags;
    142    // Note: even if bit1 and bit2 are set, the Central data will still not contain
    143    // access/create fields: only local data ever holds those! This causes
    144    // some of our implementation to look a little odd, with seemingly spurious
    145    // != null and length checks.
    146    private boolean bit0_modifyTimePresent;
    147    private boolean bit1_accessTimePresent;
    148
    149    private boolean bit2_createTimePresent;
    150
    151    private ZipLong modifyTime;
    152
    153    private ZipLong accessTime;
    154
    155    private ZipLong createTime;
    156
    157    /**
    158     * Constructor for X5455_ExtendedTimestamp.
    159     */
    160    public X5455_ExtendedTimestamp() {
    161    }
    162
    163    @Override
    164    public Object clone() throws CloneNotSupportedException {
    165        return super.clone();
    166    }
    167
    168    @Override
    169    public boolean equals(final Object o) {
    170        if (o instanceof X5455_ExtendedTimestamp) {
    171            final X5455_ExtendedTimestamp xf = (X5455_ExtendedTimestamp) o;
    172
    173            // The ZipLong==ZipLong clauses handle the cases where both are null.
    174            // and only last 3 bits of flags matter.
    175            return (flags & 0x07) == (xf.flags & 0x07) && Objects.equals(modifyTime, xf.modifyTime) && Objects.equals(accessTime, xf.accessTime)
    176                    && Objects.equals(createTime, xf.createTime);
    177        }
    178        return false;
    179    }
    180
    181    /**
    182     * Gets the access time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry. The milliseconds are always zeroed
    183     * out, since the underlying data offers only per-second precision.
    184     *
    185     * @return modify time as {@link FileTime} or null.
    186     * @since 1.23
    187     */
    188    public FileTime getAccessFileTime() {
    189        return unixTimeToFileTime(accessTime);
    190    }
    191
    192    /**
    193     * Gets the access time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry. The milliseconds are always zeroed out,
    194     * since the underlying data offers only per-second precision.
    195     *
    196     * @return access time as java.util.Date or null.
    197     */
    198    public Date getAccessJavaTime() {
    199        return zipLongToDate(accessTime);
    200    }
    201
    202    /**
    203     * Gets the access time (seconds since epoch) of this ZIP entry as a ZipLong object, or null if no such timestamp exists in the ZIP entry.
    204     *
    205     * @return access time (seconds since epoch) or null.
    206     */
    207    public ZipLong getAccessTime() {
    208        return accessTime;
    209    }
    210
    211    /**
    212     * Gets the actual data to put into central directory data - without Header-ID or length specifier.
    213     *
    214     * @return the central directory data
    215     */
    216    @Override
    217    public byte[] getCentralDirectoryData() {
    218        // Truncate out create & access time (last 8 bytes) from
    219        // the copy of the local data we obtained:
    220        return Arrays.copyOf(getLocalFileDataData(), getCentralDirectoryLength().getValue());
    221    }
    222
    223    /**
    224     * Gets the length of the extra field in the local file data - without Header-ID or length specifier.
    225     *
    226     * <p>
    227     * For X5455 the central length is often smaller than the local length, because central cannot contain access or create timestamps.
    228     * </p>
    229     *
    230     * @return a {@code ZipShort} for the length of the data of this extra field
    231     */
    232    @Override
    233    public ZipShort getCentralDirectoryLength() {
    234        return new ZipShort(1 + (bit0_modifyTimePresent ? 4 : 0));
    235    }
    236
    237    /**
    238     * Gets the create time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry. The milliseconds are always zeroed
    239     * out, since the underlying data offers only per-second precision.
    240     *
    241     * @return modify time as {@link FileTime} or null.
    242     * @since 1.23
    243     */
    244    public FileTime getCreateFileTime() {
    245        return unixTimeToFileTime(createTime);
    246    }
    247
    248    /**
    249     * <p>
    250     * Gets the create time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry. The milliseconds are always zeroed out,
    251     * since the underlying data offers only per-second precision.
    252     * </p>
    253     * <p>
    254     * Note: modern Linux file systems (e.g., ext2) do not appear to store a "create time" value, and so it's usually omitted altogether in the ZIP extra field.
    255     * Perhaps other UNIX systems track this.
    256     * </p>
    257     *
    258     * @return create time as java.util.Date or null.
    259     */
    260    public Date getCreateJavaTime() {
    261        return zipLongToDate(createTime);
    262    }
    263
    264    /**
    265     * <p>
    266     * Gets the create time (seconds since epoch) of this ZIP entry as a ZipLong object, or null if no such timestamp exists in the ZIP entry.
    267     * </p>
    268     * <p>
    269     * Note: modern Linux file systems (e.g., ext2) do not appear to store a "create time" value, and so it's usually omitted altogether in the ZIP extra field.
    270     * Perhaps other UNIX systems track this.
    271     * </p>
    272     *
    273     * @return create time (seconds since epoch) or null.
    274     */
    275    public ZipLong getCreateTime() {
    276        return createTime;
    277    }
    278
    279    /**
    280     * Gets flags byte. The flags byte tells us which of the three datestamp fields are present in the data:
    281     *
    282     * <pre>
    283     * bit0 - modify time
    284     * bit1 - access time
    285     * bit2 - create time
    286     * </pre>
    287     *
    288     * Only first 3 bits of flags are used according to the latest version of the spec (December 2012).
    289     *
    290     * @return flags byte indicating which of the three datestamp fields are present.
    291     */
    292    public byte getFlags() {
    293        return flags;
    294    }
    295
    296    /**
    297     * Gets the Header-ID.
    298     *
    299     * @return the value for the header id for this extrafield
    300     */
    301    @Override
    302    public ZipShort getHeaderId() {
    303        return HEADER_ID;
    304    }
    305
    306    /**
    307     * Gets the actual data to put into local file data - without Header-ID or length specifier.
    308     *
    309     * @return get the data
    310     */
    311    @Override
    312    public byte[] getLocalFileDataData() {
    313        final byte[] data = new byte[getLocalFileDataLength().getValue()];
    314        int pos = 0;
    315        data[pos++] = 0;
    316        if (bit0_modifyTimePresent) {
    317            data[0] |= MODIFY_TIME_BIT;
    318            System.arraycopy(modifyTime.getBytes(), 0, data, pos, 4);
    319            pos += 4;
    320        }
    321        if (bit1_accessTimePresent && accessTime != null) {
    322            data[0] |= ACCESS_TIME_BIT;
    323            System.arraycopy(accessTime.getBytes(), 0, data, pos, 4);
    324            pos += 4;
    325        }
    326        if (bit2_createTimePresent && createTime != null) {
    327            data[0] |= CREATE_TIME_BIT;
    328            System.arraycopy(createTime.getBytes(), 0, data, pos, 4);
    329            pos += 4; // NOSONAR - assignment as documentation
    330        }
    331        return data;
    332    }
    333
    334    /**
    335     * Gets the length of the extra field in the local file data - without Header-ID or length specifier.
    336     *
    337     * @return a {@code ZipShort} for the length of the data of this extra field
    338     */
    339    @Override
    340    public ZipShort getLocalFileDataLength() {
    341        return new ZipShort(1 + (bit0_modifyTimePresent ? 4 : 0) + (bit1_accessTimePresent && accessTime != null ? 4 : 0)
    342                + (bit2_createTimePresent && createTime != null ? 4 : 0));
    343    }
    344
    345    /**
    346     * Gets the modify time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry. The milliseconds are always zeroed
    347     * out, since the underlying data offers only per-second precision.
    348     *
    349     * @return modify time as {@link FileTime} or null.
    350     * @since 1.23
    351     */
    352    public FileTime getModifyFileTime() {
    353        return unixTimeToFileTime(modifyTime);
    354    }
    355
    356    /**
    357     * Gets the modify time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry. The milliseconds are always zeroed out,
    358     * since the underlying data offers only per-second precision.
    359     *
    360     * @return modify time as java.util.Date or null.
    361     */
    362    public Date getModifyJavaTime() {
    363        return zipLongToDate(modifyTime);
    364    }
    365
    366    /**
    367     * Gets the modify time (seconds since epoch) of this ZIP entry as a ZipLong object, or null if no such timestamp exists in the ZIP entry.
    368     *
    369     * @return modify time (seconds since epoch) or null.
    370     */
    371    public ZipLong getModifyTime() {
    372        return modifyTime;
    373    }
    374
    375    @Override
    376    public int hashCode() {
    377        int hc = -123 * (flags & 0x07); // only last 3 bits of flags matter
    378        if (modifyTime != null) {
    379            hc ^= modifyTime.hashCode();
    380        }
    381        if (accessTime != null) {
    382            // Since accessTime is often same as modifyTime,
    383            // this prevents them from XOR negating each other.
    384            hc ^= Integer.rotateLeft(accessTime.hashCode(), 11);
    385        }
    386        if (createTime != null) {
    387            hc ^= Integer.rotateLeft(createTime.hashCode(), 22);
    388        }
    389        return hc;
    390    }
    391
    392    /**
    393     * Tests whether bit0 of the flags byte is set or not, which should correspond to the presence or absence of a modify timestamp in this particular ZIP
    394     * entry.
    395     *
    396     * @return true if bit0 of the flags byte is set.
    397     */
    398    public boolean isBit0_modifyTimePresent() {
    399        return bit0_modifyTimePresent;
    400    }
    401
    402    /**
    403     * Tests whether bit1 of the flags byte is set or not, which should correspond to the presence or absence of a "last access" timestamp in this particular
    404     * ZIP entry.
    405     *
    406     * @return true if bit1 of the flags byte is set.
    407     */
    408    public boolean isBit1_accessTimePresent() {
    409        return bit1_accessTimePresent;
    410    }
    411
    412    /**
    413     * Tests whether bit2 of the flags byte is set or not, which should correspond to the presence or absence of a create timestamp in this particular ZIP
    414     * entry.
    415     *
    416     * @return true if bit2 of the flags byte is set.
    417     */
    418    public boolean isBit2_createTimePresent() {
    419        return bit2_createTimePresent;
    420    }
    421
    422    /**
    423     * Doesn't do anything special since this class always uses the same parsing logic for both central directory and local file data.
    424     */
    425    @Override
    426    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
    427        reset();
    428        parseFromLocalFileData(buffer, offset, length);
    429    }
    430
    431    /**
    432     * Populate data from this array as if it was in local file data.
    433     *
    434     * @param data   an array of bytes
    435     * @param offset the start offset
    436     * @param length the number of bytes in the array from offset
    437     * @throws java.util.zip.ZipException on error
    438     */
    439    @Override
    440    public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
    441        reset();
    442        if (length < 1) {
    443            throw new ZipException("X5455_ExtendedTimestamp too short, only " + length + " bytes");
    444        }
    445        final int len = offset + length;
    446        setFlags(data[offset++]);
    447        if (bit0_modifyTimePresent && offset + 4 <= len) {
    448            modifyTime = new ZipLong(data, offset);
    449            offset += 4;
    450        } else {
    451            bit0_modifyTimePresent = false;
    452        }
    453        if (bit1_accessTimePresent && offset + 4 <= len) {
    454            accessTime = new ZipLong(data, offset);
    455            offset += 4;
    456        } else {
    457            bit1_accessTimePresent = false;
    458        }
    459        if (bit2_createTimePresent && offset + 4 <= len) {
    460            createTime = new ZipLong(data, offset);
    461            offset += 4; // NOSONAR - assignment as documentation
    462        } else {
    463            bit2_createTimePresent = false;
    464        }
    465    }
    466
    467    /**
    468     * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
    469     */
    470    private void reset() {
    471        setFlags((byte) 0);
    472        this.modifyTime = null;
    473        this.accessTime = null;
    474        this.createTime = null;
    475    }
    476
    477    /**
    478     * <p>
    479     * Sets the acccess time as a {@link FileTime} of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
    480     * </p>
    481     * <p>
    482     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    483     * flags is also set.
    484     * </p>
    485     *
    486     * @param time access time as {@link FileTime}
    487     * @since 1.23
    488     */
    489    public void setAccessFileTime(final FileTime time) {
    490        setAccessTime(fileTimeToZipLong(time));
    491    }
    492
    493    /**
    494     * <p>
    495     * Sets the access time as a java.util.Date of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
    496     * </p>
    497     * <p>
    498     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    499     * flags is also set.
    500     * </p>
    501     *
    502     * @param d access time as java.util.Date
    503     */
    504    public void setAccessJavaTime(final Date d) {
    505        setAccessTime(dateToZipLong(d));
    506    }
    507
    508    /**
    509     * <p>
    510     * Sets the access time (seconds since epoch) of this ZIP entry using a ZipLong object
    511     * </p>
    512     * <p>
    513     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    514     * flags is also set.
    515     * </p>
    516     *
    517     * @param l ZipLong of the access time (seconds per epoch)
    518     */
    519    public void setAccessTime(final ZipLong l) {
    520        bit1_accessTimePresent = l != null;
    521        flags = (byte) (l != null ? flags | ACCESS_TIME_BIT : flags & ~ACCESS_TIME_BIT);
    522        this.accessTime = l;
    523    }
    524
    525    /**
    526     * <p>
    527     * Sets the create time as a {@link FileTime} of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
    528     * </p>
    529     * <p>
    530     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    531     * flags is also set.
    532     * </p>
    533     *
    534     * @param time create time as {@link FileTime}
    535     * @since 1.23
    536     */
    537    public void setCreateFileTime(final FileTime time) {
    538        setCreateTime(fileTimeToZipLong(time));
    539    }
    540
    541    /**
    542     * <p>
    543     * Sets the create time as a java.util.Date of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
    544     * </p>
    545     * <p>
    546     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    547     * flags is also set.
    548     * </p>
    549     *
    550     * @param d create time as java.util.Date
    551     */
    552    public void setCreateJavaTime(final Date d) {
    553        setCreateTime(dateToZipLong(d));
    554    }
    555
    556    /**
    557     * <p>
    558     * Sets the create time (seconds since epoch) of this ZIP entry using a ZipLong object
    559     * </p>
    560     * <p>
    561     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    562     * flags is also set.
    563     * </p>
    564     *
    565     * @param l ZipLong of the create time (seconds per epoch)
    566     */
    567    public void setCreateTime(final ZipLong l) {
    568        bit2_createTimePresent = l != null;
    569        flags = (byte) (l != null ? flags | CREATE_TIME_BIT : flags & ~CREATE_TIME_BIT);
    570        this.createTime = l;
    571    }
    572
    573    /**
    574     * Sets flags byte. The flags byte tells us which of the three datestamp fields are present in the data:
    575     *
    576     * <pre>
    577     * bit0 - modify time
    578     * bit1 - access time
    579     * bit2 - create time
    580     * </pre>
    581     *
    582     * Only first 3 bits of flags are used according to the latest version of the spec (December 2012).
    583     *
    584     * @param flags flags byte indicating which of the three datestamp fields are present.
    585     */
    586    public void setFlags(final byte flags) {
    587        this.flags = flags;
    588        this.bit0_modifyTimePresent = (flags & MODIFY_TIME_BIT) == MODIFY_TIME_BIT;
    589        this.bit1_accessTimePresent = (flags & ACCESS_TIME_BIT) == ACCESS_TIME_BIT;
    590        this.bit2_createTimePresent = (flags & CREATE_TIME_BIT) == CREATE_TIME_BIT;
    591    }
    592
    593    /**
    594     * <p>
    595     * Sets the modify time as a {@link FileTime} of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
    596     * </p>
    597     * <p>
    598     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    599     * flags is also set.
    600     * </p>
    601     *
    602     * @param time modify time as {@link FileTime}
    603     * @since 1.23
    604     */
    605    public void setModifyFileTime(final FileTime time) {
    606        setModifyTime(fileTimeToZipLong(time));
    607    }
    608
    609    /**
    610     * <p>
    611     * Sets the modify time as a java.util.Date of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
    612     * </p>
    613     * <p>
    614     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    615     * flags is also set.
    616     * </p>
    617     *
    618     * @param d modify time as java.util.Date
    619     */
    620    public void setModifyJavaTime(final Date d) {
    621        setModifyTime(dateToZipLong(d));
    622    }
    623
    624    /**
    625     * <p>
    626     * Sets the modify time (seconds since epoch) of this ZIP entry using a ZipLong object.
    627     * </p>
    628     * <p>
    629     * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
    630     * flags is also set.
    631     * </p>
    632     *
    633     * @param l ZipLong of the modify time (seconds per epoch)
    634     */
    635    public void setModifyTime(final ZipLong l) {
    636        bit0_modifyTimePresent = l != null;
    637        flags = (byte) (l != null ? flags | MODIFY_TIME_BIT : flags & ~MODIFY_TIME_BIT);
    638        this.modifyTime = l;
    639    }
    640
    641    /**
    642     * Returns a String representation of this class useful for debugging purposes.
    643     *
    644     * @return A String representation of this class useful for debugging purposes.
    645     */
    646    @Override
    647    public String toString() {
    648        final StringBuilder buf = new StringBuilder();
    649        buf.append("0x5455 Zip Extra Field: Flags=");
    650        buf.append(Integer.toBinaryString(ZipUtil.unsignedIntToSignedByte(flags))).append(" ");
    651        if (bit0_modifyTimePresent && modifyTime != null) {
    652            final Date m = getModifyJavaTime();
    653            buf.append(" Modify:[").append(m).append("] ");
    654        }
    655        if (bit1_accessTimePresent && accessTime != null) {
    656            final Date a = getAccessJavaTime();
    657            buf.append(" Access:[").append(a).append("] ");
    658        }
    659        if (bit2_createTimePresent && createTime != null) {
    660            final Date c = getCreateJavaTime();
    661            buf.append(" Create:[").append(c).append("] ");
    662        }
    663        return buf.toString();
    664    }
    665
    666}