Skip to content

Commit b710f33

Browse files
committed
Add Zip support (adapted from SCIFIO)
1 parent dc780ed commit b710f33

File tree

4 files changed

+438
-0
lines changed

4 files changed

+438
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* #%L
3+
* SCIFIO library for reading and converting scientific file formats.
4+
* %%
5+
* Copyright (C) 2011 - 2016 Board of Regents of the University of
6+
* Wisconsin-Madison
7+
* %%
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are met:
10+
*
11+
* 1. Redistributions of source code must retain the above copyright notice,
12+
* this list of conditions and the following disclaimer.
13+
* 2. Redistributions in binary form must reproduce the above copyright notice,
14+
* this list of conditions and the following disclaimer in the documentation
15+
* and/or other materials provided with the distribution.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
* #L%
29+
*/
30+
31+
package org.scijava.io.location.zip;
32+
33+
import java.io.File;
34+
import java.io.IOException;
35+
import java.util.zip.ZipEntry;
36+
import java.util.zip.ZipInputStream;
37+
38+
import org.scijava.io.DataHandle;
39+
import org.scijava.io.DataHandleInputStream;
40+
import org.scijava.io.Location;
41+
import org.scijava.io.location.AbstractCompressedHandle;
42+
import org.scijava.io.location.ResettableStreamHandle;
43+
import org.scijava.io.location.StreamHandle;
44+
import org.scijava.plugin.Plugin;
45+
46+
/**
47+
* StreamHandle implementation for reading from Zip-compressed files or byte
48+
* arrays. Instances of ZipHandle are read-only.
49+
*
50+
* @see StreamHandle
51+
* @author Melissa Linkert
52+
* @author Gabriel Einsdorf
53+
*/
54+
@Plugin(type = DataHandle.class)
55+
public class ZipHandle extends AbstractCompressedHandle<ZipLocation> {
56+
57+
// -- Fields --
58+
59+
private DataHandle<Location> in;
60+
61+
private String entryName;
62+
63+
private ZipEntry entry;
64+
65+
private long entryLength = -1l;
66+
67+
// -- Constructor --
68+
69+
// @Override
70+
// public boolean isConstructable(final String file) throws IOException {
71+
//
72+
// final byte[] b = new byte[2];
73+
// if (handle.length() >= 2) {
74+
// handle.read(b);
75+
// }
76+
// handle.close();
77+
// return new String(b, Constants.ENCODING).equals("PK");
78+
// }
79+
80+
// -- ZipHandle API methods --
81+
82+
/** Get the name of the backing Zip entry. */
83+
public String getEntryName() {
84+
return entryName;
85+
}
86+
87+
@Override
88+
public void resetStream() throws IOException {
89+
90+
if (raw() instanceof ResettableStreamHandle<?>) {
91+
((ResettableStreamHandle<?>) raw()).resetStream();
92+
}
93+
else {
94+
raw().seek(0l);
95+
}
96+
97+
inputStream = new ZipInputStream(new DataHandleInputStream<>(raw()));
98+
// FIXME add Buffering
99+
100+
seekToEntry();
101+
102+
}
103+
104+
// -- IRandomAccess API methods --
105+
106+
@Override
107+
public void close() throws IOException {
108+
inputStream = null;
109+
entryName = null;
110+
entryLength = -1;
111+
if (in != null) in.close();
112+
in = null;
113+
}
114+
115+
// -- Helper methods --
116+
117+
/**
118+
* Seeks to the relevant ZIP entry, populating the stream length accordingly.
119+
*/
120+
private void seekToEntry() throws IOException {
121+
122+
while (true) {
123+
final ZipEntry e = ((ZipInputStream) inputStream).getNextEntry();
124+
if (entryName == null) {
125+
entry = e;
126+
entryName = e.getName();
127+
}
128+
if (entryName.equals(e.getName())) {
129+
// found the matching entry name (or first entry if the name is
130+
// null)
131+
if (entryLength < 0) {
132+
final boolean resetNeeded = populateLength(e.getSize());
133+
if (resetNeeded) {
134+
// stream length was calculated by force, need to reset
135+
resetStream();
136+
}
137+
}
138+
break;
139+
}
140+
}
141+
}
142+
143+
/**
144+
* Sets the stream length, computing it by force if necessary.
145+
*
146+
* @return if the Stream needs to be reset
147+
*/
148+
private boolean populateLength(final long size) throws IOException {
149+
if (size >= 0) {
150+
entryLength = size;
151+
return false;
152+
}
153+
// size is unknown, so we must read the stream manually
154+
long length = 0;
155+
final DataHandle<Location> stream = raw();
156+
while (true) {
157+
final long skipped = stream.skip(Long.MAX_VALUE);
158+
if (skipped == 0) {
159+
// NB: End of stream, we hope. Technically there is no contract
160+
// for when skip(long) returns 0, but in practice it seems to be
161+
// when end of stream is reached.
162+
break;
163+
}
164+
length += skipped;
165+
}
166+
167+
entryLength = length;
168+
return true;
169+
}
170+
171+
@Override
172+
public Class<ZipLocation> getType() {
173+
return ZipLocation.class;
174+
}
175+
176+
@Override
177+
protected void initInputStream() throws IOException {
178+
inputStream = new ZipInputStream(new DataHandleInputStream<>(raw()));
179+
180+
entry = get().getEntry();
181+
if (entry == null) {
182+
// strip off .zip extension and directory prefix
183+
final String n = raw().get().getName();
184+
String name = n.substring(0, n.length() - 4);
185+
186+
int slash = name.lastIndexOf(File.separator);
187+
if (slash < 0) slash = name.lastIndexOf("/");
188+
if (slash >= 0) name = name.substring(slash + 1);
189+
190+
// look for Zip entry with same prefix as the Zip file itself
191+
boolean matchFound = false;
192+
ZipEntry ze;
193+
while ((ze = ((ZipInputStream) inputStream).getNextEntry()) != null) {
194+
if (entryName == null) entryName = ze.getName();
195+
if (!matchFound && ze.getName().startsWith(name)) {
196+
// found entry with matching name
197+
entryName = ze.getName();
198+
entry = ze;
199+
matchFound = true;
200+
}
201+
}
202+
resetStream();
203+
}
204+
}
205+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.io.location.zip;
33+
34+
import java.util.zip.ZipEntry;
35+
36+
import org.scijava.io.DataHandle;
37+
import org.scijava.io.Location;
38+
import org.scijava.io.location.AbstractHigherOrderLocation;
39+
import org.scijava.plugin.Plugin;
40+
41+
/**
42+
* {@link Location} backed by a {@link DataHandle} that is <code>zip</code>
43+
* compressed.
44+
*
45+
* @author Gabriel Einsdorf
46+
* @see ZipHandle
47+
*/
48+
@Plugin(type = DataHandle.class)
49+
public class ZipLocation extends AbstractHigherOrderLocation {
50+
51+
private ZipEntry entry;
52+
53+
public ZipLocation(Location location) {
54+
super(location);
55+
}
56+
57+
public ZipLocation(Location location, ZipEntry entry) {
58+
super(location);
59+
this.entry = entry;
60+
}
61+
62+
public ZipEntry getEntry() {
63+
return entry;
64+
}
65+
66+
@Override
67+
public int hashCode() {
68+
final int prime = 31;
69+
int result = super.hashCode();
70+
result = prime * result + ((entry == null) ? 0 : entry.hashCode());
71+
return result;
72+
}
73+
74+
@Override
75+
public boolean equals(Object obj) {
76+
if (this == obj) return true;
77+
if (!super.equals(obj)) return false;
78+
if (getClass() != obj.getClass()) return false;
79+
ZipLocation other = (ZipLocation) obj;
80+
if (entry == null) {
81+
if (other.entry != null) return false;
82+
}
83+
else if (!entry.equals(other.entry)) return false;
84+
return true;
85+
}
86+
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.io;
33+
34+
import java.io.File;
35+
import java.io.FileOutputStream;
36+
import java.io.IOException;
37+
import java.util.zip.ZipEntry;
38+
import java.util.zip.ZipOutputStream;
39+
40+
import org.scijava.io.location.file.FileLocation;
41+
import org.scijava.io.location.zip.ZipHandle;
42+
import org.scijava.io.location.zip.ZipLocation;
43+
44+
/**
45+
* Tests {@link ZipHandle}.
46+
*
47+
* @author Gabriel Einsdorf
48+
*/
49+
public class ZipHandleTest extends DataHandleTest {
50+
51+
@Override
52+
public Class<? extends DataHandle<?>> getExpectedHandleType() {
53+
return ZipHandle.class;
54+
}
55+
56+
@Override
57+
public Location createLocation() throws IOException {
58+
// create and populate a temp file
59+
final File tmpFile = File.createTempFile("FileHandleTest", "test-file.zip");
60+
tmpFile.deleteOnExit();
61+
62+
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
63+
tmpFile)))
64+
{
65+
out.putNextEntry(new ZipEntry(tmpFile.getName()));
66+
populateData(out);
67+
}
68+
69+
return new ZipLocation(new FileLocation(tmpFile));
70+
}
71+
72+
@Override
73+
protected <L extends Location> void checkWrites(DataHandle<L> handle)
74+
throws IOException
75+
{
76+
// NB Handle is read only
77+
}
78+
79+
@Override
80+
protected void setup() {
81+
checkLength = false;
82+
}
83+
}

0 commit comments

Comments
 (0)