Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/javax/imageio/stream/FileCacheImageInputStream.java
38918 views
/*1* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.imageio.stream;2627import java.io.File;28import java.io.InputStream;29import java.io.IOException;30import java.io.RandomAccessFile;31import java.nio.file.Files;32import com.sun.imageio.stream.StreamCloser;33import com.sun.imageio.stream.StreamFinalizer;34import sun.java2d.Disposer;35import sun.java2d.DisposerRecord;3637/**38* An implementation of <code>ImageInputStream</code> that gets its39* input from a regular <code>InputStream</code>. A file is used to40* cache previously read data.41*42*/43public class FileCacheImageInputStream extends ImageInputStreamImpl {4445private InputStream stream;4647private File cacheFile;4849private RandomAccessFile cache;5051private static final int BUFFER_LENGTH = 1024;5253private byte[] buf = new byte[BUFFER_LENGTH];5455private long length = 0L;5657private boolean foundEOF = false;5859/** The referent to be registered with the Disposer. */60private final Object disposerReferent;6162/** The DisposerRecord that closes the underlying cache. */63private final DisposerRecord disposerRecord;6465/** The CloseAction that closes the stream in66* the StreamCloser's shutdown hook */67private final StreamCloser.CloseAction closeAction;6869/**70* Constructs a <code>FileCacheImageInputStream</code> that will read71* from a given <code>InputStream</code>.72*73* <p> A temporary file is used as a cache. If74* <code>cacheDir</code>is non-<code>null</code> and is a75* directory, the file will be created there. If it is76* <code>null</code>, the system-dependent default temporary-file77* directory will be used (see the documentation for78* <code>File.createTempFile</code> for details).79*80* @param stream an <code>InputStream</code> to read from.81* @param cacheDir a <code>File</code> indicating where the82* cache file should be created, or <code>null</code> to use the83* system directory.84*85* @exception IllegalArgumentException if <code>stream</code> is86* <code>null</code>.87* @exception IllegalArgumentException if <code>cacheDir</code> is88* non-<code>null</code> but is not a directory.89* @exception IOException if a cache file cannot be created.90*/91public FileCacheImageInputStream(InputStream stream, File cacheDir)92throws IOException {93if (stream == null) {94throw new IllegalArgumentException("stream == null!");95}96if ((cacheDir != null) && !(cacheDir.isDirectory())) {97throw new IllegalArgumentException("Not a directory!");98}99this.stream = stream;100if (cacheDir == null)101this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile();102else103this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp")104.toFile();105this.cache = new RandomAccessFile(cacheFile, "rw");106107this.closeAction = StreamCloser.createCloseAction(this);108StreamCloser.addToQueue(closeAction);109110disposerRecord = new StreamDisposerRecord(cacheFile, cache);111if (getClass() == FileCacheImageInputStream.class) {112disposerReferent = new Object();113Disposer.addRecord(disposerReferent, disposerRecord);114} else {115disposerReferent = new StreamFinalizer(this);116}117}118119/**120* Ensures that at least <code>pos</code> bytes are cached,121* or the end of the source is reached. The return value122* is equal to the smaller of <code>pos</code> and the123* length of the source file.124*/125private long readUntil(long pos) throws IOException {126// We've already got enough data cached127if (pos < length) {128return pos;129}130// pos >= length but length isn't getting any bigger, so return it131if (foundEOF) {132return length;133}134135long len = pos - length;136cache.seek(length);137while (len > 0) {138// Copy a buffer's worth of data from the source to the cache139// BUFFER_LENGTH will always fit into an int so this is safe140int nbytes =141stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH));142if (nbytes == -1) {143foundEOF = true;144return length;145}146147cache.write(buf, 0, nbytes);148len -= nbytes;149length += nbytes;150}151152return pos;153}154155public int read() throws IOException {156checkClosed();157bitOffset = 0;158long next = streamPos + 1;159long pos = readUntil(next);160if (pos >= next) {161cache.seek(streamPos++);162return cache.read();163} else {164return -1;165}166}167168public int read(byte[] b, int off, int len) throws IOException {169checkClosed();170171if (b == null) {172throw new NullPointerException("b == null!");173}174// Fix 4430357 - if off + len < 0, overflow occurred175if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {176throw new IndexOutOfBoundsException177("off < 0 || len < 0 || off+len > b.length || off+len < 0!");178}179180bitOffset = 0;181182if (len == 0) {183return 0;184}185186long pos = readUntil(streamPos + len);187188// len will always fit into an int so this is safe189len = (int)Math.min((long)len, pos - streamPos);190if (len > 0) {191cache.seek(streamPos);192cache.readFully(b, off, len);193streamPos += len;194return len;195} else {196return -1;197}198}199200/**201* Returns <code>true</code> since this202* <code>ImageInputStream</code> caches data in order to allow203* seeking backwards.204*205* @return <code>true</code>.206*207* @see #isCachedMemory208* @see #isCachedFile209*/210public boolean isCached() {211return true;212}213214/**215* Returns <code>true</code> since this216* <code>ImageInputStream</code> maintains a file cache.217*218* @return <code>true</code>.219*220* @see #isCached221* @see #isCachedMemory222*/223public boolean isCachedFile() {224return true;225}226227/**228* Returns <code>false</code> since this229* <code>ImageInputStream</code> does not maintain a main memory230* cache.231*232* @return <code>false</code>.233*234* @see #isCached235* @see #isCachedFile236*/237public boolean isCachedMemory() {238return false;239}240241/**242* Closes this <code>FileCacheImageInputStream</code>, closing243* and removing the cache file. The source <code>InputStream</code>244* is not closed.245*246* @exception IOException if an error occurs.247*/248public void close() throws IOException {249super.close();250disposerRecord.dispose(); // this will close/delete the cache file251stream = null;252cache = null;253cacheFile = null;254StreamCloser.removeFromQueue(closeAction);255}256257/**258* {@inheritDoc}259*/260protected void finalize() throws Throwable {261// Empty finalizer: for performance reasons we instead use the262// Disposer mechanism for ensuring that the underlying263// RandomAccessFile is closed/deleted prior to garbage collection264}265266private static class StreamDisposerRecord implements DisposerRecord {267private File cacheFile;268private RandomAccessFile cache;269270public StreamDisposerRecord(File cacheFile, RandomAccessFile cache) {271this.cacheFile = cacheFile;272this.cache = cache;273}274275public synchronized void dispose() {276if (cache != null) {277try {278cache.close();279} catch (IOException e) {280} finally {281cache = null;282}283}284if (cacheFile != null) {285cacheFile.delete();286cacheFile = null;287}288// Note: Explicit removal of the stream from the StreamCloser289// queue is not mandatory in this case, as it will be removed290// automatically by GC shortly after this method is called.291}292}293}294295296