70 /**
71 * Constructs a <code>FileCacheImageInputStream</code> that will read
72 * from a given <code>InputStream</code>.
73 *
74 * <p> A temporary file is used as a cache. If
75 * <code>cacheDir</code>is non-<code>null</code> and is a
76 * directory, the file will be created there. If it is
77 * <code>null</code>, the system-dependent default temporary-file
78 * directory will be used (see the documentation for
79 * <code>File.createTempFile</code> for details).
80 *
81 * @param stream an <code>InputStream</code> to read from.
82 * @param cacheDir a <code>File</code> indicating where the
83 * cache file should be created, or <code>null</code> to use the
84 * system directory.
85 *
86 * @exception IllegalArgumentException if <code>stream</code> is
87 * <code>null</code>.
88 * @exception IllegalArgumentException if <code>cacheDir</code> is
89 * non-<code>null</code> but is not a directory.
90 * @exception IOException if a cache file cannot be created.
91 */
92 public FileCacheImageInputStream(InputStream stream, File cacheDir)
93 throws IOException {
94 if (stream == null) {
95 throw new IllegalArgumentException("stream == null!");
96 }
97 if ((cacheDir != null) && !(cacheDir.isDirectory())) {
98 throw new IllegalArgumentException("Not a directory!");
99 }
100 this.stream = stream;
101 if (cacheDir == null)
102 this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile();
103 else
104 this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp")
105 .toFile();
106 this.cache = new RandomAccessFile(cacheFile, "rw");
107
108 this.closeAction = StreamCloser.createCloseAction(this);
109 StreamCloser.addToQueue(closeAction);
110
111 disposerRecord = new StreamDisposerRecord(cacheFile, cache);
112 if (getClass() == FileCacheImageInputStream.class) {
113 disposerReferent = new Object();
114 Disposer.addRecord(disposerReferent, disposerRecord);
115 } else {
116 disposerReferent = new StreamFinalizer(this);
117 }
118 }
119
120 /**
121 * Ensures that at least <code>pos</code> bytes are cached,
122 * or the end of the source is reached. The return value
123 * is equal to the smaller of <code>pos</code> and the
124 * length of the source file.
125 */
126 private long readUntil(long pos) throws IOException {
127 // We've already got enough data cached
128 if (pos < length) {
129 return pos;
130 }
131 // pos >= length but length isn't getting any bigger, so return it
132 if (foundEOF) {
133 return length;
134 }
135
136 long len = pos - length;
137 cache.seek(length);
138 while (len > 0) {
139 // Copy a buffer's worth of data from the source to the cache
140 // BUFFER_LENGTH will always fit into an int so this is safe
141 int nbytes =
142 stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH));
143 if (nbytes == -1) {
144 foundEOF = true;
227
228 /**
229 * Returns <code>false</code> since this
230 * <code>ImageInputStream</code> does not maintain a main memory
231 * cache.
232 *
233 * @return <code>false</code>.
234 *
235 * @see #isCached
236 * @see #isCachedFile
237 */
238 public boolean isCachedMemory() {
239 return false;
240 }
241
242 /**
243 * Closes this <code>FileCacheImageInputStream</code>, closing
244 * and removing the cache file. The source <code>InputStream</code>
245 * is not closed.
246 *
247 * @exception IOException if an error occurs.
248 */
249 public void close() throws IOException {
250 super.close();
251 disposerRecord.dispose(); // this will close/delete the cache file
252 stream = null;
253 cache = null;
254 cacheFile = null;
255 StreamCloser.removeFromQueue(closeAction);
256 }
257
258 /**
259 * {@inheritDoc}
260 */
261 protected void finalize() throws Throwable {
262 // Empty finalizer: for performance reasons we instead use the
263 // Disposer mechanism for ensuring that the underlying
264 // RandomAccessFile is closed/deleted prior to garbage collection
265 }
266
267 private static class StreamDisposerRecord implements DisposerRecord {
|
70 /**
71 * Constructs a <code>FileCacheImageInputStream</code> that will read
72 * from a given <code>InputStream</code>.
73 *
74 * <p> A temporary file is used as a cache. If
75 * <code>cacheDir</code>is non-<code>null</code> and is a
76 * directory, the file will be created there. If it is
77 * <code>null</code>, the system-dependent default temporary-file
78 * directory will be used (see the documentation for
79 * <code>File.createTempFile</code> for details).
80 *
81 * @param stream an <code>InputStream</code> to read from.
82 * @param cacheDir a <code>File</code> indicating where the
83 * cache file should be created, or <code>null</code> to use the
84 * system directory.
85 *
86 * @exception IllegalArgumentException if <code>stream</code> is
87 * <code>null</code>.
88 * @exception IllegalArgumentException if <code>cacheDir</code> is
89 * non-<code>null</code> but is not a directory.
90 * @throws IOException if a cache file cannot be created.
91 */
92 public FileCacheImageInputStream(InputStream stream, File cacheDir)
93 throws IOException {
94 if (stream == null) {
95 throw new IllegalArgumentException("stream == null!");
96 }
97 if ((cacheDir != null) && !(cacheDir.isDirectory())) {
98 throw new IllegalArgumentException("Not a directory!");
99 }
100 this.stream = stream;
101 if (cacheDir == null)
102 this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile();
103 else
104 this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp")
105 .toFile();
106 this.cache = new RandomAccessFile(cacheFile, "rw");
107
108 this.closeAction = StreamCloser.createCloseAction(this);
109 StreamCloser.addToQueue(closeAction);
110
111 disposerRecord = new StreamDisposerRecord(cacheFile, cache);
112 if (getClass() == FileCacheImageInputStream.class) {
113 disposerReferent = new Object();
114 Disposer.addRecord(disposerReferent, disposerRecord);
115 } else {
116 disposerReferent = new StreamFinalizer(this);
117 }
118 }
119
120 /**
121 * Ensures that at least <code>pos</code> bytes are cached,
122 * or the end of the source is reached. The return value
123 * is equal to the smaller of <code>pos</code> and the
124 * length of the source file.
125 *
126 * @throws IOException if an I/O error occurs while reading from the
127 * source file
128 */
129 private long readUntil(long pos) throws IOException {
130 // We've already got enough data cached
131 if (pos < length) {
132 return pos;
133 }
134 // pos >= length but length isn't getting any bigger, so return it
135 if (foundEOF) {
136 return length;
137 }
138
139 long len = pos - length;
140 cache.seek(length);
141 while (len > 0) {
142 // Copy a buffer's worth of data from the source to the cache
143 // BUFFER_LENGTH will always fit into an int so this is safe
144 int nbytes =
145 stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH));
146 if (nbytes == -1) {
147 foundEOF = true;
230
231 /**
232 * Returns <code>false</code> since this
233 * <code>ImageInputStream</code> does not maintain a main memory
234 * cache.
235 *
236 * @return <code>false</code>.
237 *
238 * @see #isCached
239 * @see #isCachedFile
240 */
241 public boolean isCachedMemory() {
242 return false;
243 }
244
245 /**
246 * Closes this <code>FileCacheImageInputStream</code>, closing
247 * and removing the cache file. The source <code>InputStream</code>
248 * is not closed.
249 *
250 * @throws IOException if an error occurs.
251 */
252 public void close() throws IOException {
253 super.close();
254 disposerRecord.dispose(); // this will close/delete the cache file
255 stream = null;
256 cache = null;
257 cacheFile = null;
258 StreamCloser.removeFromQueue(closeAction);
259 }
260
261 /**
262 * {@inheritDoc}
263 */
264 protected void finalize() throws Throwable {
265 // Empty finalizer: for performance reasons we instead use the
266 // Disposer mechanism for ensuring that the underlying
267 // RandomAccessFile is closed/deleted prior to garbage collection
268 }
269
270 private static class StreamDisposerRecord implements DisposerRecord {
|