Skip to content

Commit bfc517a

Browse files
gab1onectrueden
authored andcommitted
Add zip support (ported from SCIFIO)
1 parent e1592e6 commit bfc517a

File tree

4 files changed

+439
-0
lines changed

4 files changed

+439
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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.handle;
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.location.AbstractCompressedHandle;
39+
import org.scijava.io.location.Location;
40+
import org.scijava.io.location.ZipLocation;
41+
import org.scijava.plugin.Plugin;
42+
43+
/**
44+
* StreamHandle implementation for reading from Zip-compressed files or byte
45+
* arrays. Instances of ZipHandle are read-only.
46+
*
47+
* @see StreamHandle
48+
* @author Melissa Linkert
49+
* @author Gabriel Einsdorf
50+
*/
51+
@Plugin(type = DataHandle.class)
52+
public class ZipHandle extends AbstractCompressedHandle<ZipLocation> {
53+
54+
// -- Fields --
55+
56+
private DataHandle<Location> in;
57+
58+
private String entryName;
59+
60+
private ZipEntry entry;
61+
62+
private long entryLength = -1l;
63+
64+
// -- ZipHandle API methods --
65+
66+
/** Get the name of the backing Zip entry. */
67+
public String getEntryName() {
68+
return entryName;
69+
}
70+
71+
@Override
72+
public void resetStream() throws IOException {
73+
74+
if (raw() instanceof ResettableStreamHandle<?>) {
75+
((ResettableStreamHandle<?>) raw()).resetStream();
76+
}
77+
else {
78+
raw().seek(0l);
79+
}
80+
81+
inputStream = new ZipInputStream(new DataHandleInputStream<>(raw()));
82+
// FIXME add Buffering
83+
84+
seekToEntry();
85+
86+
}
87+
88+
// -- IRandomAccess API methods --
89+
90+
@Override
91+
public void close() throws IOException {
92+
inputStream = null;
93+
entryName = null;
94+
entryLength = -1;
95+
if (in != null) in.close();
96+
in = null;
97+
}
98+
99+
// -- Helper methods --
100+
101+
/**
102+
* Seeks to the relevant ZIP entry, populating the stream length accordingly.
103+
*/
104+
private void seekToEntry() throws IOException {
105+
106+
while (true) {
107+
final ZipEntry e = ((ZipInputStream) inputStream).getNextEntry();
108+
if (entryName == null) {
109+
entry = e;
110+
entryName = e.getName();
111+
}
112+
if (entryName.equals(e.getName())) {
113+
// found the matching entry name (or first entry if the name is
114+
// null)
115+
if (entryLength < 0) {
116+
final boolean resetNeeded = populateLength(e.getSize());
117+
if (resetNeeded) {
118+
// stream length was calculated by force, need to reset
119+
resetStream();
120+
}
121+
}
122+
break;
123+
}
124+
}
125+
}
126+
127+
/**
128+
* Sets the stream length, computing it by force if necessary.
129+
*
130+
* @return if the Stream needs to be reset
131+
*/
132+
private boolean populateLength(final long size) throws IOException {
133+
if (size >= 0) {
134+
entryLength = size;
135+
return false;
136+
}
137+
// size is unknown, so we must read the stream manually
138+
long length = 0;
139+
final DataHandle<Location> stream = raw();
140+
while (true) {
141+
final long skipped = stream.skip(Long.MAX_VALUE);
142+
if (skipped == 0) {
143+
// NB: End of stream, we hope. Technically there is no contract
144+
// for when skip(long) returns 0, but in practice it seems to be
145+
// when end of stream is reached.
146+
break;
147+
}
148+
length += skipped;
149+
}
150+
151+
entryLength = length;
152+
return true;
153+
}
154+
155+
@Override
156+
public Class<ZipLocation> getType() {
157+
return ZipLocation.class;
158+
}
159+
160+
@Override
161+
protected void initInputStream() throws IOException {
162+
inputStream = new ZipInputStream(new DataHandleInputStream<>(raw()));
163+
164+
entry = get().getEntry();
165+
if (entry == null) {
166+
// strip off .zip extension and directory prefix
167+
final String n = raw().get().getName();
168+
String name = n.substring(0, n.length() - 4);
169+
170+
int slash = name.lastIndexOf(File.separator);
171+
if (slash < 0) slash = name.lastIndexOf("/");
172+
if (slash >= 0) name = name.substring(slash + 1);
173+
174+
// look for Zip entry with same prefix as the Zip file itself
175+
boolean matchFound = false;
176+
ZipEntry ze;
177+
while ((ze = ((ZipInputStream) inputStream).getNextEntry()) != null) {
178+
if (entryName == null) entryName = ze.getName();
179+
if (!matchFound && ze.getName().startsWith(name)) {
180+
// found entry with matching name
181+
entryName = ze.getName();
182+
entry = ze;
183+
matchFound = true;
184+
}
185+
}
186+
resetStream();
187+
}
188+
}
189+
190+
@Override
191+
public long length() throws IOException {
192+
if (entry == null) {
193+
return -1;
194+
}
195+
return entry.getSize();
196+
}
197+
198+
public long getEntryLength() {
199+
return entryLength;
200+
}
201+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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;
33+
34+
import java.util.zip.ZipEntry;
35+
36+
import org.scijava.io.handle.DataHandle;
37+
import org.scijava.io.handle.ZipHandle;
38+
import org.scijava.plugin.Plugin;
39+
40+
/**
41+
* {@link Location} backed by a {@link DataHandle} that is <code>zip</code>
42+
* compressed.
43+
*
44+
* @author Gabriel Einsdorf
45+
* @see ZipHandle
46+
*/
47+
@Plugin(type = DataHandle.class)
48+
public class ZipLocation extends AbstractHigherOrderLocation {
49+
50+
private ZipEntry entry;
51+
52+
public ZipLocation(Location location) {
53+
super(location);
54+
}
55+
56+
public ZipLocation(Location location, ZipEntry entry) {
57+
super(location);
58+
this.entry = entry;
59+
}
60+
61+
public ZipEntry getEntry() {
62+
return entry;
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
final int prime = 31;
68+
int result = super.hashCode();
69+
result = prime * result + ((entry == null) ? 0 : entry.hashCode());
70+
return result;
71+
}
72+
73+
@Override
74+
public boolean equals(Object obj) {
75+
if (this == obj) return true;
76+
if (!super.equals(obj)) return false;
77+
if (getClass() != obj.getClass()) return false;
78+
ZipLocation other = (ZipLocation) obj;
79+
if (entry == null) {
80+
if (other.entry != null) return false;
81+
}
82+
else if (!entry.equals(other.entry)) return false;
83+
return true;
84+
}
85+
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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.handle;
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.junit.Ignore;
41+
import org.junit.Test;
42+
import org.scijava.io.handle.DataHandle;
43+
import org.scijava.io.location.FileLocation;
44+
import org.scijava.io.location.Location;
45+
import org.scijava.io.location.ZipLocation;
46+
47+
/**
48+
* Tests {@link ZipHandle}.
49+
*
50+
* @author Gabriel Einsdorf
51+
*/
52+
public class ZipHandleTest extends DataHandleTest {
53+
54+
@Override
55+
public Class<? extends DataHandle<?>> getExpectedHandleType() {
56+
return ZipHandle.class;
57+
}
58+
59+
@Override
60+
public Location createLocation() throws IOException {
61+
// create and populate a temp file
62+
final File tmpFile = File.createTempFile("FileHandleTest", "test-file.zip");
63+
tmpFile.deleteOnExit();
64+
65+
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
66+
tmpFile)))
67+
{
68+
out.putNextEntry(new ZipEntry(tmpFile.getName()));
69+
populateData(out);
70+
}
71+
72+
return new ZipLocation(new FileLocation(tmpFile));
73+
}
74+
75+
@Test
76+
@Override
77+
public void testWriting() throws IOException {
78+
// no Op
79+
}
80+
81+
@Override
82+
@Test
83+
public void testReading() throws IOException {
84+
try (final DataHandle<? extends Location> handle = createHandle()) {
85+
checkBasicReadMethods(handle, false);
86+
checkEndiannessReading(handle);
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)