001/* 002 * $RCSfile: SegmentedImageInputStream.java,v $ 003 * 004 * 005 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * - Redistribution of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * - Redistribution in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * Neither the name of Sun Microsystems, Inc. or the names of 020 * contributors may be used to endorse or promote products derived 021 * from this software without specific prior written permission. 022 * 023 * This software is provided "AS IS," without a warranty of any 024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 035 * POSSIBILITY OF SUCH DAMAGES. 036 * 037 * You acknowledge that this software is not designed or intended for 038 * use in the design, construction, operation or maintenance of any 039 * nuclear facility. 040 * 041 * $Revision: 1.2 $ 042 * $Date: 2007/08/28 01:12:56 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.stream; 046 047import java.io.IOException; 048import javax.imageio.stream.ImageInputStream; 049import javax.imageio.stream.ImageInputStreamImpl; 050 051/** 052 * An implementation of the <code>StreamSegmentMapper</code> interface 053 * that requires an explicit list of the starting locations and 054 * lengths of the source segments. 055 */ 056class StreamSegmentMapperImpl implements StreamSegmentMapper { 057 058 private long[] segmentPositions; 059 060 private int[] segmentLengths; 061 062 public StreamSegmentMapperImpl(long[] segmentPositions, 063 int[] segmentLengths) { 064 this.segmentPositions = (long[])segmentPositions.clone(); 065 this.segmentLengths = (int[])segmentLengths.clone(); 066 } 067 068 public StreamSegment getStreamSegment(long position, int length) { 069 int numSegments = segmentLengths.length; 070 for (int i = 0; i < numSegments; i++) { 071 int len = segmentLengths[i]; 072 if (position < len) { 073 return new StreamSegment(segmentPositions[i] + position, 074 Math.min(len - (int)position, 075 length)); 076 } 077 position -= len; 078 } 079 080 return null; 081 } 082 083 public void getStreamSegment(long position, int length, 084 StreamSegment seg) { 085 int numSegments = segmentLengths.length; 086 for (int i = 0; i < numSegments; i++) { 087 int len = segmentLengths[i]; 088 if (position < len) { 089 seg.setStartPos(segmentPositions[i] + position); 090 seg.setSegmentLength(Math.min(len - (int)position, length)); 091 return; 092 } 093 position -= len; 094 } 095 096 seg.setStartPos(-1); 097 seg.setSegmentLength(-1); 098 return; 099 } 100 101 long length() { 102 int numSegments = segmentLengths.length; 103 long len = 0L; 104 105 for(int i = 0; i < numSegments; i++) { 106 len += segmentLengths[i]; 107 } 108 109 return len; 110 } 111} 112 113/** 114 * An implementation of the <code>StreamSegmentMapper</code> interface 115 * for segments of equal length. 116 */ 117class SectorStreamSegmentMapper implements StreamSegmentMapper { 118 119 long[] segmentPositions; 120 int segmentLength; 121 int totalLength; 122 int lastSegmentLength; 123 124 public SectorStreamSegmentMapper(long[] segmentPositions, 125 int segmentLength, 126 int totalLength) { 127 this.segmentPositions = (long[])segmentPositions.clone(); 128 this.segmentLength = segmentLength; 129 this.totalLength = totalLength; 130 this.lastSegmentLength = totalLength - 131 (segmentPositions.length - 1)*segmentLength; 132 } 133 134 public StreamSegment getStreamSegment(long position, int length) { 135 int index = (int) (position/segmentLength); 136 137 // Compute segment length 138 int len = (index == segmentPositions.length - 1) ? 139 lastSegmentLength : segmentLength; 140 141 // Compute position within the segment 142 position -= index*segmentLength; 143 144 // Compute maximum legal length 145 len -= position; 146 if (len > length) { 147 len = length; 148 } 149 return new StreamSegment(segmentPositions[index] + position, len); 150 } 151 152 public void getStreamSegment(long position, int length, 153 StreamSegment seg) { 154 int index = (int) (position/segmentLength); 155 156 // Compute segment length 157 int len = (index == segmentPositions.length - 1) ? 158 lastSegmentLength : segmentLength; 159 160 // Compute position within the segment 161 position -= index*segmentLength; 162 163 // Compute maximum legal length 164 len -= position; 165 if (len > length) { 166 len = length; 167 } 168 169 seg.setStartPos(segmentPositions[index] + position); 170 seg.setSegmentLength(len); 171 } 172 173 long length() { 174 return (long)totalLength; 175 } 176} 177 178/** 179 * A <code>SegmentedImageInputStream</code> provides a view of a 180 * subset of another <code>ImageInputStream</code> consiting of a series 181 * of segments with given starting positions in the source stream and 182 * lengths. The resulting stream behaves like an ordinary 183 * <code>ImageInputStream</code>. 184 * 185 * <p> For example, given a <code>ImageInputStream</code> containing 186 * data in a format consisting of a number of sub-streams stored in 187 * non-contiguous sectors indexed by a directory, it is possible to 188 * construct a set of <code>SegmentedImageInputStream</code>s, one for 189 * each sub-stream, that each provide a view of the sectors comprising 190 * a particular stream by providing the positions and lengths of the 191 * stream's sectors as indicated by the directory. The complex 192 * multi-stream structure of the original stream may be ignored by 193 * users of the <code>SegmentedImageInputStream</code>, who see a 194 * separate <code>ImageInputStream</code> for each sub-stream and do not 195 * need to understand the directory structure at all. 196 * 197 * <p> For further efficiency, a directory structure such as in the 198 * example described above need not be fully parsed in order to build 199 * a <code>SegmentedImageInputStream</code>. Instead, the 200 * <code>StreamSegmentMapper</code> interface allows the association 201 * between a desired region of the output and an input segment to be 202 * provided dynamically. This mapping might be computed by reading 203 * from a directory in piecemeal fashion in order to avoid consuming 204 * memory resources. 205 */ 206public class SegmentedImageInputStream extends ImageInputStreamImpl { 207 208 private ImageInputStream stream; 209 private StreamSegmentMapper mapper; 210 211 /** 212 * Constructs a <code>SegmentedImageInputStream</code> 213 * given a <code>ImageInputStream</code> as input 214 * and an instance of <code>StreamSegmentMapper</code>. 215 * 216 * @param stream A source <code>ImageInputStream</code> 217 * @param mapper An instance of the <code>StreamSegmentMapper</code> 218 * interface. 219 */ 220 public SegmentedImageInputStream(ImageInputStream stream, 221 StreamSegmentMapper mapper) { 222 super(); 223 224 this.stream = stream; 225 this.mapper = mapper; 226 } 227 228 /** 229 * Constructs a <code>SegmentedImageInputStream</code> given a 230 * <code>ImageInputStream</code> as input and a list of the starting 231 * positions and lengths of the segments of the source stream. 232 * 233 * @param stream A source <code>ImageInputStream</code> 234 * @param segmentPositions An array of <code>long</code>s 235 * giving the starting positions of the segments in the 236 * source stream. 237 * @param segmentLengths An array of <code>int</code>s 238 * giving the lengths of segments in the source stream. 239 */ 240 public SegmentedImageInputStream(ImageInputStream stream, 241 long[] segmentPositions, 242 int[] segmentLengths) { 243 this(stream, 244 new StreamSegmentMapperImpl(segmentPositions, segmentLengths)); 245 } 246 247 /** 248 * Constructs a <code>SegmentedImageInputStream</code> given a 249 * <code>ImageInputStream</code> as input, a list of the starting 250 * positions of the segments of the source stream, the common 251 * length of each segment, and the total length of the segments. 252 * 253 * <p> This constructor is useful for selecting substreams 254 * of sector-oriented file formats in which each segment 255 * of the substream (except possibly the final segment) 256 * occupies a fixed-length sector. 257 * 258 * @param stream A source <code>ImageInputStream</code> 259 * @param segmentPositions An array of <code>long</code>s 260 * giving the starting positions of the segments in the 261 * source stream. 262 * @param segmentLength The common length of each segment. 263 * @param totalLength The total length of the source segments. 264 */ 265 public SegmentedImageInputStream(ImageInputStream stream, 266 long[] segmentPositions, 267 int segmentLength, 268 int totalLength) { 269 this(stream, 270 new SectorStreamSegmentMapper(segmentPositions, 271 segmentLength, 272 totalLength)); 273 } 274 275 private StreamSegment streamSegment = new StreamSegment(); 276 277 /** 278 * Reads the next byte of data from the input stream. The value byte is 279 * returned as an <code>int</code> in the range <code>0</code> to 280 * <code>255</code>. If no byte is available because the end of the stream 281 * has been reached, the value <code>-1</code> is returned. This method 282 * blocks until input data is available, the end of the stream is detected, 283 * or an exception is thrown. 284 * 285 * @return the next byte of data, or <code>-1</code> if the end of the 286 * stream is reached. 287 * @exception IOException if an I/O error occurs. 288 */ 289 public int read() throws IOException { 290 mapper.getStreamSegment(streamPos, 1, streamSegment); 291 int streamSegmentLength = streamSegment.getSegmentLength(); 292 if(streamSegmentLength < 0) { 293 return -1; 294 } 295 stream.seek(streamSegment.getStartPos()); 296 297 // XXX What happens if streamSegmentLength == 0? Should this 298 // method also return -1 as above for streamSegmentLength < 0? 299 int val = stream.read(); 300 ++streamPos; 301 return val; 302 } 303 304 /** 305 * Reads up to <code>len</code> bytes of data from the input stream into 306 * an array of bytes. An attempt is made to read as many as 307 * <code>len</code> bytes, but a smaller number may be read, possibly 308 * zero. The number of bytes actually read is returned as an integer. 309 * 310 * <p> This method blocks until input data is available, end of stream is 311 * detected, or an exception is thrown. 312 * 313 * <p> If <code>b</code> is <code>null</code>, a 314 * <code>NullPointerException</code> is thrown. 315 * 316 * <p> If <code>off</code> is negative, or <code>len</code> is negative, or 317 * <code>off+len</code> is greater than the length of the array 318 * <code>b</code>, then an <code>IndexOutOfBoundsException</code> is 319 * thrown. 320 * 321 * <p> If <code>len</code> is zero, then no bytes are read and 322 * <code>0</code> is returned; otherwise, there is an attempt to read at 323 * least one byte. If no byte is available because the stream is at end of 324 * stream, the value <code>-1</code> is returned; otherwise, at least one 325 * byte is read and stored into <code>b</code>. 326 * 327 * <p> The first byte read is stored into element <code>b[off]</code>, the 328 * next one into <code>b[off+1]</code>, and so on. The number of bytes read 329 * is, at most, equal to <code>len</code>. Let <i>k</i> be the number of 330 * bytes actually read; these bytes will be stored in elements 331 * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>, 332 * leaving elements <code>b[off+</code><i>k</i><code>]</code> through 333 * <code>b[off+len-1]</code> unaffected. 334 * 335 * <p> In every case, elements <code>b[0]</code> through 336 * <code>b[off]</code> and elements <code>b[off+len]</code> through 337 * <code>b[b.length-1]</code> are unaffected. 338 * 339 * <p> If the first byte cannot be read for any reason other than end of 340 * stream, then an <code>IOException</code> is thrown. In particular, an 341 * <code>IOException</code> is thrown if the input stream has been closed. 342 * 343 * @param b the buffer into which the data is read. 344 * @param off the start offset in array <code>b</code> 345 * at which the data is written. 346 * @param len the maximum number of bytes to read. 347 * @return the total number of bytes read into the buffer, or 348 * <code>-1</code> if there is no more data because the end of 349 * the stream has been reached. 350 * @exception IOException if an I/O error occurs. 351 */ 352 public int read(byte[] b, int off, int len) throws IOException { 353 if (b == null) { 354 throw new NullPointerException(); 355 } 356 if ((off < 0) || (len < 0) || (off + len > b.length)) { 357 throw new IndexOutOfBoundsException(); 358 } 359 if (len == 0) { 360 return 0; 361 } 362 363 mapper.getStreamSegment(streamPos, len, streamSegment); 364 int streamSegmentLength = streamSegment.getSegmentLength(); 365 if(streamSegmentLength < 0) { 366 return -1; 367 } 368 stream.seek(streamSegment.getStartPos()); 369 370 int nbytes = stream.read(b, off, streamSegmentLength); 371 streamPos += nbytes; 372 return nbytes; 373 } 374 375 public long length() { 376 long len; 377 if(mapper instanceof StreamSegmentMapperImpl) { 378 len = ((StreamSegmentMapperImpl)mapper).length(); 379 } else if(mapper instanceof SectorStreamSegmentMapper) { 380 len = ((SectorStreamSegmentMapper)mapper).length(); 381 } else if(mapper != null) { 382 long pos = len = 0L; 383 StreamSegment seg = mapper.getStreamSegment(pos, Integer.MAX_VALUE); 384 while((len = seg.getSegmentLength()) > 0) { 385 pos += len; 386 seg.setSegmentLength(0); 387 mapper.getStreamSegment(pos, Integer.MAX_VALUE, seg); 388 } 389 len = pos; 390 } else { 391 len = super.length(); 392 } 393 394 return len; 395 } 396}