001: /*
002: * $RCSfile: SegmentedSeekableStream.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:55:33 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codec;
013:
014: import java.io.IOException;
015:
016: /**
017: * An implementation of the <code>StreamSegmentMapper</code> interface
018: * that requires an explicit list of the starting locations and
019: * lengths of the source segments.
020: */
021: class StreamSegmentMapperImpl implements StreamSegmentMapper {
022:
023: private long[] segmentPositions;
024:
025: private int[] segmentLengths;
026:
027: public StreamSegmentMapperImpl(long[] segmentPositions,
028: int[] segmentLengths) {
029: this .segmentPositions = (long[]) segmentPositions.clone();
030: this .segmentLengths = (int[]) segmentLengths.clone();
031: }
032:
033: public StreamSegment getStreamSegment(long position, int length) {
034: int numSegments = segmentLengths.length;
035: for (int i = 0; i < numSegments; i++) {
036: int len = segmentLengths[i];
037: if (position < len) {
038: return new StreamSegment(
039: segmentPositions[i] + position, Math.min(len
040: - (int) position, length));
041: }
042: position -= len;
043: }
044:
045: return null;
046: }
047:
048: public void getStreamSegment(long position, int length,
049: StreamSegment seg) {
050: int numSegments = segmentLengths.length;
051: for (int i = 0; i < numSegments; i++) {
052: int len = segmentLengths[i];
053: if (position < len) {
054: seg.setStartPos(segmentPositions[i] + position);
055: seg.setSegmentLength(Math.min(len - (int) position,
056: length));
057: return;
058: }
059: position -= len;
060: }
061:
062: seg.setStartPos(-1);
063: seg.setSegmentLength(-1);
064: return;
065: }
066: }
067:
068: /**
069: * An implementation of the <code>StreamSegmentMapper</code> interface
070: * for segments of equal length.
071: */
072: class SectorStreamSegmentMapper implements StreamSegmentMapper {
073:
074: long[] segmentPositions;
075: int segmentLength;
076: int totalLength;
077: int lastSegmentLength;
078:
079: public SectorStreamSegmentMapper(long[] segmentPositions,
080: int segmentLength, int totalLength) {
081: this .segmentPositions = (long[]) segmentPositions.clone();
082: this .segmentLength = segmentLength;
083: this .totalLength = totalLength;
084: this .lastSegmentLength = totalLength
085: - (segmentPositions.length - 1) * segmentLength;
086: }
087:
088: public StreamSegment getStreamSegment(long position, int length) {
089: int index = (int) (position / segmentLength);
090:
091: // Compute segment length
092: int len = (index == segmentPositions.length - 1) ? lastSegmentLength
093: : segmentLength;
094:
095: // Compute position within the segment
096: position -= index * segmentLength;
097:
098: // Compute maximum legal length
099: len -= position;
100: if (len > length) {
101: len = length;
102: }
103: return new StreamSegment(segmentPositions[index] + position,
104: len);
105: }
106:
107: public void getStreamSegment(long position, int length,
108: StreamSegment seg) {
109: int index = (int) (position / segmentLength);
110:
111: // Compute segment length
112: int len = (index == segmentPositions.length - 1) ? lastSegmentLength
113: : segmentLength;
114:
115: // Compute position within the segment
116: position -= index * segmentLength;
117:
118: // Compute maximum legal length
119: len -= position;
120: if (len > length) {
121: len = length;
122: }
123:
124: seg.setStartPos(segmentPositions[index] + position);
125: seg.setSegmentLength(len);
126: }
127: }
128:
129: /**
130: * A <code>SegmentedSeekableStream</code> provides a view of a
131: * subset of another <code>SeekableStream</code> consiting of a series
132: * of segments with given starting positions in the source stream and
133: * lengths. The resulting stream behaves like an ordinary
134: * <code>SeekableStream</code>.
135: *
136: * <p> For example, given a <code>SeekableStream</code> containing
137: * data in a format consisting of a number of sub-streams stored in
138: * non-contiguous sectors indexed by a directory, it is possible to
139: * construct a set of <code>SegmentedSeekableStream</code>s, one for
140: * each sub-stream, that each provide a view of the sectors comprising
141: * a particular stream by providing the positions and lengths of the
142: * stream's sectors as indicated by the directory. The complex
143: * multi-stream structure of the original stream may be ignored by
144: * users of the <code>SegmentedSeekableStream</code>, who see a
145: * separate <code>SeekableStream</code> for each sub-stream and do not
146: * need to understand the directory structure at all.
147: *
148: * <p> For further efficiency, a directory structure such as in the
149: * example described above need not be fully parsed in order to build
150: * a <code>SegmentedSeekableStream</code>. Instead, the
151: * <code>StreamSegmentMapper</code> interface allows the association
152: * between a desired region of the output and an input segment to be
153: * provided dynamically. This mapping might be computed by reading
154: * from a directory in piecemeal fashion in order to avoid consuming
155: * memory resources.
156: *
157: * <p> It is the responsibility of the user of this class to determine
158: * whether backwards seeking should be enabled. If the source stream
159: * supports only forward seeking, backwards seeking must be disabled
160: * and the <code>StreamSegmentMapper</code> must be monotone; that is,
161: * forward motion in the destination must always result in forward
162: * motion within the source. If the source stream supports backwards
163: * seeking, there are no restrictions on the
164: * <code>StreamSegmentMapper</code> and backwards seeking may always
165: * be enabled for the <code>SegmentedSeekableStream</code>.
166: *
167: * <p><b> This class is not a committed part of the JAI API. It may
168: * be removed or changed in future releases of JAI.</b>
169: */
170: public class SegmentedSeekableStream extends SeekableStream {
171:
172: private SeekableStream stream;
173: private StreamSegmentMapper mapper;
174: private long pointer = 0;
175: private boolean canSeekBackwards;
176:
177: /**
178: * Constructs a <code>SegmentedSeekableStream</code>
179: * given a <code>SeekableStream</code> as input,
180: * an instance of <code>StreamSegmentMapper</code>,
181: * and a <code>boolean</code> indicating whether the
182: * output <code>SegmentedSeekableStream</code> should
183: * support seeking backwards. If <code>canSeekBackwards</code>
184: * is <code>true</code>, the source stream must itself
185: * support seeking backwards.
186: *
187: * @param stream A source <code>SeekableStream</code>
188: * @param mapper An instance of the <code>StreamSegmentMapper</code>
189: * interface.
190: * @param canSeekBackwards <code>true</code> if the ability to
191: * seek backwards is desired.
192: */
193: public SegmentedSeekableStream(SeekableStream stream,
194: StreamSegmentMapper mapper, boolean canSeekBackwards) {
195: this .stream = stream;
196: this .mapper = mapper;
197: this .canSeekBackwards = canSeekBackwards;
198:
199: if (canSeekBackwards && !stream.canSeekBackwards()) {
200: throw new IllegalArgumentException(JaiI18N
201: .getString("SegmentedSeekableStream0"));
202: }
203: }
204:
205: /**
206: * Constructs a <code>SegmentedSeekableStream</code> given a
207: * <code>SeekableStream</code> as input, a list of the starting
208: * positions and lengths of the segments of the source stream, and
209: * a <code>boolean</code> indicating whether the output
210: * <code>SegmentedSeekableStream</code> should support seeking
211: * backwards. If <code>canSeekBakckwards</code> is
212: * <code>true</code>, the source stream must itself support
213: * seeking backwards.
214: *
215: * @param stream A source <code>SeekableStream</code>
216: * @param segmentPositions An array of <code>long</code>s
217: * giving the starting positions of the segments in the
218: * source stream.
219: * @param segmentLengths An array of <code>int</code>s
220: * giving the lengths of segments in the source stream.
221: * @param canSeekBackwards <code>true</code> if the ability to
222: * seek backwards is desired.
223: */
224: public SegmentedSeekableStream(SeekableStream stream,
225: long[] segmentPositions, int[] segmentLengths,
226: boolean canSeekBackwards) {
227: this (stream, new StreamSegmentMapperImpl(segmentPositions,
228: segmentLengths), canSeekBackwards);
229: }
230:
231: /**
232: * Constructs a <code>SegmentedSeekableStream</code> given a
233: * <code>SeekableStream</code> as input, a list of the starting
234: * positions of the segments of the source stream, the common
235: * length of each segment, the total length of the segments and
236: * a <code>boolean</code> indicating whether the output
237: * <code>SegmentedSeekableStream</code> should support seeking
238: * backwards. If <code>canSeekBakckwards</code> is
239: * <code>true</code>, the source stream must itself support
240: * seeking backwards.
241: *
242: * <p> This constructor is useful for selecting substreams
243: * of sector-oriented file formats in which each segment
244: * of the substream (except possibly the final segment)
245: * occupies a fixed-length sector.
246: *
247: * @param stream A source <code>SeekableStream</code>
248: * @param segmentPositions An array of <code>long</code>s
249: * giving the starting positions of the segments in the
250: * source stream.
251: * @param segmentLength The common length of each segment.
252: * @param totalLength The total length of the source segments.
253: * @param canSeekBackwards <code>true</code> if the ability to
254: * seek backwards is desired.
255: */
256: public SegmentedSeekableStream(SeekableStream stream,
257: long[] segmentPositions, int segmentLength,
258: int totalLength, boolean canSeekBackwards) {
259: this (stream, new SectorStreamSegmentMapper(segmentPositions,
260: segmentLength, totalLength), canSeekBackwards);
261: }
262:
263: /**
264: * Returns the current offset in this stream.
265: *
266: * @return the offset from the beginning of the stream, in bytes,
267: * at which the next read occurs.
268: */
269: public long getFilePointer() {
270: return (long) pointer;
271: }
272:
273: /**
274: * Returns <code>true</code> if seeking backwards is supported.
275: * Support is determined by the value of the
276: * <code>canSeekBackwards</code> parameter at construction time.
277: */
278: public boolean canSeekBackwards() {
279: return canSeekBackwards;
280: }
281:
282: /**
283: * Sets the offset, measured from the beginning of this
284: * stream, at which the next read occurs.
285: *
286: * <p> If <code>canSeekBackwards()</code> returns <code>false</code>,
287: * then setting <code>pos</code> to an offset smaller than
288: * the current value of <code>getFilePointer()</code> will have
289: * no effect.
290: *
291: * @param pos the offset position, measured in bytes from the
292: * beginning of the stream, at which to set the stream
293: * pointer.
294: * @exception IOException if <code>pos</code> is less than
295: * <code>0</code> or if an I/O error occurs.
296: */
297: public void seek(long pos) throws IOException {
298: if (pos < 0) {
299: throw new IOException();
300: }
301: pointer = pos;
302: }
303:
304: private StreamSegment streamSegment = new StreamSegment();
305:
306: /**
307: * Reads the next byte of data from the input stream. The value byte is
308: * returned as an <code>int</code> in the range <code>0</code> to
309: * <code>255</code>. If no byte is available because the end of the stream
310: * has been reached, the value <code>-1</code> is returned. This method
311: * blocks until input data is available, the end of the stream is detected,
312: * or an exception is thrown.
313: *
314: * @return the next byte of data, or <code>-1</code> if the end of the
315: * stream is reached.
316: * @exception IOException if an I/O error occurs.
317: */
318: public int read() throws IOException {
319: mapper.getStreamSegment(pointer, 1, streamSegment);
320: stream.seek(streamSegment.getStartPos());
321:
322: int val = stream.read();
323: ++pointer;
324: return val;
325: }
326:
327: /**
328: * Reads up to <code>len</code> bytes of data from the input stream into
329: * an array of bytes. An attempt is made to read as many as
330: * <code>len</code> bytes, but a smaller number may be read, possibly
331: * zero. The number of bytes actually read is returned as an integer.
332: *
333: * <p> This method blocks until input data is available, end of stream is
334: * detected, or an exception is thrown.
335: *
336: * <p> If <code>b</code> is <code>null</code>, a
337: * <code>NullPointerException</code> is thrown.
338: *
339: * <p> If <code>off</code> is negative, or <code>len</code> is negative, or
340: * <code>off+len</code> is greater than the length of the array
341: * <code>b</code>, then an <code>IndexOutOfBoundsException</code> is
342: * thrown.
343: *
344: * <p> If <code>len</code> is zero, then no bytes are read and
345: * <code>0</code> is returned; otherwise, there is an attempt to read at
346: * least one byte. If no byte is available because the stream is at end of
347: * stream, the value <code>-1</code> is returned; otherwise, at least one
348: * byte is read and stored into <code>b</code>.
349: *
350: * <p> The first byte read is stored into element <code>b[off]</code>, the
351: * next one into <code>b[off+1]</code>, and so on. The number of bytes read
352: * is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
353: * bytes actually read; these bytes will be stored in elements
354: * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
355: * leaving elements <code>b[off+</code><i>k</i><code>]</code> through
356: * <code>b[off+len-1]</code> unaffected.
357: *
358: * <p> In every case, elements <code>b[0]</code> through
359: * <code>b[off]</code> and elements <code>b[off+len]</code> through
360: * <code>b[b.length-1]</code> are unaffected.
361: *
362: * <p> If the first byte cannot be read for any reason other than end of
363: * stream, then an <code>IOException</code> is thrown. In particular, an
364: * <code>IOException</code> is thrown if the input stream has been closed.
365: *
366: * @param b the buffer into which the data is read.
367: * @param off the start offset in array <code>b</code>
368: * at which the data is written.
369: * @param len the maximum number of bytes to read.
370: * @return the total number of bytes read into the buffer, or
371: * <code>-1</code> if there is no more data because the end of
372: * the stream has been reached.
373: * @exception IOException if an I/O error occurs.
374: */
375: public int read(byte[] b, int off, int len) throws IOException {
376: if (b == null) {
377: throw new NullPointerException();
378: }
379: if ((off < 0) || (len < 0) || (off + len > b.length)) {
380: throw new IndexOutOfBoundsException();
381: }
382: if (len == 0) {
383: return 0;
384: }
385:
386: mapper.getStreamSegment(pointer, len, streamSegment);
387: stream.seek(streamSegment.getStartPos());
388:
389: int nbytes = stream.read(b, off, streamSegment
390: .getSegmentLength());
391: pointer += nbytes;
392: return nbytes;
393: }
394: }
|