1 /* 2 * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.imageio.stream; 27 28 import java.io.File; 29 import java.io.InputStream; 30 import java.io.IOException; 31 import java.io.RandomAccessFile; 32 import java.nio.file.Files; 33 import com.sun.imageio.stream.StreamCloser; 34 import com.sun.imageio.stream.StreamFinalizer; 35 import sun.java2d.Disposer; 36 import sun.java2d.DisposerRecord; 37 38 /** 39 * An implementation of <code>ImageInputStream</code> that gets its 40 * input from a regular <code>InputStream</code>. A file is used to 41 * cache previously read data. 42 * 43 */ 44 public class FileCacheImageInputStream extends ImageInputStreamImpl { 45 46 private InputStream stream; 47 48 private File cacheFile; 49 50 private RandomAccessFile cache; 51 52 private static final int BUFFER_LENGTH = 1024; 53 54 private byte[] buf = new byte[BUFFER_LENGTH]; 55 56 private long length = 0L; 57 58 private boolean foundEOF = false; 59 60 /** The referent to be registered with the Disposer. */ 61 private final Object disposerReferent; 62 63 /** The DisposerRecord that closes the underlying cache. */ 64 private final DisposerRecord disposerRecord; 65 66 /** The CloseAction that closes the stream in 67 * the StreamCloser's shutdown hook */ 68 private final StreamCloser.CloseAction closeAction; 69 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; 145 return length; 146 } 147 148 cache.write(buf, 0, nbytes); 149 len -= nbytes; 150 length += nbytes; 151 } 152 153 return pos; 154 } 155 156 public int read() throws IOException { 157 checkClosed(); 158 bitOffset = 0; 159 long next = streamPos + 1; 160 long pos = readUntil(next); 161 if (pos >= next) { 162 cache.seek(streamPos++); 163 return cache.read(); 164 } else { 165 return -1; 166 } 167 } 168 169 public int read(byte[] b, int off, int len) throws IOException { 170 checkClosed(); 171 172 if (b == null) { 173 throw new NullPointerException("b == null!"); 174 } 175 // Fix 4430357 - if off + len < 0, overflow occurred 176 if (off < 0 || len < 0 || off + len > b.length || off + len < 0) { 177 throw new IndexOutOfBoundsException 178 ("off < 0 || len < 0 || off+len > b.length || off+len < 0!"); 179 } 180 181 bitOffset = 0; 182 183 if (len == 0) { 184 return 0; 185 } 186 187 long pos = readUntil(streamPos + len); 188 189 // len will always fit into an int so this is safe 190 len = (int)Math.min((long)len, pos - streamPos); 191 if (len > 0) { 192 cache.seek(streamPos); 193 cache.readFully(b, off, len); 194 streamPos += len; 195 return len; 196 } else { 197 return -1; 198 } 199 } 200 201 /** 202 * Returns <code>true</code> since this 203 * <code>ImageInputStream</code> caches data in order to allow 204 * seeking backwards. 205 * 206 * @return <code>true</code>. 207 * 208 * @see #isCachedMemory 209 * @see #isCachedFile 210 */ 211 public boolean isCached() { 212 return true; 213 } 214 215 /** 216 * Returns <code>true</code> since this 217 * <code>ImageInputStream</code> maintains a file cache. 218 * 219 * @return <code>true</code>. 220 * 221 * @see #isCached 222 * @see #isCachedMemory 223 */ 224 public boolean isCachedFile() { 225 return true; 226 } 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 { 268 private File cacheFile; 269 private RandomAccessFile cache; 270 271 public StreamDisposerRecord(File cacheFile, RandomAccessFile cache) { 272 this.cacheFile = cacheFile; 273 this.cache = cache; 274 } 275 276 public synchronized void dispose() { 277 if (cache != null) { 278 try { 279 cache.close(); 280 } catch (IOException e) { 281 } finally { 282 cache = null; 283 } 284 } 285 if (cacheFile != null) { 286 cacheFile.delete(); 287 cacheFile = null; 288 } 289 // Note: Explicit removal of the stream from the StreamCloser 290 // queue is not mandatory in this case, as it will be removed 291 // automatically by GC shortly after this method is called. 292 } 293 } 294 }