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