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}