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}