001 /*
002 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.imageio.stream;
027
028 import java.io.DataInput;
029 import java.io.File;
030 import java.io.InputStream;
031 import java.io.IOException;
032 import java.io.RandomAccessFile;
033 import com.sun.imageio.stream.StreamCloser;
034 import com.sun.imageio.stream.StreamFinalizer;
035 import sun.java2d.Disposer;
036 import sun.java2d.DisposerRecord;
037
038 /**
039 * An implementation of <code>ImageInputStream</code> that gets its
040 * input from a regular <code>InputStream</code>. A file is used to
041 * cache previously read data.
042 *
043 * @version 0.5
044 */
045 public class FileCacheImageInputStream extends ImageInputStreamImpl {
046
047 private InputStream stream;
048
049 private File cacheFile;
050
051 private RandomAccessFile cache;
052
053 private static final int BUFFER_LENGTH = 1024;
054
055 private byte[] buf = new byte[BUFFER_LENGTH];
056
057 private long length = 0L;
058
059 private boolean foundEOF = false;
060
061 /** The referent to be registered with the Disposer. */
062 private final Object disposerReferent;
063
064 /** The DisposerRecord that closes the underlying cache. */
065 private final DisposerRecord disposerRecord;
066
067 /**
068 * Constructs a <code>FileCacheImageInputStream</code> that will read
069 * from a given <code>InputStream</code>.
070 *
071 * <p> A temporary file is used as a cache. If
072 * <code>cacheDir</code>is non-<code>null</code> and is a
073 * directory, the file will be created there. If it is
074 * <code>null</code>, the system-dependent default temporary-file
075 * directory will be used (see the documentation for
076 * <code>File.createTempFile</code> for details).
077 *
078 * @param stream an <code>InputStream</code> to read from.
079 * @param cacheDir a <code>File</code> indicating where the
080 * cache file should be created, or <code>null</code> to use the
081 * system directory.
082 *
083 * @exception IllegalArgumentException if <code>stream</code> is
084 * <code>null</code>.
085 * @exception IllegalArgumentException if <code>cacheDir</code> is
086 * non-<code>null</code> but is not a directory.
087 * @exception IOException if a cache file cannot be created.
088 */
089 public FileCacheImageInputStream(InputStream stream, File cacheDir)
090 throws IOException {
091 if (stream == null) {
092 throw new IllegalArgumentException("stream == null!");
093 }
094 if ((cacheDir != null) && !(cacheDir.isDirectory())) {
095 throw new IllegalArgumentException("Not a directory!");
096 }
097 this .stream = stream;
098 this .cacheFile = File.createTempFile("imageio", ".tmp",
099 cacheDir);
100 this .cache = new RandomAccessFile(cacheFile, "rw");
101 StreamCloser.addToQueue(this );
102
103 disposerRecord = new StreamDisposerRecord(cacheFile, cache);
104 if (getClass() == FileCacheImageInputStream.class) {
105 disposerReferent = new Object();
106 Disposer.addRecord(disposerReferent, disposerRecord);
107 } else {
108 disposerReferent = new StreamFinalizer(this );
109 }
110 }
111
112 /**
113 * Ensures that at least <code>pos</code> bytes are cached,
114 * or the end of the source is reached. The return value
115 * is equal to the smaller of <code>pos</code> and the
116 * length of the source file.
117 */
118 private long readUntil(long pos) throws IOException {
119 // We've already got enough data cached
120 if (pos < length) {
121 return pos;
122 }
123 // pos >= length but length isn't getting any bigger, so return it
124 if (foundEOF) {
125 return length;
126 }
127
128 long len = pos - length;
129 cache.seek(length);
130 while (len > 0) {
131 // Copy a buffer's worth of data from the source to the cache
132 // BUFFER_LENGTH will always fit into an int so this is safe
133 int nbytes = stream.read(buf, 0, (int) Math.min(len,
134 (long) BUFFER_LENGTH));
135 if (nbytes == -1) {
136 foundEOF = true;
137 return length;
138 }
139
140 cache.write(buf, 0, nbytes);
141 len -= nbytes;
142 length += nbytes;
143 }
144
145 return pos;
146 }
147
148 public int read() throws IOException {
149 checkClosed();
150 bitOffset = 0;
151 long next = streamPos + 1;
152 long pos = readUntil(next);
153 if (pos >= next) {
154 cache.seek(streamPos++);
155 return cache.read();
156 } else {
157 return -1;
158 }
159 }
160
161 public int read(byte[] b, int off, int len) throws IOException {
162 checkClosed();
163
164 if (b == null) {
165 throw new NullPointerException("b == null!");
166 }
167 // Fix 4430357 - if off + len < 0, overflow occurred
168 if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
169 throw new IndexOutOfBoundsException(
170 "off < 0 || len < 0 || off+len > b.length || off+len < 0!");
171 }
172
173 bitOffset = 0;
174
175 if (len == 0) {
176 return 0;
177 }
178
179 long pos = readUntil(streamPos + len);
180
181 // len will always fit into an int so this is safe
182 len = (int) Math.min((long) len, pos - streamPos);
183 if (len > 0) {
184 cache.seek(streamPos);
185 cache.readFully(b, off, len);
186 streamPos += len;
187 return len;
188 } else {
189 return -1;
190 }
191 }
192
193 /**
194 * Returns <code>true</code> since this
195 * <code>ImageInputStream</code> caches data in order to allow
196 * seeking backwards.
197 *
198 * @return <code>true</code>.
199 *
200 * @see #isCachedMemory
201 * @see #isCachedFile
202 */
203 public boolean isCached() {
204 return true;
205 }
206
207 /**
208 * Returns <code>true</code> since this
209 * <code>ImageInputStream</code> maintains a file cache.
210 *
211 * @return <code>true</code>.
212 *
213 * @see #isCached
214 * @see #isCachedMemory
215 */
216 public boolean isCachedFile() {
217 return true;
218 }
219
220 /**
221 * Returns <code>false</code> since this
222 * <code>ImageInputStream</code> does not maintain a main memory
223 * cache.
224 *
225 * @return <code>false</code>.
226 *
227 * @see #isCached
228 * @see #isCachedFile
229 */
230 public boolean isCachedMemory() {
231 return false;
232 }
233
234 /**
235 * Closes this <code>FileCacheImageInputStream</code>, closing
236 * and removing the cache file. The source <code>InputStream</code>
237 * is not closed.
238 *
239 * @exception IOException if an error occurs.
240 */
241 public void close() throws IOException {
242 super .close();
243 disposerRecord.dispose(); // this will close/delete the cache file
244 stream = null;
245 cache = null;
246 cacheFile = null;
247 StreamCloser.removeFromQueue(this );
248 }
249
250 /**
251 * {@inheritDoc}
252 */
253 protected void finalize() throws Throwable {
254 // Empty finalizer: for performance reasons we instead use the
255 // Disposer mechanism for ensuring that the underlying
256 // RandomAccessFile is closed/deleted prior to garbage collection
257 }
258
259 private static class StreamDisposerRecord implements DisposerRecord {
260 private File cacheFile;
261 private RandomAccessFile cache;
262
263 public StreamDisposerRecord(File cacheFile,
264 RandomAccessFile cache) {
265 this .cacheFile = cacheFile;
266 this .cache = cache;
267 }
268
269 public synchronized void dispose() {
270 if (cache != null) {
271 try {
272 cache.close();
273 } catch (IOException e) {
274 } finally {
275 cache = null;
276 }
277 }
278 if (cacheFile != null) {
279 cacheFile.delete();
280 cacheFile = null;
281 }
282 // Note: Explicit removal of the stream from the StreamCloser
283 // queue is not mandatory in this case, as it will be removed
284 // automatically by GC shortly after this method is called.
285 }
286 }
287 }
|