001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: MemoryCacheSeekableStream.java 496556 2007-01-16 00:59:48Z cam $ */
019:
020: package org.apache.xmlgraphics.image.codec.util;
021:
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.util.List;
025: import java.util.ArrayList;
026:
027: /**
028: * A subclass of <code>SeekableStream</code> that may be used to wrap
029: * a regular <code>InputStream</code>. Seeking backwards is supported
030: * by means of an in-memory cache. For greater efficiency,
031: * <code>FileCacheSeekableStream</code> should be used in
032: * circumstances that allow the creation of a temporary file.
033: *
034: * <p> The <code>mark()</code> and <code>reset()</code> methods are
035: * supported.
036: *
037: * <p><b> This class is not a committed part of the JAI API. It may
038: * be removed or changed in future releases of JAI.</b>
039: */
040: public final class MemoryCacheSeekableStream extends SeekableStream {
041:
042: /** The source input stream. */
043: private InputStream src;
044:
045: /** Position of first unread byte. */
046: private long pointer = 0;
047:
048: /** Log_2 of the sector size. */
049: private static final int SECTOR_SHIFT = 9;
050:
051: /** The sector size. */
052: private static final int SECTOR_SIZE = 1 << SECTOR_SHIFT;
053:
054: /** A mask to determine the offset within a sector. */
055: private static final int SECTOR_MASK = SECTOR_SIZE - 1;
056:
057: /** A Vector of source sectors. */
058: private List data = new ArrayList();
059:
060: /** Number of sectors stored. */
061: int sectors = 0;
062:
063: /** Number of bytes read. */
064: int length = 0;
065:
066: /** True if we've previously reached the end of the source stream */
067: boolean foundEOS = false;
068:
069: /**
070: * Constructs a <code>MemoryCacheSeekableStream</code> that takes
071: * its source data from a regular <code>InputStream</code>.
072: * Seeking backwards is supported by means of an in-memory cache.
073: */
074: public MemoryCacheSeekableStream(InputStream src) {
075: this .src = src;
076: }
077:
078: /**
079: * Ensures that at least <code>pos</code> bytes are cached,
080: * or the end of the source is reached. The return value
081: * is equal to the smaller of <code>pos</code> and the
082: * length of the source stream.
083: */
084: private long readUntil(long pos) throws IOException {
085: // We've already got enough data cached
086: if (pos < length) {
087: return pos;
088: }
089: // pos >= length but length isn't getting any bigger, so return it
090: if (foundEOS) {
091: return length;
092: }
093:
094: int sector = (int) (pos >> SECTOR_SHIFT);
095:
096: // First unread sector
097: int startSector = length >> SECTOR_SHIFT;
098:
099: // Read sectors until the desired sector
100: for (int i = startSector; i <= sector; i++) {
101: byte[] buf = new byte[SECTOR_SIZE];
102: data.add(buf);
103:
104: // Read up to SECTOR_SIZE bytes
105: int len = SECTOR_SIZE;
106: int off = 0;
107: while (len > 0) {
108: int nbytes = src.read(buf, off, len);
109: // Found the end-of-stream
110: if (nbytes == -1) {
111: foundEOS = true;
112: return length;
113: }
114: off += nbytes;
115: len -= nbytes;
116:
117: // Record new data length
118: length += nbytes;
119: }
120: }
121:
122: return length;
123: }
124:
125: /**
126: * Returns <code>true</code> since all
127: * <code>MemoryCacheSeekableStream</code> instances support seeking
128: * backwards.
129: */
130: public boolean canSeekBackwards() {
131: return true;
132: }
133:
134: /**
135: * Returns the current offset in this file.
136: *
137: * @return the offset from the beginning of the file, in bytes,
138: * at which the next read occurs.
139: */
140: public long getFilePointer() {
141: return pointer;
142: }
143:
144: /**
145: * Sets the file-pointer offset, measured from the beginning of this
146: * file, at which the next read occurs.
147: *
148: * @param pos the offset position, measured in bytes from the
149: * beginning of the file, at which to set the file
150: * pointer.
151: * @exception IOException if <code>pos</code> is less than
152: * <code>0</code> or if an I/O error occurs.
153: */
154: public void seek(long pos) throws IOException {
155: if (pos < 0) {
156: throw new IOException(PropertyUtil
157: .getString("MemoryCacheSeekableStream0"));
158: }
159: pointer = pos;
160: }
161:
162: /**
163: * Reads the next byte of data from the input stream. The value byte is
164: * returned as an <code>int</code> in the range <code>0</code> to
165: * <code>255</code>. If no byte is available because the end of the stream
166: * has been reached, the value <code>-1</code> is returned. This method
167: * blocks until input data is available, the end of the stream is detected,
168: * or an exception is thrown.
169: *
170: * @return the next byte of data, or <code>-1</code> if the end of the
171: * stream is reached.
172: */
173: public int read() throws IOException {
174: long next = pointer + 1;
175: long pos = readUntil(next);
176: if (pos >= next) {
177: byte[] buf = (byte[]) data
178: .get((int) (pointer >> SECTOR_SHIFT));
179: return buf[(int) (pointer++ & SECTOR_MASK)] & 0xff;
180: } else {
181: return -1;
182: }
183: }
184:
185: /**
186: * Reads up to <code>len</code> bytes of data from the input stream into
187: * an array of bytes. An attempt is made to read as many as
188: * <code>len</code> bytes, but a smaller number may be read, possibly
189: * zero. The number of bytes actually read is returned as an integer.
190: *
191: * <p> This method blocks until input data is available, end of file is
192: * detected, or an exception is thrown.
193: *
194: * <p> If <code>b</code> is <code>null</code>, a
195: * <code>NullPointerException</code> is thrown.
196: *
197: * <p> If <code>off</code> is negative, or <code>len</code> is negative, or
198: * <code>off+len</code> is greater than the length of the array
199: * <code>b</code>, then an <code>IndexOutOfBoundsException</code> is
200: * thrown.
201: *
202: * <p> If <code>len</code> is zero, then no bytes are read and
203: * <code>0</code> is returned; otherwise, there is an attempt to read at
204: * least one byte. If no byte is available because the stream is at end of
205: * file, the value <code>-1</code> is returned; otherwise, at least one
206: * byte is read and stored into <code>b</code>.
207: *
208: * <p> The first byte read is stored into element <code>b[off]</code>, the
209: * next one into <code>b[off+1]</code>, and so on. The number of bytes read
210: * is, at most, equal to <code>len</code>. Let <i>k</i> be the number of
211: * bytes actually read; these bytes will be stored in elements
212: * <code>b[off]</code> through <code>b[off+</code><i>k</i><code>-1]</code>,
213: * leaving elements <code>b[off+</code><i>k</i><code>]</code> through
214: * <code>b[off+len-1]</code> unaffected.
215: *
216: * <p> In every case, elements <code>b[0]</code> through
217: * <code>b[off]</code> and elements <code>b[off+len]</code> through
218: * <code>b[b.length-1]</code> are unaffected.
219: *
220: * <p> If the first byte cannot be read for any reason other than end of
221: * file, then an <code>IOException</code> is thrown. In particular, an
222: * <code>IOException</code> is thrown if the input stream has been closed.
223: *
224: * @param b the buffer into which the data is read.
225: * @param off the start offset in array <code>b</code>
226: * at which the data is written.
227: * @param len the maximum number of bytes to read.
228: * @return the total number of bytes read into the buffer, or
229: * <code>-1</code> if there is no more data because the end of
230: * the stream has been reached.
231: */
232: public int read(byte[] b, int off, int len) throws IOException {
233: if (b == null) {
234: throw new NullPointerException();
235: }
236: if ((off < 0) || (len < 0) || (off + len > b.length)) {
237: throw new IndexOutOfBoundsException();
238: }
239: if (len == 0) {
240: return 0;
241: }
242:
243: long pos = readUntil(pointer + len);
244: // End-of-stream
245: if (pos <= pointer) {
246: return -1;
247: }
248:
249: byte[] buf = (byte[]) data.get((int) (pointer >> SECTOR_SHIFT));
250: int nbytes = Math.min(len, SECTOR_SIZE
251: - (int) (pointer & SECTOR_MASK));
252: System.arraycopy(buf, (int) (pointer & SECTOR_MASK), b, off,
253: nbytes);
254: pointer += nbytes;
255: return nbytes;
256: }
257: }
|