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