From 14ec66bf947e34ab590aa98bfe57fcbc3630bcc7 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Mon, 12 Jun 2017 14:53:52 +0200 Subject: [PATCH] Add Zip support (adapted from SCIFIO) --- .../scijava/io/location/zip/ZipHandle.java | 205 ++++++++++++++++++ .../scijava/io/location/zip/ZipLocation.java | 87 ++++++++ .../java/org/scijava/io/ZipHandleTest.java | 83 +++++++ .../java/org/scijava/io/ZipLocationTest.java | 63 ++++++ 4 files changed, 438 insertions(+) create mode 100644 src/main/java/org/scijava/io/location/zip/ZipHandle.java create mode 100644 src/main/java/org/scijava/io/location/zip/ZipLocation.java create mode 100644 src/test/java/org/scijava/io/ZipHandleTest.java create mode 100644 src/test/java/org/scijava/io/ZipLocationTest.java diff --git a/src/main/java/org/scijava/io/location/zip/ZipHandle.java b/src/main/java/org/scijava/io/location/zip/ZipHandle.java new file mode 100644 index 000000000..564378551 --- /dev/null +++ b/src/main/java/org/scijava/io/location/zip/ZipHandle.java @@ -0,0 +1,205 @@ +/* + * #%L + * SCIFIO library for reading and converting scientific file formats. + * %% + * Copyright (C) 2011 - 2016 Board of Regents of the University of + * Wisconsin-Madison + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location.zip; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.scijava.io.DataHandle; +import org.scijava.io.DataHandleInputStream; +import org.scijava.io.Location; +import org.scijava.io.location.AbstractCompressedHandle; +import org.scijava.io.location.ResettableStreamHandle; +import org.scijava.io.location.StreamHandle; +import org.scijava.plugin.Plugin; + +/** + * StreamHandle implementation for reading from Zip-compressed files or byte + * arrays. Instances of ZipHandle are read-only. + * + * @see StreamHandle + * @author Melissa Linkert + * @author Gabriel Einsdorf + */ +@Plugin(type = DataHandle.class) +public class ZipHandle extends AbstractCompressedHandle { + + // -- Fields -- + + private DataHandle in; + + private String entryName; + + private ZipEntry entry; + + private long entryLength = -1l; + + // -- Constructor -- + +// @Override +// public boolean isConstructable(final String file) throws IOException { +// +// final byte[] b = new byte[2]; +// if (handle.length() >= 2) { +// handle.read(b); +// } +// handle.close(); +// return new String(b, Constants.ENCODING).equals("PK"); +// } + + // -- ZipHandle API methods -- + + /** Get the name of the backing Zip entry. */ + public String getEntryName() { + return entryName; + } + + @Override + public void resetStream() throws IOException { + + if (raw() instanceof ResettableStreamHandle) { + ((ResettableStreamHandle) raw()).resetStream(); + } + else { + raw().seek(0l); + } + + inputStream = new ZipInputStream(new DataHandleInputStream<>(raw())); + // FIXME add Buffering + + seekToEntry(); + + } + + // -- IRandomAccess API methods -- + + @Override + public void close() throws IOException { + inputStream = null; + entryName = null; + entryLength = -1; + if (in != null) in.close(); + in = null; + } + + // -- Helper methods -- + + /** + * Seeks to the relevant ZIP entry, populating the stream length accordingly. + */ + private void seekToEntry() throws IOException { + + while (true) { + final ZipEntry e = ((ZipInputStream) inputStream).getNextEntry(); + if (entryName == null) { + entry = e; + entryName = e.getName(); + } + if (entryName.equals(e.getName())) { + // found the matching entry name (or first entry if the name is + // null) + if (entryLength < 0) { + final boolean resetNeeded = populateLength(e.getSize()); + if (resetNeeded) { + // stream length was calculated by force, need to reset + resetStream(); + } + } + break; + } + } + } + + /** + * Sets the stream length, computing it by force if necessary. + * + * @return if the Stream needs to be reset + */ + private boolean populateLength(final long size) throws IOException { + if (size >= 0) { + entryLength = size; + return false; + } + // size is unknown, so we must read the stream manually + long length = 0; + final DataHandle stream = raw(); + while (true) { + final long skipped = stream.skip(Long.MAX_VALUE); + if (skipped == 0) { + // NB: End of stream, we hope. Technically there is no contract + // for when skip(long) returns 0, but in practice it seems to be + // when end of stream is reached. + break; + } + length += skipped; + } + + entryLength = length; + return true; + } + + @Override + public Class getType() { + return ZipLocation.class; + } + + @Override + protected void initInputStream() throws IOException { + inputStream = new ZipInputStream(new DataHandleInputStream<>(raw())); + + entry = get().getEntry(); + if (entry == null) { + // strip off .zip extension and directory prefix + final String n = raw().get().getName(); + String name = n.substring(0, n.length() - 4); + + int slash = name.lastIndexOf(File.separator); + if (slash < 0) slash = name.lastIndexOf("/"); + if (slash >= 0) name = name.substring(slash + 1); + + // look for Zip entry with same prefix as the Zip file itself + boolean matchFound = false; + ZipEntry ze; + while ((ze = ((ZipInputStream) inputStream).getNextEntry()) != null) { + if (entryName == null) entryName = ze.getName(); + if (!matchFound && ze.getName().startsWith(name)) { + // found entry with matching name + entryName = ze.getName(); + entry = ze; + matchFound = true; + } + } + resetStream(); + } + } +} diff --git a/src/main/java/org/scijava/io/location/zip/ZipLocation.java b/src/main/java/org/scijava/io/location/zip/ZipLocation.java new file mode 100644 index 000000000..69dcaa482 --- /dev/null +++ b/src/main/java/org/scijava/io/location/zip/ZipLocation.java @@ -0,0 +1,87 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location.zip; + +import java.util.zip.ZipEntry; + +import org.scijava.io.DataHandle; +import org.scijava.io.Location; +import org.scijava.io.location.AbstractHigherOrderLocation; +import org.scijava.plugin.Plugin; + +/** + * {@link Location} backed by a {@link DataHandle} that is zip + * compressed. + * + * @author Gabriel Einsdorf + * @see ZipHandle + */ +@Plugin(type = DataHandle.class) +public class ZipLocation extends AbstractHigherOrderLocation { + + private ZipEntry entry; + + public ZipLocation(Location location) { + super(location); + } + + public ZipLocation(Location location, ZipEntry entry) { + super(location); + this.entry = entry; + } + + public ZipEntry getEntry() { + return entry; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((entry == null) ? 0 : entry.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + ZipLocation other = (ZipLocation) obj; + if (entry == null) { + if (other.entry != null) return false; + } + else if (!entry.equals(other.entry)) return false; + return true; + } + +} diff --git a/src/test/java/org/scijava/io/ZipHandleTest.java b/src/test/java/org/scijava/io/ZipHandleTest.java new file mode 100644 index 000000000..4c953f8a4 --- /dev/null +++ b/src/test/java/org/scijava/io/ZipHandleTest.java @@ -0,0 +1,83 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.scijava.io.location.file.FileLocation; +import org.scijava.io.location.zip.ZipHandle; +import org.scijava.io.location.zip.ZipLocation; + +/** + * Tests {@link ZipHandle}. + * + * @author Gabriel Einsdorf + */ +public class ZipHandleTest extends DataHandleTest { + + @Override + public Class> getExpectedHandleType() { + return ZipHandle.class; + } + + @Override + public Location createLocation() throws IOException { + // create and populate a temp file + final File tmpFile = File.createTempFile("FileHandleTest", "test-file.zip"); + tmpFile.deleteOnExit(); + + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream( + tmpFile))) + { + out.putNextEntry(new ZipEntry(tmpFile.getName())); + populateData(out); + } + + return new ZipLocation(new FileLocation(tmpFile)); + } + + @Override + protected void checkWrites(DataHandle handle) + throws IOException + { + // NB Handle is read only + } + + @Override + protected void setup() { + checkLength = false; + } +} diff --git a/src/test/java/org/scijava/io/ZipLocationTest.java b/src/test/java/org/scijava/io/ZipLocationTest.java new file mode 100644 index 000000000..4acc19749 --- /dev/null +++ b/src/test/java/org/scijava/io/ZipLocationTest.java @@ -0,0 +1,63 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 Board of Regents of the University of + * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; +import org.scijava.io.location.file.FileLocation; +import org.scijava.io.location.zip.ZipLocation; + +/** + * Tests {@link ZipLocation}. + * + * @author Gabriel Einsdorf + */ +public class ZipLocationTest { + + /** + * Tests {@link ZipLocation#ZipLocation(Location)}. + * + * @throws IOException + */ + @Test + public void testFile() throws IOException { + final String path = "/not/actually/a/real-file"; + final FileLocation loc = new FileLocation(path); + ZipLocation zLoc = new ZipLocation(loc); + + assertEquals(zLoc.getBaseLocation(), loc); + } + +}