diff --git a/pom.xml b/pom.xml
index 5634925..920fee8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,11 @@
junit-jupiter-engine
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
diff --git a/src/main/java/io/mkr/archivefs/archive/Archive.java b/src/main/java/io/mkr/archivefs/archive/Archive.java
index 7928c0e..277a0ce 100644
--- a/src/main/java/io/mkr/archivefs/archive/Archive.java
+++ b/src/main/java/io/mkr/archivefs/archive/Archive.java
@@ -1,5 +1,6 @@
package io.mkr.archivefs.archive;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
@@ -8,7 +9,7 @@
* An archive is package of directories and files and their associated metadata. Classes implementing this interface
* provide non-sequential ("random") access to the contents of an archive.
*/
-public interface Archive {
+public interface Archive extends Closeable {
/**
* Returns metadata items for all entries in the archive
diff --git a/src/main/java/io/mkr/archivefs/archive/ArchiveFactory.java b/src/main/java/io/mkr/archivefs/archive/ArchiveFactory.java
index 94b683c..11ee89c 100644
--- a/src/main/java/io/mkr/archivefs/archive/ArchiveFactory.java
+++ b/src/main/java/io/mkr/archivefs/archive/ArchiveFactory.java
@@ -14,7 +14,6 @@
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
-import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
@@ -66,8 +65,9 @@ public static boolean supportedFromStream(String mime) {
* @param mimeType The mime type to create the archive for
* @param file the {@link File}
* @return the {@link Archive} or null if the type is not supported
+ * @throws IOException If creating the archive fails
*/
- public Archive archiveForFile(String mimeType, final File file) {
+ public Archive archiveForFile(String mimeType, final File file) throws IOException {
switch (mimeType) {
case MIME_ZIP:
case MIME_JAR:
@@ -75,17 +75,7 @@ public Archive archiveForFile(String mimeType, final File file) {
case MIME_RAR:
return new RarFileArchive(file);
case MIME_7Z:
- // The 7Z file implementation does not allow random-access, map it to an in-memory archive
- return archiveForInputStream(mimeType, new Creator() {
- @Override
- public CCSeven7ArchiveInputStream create() {
- try {
- return new CCSeven7ArchiveInputStream(new SevenZFile(file));
- } catch (IOException e) {
- throw new IllegalArgumentException("Error accessing file", e);
- }
- }
- });
+ return new CCSevenZArchive(file);
case MIME_LZH1:
case MIME_LZH2:
case MIME_LZH3:
diff --git a/src/main/java/io/mkr/archivefs/archive/MemoryMappedArchive.java b/src/main/java/io/mkr/archivefs/archive/MemoryMappedArchive.java
index 726b3e8..394bdb2 100644
--- a/src/main/java/io/mkr/archivefs/archive/MemoryMappedArchive.java
+++ b/src/main/java/io/mkr/archivefs/archive/MemoryMappedArchive.java
@@ -85,4 +85,8 @@ public Exception getReadException() {
return readException;
}
+ @Override
+ public void close() throws IOException {
+
+ }
}
diff --git a/src/main/java/io/mkr/archivefs/archive/StreamingArchive.java b/src/main/java/io/mkr/archivefs/archive/StreamingArchive.java
index 7558e5d..93b75a8 100644
--- a/src/main/java/io/mkr/archivefs/archive/StreamingArchive.java
+++ b/src/main/java/io/mkr/archivefs/archive/StreamingArchive.java
@@ -68,4 +68,8 @@ public InputStream contentFor(ArchiveItem entry) throws IOException {
return null;
}
+ @Override
+ public void close() throws IOException {
+
+ }
}
diff --git a/src/main/java/io/mkr/archivefs/archive/cc/CCSevenZArchive.java b/src/main/java/io/mkr/archivefs/archive/cc/CCSevenZArchive.java
new file mode 100644
index 0000000..0e2b588
--- /dev/null
+++ b/src/main/java/io/mkr/archivefs/archive/cc/CCSevenZArchive.java
@@ -0,0 +1,85 @@
+package io.mkr.archivefs.archive.cc;
+
+import io.mkr.archivefs.archive.Archive;
+import io.mkr.archivefs.archive.ArchiveItem;
+import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
+import org.apache.commons.compress.archivers.sevenz.SevenZFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Wrapper for 7z archives. Commons-compress does support random acces on 7z archives since 1.20.
+ */
+public class CCSevenZArchive implements Archive {
+
+ private SevenZFile sevenZFile;
+
+ private boolean closed;
+
+ /**
+ * Constructor for a new 7z archive.
+ * @param file File which is a 7z archive.
+ */
+ public CCSevenZArchive(final File file) throws IOException {
+ this(file.toPath());
+ }
+
+ /**
+ * Constructor for a new 7z archive.
+ * @param file Path to file which is a 7z archive.
+ */
+ public CCSevenZArchive(final Path file) throws IOException {
+ this.sevenZFile = new SevenZFile(Files.newByteChannel(file));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Collection getItems() throws IOException {
+ checkArchiveClosed();
+ final List entriesMeta = new ArrayList<>();
+ for (final SevenZArchiveEntry entry : sevenZFile.getEntries()) {
+ entriesMeta.add(new CCArchiveEntryItem(entry));
+ }
+ return entriesMeta;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public InputStream contentFor(ArchiveItem entry) throws IOException {
+ checkArchiveClosed();
+ return sevenZFile.getInputStream((SevenZArchiveEntry) ((CCArchiveEntryItem) entry).getDelegate());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ closed = true;
+ sevenZFile.close();
+ sevenZFile = null;
+ }
+ }
+
+ /**
+ * Checks if the archive is already closed. An {@link IOException} is thrown if the archive is already closed.
+ * @throws IOException if the archive is already closed
+ */
+ private void checkArchiveClosed() throws IOException {
+ if (closed) {
+ throw new IOException("Archive is already closed!");
+ }
+ }
+}
diff --git a/src/test/java/io/mkr/archivefs/archive/cc/FlatArchiveTest.java b/src/test/java/io/mkr/archivefs/archive/cc/FlatArchiveTest.java
new file mode 100644
index 0000000..1c5dd44
--- /dev/null
+++ b/src/test/java/io/mkr/archivefs/archive/cc/FlatArchiveTest.java
@@ -0,0 +1,73 @@
+package io.mkr.archivefs.archive.cc;
+
+import io.mkr.archivefs.archive.util.ArchiveConstants;
+import io.mkr.archivefs.archive.util.ArchiveCreator;
+import io.mkr.archivefs.archive.util.SevenZArchiveCreator;
+import io.mkr.archivefs.fs.ArchiveFilesystemProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class FlatArchiveTest {
+
+ private static final ArchiveFilesystemProvider afsp = new ArchiveFilesystemProvider();
+
+ @TempDir
+ Path tempDir;
+
+ private Path testFile;
+
+ private static Stream archiveCreators() {
+ return Stream.of(
+ new SevenZArchiveCreator()
+ );
+ }
+
+ @BeforeEach
+ void createTempDirectory() {
+ testFile = tempDir.resolve("testfile");
+ }
+
+ @ParameterizedTest
+ @MethodSource("archiveCreators")
+ void readContentFromFlatArchive(final ArchiveCreator archiveCreator) throws IOException {
+ archiveCreator.createFlatArchive(testFile);
+ assertTrue(Files.exists(testFile));
+
+ try (final FileSystem afs = afsp.newFileSystem(testFile, new HashMap<>())) {
+ final Path testFileInArchive = afs.getPath("/" + ArchiveConstants.TEST_FILE_NAME);
+ assertAll(() -> assertTrue(Files.exists(testFileInArchive)),
+ () -> assertArrayEquals(ArchiveConstants.TEST_FILE_CONTENT.getBytes(StandardCharsets.UTF_8),
+ Files.readAllBytes(testFileInArchive)));
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("archiveCreators")
+ void accessFileSystemAfterClose(final ArchiveCreator archiveCreator) throws IOException {
+ archiveCreator.createFlatArchive(testFile);
+ assertTrue(Files.exists(testFile));
+
+ final FileSystem afs = afsp.newFileSystem(testFile, new HashMap<>());
+ final Path testFileInArchive = afs.getPath("/" + ArchiveConstants.TEST_FILE_NAME);
+ afs.close();
+
+ assertNotNull(afs.getPath("/" + ArchiveConstants.TEST_FILE_NAME));
+ assertThrows(IOException.class, () -> Files.readAllBytes(testFileInArchive));
+ }
+}
diff --git a/src/test/java/io/mkr/archivefs/archive/util/ArchiveConstants.java b/src/test/java/io/mkr/archivefs/archive/util/ArchiveConstants.java
new file mode 100644
index 0000000..c42aa3f
--- /dev/null
+++ b/src/test/java/io/mkr/archivefs/archive/util/ArchiveConstants.java
@@ -0,0 +1,8 @@
+package io.mkr.archivefs.archive.util;
+
+public class ArchiveConstants {
+
+ public static final String TEST_FILE_CONTENT = "Test";
+
+ public static final String TEST_FILE_NAME = "test.txt";
+}
diff --git a/src/test/java/io/mkr/archivefs/archive/util/ArchiveCreationHelper.java b/src/test/java/io/mkr/archivefs/archive/util/ArchiveCreationHelper.java
new file mode 100644
index 0000000..cae6477
--- /dev/null
+++ b/src/test/java/io/mkr/archivefs/archive/util/ArchiveCreationHelper.java
@@ -0,0 +1,31 @@
+package io.mkr.archivefs.archive.util;
+
+import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
+import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class ArchiveCreationHelper {
+
+ /**
+ * Create a 7z file.
+ * @param path Path where the 7z file should be created
+ * @throws IOException If creating the 7z file fails
+ */
+ public static void createSevenZArchive(final Path path) throws IOException {
+ try (final SevenZOutputFile out = new SevenZOutputFile(path.toFile())) {
+ final File inputFile = Files.createTempFile("SevenZBaseTemp", "").toFile();
+
+ SevenZArchiveEntry entry = out.createArchiveEntry(inputFile, ArchiveConstants.TEST_FILE_NAME);
+ out.putArchiveEntry(entry);
+ out.write(ArchiveConstants.TEST_FILE_CONTENT.getBytes(StandardCharsets.UTF_8));
+ out.closeArchiveEntry();
+
+ Files.deleteIfExists(inputFile.toPath());
+ }
+ }
+}
diff --git a/src/test/java/io/mkr/archivefs/archive/util/ArchiveCreator.java b/src/test/java/io/mkr/archivefs/archive/util/ArchiveCreator.java
new file mode 100644
index 0000000..5315baa
--- /dev/null
+++ b/src/test/java/io/mkr/archivefs/archive/util/ArchiveCreator.java
@@ -0,0 +1,10 @@
+package io.mkr.archivefs.archive.util;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface ArchiveCreator {
+
+ void createFlatArchive(Path path) throws IOException;
+
+}
diff --git a/src/test/java/io/mkr/archivefs/archive/util/SevenZArchiveCreator.java b/src/test/java/io/mkr/archivefs/archive/util/SevenZArchiveCreator.java
new file mode 100644
index 0000000..2031e3c
--- /dev/null
+++ b/src/test/java/io/mkr/archivefs/archive/util/SevenZArchiveCreator.java
@@ -0,0 +1,12 @@
+package io.mkr.archivefs.archive.util;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class SevenZArchiveCreator implements ArchiveCreator {
+
+ @Override
+ public void createFlatArchive(final Path path) throws IOException {
+ ArchiveCreationHelper.createSevenZArchive(path);
+ }
+}