|  | 
|  | 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 | +} | 
0 commit comments