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; 048 049import javax.imageio.stream.ImageInputStream; 050import javax.imageio.stream.ImageInputStreamImpl; 051 052/** 053 * An implementation of the <code>StreamSegmentMapper</code> interface 054 * that requires an explicit list of the starting locations and 055 * lengths of the source segments. 056 */ 057class StreamSegmentMapperImpl implements StreamSegmentMapper { 058 059 private long[] segmentPositions; 060 061 private int[] segmentLengths; 062 063 public StreamSegmentMapperImpl(long[] segmentPositions, 064 int[] segmentLengths) { 065 this.segmentPositions = (long[])segmentPositions.clone(); 066 this.segmentLengths = (int[])segmentLengths.clone(); 067 } 068 069 public StreamSegment getStreamSegment(long position, int length) { 070 int numSegments = segmentLengths.length; 071 for (int i = 0; i < numSegments; i++) { 072 int len = segmentLengths[i]; 073 if (position < len) { 074 return new StreamSegment(segmentPositions[i] + position, 075 Math.min(len - (int)position, 076 length)); 077 } 078 position -= len; 079 } 080 081 return null; 082 } 083 084 public void getStreamSegment(long position, int length, 085 StreamSegment seg) { 086 int numSegments = segmentLengths.length; 087 for (int i = 0; i < numSegments; i++) { 088 int len = segmentLengths[i]; 089 if (position < len) { 090 seg.setStartPos(segmentPositions[i] + position); 091 seg.setSegmentLength(Math.min(len - (int)position, length)); 092 return; 093 } 094 position -= len; 095 } 096 097 seg.setStartPos(-1); 098 seg.setSegmentLength(-1); 099 return; 100 } 101 102 long length() { 103 int numSegments = segmentLengths.length; 104 long len = 0L; 105 106 for(int i = 0; i < numSegments; i++) { 107 len += segmentLengths[i]; 108 } 109 110 return len; 111 } 112} 113 114/** 115 * An implementation of the <code>StreamSegmentMapper</code> interface 116 * for segments of equal length. 117 */ 118class SectorStreamSegmentMapper implements StreamSegmentMapper { 119 120 long[] segmentPositions; 121 int segmentLength; 122 int totalLength; 123 int lastSegmentLength; 124 125 public SectorStreamSegmentMapper(long[] segmentPositions, 126 int segmentLength, 127 int totalLength) { 128 this.segmentPositions = (long[])segmentPositions.clone(); 129 this.segmentLength = segmentLength; 130 this.totalLength = totalLength; 131 this.lastSegmentLength = totalLength - 132 (segmentPositions.length - 1)*segmentLength; 133 } 134 135 public StreamSegment getStreamSegment(long position, int length) { 136 int index = (int) (position/segmentLength); 137 138 // Compute segment length 139 int len = (index == segmentPositions.length - 1) ? 140 lastSegmentLength : segmentLength; 141 142 // Compute position within the segment 143 position -= index*segmentLength; 144 145 // Compute maximum legal length 146 len -= position; 147 if (len > length) { 148 len = length; 149 } 150 return new StreamSegment(segmentPositions[index] + position, len); 151 } 152 153 public void getStreamSegment(long position, int length, 154 StreamSegment seg) { 155 int index = (int) (position/segmentLength); 156 157 // Compute segment length 158 int len = (index == segmentPositions.length - 1) ? 159 lastSegmentLength : segmentLength; 160 161 // Compute position within the segment 162 position -= index*segmentLength; 163 164 // Compute maximum legal length 165 len -= position; 166 if (len > length) { 167 len = length; 168 } 169 170 seg.setStartPos(segmentPositions[index] + position); 171 seg.setSegmentLength(len); 172 } 173 174 long length() { 175 return (long)totalLength; 176 } 177} 178 179/** 180 * A <code>SegmentedImageInputStream</code> provides a view of a 181 * subset of another <code>ImageInputStream</code> consiting of a series 182 * of segments with given starting positions in the source stream and 183 * lengths. The resulting stream behaves like an ordinary 184 * <code>ImageInputStream</code>. 185 * 186 * <p> For example, given a <code>ImageInputStream</code> containing 187 * data in a format consisting of a number of sub-streams stored in 188 * non-contiguous sectors indexed by a directory, it is possible to 189 * construct a set of <code>SegmentedImageInputStream</code>s, one for 190 * each sub-stream, that each provide a view of the sectors comprising 191 * a particular stream by providing the positions and lengths of the 192 * stream's sectors as indicated by the directory. The complex 193 * multi-stream structure of the original stream may be ignored by 194 * users of the <code>SegmentedImageInputStream</code>, who see a 195 * separate <code>ImageInputStream</code> for each sub-stream and do not 196 * need to understand the directory structure at all. 197 * 198 * <p> For further efficiency, a directory structure such as in the 199 * example described above need not be fully parsed in order to build 200 * a <code>SegmentedImageInputStream</code>. Instead, the 201 * <code>StreamSegmentMapper</code> interface allows the association 202 * between a desired region of the output and an input segment to be 203 * provided dynamically. This mapping might be computed by reading 204 * from a directory in piecemeal fashion in order to avoid consuming 205 * memory resources. 206 */ 207public class SegmentedImageInputStream extends ImageInputStreamImpl { 208 209 private ImageInputStream stream; 210 private StreamSegmentMapper mapper; 211 212 /** 213 * Constructs a <code>SegmentedImageInputStream</code> 214 * given a <code>ImageInputStream</code> as input 215 * and an instance of <code>StreamSegmentMapper</code>. 216 * 217 * @param stream A source <code>ImageInputStream</code> 218 * @param mapper An instance of the <code>StreamSegmentMapper</code> 219 * interface. 220 */ 221 public SegmentedImageInputStream(ImageInputStream stream, 222 StreamSegmentMapper mapper) { 223 super(); 224 225 this.stream = stream; 226 this.mapper = mapper; 227 } 228 229 /** 230 * Constructs a <code>SegmentedImageInputStream</code> given a 231 * <code>ImageInputStream</code> as input and a list of the starting 232 * positions and lengths of the segments of the source stream. 233 * 234 * @param stream A source <code>ImageInputStream</code> 235 * @param segmentPositions An array of <code>long</code>s 236 * giving the starting positions of the segments in the 237 * source stream. 238 * @param segmentLengths An array of <code>int</code>s 239 * giving the lengths of segments in the source stream. 240 */ 241 public SegmentedImageInputStream(ImageInputStream stream, 242 long[] segmentPositions, 243 int[] segmentLengths) { 244 this(stream, 245 new StreamSegmentMapperImpl(segmentPositions, segmentLengths)); 246 } 247 248 /** 249 * Constructs a <code>SegmentedImageInputStream</code> given a 250 * <code>ImageInputStream</code> as input, a list of the starting 251 * positions of the segments of the source stream, the common 252 * length of each segment, and the total length of the segments. 253 * 254 * <p> This constructor is useful for selecting substreams 255 * of sector-oriented file formats in which each segment 256 * of the substream (except possibly the final segment) 257 * occupies a fixed-length sector. 258 * 259 * @param stream A source <code>ImageInputStream</code> 260 * @param segmentPositions An array of <code>long</code>s 261 * giving the starting positions of the segments in the 262 * source stream. 263 * @param segmentLength The common length of each segment. 264 * @param totalLength The total length of the source segments. 265 */ 266 public SegmentedImageInputStream(ImageInputStream stream, 267 long[] segmentPositions, 268 int segmentLength, 269 int totalLength) { 270 this(stream, 271 new SectorStreamSegmentMapper(segmentPositions, 272 segmentLength, 273 totalLength)); 274 } 275 276 private StreamSegment streamSegment = new StreamSegment(); 277 278 /** 279 * Reads the next byte of data from the input stream. The value byte is 280 * returned as an <code>int</code> in the range <code>0</code> to 281 * <code>255</code>. If no byte is available because the end of the stream 282 * has been reached, the value <code>-1</code> is returned. This method 283 * blocks until input data is available, the end of the stream is detected, 284 * or an exception is thrown. 285 * 286 * @return the next byte of data, or <code>-1</code> if the end of the 287 * stream is reached. 288 * @exception IOException if an I/O error occurs. 289 */ 290 public int read() throws IOException { 291 mapper.getStreamSegment(streamPos, 1, streamSegment); 292 int streamSegmentLength = streamSegment.getSegmentLength(); 293 if(streamSegmentLength < 0) { 294 return -1; 295 } 296 stream.seek(streamSegment.getStartPos()); 297 298 // XXX What happens if streamSegmentLength == 0? Should this 299 // method also return -1 as above for streamSegmentLength < 0? 300 int val = stream.read(); 301 ++streamPos; 302 return val; 303 } 304 305 /** 306 * Reads up to <code>len</code> bytes of data from the input stream into 307 * an array of bytes. An attempt is made to read as many as 308 * <code>len</code> bytes, but a smaller number may be read, possibly 309 * zero. The number of bytes actually read is returned as an integer. 310 * 311 * <p> This method blocks until input data is available, end of stream is 312 * detected, or an exception is thrown. 313 * 314 * <p> If <code>b</code> is <code>null</code>, a 315 * <code>NullPointerException</code> is thrown. 316 * 317 * <p> If <code>off</code> is negative, or <code>len</code> is negative, or 318 * <code>off+len</code> is greater than the length of the array 319 * <code>b</code>, then an <code>IndexOutOfBoundsException</code> is 320 * thrown. 321 * 322 * <p> If <code>len</code> is zero, then no bytes are read and 323 * <code>0</code> is returned; otherwise, there is an attempt to read at 324 * least one byte. If no byte is available because the stream is at end of 325 * stream, the value <code>-1</code> is returned; otherwise, at least one 326 * byte is read and stored into <code>b</code>. 327 * 328 * <p> The first byte read is stored into element <code>b[off]</code>, the 329 * next one into <code>b[off+1]</code>, and so on. The number of bytes read 330 * is, at most, equal to <code>len</code>. Let <i>k</i> be the number of 331 * bytes actually read; these bytes will be stored in elements 332 * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>, 333 * leaving elements <code>b[off+</code><i>k</i><code>]</code> through 334 * <code>b[off+len-1]</code> unaffected. 335 * 336 * <p> In every case, elements <code>b[0]</code> through 337 * <code>b[off]</code> and elements <code>b[off+len]</code> through 338 * <code>b[b.length-1]</code> are unaffected. 339 * 340 * <p> If the first byte cannot be read for any reason other than end of 341 * stream, then an <code>IOException</code> is thrown. In particular, an 342 * <code>IOException</code> is thrown if the input stream has been closed. 343 * 344 * @param b the buffer into which the data is read. 345 * @param off the start offset in array <code>b</code> 346 * at which the data is written. 347 * @param len the maximum number of bytes to read. 348 * @return the total number of bytes read into the buffer, or 349 * <code>-1</code> if there is no more data because the end of 350 * the stream has been reached. 351 * @exception IOException if an I/O error occurs. 352 */ 353 public int read(byte[] b, int off, int len) throws IOException { 354 if (b == null) { 355 throw new NullPointerException(); 356 } 357 if ((off < 0) || (len < 0) || (off + len > b.length)) { 358 throw new IndexOutOfBoundsException(); 359 } 360 if (len == 0) { 361 return 0; 362 } 363 364 mapper.getStreamSegment(streamPos, len, streamSegment); 365 int streamSegmentLength = streamSegment.getSegmentLength(); 366 if(streamSegmentLength < 0) { 367 return -1; 368 } 369 stream.seek(streamSegment.getStartPos()); 370 371 int nbytes = stream.read(b, off, streamSegmentLength); 372 streamPos += nbytes; 373 return nbytes; 374 } 375 376 public long length() { 377 long len; 378 if(mapper instanceof StreamSegmentMapperImpl) { 379 len = ((StreamSegmentMapperImpl)mapper).length(); 380 } else if(mapper instanceof SectorStreamSegmentMapper) { 381 len = ((SectorStreamSegmentMapper)mapper).length(); 382 } else if(mapper != null) { 383 long pos = len = 0L; 384 StreamSegment seg = mapper.getStreamSegment(pos, Integer.MAX_VALUE); 385 while((len = seg.getSegmentLength()) > 0) { 386 pos += len; 387 seg.setSegmentLength(0); 388 mapper.getStreamSegment(pos, Integer.MAX_VALUE, seg); 389 } 390 len = pos; 391 } else { 392 len = super.length(); 393 } 394 395 return len; 396 } 397}