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}