1 /*
  2  * Copyright (c) 2000, 2017, 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.InputStream;
 29 import java.io.IOException;
 30 import com.sun.imageio.stream.StreamFinalizer;
 31 import sun.java2d.Disposer;
 32 import sun.java2d.DisposerRecord;
 33 
 34 /**
 35  * An implementation of {@code ImageInputStream} that gets its
 36  * input from a regular {@code InputStream}.  A memory buffer is
 37  * used to cache at least the data between the discard position and
 38  * the current read position.
 39  *
 40  * <p> In general, it is preferable to use a
 41  * {@code FileCacheImageInputStream} when reading from a regular
 42  * {@code InputStream}.  This class is provided for cases where
 43  * it is not possible to create a writable temporary file.
 44  *
 45  */
 46 public class MemoryCacheImageInputStream extends ImageInputStreamImpl {
 47 
 48     private InputStream stream;
 49 
 50     private MemoryCache cache = new MemoryCache();
 51 
 52     /** The referent to be registered with the Disposer. */
 53     private final Object disposerReferent;
 54 
 55     /** The DisposerRecord that resets the underlying MemoryCache. */
 56     private final DisposerRecord disposerRecord;
 57 
 58     /**
 59      * Constructs a {@code MemoryCacheImageInputStream} that will read
 60      * from a given {@code InputStream}.
 61      *
 62      * @param stream an {@code InputStream} to read from.
 63      *
 64      * @exception IllegalArgumentException if {@code stream} is
 65      * {@code null}.
 66      */
 67     public MemoryCacheImageInputStream(InputStream stream) {
 68         if (stream == null) {
 69             throw new IllegalArgumentException("stream == null!");
 70         }
 71         this.stream = stream;
 72 
 73         disposerRecord = new StreamDisposerRecord(cache);
 74         if (getClass() == MemoryCacheImageInputStream.class) {
 75             disposerReferent = new Object();
 76             Disposer.addRecord(disposerReferent, disposerRecord);
 77         } else {
 78             disposerReferent = new StreamFinalizer(this);
 79         }
 80     }
 81 
 82     public int read() throws IOException {
 83         checkClosed();
 84         bitOffset = 0;
 85         long pos = cache.loadFromStream(stream, streamPos+1);
 86         if (pos >= streamPos+1) {
 87             return cache.read(streamPos++);
 88         } else {
 89             return -1;
 90         }
 91     }
 92 
 93     public int read(byte[] b, int off, int len) throws IOException {
 94         checkClosed();
 95 
 96         if (b == null) {
 97             throw new NullPointerException("b == null!");
 98         }
 99         if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
100             throw new IndexOutOfBoundsException
101                 ("off < 0 || len < 0 || off+len > b.length || off+len < 0!");
102         }
103 
104         bitOffset = 0;
105 
106         if (len == 0) {
107             return 0;
108         }
109 
110         long pos = cache.loadFromStream(stream, streamPos+len);
111 
112         len = (int)(pos - streamPos);  // In case stream ended early
113 
114         if (len > 0) {
115             cache.read(b, off, len, streamPos);
116             streamPos += len;
117             return len;
118         } else {
119             return -1;
120         }
121     }
122 
123     public void flushBefore(long pos) throws IOException {
124         super.flushBefore(pos); // this will call checkClosed() for us
125         cache.disposeBefore(pos);
126     }
127 
128     /**
129      * Returns {@code true} since this
130      * {@code ImageInputStream} caches data in order to allow
131      * seeking backwards.
132      *
133      * @return {@code true}.
134      *
135      * @see #isCachedMemory
136      * @see #isCachedFile
137      */
138     public boolean isCached() {
139         return true;
140     }
141 
142     /**
143      * Returns {@code false} since this
144      * {@code ImageInputStream} does not maintain a file cache.
145      *
146      * @return {@code false}.
147      *
148      * @see #isCached
149      * @see #isCachedMemory
150      */
151     public boolean isCachedFile() {
152         return false;
153     }
154 
155     /**
156      * Returns {@code true} since this
157      * {@code ImageInputStream} maintains a main memory cache.
158      *
159      * @return {@code true}.
160      *
161      * @see #isCached
162      * @see #isCachedFile
163      */
164     public boolean isCachedMemory() {
165         return true;
166     }
167 
168     /**
169      * Closes this {@code MemoryCacheImageInputStream}, freeing
170      * the cache.  The source {@code InputStream} is not closed.
171      */
172     public void close() throws IOException {
173         super.close();
174         disposerRecord.dispose(); // this resets the MemoryCache
175         stream = null;
176         cache = null;
177     }
178 
179     /**
180      * {@inheritDoc}
181      *
182      * @deprecated Finalization has been deprecated for removal.  See
183      * {@link java.lang.Object#finalize} for background information and details
184      * about migration options.
185      */
186     @Deprecated(since="9", forRemoval=true)
187     @SuppressWarnings("removal")
188     protected void finalize() throws Throwable {
189         // Empty finalizer: for performance reasons we instead use the
190         // Disposer mechanism for ensuring that the underlying
191         // MemoryCache is reset prior to garbage collection
192     }
193 
194     private static class StreamDisposerRecord implements DisposerRecord {
195         private MemoryCache cache;
196 
197         public StreamDisposerRecord(MemoryCache cache) {
198             this.cache = cache;
199         }
200 
201         public synchronized void dispose() {
202             if (cache != null) {
203                 cache.reset();
204                 cache = null;
205             }
206         }
207     }
208 }