Repository: commons-compress Updated Branches: refs/heads/compress-2.0 7c8c870c0 -> 1a377507a
experimenting with Java8 - embrace BasicFileAttributes and Optional Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/c6eed140 Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/c6eed140 Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/c6eed140 Branch: refs/heads/compress-2.0 Commit: c6eed140b0b4579fd11ade0b66ada704bb116eca Parents: 7c8c870 Author: Stefan Bodewig <bode...@apache.org> Authored: Sun Mar 27 15:48:30 2016 +0200 Committer: Stefan Bodewig <bode...@apache.org> Committed: Sun Mar 27 15:48:30 2016 +0200 ---------------------------------------------------------------------- .../compress2/archivers/ArchiveEntry.java | 71 +++-- .../archivers/ArchiveEntryParameters.java | 318 ++++++++++++++++--- .../archivers/spi/SimpleArchiveEntry.java | 82 ++++- .../compress2/formats/ar/ArArchiveEntry.java | 28 +- .../compress2/formats/ar/ArArchiveInput.java | 10 +- .../compress2/formats/ar/ArArchiveOutput.java | 22 +- .../archivers/ArchiveEntryParametersTest.java | 129 ++++++-- .../compress2/formats/ar/RoundTripTest.java | 5 +- 8 files changed, 510 insertions(+), 155 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntry.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntry.java b/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntry.java index bd4b713..e2573ce 100644 --- a/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntry.java +++ b/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntry.java @@ -18,15 +18,36 @@ */ package org.apache.commons.compress2.archivers; -import java.time.Instant; -import java.util.Map; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Optional; import java.util.Set; /** * Represents an entry of an archive. + * + * <p>The scope of {@link BasicFileAttributes} attributes really + * supported by an {@link ArchiveEntry} depends on the format. All + * formats support the mandatory attributes of {@link + * java.nio.file.attribute.BasicFileAttributes}.</p> * @Immutable */ -public interface ArchiveEntry { +public interface ArchiveEntry extends BasicFileAttributes { + + /** + * The supported types of files, maps to the {@code isFoo} methods + * in {@link BasicFileAttributes}. + */ + public static enum FileType { + /** A regular file. */ + REGULAR_FILE, + /** A directory. */ + DIR, + /** A symbolic link. */ + SYMLINK, + /** Something that is neither a regular file nor a directory nor a symbolic link. */ + OTHER + }; /** Special value indicating that the size is unknown */ static final long SIZE_UNKNOWN = -1; @@ -42,30 +63,42 @@ public interface ArchiveEntry { String getName(); /** - * Gets the uncompressed size of this entry. May be -1 ({@link #SIZE_UNKNOWN}) if the size is unknown. - * - * @return the uncompressed size of this entry. + * Provides information about the owner. + * + * @return information about the entry's owner or {@link Optional#empty} if the format doesn't support owner information */ - long getSize(); + Optional<OwnerInformation> getOwnerInformation(); /** - * Returns true if this entry refers to a directory. - * - * @return true if this entry refers to a directory. + * The "mode" associated with the entry. + * + * <p>Many formats support a mode attribute that is inspired by + * the Unix stat(2) system call. Some even extend it beyond {@code + * st_mode} which is only 16 bits, therefore a long is used to + * accomodate these formats.</p> + * + * @return the format-specific mode if the format supports modes. */ - boolean isDirectory(); + Optional<Long> getMode(); /** - * Gets the last modified date/time of this entry. - * - * @return the last modified date/time of this entry. + * Permissions associated with the the entry. + * @return the set of recognized permissions or {@link Optional#empty} if the format doesn't support permissions. */ - Instant getLastModified(); + Optional<Set<PosixFilePermission>> getPermissions(); /** - * Provides information about the owner. - * - * @return information about the entry's owner or null if the format doesn't support owner information + * The type of entry. + * @return the type of the entry. */ - OwnerInformation getOwnerInformation(); + default FileType getType() { + if (isRegularFile()) { + return FileType.REGULAR_FILE; + } else if (isDirectory()) { + return FileType.DIR; + } else if (isSymbolicLink()) { + return FileType.SYMLINK; + } + return FileType.OTHER; + } } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntryParameters.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntryParameters.java b/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntryParameters.java index 64a0d88..3147242 100644 --- a/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntryParameters.java +++ b/src/main/java/org/apache/commons/compress2/archivers/ArchiveEntryParameters.java @@ -19,21 +19,33 @@ package org.apache.commons.compress2.archivers; import java.io.File; -import java.time.Instant; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; /** * A parameter object useful for creating new ArchiveEntries. * @NotThreadSafe */ -public class ArchiveEntryParameters { +public class ArchiveEntryParameters implements ArchiveEntry { private static final char SLASH = '/'; private String name; private long size = ArchiveEntry.SIZE_UNKNOWN; - private boolean dirFlag = false; - private Instant lastModified; - private OwnerInformation owner; + private Object fileKey; + private FileType type = FileType.REGULAR_FILE; + private FileTime lastModified, created, lastAccess; + private Optional<OwnerInformation> owner = Optional.empty(); + private Optional<Long> mode = Optional.empty(); + private Optional<Set<PosixFilePermission>> permissions = Optional.empty(); /** * Creates parameters as a copy of an existing entry. @@ -43,10 +55,11 @@ public class ArchiveEntryParameters { public static ArchiveEntryParameters copyOf(ArchiveEntry otherEntry) { return new ArchiveEntryParameters() .withName(otherEntry.getName()) - .asDirectory(otherEntry.isDirectory()) - .withSize(otherEntry.getSize()) - .withLastModified(otherEntry.getLastModified()) - .withOwnerInformation(otherEntry.getOwnerInformation()); + .withAttributes(otherEntry) + .withType(otherEntry.getType()) + .withOwnerInformation(otherEntry.getOwnerInformation()) + .withMode(otherEntry.getMode()) + .withPermissions(otherEntry.getPermissions()); } /** @@ -54,12 +67,21 @@ public class ArchiveEntryParameters { * @param file the File to read information from * @return parameters populated from the file instance */ - public static ArchiveEntryParameters fromFile(File file) { - return new ArchiveEntryParameters() - .withName(file.getName()) - .asDirectory(file.isDirectory()) - .withSize(file.exists() ? file.length() : ArchiveEntry.SIZE_UNKNOWN) - .withLastModified(Instant.ofEpochMilli(file.lastModified())); + public static ArchiveEntryParameters fromFile(File file) throws IOException { + Path path = file.toPath(); + ArchiveEntryParameters params = new ArchiveEntryParameters() + .withName(file.getName()); + if (file.exists()) { + params = params + .withAttributes(Files.readAttributes(path, BasicFileAttributes.class)); + try { + params = params.withPermissions(Files.readAttributes(path, PosixFileAttributes.class) + .permissions()); + } catch (UnsupportedOperationException ex) { + // file system without support for POSIX attributes + } + } + return params; } /** @@ -77,8 +99,48 @@ public class ArchiveEntryParameters { } /** + * Sets the creation time of the entry. + * @param creationTime the creation time for the entry to build + * @return the parameters object + */ + public ArchiveEntryParameters withCreationTime(FileTime creationTime) { + this.created = creationTime; + return this; + } + + /** + * Sets the last access time of the entry. + * @param lastAccessTime the last access time for the entry to build + * @return the parameters object + */ + public ArchiveEntryParameters withLastAccessTime(FileTime lastAccessTime) { + this.lastAccess = lastAccessTime; + return this; + } + + /** + * Sets the last modified time of the entry. + * @param lastModifiedTime the last modified time for the entry to build + * @return the parameters object + */ + public ArchiveEntryParameters withLastModifiedTime(FileTime lastModifiedTime) { + this.lastModified = lastModifiedTime; + return this; + } + + /** + * Sets the file key of the entry. + * @param key the file key for the entry to build + * @return the parameters object + */ + public ArchiveEntryParameters withFileKey(Object key) { + this.fileKey = key; + return this; + } + + /** * Sets the size of the entry. - * @param size the size of the entry to build + * @param size the size for the entry to build * @return the parameters object */ public ArchiveEntryParameters withSize(long size) { @@ -87,22 +149,22 @@ public class ArchiveEntryParameters { } /** - * Marks the entry to build as a directory. - * @param b whether the entry is supposed to represent a directory + * Sets the type of the entry. + * @param type the type for the entry to build * @return the parameters object */ - public ArchiveEntryParameters asDirectory(boolean b) { - this.dirFlag = b; + public ArchiveEntryParameters withType(FileType type) { + this.type = type; return this; } /** - * Sets the last modified date/time of the entry. - * @param lastModified the last modified date/time of the entry to build + * Sets the owner information of the entry. + * @param owner the owner information for the entry to build * @return the parameters object */ - public ArchiveEntryParameters withLastModified(Instant lastModified) { - this.lastModified = lastModified; + public ArchiveEntryParameters withOwnerInformation(Optional<OwnerInformation> owner) { + this.owner = owner; return this; } @@ -112,56 +174,182 @@ public class ArchiveEntryParameters { * @return the parameters object */ public ArchiveEntryParameters withOwnerInformation(OwnerInformation owner) { - this.owner = owner; + return withOwnerInformation(Optional.ofNullable(owner)); + } + + /** + * Sets the "mode" of the entry. + * @param mode the mode for the entry to build + * @return the parameters object + */ + public ArchiveEntryParameters withMode(Optional<Long> mode) { + this.mode = mode; return this; } /** - * Gets the configured name. - * - * <p>The name will use '/' as directory separator and end with a '/' if and only if the entry represents a - * directory.</p> - * - * @return the normalized name + * Sets the "mode" of the entry. + * @param mode the mode for the entry to build + * @return the parameters object */ - public String getName() { - return normalize(name, dirFlag); + public ArchiveEntryParameters withMode(long mode) { + return withMode(Optional.of(mode)); } /** - * Gets the configured size or {@link #SIZE_UNKNOWN}) if the size is not configured. - * - * @return the configured size + * Sets the permissions of the entry. + * @param permissions the permissions for the entry to build + * @return the parameters object */ - public long getSize() { - return dirFlag ? 0 : size; + public ArchiveEntryParameters withPermissions(Optional<Set<PosixFilePermission>> permissions) { + this.permissions = permissions; + return this; } /** - * Returns true if parameters are configured to represent a directory. - * - * @return true if this parameters refer to a directory. + * Sets the permissions of the entry. + * @param permissions the permissions for the entry to build + * @return the parameters object */ - public boolean isDirectory() { - return dirFlag; + public ArchiveEntryParameters withPermissions(Set<PosixFilePermission> permissions) { + return withPermissions(Optional.ofNullable(permissions)); } /** - * Gets the configured last modified date/time. - * - * @return the configured last modified date/time or null if no date was configured. + * Sets the basic attributes. + * @param attributes the attributes of the entry to build + * @return the parameters object */ - public Instant getLastModified() { + public ArchiveEntryParameters withAttributes(BasicFileAttributes attributes) { + if (attributes.isRegularFile()) { + type = FileType.REGULAR_FILE; + } else if (attributes.isDirectory()) { + type = FileType.DIR; + } else if (attributes.isSymbolicLink()) { + type = FileType.SYMLINK; + } else { + type = FileType.OTHER; + } + return withCreationTime(attributes.creationTime()) + .withFileKey(attributes.fileKey()) + .withLastAccessTime(attributes.lastAccessTime()) + .withLastModifiedTime(attributes.lastModifiedTime()) + .withSize(attributes.size()); + } + + @Override + public String getName() { + return normalize(name, isDirectory()); + } + + @Override + public FileTime creationTime() { + return created; + } + + @Override + public Object fileKey() { + return fileKey; + } + + @Override + public boolean isDirectory() { + return type == FileType.DIR; + } + + @Override + public boolean isOther() { + return type == FileType.OTHER; + } + + @Override + public boolean isRegularFile() { + return type == FileType.REGULAR_FILE; + } + + @Override + public boolean isSymbolicLink() { + return type == FileType.SYMLINK; + } + + @Override + public FileTime lastAccessTime() { + return lastAccess; + } + + @Override + public FileTime lastModifiedTime() { return lastModified; } + @Override + public long size() { + return type == FileType.DIR ? 0 : size; + } + + @Override + public Optional<OwnerInformation> getOwnerInformation() { + return owner; + } + + @Override + public Optional<Long> getMode() { + return mode.isPresent() ? mode : permissions.map(p -> modeFromPermissions(p, type)); + } + + @Override + public Optional<Set<PosixFilePermission>> getPermissions() { + return permissions.isPresent() ? permissions + : mode.map(ArchiveEntryParameters::permissionsFromMode); + } + + @Override + public FileType getType() { + return type; + } + /** - * Gets the configured information about the owner. - * - * @return information about the entry's owner or null if no information was configured + * Translates a set of permissons into a Unix stat(2) {@code st_mode} result + * @param permissions the permissions + * @param type the file type + * @return the "mode" */ - public OwnerInformation getOwnerInformation() { - return owner; + public static long modeFromPermissions(Set<PosixFilePermission> permissions, FileType type) { + long mode; + switch (type) { + case SYMLINK: + mode = 012; + break; + case REGULAR_FILE: + mode = 010; + break; + case DIR: + mode = 004; + break; + default: + // OTHER could be a character or block device, a socket or a FIFO - so don't set anything + mode = 0; + break; + } + mode <<= 3; + mode <<= 3; // we don't support sticky, setuid, setgid + mode |= modeFromPermissions(permissions, "OWNER"); + mode <<= 3; + mode |= modeFromPermissions(permissions, "GROUP"); + mode <<= 3; + mode |= modeFromPermissions(permissions, "OTHERS"); + return mode; + } + + /** + * Translates a Unix stat(2) {@code st_mode} compatible value into a set of permissions. + */ + public static Set<PosixFilePermission> permissionsFromMode(long mode) { + Set<PosixFilePermission> permissions = EnumSet.noneOf(PosixFilePermission.class); + addPermissions(permissions, "OTHERS", mode); + addPermissions(permissions, "GROUP", mode >> 3); + addPermissions(permissions, "OWNER", mode >> 6); + return permissions; } private static String normalize(String name, boolean dirFlag) { @@ -179,4 +367,30 @@ public class ArchiveEntryParameters { } return name; } + + private static long modeFromPermissions(Set<PosixFilePermission> permissions, String prefix) { + long mode = 0; + if (permissions.contains(PosixFilePermission.valueOf(prefix + "_READ"))) { + mode |= 4; + } + if (permissions.contains(PosixFilePermission.valueOf(prefix + "_WRITE"))) { + mode |= 2; + } + if (permissions.contains(PosixFilePermission.valueOf(prefix + "_EXECUTE"))) { + mode |= 1; + } + return mode; + } + + private static void addPermissions(Set<PosixFilePermission> permissions, String prefix, long mode) { + if ((mode & 1) == 1) { + permissions.add(PosixFilePermission.valueOf(prefix + "_EXECUTE")); + } + if ((mode & 2) == 2) { + permissions.add(PosixFilePermission.valueOf(prefix + "_WRITE")); + } + if ((mode & 4) == 4) { + permissions.add(PosixFilePermission.valueOf(prefix + "_READ")); + } + } } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/main/java/org/apache/commons/compress2/archivers/spi/SimpleArchiveEntry.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress2/archivers/spi/SimpleArchiveEntry.java b/src/main/java/org/apache/commons/compress2/archivers/spi/SimpleArchiveEntry.java index dbe04da..ac1294d 100644 --- a/src/main/java/org/apache/commons/compress2/archivers/spi/SimpleArchiveEntry.java +++ b/src/main/java/org/apache/commons/compress2/archivers/spi/SimpleArchiveEntry.java @@ -18,9 +18,12 @@ */ package org.apache.commons.compress2.archivers.spi; -import java.time.Instant; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Collections; import java.util.Objects; - +import java.util.Optional; +import java.util.Set; import org.apache.commons.compress2.archivers.ArchiveEntry; import org.apache.commons.compress2.archivers.ArchiveEntryParameters; import org.apache.commons.compress2.archivers.OwnerInformation; @@ -32,9 +35,12 @@ import org.apache.commons.compress2.archivers.OwnerInformation; public class SimpleArchiveEntry implements ArchiveEntry { private final String name; private final long size; - private final boolean dirFlag; - private final Instant lastModified; - private final OwnerInformation owner; + private final ArchiveEntry.FileType type; + private final Object fileKey; + private final FileTime lastModified, lastAccess, created; + private final Optional<OwnerInformation> owner; + private final Optional<Set<PosixFilePermission>> permissions; + private final Optional<Long> mode; /** * Creates a SimpleArchiveEntry from a parameter object. @@ -42,10 +48,15 @@ public class SimpleArchiveEntry implements ArchiveEntry { */ public SimpleArchiveEntry(ArchiveEntryParameters params) { this.name = params.getName(); - this.size = params.getSize(); - this.dirFlag = params.isDirectory(); - this.lastModified = params.getLastModified(); + this.size = params.size(); + this.type = params.getType(); + this.fileKey = params.fileKey(); + this.lastModified = params.lastModifiedTime(); + this.lastAccess = params.lastAccessTime(); + this.created = params.creationTime(); this.owner = params.getOwnerInformation(); + this.permissions = params.getPermissions().map(Collections::unmodifiableSet); + this.mode = params.getMode(); } @Override @@ -54,26 +65,66 @@ public class SimpleArchiveEntry implements ArchiveEntry { } @Override - public long getSize() { + public long size() { return size; } @Override public boolean isDirectory() { - return dirFlag; + return type == FileType.DIR; + } + + @Override + public boolean isRegularFile() { + return type == FileType.REGULAR_FILE; + } + + @Override + public boolean isSymbolicLink() { + return type == FileType.SYMLINK; } @Override - public Instant getLastModified() { + public boolean isOther() { + return type == FileType.OTHER; + } + + @Override + public FileTime lastModifiedTime() { return lastModified; } @Override - public OwnerInformation getOwnerInformation() { + public FileTime lastAccessTime() { + return lastAccess; + } + + @Override + public FileTime creationTime() { + return created; + } + + @Override + public Object fileKey() { + return fileKey; + } + + @Override + public Optional<OwnerInformation> getOwnerInformation() { return owner; } @Override + public Optional<Long> getMode() { + return mode; + } + + @Override + public Optional<Set<PosixFilePermission>> getPermissions() { + return permissions; + } + + @Override public int hashCode() { final int prime = 31; int result = 1; @@ -92,8 +143,13 @@ public class SimpleArchiveEntry implements ArchiveEntry { SimpleArchiveEntry other = (SimpleArchiveEntry) obj; return Objects.equals(name, other.name) && size == other.size - && dirFlag == other.dirFlag + && type == other.type + && Objects.equals(fileKey, other.fileKey) && Objects.equals(lastModified, other.lastModified) + && Objects.equals(lastAccess, other.lastAccess) + && Objects.equals(created, other.created) + && Objects.equals(mode, other.mode) + && Objects.equals(permissions, other.permissions) && Objects.equals(owner, other.owner); } } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveEntry.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveEntry.java b/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveEntry.java index 9233bc4..584d9fe 100644 --- a/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveEntry.java +++ b/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveEntry.java @@ -57,37 +57,17 @@ public class ArArchiveEntry extends SimpleArchiveEntry { /** The trailer for each entry */ public static final String TRAILER = "`\012"; - // TODO revisit once the permissions stuff is sorted out - private final int mode; - private static final int DEFAULT_MODE = 33188; // = (octal) 0100644 - /** - * Creates an ArArchiveEntry from a parameter object. - * @param params the parameters describing the archive entry. + * The default mode (a world readable, owner writable regular file). */ - public ArArchiveEntry(ArchiveEntryParameters params) { - this(params, DEFAULT_MODE); - } + public static final long DEFAULT_MODE = 0100644l; /** - * Creates an ArArchiveEntry from a parameter object and an octal mode. + * Creates an ArArchiveEntry from a parameter object. * @param params the parameters describing the archive entry. - * @param mode the file/dir mode of the entry */ - public ArArchiveEntry(ArchiveEntryParameters params, int mode) { + public ArArchiveEntry(ArchiveEntryParameters params) { super(params); - this.mode = mode; - } - - // TODO revisit once the permissions stuff is sorted out - public int getMode() { - return mode; - } - - // TODO revisit once the permissions stuff is sorted out - @Override - public boolean equals(Object obj) { - return super.equals(obj) && mode == ((ArArchiveEntry) obj).mode; } } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveInput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveInput.java b/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveInput.java index 8e90f97..8a62f8a 100644 --- a/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveInput.java +++ b/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveInput.java @@ -26,7 +26,7 @@ import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; -import java.time.Instant; +import java.nio.file.attribute.FileTime; import org.apache.commons.compress2.archivers.ArchiveEntryParameters; import org.apache.commons.compress2.archivers.OwnerInformation; @@ -87,7 +87,7 @@ public class ArArchiveInput extends AbstractArchiveInput<ArArchiveEntry> { @Override public ArArchiveEntry next() throws IOException { if (currentEntry != null) { - final long entryEnd = entryOffset + currentEntry.getSize(); + final long entryEnd = entryOffset + currentEntry.size(); IOUtils.skip(wrappedStream, entryEnd - offset); currentEntry = null; } @@ -166,8 +166,8 @@ public class ArArchiveInput extends AbstractArchiveInput<ArArchiveEntry> { currentEntry = new ArArchiveEntry(new ArchiveEntryParameters().withName(temp).withSize(len) .withOwnerInformation(new OwnerInformation(userId, asInt(ID_BUF, true))) - .withLastModified(Instant.ofEpochSecond(asLong(LAST_MODIFIED_BUF))), - asInt(FILE_MODE_BUF, 8)); + .withLastModifiedTime(FileTime.fromMillis(asLong(LAST_MODIFIED_BUF) * 1000)) + .withMode(asInt(FILE_MODE_BUF, 8))); return currentEntry; } @@ -248,7 +248,7 @@ public class ArArchiveInput extends AbstractArchiveInput<ArArchiveEntry> { public int read(byte[] b, final int off, final int len) throws IOException { int toRead = len; if (currentEntry != null) { - final long entryEnd = entryOffset + currentEntry.getSize(); + final long entryEnd = entryOffset + currentEntry.size(); if (len > 0 && entryEnd > offset) { toRead = (int) Math.min(len, entryEnd - offset); } else { http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveOutput.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveOutput.java b/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveOutput.java index 7cd7639..92eb9aa 100644 --- a/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveOutput.java +++ b/src/main/java/org/apache/commons/compress2/formats/ar/ArArchiveOutput.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.nio.charset.StandardCharsets; +import java.util.Optional; import org.apache.commons.compress2.archivers.ArchiveEntryParameters; import org.apache.commons.compress2.archivers.OwnerInformation; @@ -95,8 +96,8 @@ public class ArArchiveOutput extends AbstractArchiveOutput<ArArchiveEntry> { if (prevEntry == null) { writeArchiveHeader(); } else { - if (prevEntry.getSize() != entryOffset) { - throw new IOException("length does not match entry (" + prevEntry.getSize() + " != " + entryOffset); + if (prevEntry.size() != entryOffset) { + throw new IOException("length does not match entry (" + prevEntry.size() + " != " + entryOffset); } if (haveUnclosedEntry) { @@ -154,7 +155,7 @@ public class ArArchiveOutput extends AbstractArchiveOutput<ArArchiveEntry> { } offset = fill(offset, 16, (byte) ' '); - final String m = "" + (pEntry.getLastModified().getEpochSecond()); + final String m = "" + (pEntry.lastModifiedTime().toMillis() / 1000); if (m.length() > 12) { throw new IOException("modified too long"); } @@ -175,7 +176,7 @@ public class ArArchiveOutput extends AbstractArchiveOutput<ArArchiveEntry> { offset += write(g); offset = fill(offset, 40, (byte) ' '); - final String fm = "" + Integer.toString(pEntry.getMode(), 8); + final String fm = "" + Integer.toString(pEntry.getMode().orElse(0l).intValue(), 8); if (fm.length() > 8) { throw new IOException("filemode too long"); } @@ -183,7 +184,7 @@ public class ArArchiveOutput extends AbstractArchiveOutput<ArArchiveEntry> { offset = fill(offset, 48, (byte) ' '); final String s = - String.valueOf(pEntry.getSize() + String.valueOf(pEntry.size() + (mustAppendName ? n.length() : 0)); if (s.length() > 10) { throw new IOException("size too long"); @@ -215,6 +216,9 @@ public class ArArchiveOutput extends AbstractArchiveOutput<ArArchiveEntry> { @Override public ArArchiveEntry createEntry(ArchiveEntryParameters params) { + if (!params.getMode().isPresent()) { + params = params.withMode(ArArchiveEntry.DEFAULT_MODE); + } return new ArArchiveEntry(params); } @@ -228,12 +232,12 @@ public class ArArchiveOutput extends AbstractArchiveOutput<ArArchiveEntry> { finished = true; } - private int getUserId(OwnerInformation info) { - return info == null ? 0 : info.getUserId(); + private int getUserId(Optional<OwnerInformation> info) { + return info.map(OwnerInformation::getUserId).orElse(0); } - private int getGroupId(OwnerInformation info) { - return info == null ? 0 : info.getGroupId(); + private int getGroupId(Optional<OwnerInformation> info) { + return info.map(OwnerInformation::getGroupId).orElse(0); } private class CurrentChannel implements WritableByteChannel { http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/test/java/org/apache/commons/compress2/archivers/ArchiveEntryParametersTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress2/archivers/ArchiveEntryParametersTest.java b/src/test/java/org/apache/commons/compress2/archivers/ArchiveEntryParametersTest.java index 38305de..c6f81b3 100644 --- a/src/test/java/org/apache/commons/compress2/archivers/ArchiveEntryParametersTest.java +++ b/src/test/java/org/apache/commons/compress2/archivers/ArchiveEntryParametersTest.java @@ -19,11 +19,20 @@ package org.apache.commons.compress2.archivers; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFilePermission; import java.time.Duration; import java.time.Instant; +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; + import org.junit.Test; public class ArchiveEntryParametersTest { @@ -31,17 +40,25 @@ public class ArchiveEntryParametersTest { @Test public void defaultValues() { ArchiveEntryParameters p = new ArchiveEntryParameters(); - assertEquals(null, p.getName()); - assertEquals(-1, p.getSize()); - assertEquals(false, p.isDirectory()); - assertEquals(null, p.getLastModified()); - assertEquals(null, p.getOwnerInformation()); + assertNull(p.getName()); + assertEquals(-1, p.size()); + assertFalse(p.isDirectory()); + assertFalse(p.isSymbolicLink()); + assertFalse(p.isOther()); + assertTrue(p.isRegularFile()); + assertNull(p.lastModifiedTime()); + assertNull(p.lastAccessTime()); + assertNull(p.creationTime()); + assertNull(p.fileKey()); + assertFalse(p.getOwnerInformation().isPresent()); + assertFalse(p.getPermissions().isPresent()); + assertEquals(ArchiveEntry.FileType.REGULAR_FILE, p.getType()); } @Test public void shouldAddTrailingSlashForDirectories() { ArchiveEntryParameters p = new ArchiveEntryParameters() - .withName("foo").asDirectory(true); + .withName("foo").withType(ArchiveEntry.FileType.DIR); assertEquals("foo/", p.getName()); p.withName("foo/"); assertEquals("foo/", p.getName()); @@ -52,7 +69,7 @@ public class ArchiveEntryParametersTest { @Test public void shouldStripTrailingSlashForNonDirectories() { ArchiveEntryParameters p = new ArchiveEntryParameters() - .withName("foo").asDirectory(false); + .withName("foo").withType(ArchiveEntry.FileType.REGULAR_FILE); assertEquals("foo", p.getName()); p.withName("foo/"); assertEquals("foo", p.getName()); @@ -63,28 +80,63 @@ public class ArchiveEntryParametersTest { @Test public void sizeShouldBe0ForDirectories() { ArchiveEntryParameters p = new ArchiveEntryParameters() - .asDirectory(true); - assertEquals(0, p.getSize()); + .withType(ArchiveEntry.FileType.DIR); + assertEquals(0, p.size()); p.withSize(42); - assertEquals(0, p.getSize()); + assertEquals(0, p.size()); } @Test public void copyActuallyCopies() { - final Instant d = Instant.now(); + final FileTime d1 = FileTime.from(Instant.now()); + final FileTime d2 = FileTime.from(Instant.now()); final OwnerInformation o = new OwnerInformation(17, 4); ArchiveEntryParameters p = ArchiveEntryParameters.copyOf(new ArchiveEntry() { + @Override public String getName() {return "baz";} - public long getSize() {return 42;} + @Override + public long size() {return 42;} + @Override public boolean isDirectory() {return false;} - public Instant getLastModified() {return d;} - public OwnerInformation getOwnerInformation() {return o;} + @Override + public boolean isSymbolicLink() {return false;} + @Override + public boolean isOther() {return false;} + @Override + public boolean isRegularFile() {return true;} + @Override + public FileTime lastModifiedTime() {return d1;} + @Override + public FileTime lastAccessTime() {return null;} + @Override + public FileTime creationTime() {return d2;} + @Override + public Optional<OwnerInformation> getOwnerInformation() {return Optional.of(o);} + @Override + public Optional<Long> getMode() { return Optional.of(4711l); } + @Override + public Optional<Set<PosixFilePermission>> getPermissions() { + return Optional.of(EnumSet.of(PosixFilePermission.OWNER_READ)); + } + @Override + public Object fileKey() { + return "foo"; + } }); assertEquals("baz", p.getName()); - assertEquals(42, p.getSize()); - assertEquals(false, p.isDirectory()); - assertEquals(d, p.getLastModified()); - assertEquals(o, p.getOwnerInformation()); + assertEquals(42, p.size()); + assertFalse(p.isDirectory()); + assertFalse(p.isSymbolicLink()); + assertFalse(p.isOther()); + assertTrue(p.isRegularFile()); + assertEquals(d1, p.lastModifiedTime()); + assertEquals(d2, p.creationTime()); + assertNull(p.lastAccessTime()); + assertEquals("foo", p.fileKey()); + assertEquals(ArchiveEntry.FileType.REGULAR_FILE, p.getType()); + assertEquals(o, p.getOwnerInformation().get()); + assertEquals(4711l, p.getMode().get().longValue()); + assertTrue(p.getPermissions().get().contains(PosixFilePermission.OWNER_READ)); } @Test @@ -96,10 +148,10 @@ public class ArchiveEntryParametersTest { ArchiveEntryParameters p = ArchiveEntryParameters.fromFile(f); assert p.getName().endsWith("suf"); assert p.getName().startsWith("pre"); - assertEquals(0, p.getSize()); - assertEquals(false, p.isDirectory()); - assertWithinTwoSecondsOf(d, p.getLastModified()); - assertEquals(null, p.getOwnerInformation()); + assertEquals(0, p.size()); + assertEquals(ArchiveEntry.FileType.REGULAR_FILE, p.getType()); + assertWithinTwoSecondsOf(d, p.lastModifiedTime()); + assertFalse(p.getOwnerInformation().isPresent()); } @Test @@ -113,10 +165,10 @@ public class ArchiveEntryParametersTest { ArchiveEntryParameters p = ArchiveEntryParameters.fromFile(f); assert p.getName().endsWith("suf/"); assert p.getName().startsWith("pre"); - assertEquals(0, p.getSize()); - assertEquals(true, p.isDirectory()); - assertWithinTwoSecondsOf(d, p.getLastModified()); - assertEquals(null, p.getOwnerInformation()); + assertEquals(0, p.size()); + assertEquals(ArchiveEntry.FileType.DIR, p.getType()); + assertWithinTwoSecondsOf(d, p.lastModifiedTime()); + assertFalse(p.getOwnerInformation().isPresent()); } @Test @@ -124,10 +176,29 @@ public class ArchiveEntryParametersTest { File f = File.createTempFile("pre", "suf"); assert f.delete(); ArchiveEntryParameters p = ArchiveEntryParameters.fromFile(f); - assertEquals(-1, p.getSize()); + assertEquals(-1, p.size()); + } + + @Test + public void getModeConstructsModeFromPermissions() { + ArchiveEntryParameters p = new ArchiveEntryParameters() + .withPermissions(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE)); + assertEquals("100700", Long.toString(p.getMode().get(), 8)); + } + + @Test + public void getPermissionsConstructsPermissionsFromMode() { + ArchiveEntryParameters p = new ArchiveEntryParameters() + .withMode(0100753l); + Set<PosixFilePermission> s = p.getPermissions().get(); + assertEquals(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_READ, + PosixFilePermission.GROUP_EXECUTE, PosixFilePermission.OTHERS_WRITE, + PosixFilePermission.OTHERS_EXECUTE), + s); } - private static void assertWithinTwoSecondsOf(Instant expected, Instant actual) { - assert Math.abs(Duration.between(expected, actual).getSeconds()) < 2; + private static void assertWithinTwoSecondsOf(Instant expected, FileTime actual) { + assert Math.abs(Duration.between(expected, actual.toInstant()).getSeconds()) < 2; } } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/c6eed140/src/test/java/org/apache/commons/compress2/formats/ar/RoundTripTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress2/formats/ar/RoundTripTest.java b/src/test/java/org/apache/commons/compress2/formats/ar/RoundTripTest.java index 250c505..491c964 100644 --- a/src/test/java/org/apache/commons/compress2/formats/ar/RoundTripTest.java +++ b/src/test/java/org/apache/commons/compress2/formats/ar/RoundTripTest.java @@ -186,10 +186,7 @@ public class RoundTripTest { try { uri = url.toURI(); } catch (java.net.URISyntaxException ex) { -// throw new IOException(ex); // JDK 1.6+ - IOException ioe = new IOException(); - ioe.initCause(ex); - throw ioe; + throw new IOException(ex); } return new File(uri); }