    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 *
    017 */
    018package org.apache.commons.compress.archivers.sevenz;
    019
    020import java.util.Calendar;
    021import java.util.Collections;
    022import java.util.Date;
    023import java.util.Iterator;
    024import java.util.LinkedList;
    025import java.util.Objects;
    026import java.util.TimeZone;
    027
    028import org.apache.commons.compress.archivers.ArchiveEntry;
    029
    030/**
    031 * An entry in a 7z archive.
    032 *
    033 * @NotThreadSafe
    034 * @since 1.6
    035 */
    036public class SevenZArchiveEntry implements ArchiveEntry {
    037    private String name;
    038    private boolean hasStream;
    039    private boolean isDirectory;
    040    private boolean isAntiItem;
    041    private boolean hasCreationDate;
    042    private boolean hasLastModifiedDate;
    043    private boolean hasAccessDate;
    044    private long creationDate;
    045    private long lastModifiedDate;
    046    private long accessDate;
    047    private boolean hasWindowsAttributes;
    048    private int windowsAttributes;
    049    private boolean hasCrc;
    050    private long crc, compressedCrc;
    051    private long size, compressedSize;
    052    private Iterable<? extends SevenZMethodConfiguration> contentMethods;
    053    static final SevenZArchiveEntry[] EMPTY_SEVEN_Z_ARCHIVE_ENTRY_ARRAY = new SevenZArchiveEntry[0];
    054
    055    public SevenZArchiveEntry() {
    056    }
    057
    058    /**
    059     * Get this entry's name.
    060     *
    061     * <p>This method returns the raw name as it is stored inside of the archive.</p>
    062     *
    063     * @return This entry's name.
    064     */
    065    @Override
    066    public String getName() {
    067        return name;
    068    }
    069
    070    /**
    071     * Set this entry's name.
    072     *
    073     * @param name This entry's new name.
    074     */
    075    public void setName(final String name) {
    076        this.name = name;
    077    }
    078
    079    /**
    080     * Whether there is any content associated with this entry.
    081     * @return whether there is any content associated with this entry.
    082     */
    083    public boolean hasStream() {
    084        return hasStream;
    085    }
    086
    087    /**
    088     * Sets whether there is any content associated with this entry.
    089     * @param hasStream whether there is any content associated with this entry.
    090     */
    091    public void setHasStream(final boolean hasStream) {
    092        this.hasStream = hasStream;
    093    }
    094
    095    /**
    096     * Return whether or not this entry represents a directory.
    097     *
    098     * @return True if this entry is a directory.
    099     */
    100    @Override
    101    public boolean isDirectory() {
    102        return isDirectory;
    103    }
    104
    105    /**
    106     * Sets whether or not this entry represents a directory.
    107     *
    108     * @param isDirectory True if this entry is a directory.
    109     */
    110    public void setDirectory(final boolean isDirectory) {
    111        this.isDirectory = isDirectory;
    112    }
    113
    114    /**
    115     * Indicates whether this is an "anti-item" used in differential backups,
    116     * meaning it should delete the same file from a previous backup.
    117     * @return true if it is an anti-item, false otherwise
    118     */
    119    public boolean isAntiItem() {
    120        return isAntiItem;
    121    }
    122
    123    /**
    124     * Sets whether this is an "anti-item" used in differential backups,
    125     * meaning it should delete the same file from a previous backup.
    126     * @param isAntiItem true if it is an anti-item, false otherwise
    127     */
    128    public void setAntiItem(final boolean isAntiItem) {
    129        this.isAntiItem = isAntiItem;
    130    }
    131
    132    /**
    133     * Returns whether this entry has got a creation date at all.
    134     * @return whether the entry has got a creation date
    135     */
    136    public boolean getHasCreationDate() {
    137        return hasCreationDate;
    138    }
    139
    140    /**
    141     * Sets whether this entry has got a creation date at all.
    142     * @param hasCreationDate whether the entry has got a creation date
    143     */
    144    public void setHasCreationDate(final boolean hasCreationDate) {
    145        this.hasCreationDate = hasCreationDate;
    146    }
    147
    148    /**
    149     * Gets the creation date.
    150     * @throws UnsupportedOperationException if the entry hasn't got a
    151     * creation date.
    152     * @return the creation date
    153     */
    154    public Date getCreationDate() {
    155        if (hasCreationDate) {
    156            return ntfsTimeToJavaTime(creationDate);
    157        }
    158        throw new UnsupportedOperationException(
    159                "The entry doesn't have this timestamp");
    160    }
    161
    162    /**
    163     * Sets the creation date using NTFS time (100 nanosecond units
    164     * since 1 January 1601)
    165     * @param ntfsCreationDate the creation date
    166     */
    167    public void setCreationDate(final long ntfsCreationDate) {
    168        this.creationDate = ntfsCreationDate;
    169    }
    170
    171    /**
    172     * Sets the creation date,
    173     * @param creationDate the creation date
    174     */
    175    public void setCreationDate(final Date creationDate) {
    176        hasCreationDate = creationDate != null;
    177        if (hasCreationDate) {
    178            this.creationDate = javaTimeToNtfsTime(creationDate);
    179        }
    180    }
    181
    182    /**
    183     * Returns whether this entry has got a last modified date at all.
    184     * @return whether this entry has got a last modified date at all
    185     */
    186    public boolean getHasLastModifiedDate() {
    187        return hasLastModifiedDate;
    188    }
    189
    190    /**
    191     * Sets whether this entry has got a last modified date at all.
    192     * @param hasLastModifiedDate whether this entry has got a last
    193     * modified date at all
    194     */
    195    public void setHasLastModifiedDate(final boolean hasLastModifiedDate) {
    196        this.hasLastModifiedDate = hasLastModifiedDate;
    197    }
    198
    199    /**
    200     * Gets the last modified date.
    201     * @throws UnsupportedOperationException if the entry hasn't got a
    202     * last modified date.
    203     * @return the last modified date
    204     */
    205    @Override
    206    public Date getLastModifiedDate() {
    207        if (hasLastModifiedDate) {
    208            return ntfsTimeToJavaTime(lastModifiedDate);
    209        }
    210        throw new UnsupportedOperationException(
    211                "The entry doesn't have this timestamp");
    212    }
    213
    214    /**
    215     * Sets the last modified date using NTFS time (100 nanosecond
    216     * units since 1 January 1601)
    217     * @param ntfsLastModifiedDate the last modified date
    218     */
    219    public void setLastModifiedDate(final long ntfsLastModifiedDate) {
    220        this.lastModifiedDate = ntfsLastModifiedDate;
    221    }
    222
    223    /**
    224     * Sets the last modified date,
    225     * @param lastModifiedDate the last modified date
    226     */
    227    public void setLastModifiedDate(final Date lastModifiedDate) {
    228        hasLastModifiedDate = lastModifiedDate != null;
    229        if (hasLastModifiedDate) {
    230            this.lastModifiedDate = javaTimeToNtfsTime(lastModifiedDate);
    231        }
    232    }
    233
    234    /**
    235     * Returns whether this entry has got an access date at all.
    236     * @return whether this entry has got an access date at all.
    237     */
    238    public boolean getHasAccessDate() {
    239        return hasAccessDate;
    240    }
    241
    242    /**
    243     * Sets whether this entry has got an access date at all.
    244     * @param hasAcessDate whether this entry has got an access date at all.
    245     */
    246    public void setHasAccessDate(final boolean hasAcessDate) {
    247        this.hasAccessDate = hasAcessDate;
    248    }
    249
    250    /**
    251     * Gets the access date.
    252     * @throws UnsupportedOperationException if the entry hasn't got a
    253     * access date.
    254     * @return the access date
    255     */
    256    public Date getAccessDate() {
    257        if (hasAccessDate) {
    258            return ntfsTimeToJavaTime(accessDate);
    259        }
    260        throw new UnsupportedOperationException(
    261                "The entry doesn't have this timestamp");
    262    }
    263
    264    /**
    265     * Sets the access date using NTFS time (100 nanosecond units
    266     * since 1 January 1601)
    267     * @param ntfsAccessDate the access date
    268     */
    269    public void setAccessDate(final long ntfsAccessDate) {
    270        this.accessDate = ntfsAccessDate;
    271    }
    272
    273    /**
    274     * Sets the access date,
    275     * @param accessDate the access date
    276     */
    277    public void setAccessDate(final Date accessDate) {
    278        hasAccessDate = accessDate != null;
    279        if (hasAccessDate) {
    280            this.accessDate = javaTimeToNtfsTime(accessDate);
    281        }
    282    }
    283
    284    /**
    285     * Returns whether this entry has windows attributes.
    286     * @return whether this entry has windows attributes.
    287     */
    288    public boolean getHasWindowsAttributes() {
    289        return hasWindowsAttributes;
    290    }
    291
    292    /**
    293     * Sets whether this entry has windows attributes.
    294     * @param hasWindowsAttributes whether this entry has windows attributes.
    295     */
    296    public void setHasWindowsAttributes(final boolean hasWindowsAttributes) {
    297        this.hasWindowsAttributes = hasWindowsAttributes;
    298    }
    299
    300    /**
    301     * Gets the windows attributes.
    302     * @return the windows attributes
    303     */
    304    public int getWindowsAttributes() {
    305        return windowsAttributes;
    306    }
    307
    308    /**
    309     * Sets the windows attributes.
    310     * @param windowsAttributes the windows attributes
    311     */
    312    public void setWindowsAttributes(final int windowsAttributes) {
    313        this.windowsAttributes = windowsAttributes;
    314    }
    315
    316    /**
    317     * Returns whether this entry has got a crc.
    318     *
    319     * <p>In general entries without streams don't have a CRC either.</p>
    320     * @return whether this entry has got a crc.
    321     */
    322    public boolean getHasCrc() {
    323        return hasCrc;
    324    }
    325
    326    /**
    327     * Sets whether this entry has got a crc.
    328     * @param hasCrc whether this entry has got a crc.
    329     */
    330    public void setHasCrc(final boolean hasCrc) {
    331        this.hasCrc = hasCrc;
    332    }
    333
    334    /**
    335     * Gets the CRC.
    336     * @deprecated use getCrcValue instead.
    337     * @return the CRC
    338     */
    339    @Deprecated
    340    public int getCrc() {
    341        return (int) crc;
    342    }
    343
    344    /**
    345     * Sets the CRC.
    346     * @deprecated use setCrcValue instead.
    347     * @param crc the CRC
    348     */
    349    @Deprecated
    350    public void setCrc(final int crc) {
    351        this.crc = crc;
    352    }
    353
    354    /**
    355     * Gets the CRC.
    356     * @since Compress 1.7
    357     * @return the CRC
    358     */
    359    public long getCrcValue() {
    360        return crc;
    361    }
    362
    363    /**
    364     * Sets the CRC.
    365     * @since Compress 1.7
    366     * @param crc the CRC
    367     */
    368    public void setCrcValue(final long crc) {
    369        this.crc = crc;
    370    }
    371
    372    /**
    373     * Gets the compressed CRC.
    374     * @deprecated use getCompressedCrcValue instead.
    375     * @return the compressed CRC
    376     */
    377    @Deprecated
    378    int getCompressedCrc() {
    379        return (int) compressedCrc;
    380    }
    381
    382    /**
    383     * Sets the compressed CRC.
    384     * @deprecated use setCompressedCrcValue instead.
    385     * @param crc the CRC
    386     */
    387    @Deprecated
    388    void setCompressedCrc(final int crc) {
    389        this.compressedCrc = crc;
    390    }
    391
    392    /**
    393     * Gets the compressed CRC.
    394     * @since Compress 1.7
    395     * @return the CRC
    396     */
    397    long getCompressedCrcValue() {
    398        return compressedCrc;
    399    }
    400
    401    /**
    402     * Sets the compressed CRC.
    403     * @since Compress 1.7
    404     * @param crc the CRC
    405     */
    406    void setCompressedCrcValue(final long crc) {
    407        this.compressedCrc = crc;
    408    }
    409
    410    /**
    411     * Get this entry's file size.
    412     *
    413     * @return This entry's file size.
    414     */
    415    @Override
    416    public long getSize() {
    417        return size;
    418    }
    419
    420    /**
    421     * Set this entry's file size.
    422     *
    423     * @param size This entry's new file size.
    424     */
    425    public void setSize(final long size) {
    426        this.size = size;
    427    }
    428
    429    /**
    430     * Get this entry's compressed file size.
    431     *
    432     * @return This entry's compressed file size.
    433     */
    434    long getCompressedSize() {
    435        return compressedSize;
    436    }
    437
    438    /**
    439     * Set this entry's compressed file size.
    440     *
    441     * @param size This entry's new compressed file size.
    442     */
    443    void setCompressedSize(final long size) {
    444        this.compressedSize = size;
    445    }
    446
    447    /**
    448     * Sets the (compression) methods to use for entry's content - the
    449     * default is LZMA2.
    450     *
    451     * <p>Currently only {@link SevenZMethod#COPY}, {@link
    452     * SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link
    453     * SevenZMethod#DEFLATE} are supported when writing archives.</p>
    454     *
    455     * <p>The methods will be consulted in iteration order to create
    456     * the final output.</p>
    457     *
    458     * @param methods the methods to use for the content
    459     * @since 1.8
    460     */
    461    public void setContentMethods(final Iterable<? extends SevenZMethodConfiguration> methods) {
    462        if (methods != null) {
    463            final LinkedList<SevenZMethodConfiguration> l = new LinkedList<>();
    464            for (final SevenZMethodConfiguration m : methods) {
    465                l.addLast(m);
    466            }
    467            contentMethods = Collections.unmodifiableList(l);
    468        } else {
    469            contentMethods = null;
    470        }
    471    }
    472
    473    /**
    474     * Gets the (compression) methods to use for entry's content - the
    475     * default is LZMA2.
    476     *
    477     * <p>Currently only {@link SevenZMethod#COPY}, {@link
    478     * SevenZMethod#LZMA2}, {@link SevenZMethod#BZIP2} and {@link
    479     * SevenZMethod#DEFLATE} are supported when writing archives.</p>
    480     *
    481     * <p>The methods will be consulted in iteration order to create
    482     * the final output.</p>
    483     *
    484     * @since 1.8
    485     * @return the methods to use for the content
    486     */
    487    public Iterable<? extends SevenZMethodConfiguration> getContentMethods() {
    488        return contentMethods;
    489    }
    490
    491    @Override
    492    public int hashCode() {
    493        final String n = getName();
    494        return n == null ? 0 : n.hashCode();
    495    }
    496
    497    @Override
    498    public boolean equals(final Object obj) {
    499        if (this == obj) {
    500            return true;
    501        }
    502        if (obj == null || getClass() != obj.getClass()) {
    503            return false;
    504        }
    505        final SevenZArchiveEntry other = (SevenZArchiveEntry) obj;
    506        return
    507            Objects.equals(name, other.name) &&
    508            hasStream == other.hasStream &&
    509            isDirectory == other.isDirectory &&
    510            isAntiItem == other.isAntiItem &&
    511            hasCreationDate == other.hasCreationDate &&
    512            hasLastModifiedDate == other.hasLastModifiedDate &&
    513            hasAccessDate == other.hasAccessDate &&
    514            creationDate == other.creationDate &&
    515            lastModifiedDate == other.lastModifiedDate &&
    516            accessDate == other.accessDate &&
    517            hasWindowsAttributes == other.hasWindowsAttributes &&
    518            windowsAttributes == other.windowsAttributes &&
    519            hasCrc == other.hasCrc &&
    520            crc == other.crc &&
    521            compressedCrc == other.compressedCrc &&
    522            size == other.size &&
    523            compressedSize == other.compressedSize &&
    524            equalSevenZMethods(contentMethods, other.contentMethods);
    525    }
    526
    527    /**
    528     * Converts NTFS time (100 nanosecond units since 1 January 1601)
    529     * to Java time.
    530     * @param ntfsTime the NTFS time in 100 nanosecond units
    531     * @return the Java time
    532     */
    533    public static Date ntfsTimeToJavaTime(final long ntfsTime) {
    534        final Calendar ntfsEpoch = Calendar.getInstance();
    535        ntfsEpoch.setTimeZone(TimeZone.getTimeZone("GMT+0"));
    536        ntfsEpoch.set(1601, 0, 1, 0, 0, 0);
    537        ntfsEpoch.set(Calendar.MILLISECOND, 0);
    538        final long realTime = ntfsEpoch.getTimeInMillis() + (ntfsTime / (10*1000));
    539        return new Date(realTime);
    540    }
    541
    542    /**
    543     * Converts Java time to NTFS time.
    544     * @param date the Java time
    545     * @return the NTFS time
    546     */
    547    public static long javaTimeToNtfsTime(final Date date) {
    548        final Calendar ntfsEpoch = Calendar.getInstance();
    549        ntfsEpoch.setTimeZone(TimeZone.getTimeZone("GMT+0"));
    550        ntfsEpoch.set(1601, 0, 1, 0, 0, 0);
    551        ntfsEpoch.set(Calendar.MILLISECOND, 0);
    552        return ((date.getTime() - ntfsEpoch.getTimeInMillis())* 1000 * 10);
    553    }
    554
    555    private boolean equalSevenZMethods(final Iterable<? extends SevenZMethodConfiguration> c1,
    556        final Iterable<? extends SevenZMethodConfiguration> c2) {
    557        if (c1 == null) {
    558            return c2 == null;
    559        }
    560        if (c2 == null) {
    561            return false;
    562        }
    563        final Iterator<? extends SevenZMethodConfiguration> i1 = c1.iterator();
    564        final Iterator<? extends SevenZMethodConfiguration> i2 = c2.iterator();
    565        while (i1.hasNext()) {
    566            if (!i2.hasNext()) {
    567                return false;
    568            }
    569            if (!i1.next().equals(i2.next())) {
    570                return false;
    571            }
    572        }
    573        return !i2.hasNext();
    574    }
    575}