| 
 | 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