Path: blob/master/src/java.desktop/share/classes/javax/imageio/stream/FileCacheImageInputStream.java
41153 views
/*1* Copyright (c) 2000, 2017, 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} that gets its39* input from a regular {@code InputStream}. 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} that will read71* from a given {@code InputStream}.72*73* <p> A temporary file is used as a cache. If74* {@code cacheDir} is non-{@code null} and is a75* directory, the file will be created there. If it is76* {@code null}, the system-dependent default temporary-file77* directory will be used (see the documentation for78* {@code File.createTempFile} for details).79*80* @param stream an {@code InputStream} to read from.81* @param cacheDir a {@code File} indicating where the82* cache file should be created, or {@code null} to use the83* system directory.84*85* @exception IllegalArgumentException if {@code stream} is86* {@code null}.87* @exception IllegalArgumentException if {@code cacheDir} is88* non-{@code null} but is not a directory.89* @throws 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} bytes are cached,121* or the end of the source is reached. The return value122* is equal to the smaller of {@code pos} and the123* length of the source file.124*125* @throws IOException if an I/O error occurs while reading from the126* source file127*/128private long readUntil(long pos) throws IOException {129// We've already got enough data cached130if (pos < length) {131return pos;132}133// pos >= length but length isn't getting any bigger, so return it134if (foundEOF) {135return length;136}137138long len = pos - length;139cache.seek(length);140while (len > 0) {141// Copy a buffer's worth of data from the source to the cache142// BUFFER_LENGTH will always fit into an int so this is safe143int nbytes =144stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH));145if (nbytes == -1) {146foundEOF = true;147return length;148}149150cache.write(buf, 0, nbytes);151len -= nbytes;152length += nbytes;153}154155return pos;156}157158public int read() throws IOException {159checkClosed();160bitOffset = 0;161long next = streamPos + 1;162long pos = readUntil(next);163if (pos >= next) {164cache.seek(streamPos++);165return cache.read();166} else {167return -1;168}169}170171public int read(byte[] b, int off, int len) throws IOException {172checkClosed();173174if (b == null) {175throw new NullPointerException("b == null!");176}177// Fix 4430357 - if off + len < 0, overflow occurred178if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {179throw new IndexOutOfBoundsException180("off < 0 || len < 0 || off+len > b.length || off+len < 0!");181}182183bitOffset = 0;184185if (len == 0) {186return 0;187}188189long pos = readUntil(streamPos + len);190191// len will always fit into an int so this is safe192len = (int)Math.min((long)len, pos - streamPos);193if (len > 0) {194cache.seek(streamPos);195cache.readFully(b, off, len);196streamPos += len;197return len;198} else {199return -1;200}201}202203/**204* Returns {@code true} since this205* {@code ImageInputStream} caches data in order to allow206* seeking backwards.207*208* @return {@code true}.209*210* @see #isCachedMemory211* @see #isCachedFile212*/213public boolean isCached() {214return true;215}216217/**218* Returns {@code true} since this219* {@code ImageInputStream} maintains a file cache.220*221* @return {@code true}.222*223* @see #isCached224* @see #isCachedMemory225*/226public boolean isCachedFile() {227return true;228}229230/**231* Returns {@code false} since this232* {@code ImageInputStream} does not maintain a main memory233* cache.234*235* @return {@code false}.236*237* @see #isCached238* @see #isCachedFile239*/240public boolean isCachedMemory() {241return false;242}243244/**245* Closes this {@code FileCacheImageInputStream}, closing246* and removing the cache file. The source {@code InputStream}247* is not closed.248*249* @throws IOException if an error occurs.250*/251public void close() throws IOException {252super.close();253disposerRecord.dispose(); // this will close/delete the cache file254stream = null;255cache = null;256cacheFile = null;257StreamCloser.removeFromQueue(closeAction);258}259260/**261* {@inheritDoc}262*263* @deprecated The {@code finalize} method has been deprecated.264* Subclasses that override {@code finalize} in order to perform cleanup265* should be modified to use alternative cleanup mechanisms and266* to remove the overriding {@code finalize} method.267* When overriding the {@code finalize} method, its implementation must explicitly268* ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}.269* See the specification for {@link Object#finalize()} for further270* information about migration options.271*/272@Deprecated(since="9")273protected void finalize() throws Throwable {274// Empty finalizer: for performance reasons we instead use the275// Disposer mechanism for ensuring that the underlying276// RandomAccessFile is closed/deleted prior to garbage collection277}278279private static class StreamDisposerRecord implements DisposerRecord {280private File cacheFile;281private RandomAccessFile cache;282283public StreamDisposerRecord(File cacheFile, RandomAccessFile cache) {284this.cacheFile = cacheFile;285this.cache = cache;286}287288public synchronized void dispose() {289if (cache != null) {290try {291cache.close();292} catch (IOException e) {293} finally {294cache = null;295}296}297if (cacheFile != null) {298cacheFile.delete();299cacheFile = null;300}301// Note: Explicit removal of the stream from the StreamCloser302// queue is not mandatory in this case, as it will be removed303// automatically by GC shortly after this method is called.304}305}306}307308309