1 /*
2 * Copyright 2000-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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</code> that gets its
36 * input from a regular <code>InputStream</code>. 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</code> when reading from a regular
42 * <code>InputStream</code>. 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</code> that will read
60 * from a given <code>InputStream</code>.
61 *
62 * @param stream an <code>InputStream</code> to read from.
63 *
64 * @exception IllegalArgumentException if <code>stream</code> is
65 * <code>null</code>.
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</code> since this
130 * <code>ImageInputStream</code> caches data in order to allow
131 * seeking backwards.
132 *
133 * @return <code>true</code>.
134 *
135 * @see #isCachedMemory
136 * @see #isCachedFile
137 */
138 public boolean isCached() {
139 return true;
140 }
141
142 /**
143 * Returns <code>false</code> since this
144 * <code>ImageInputStream</code> does not maintain a file cache.
145 *
146 * @return <code>false</code>.
147 *
148 * @see #isCached
149 * @see #isCachedMemory
150 */
151 public boolean isCachedFile() {
152 return false;
153 }
154
155 /**
156 * Returns <code>true</code> since this
157 * <code>ImageInputStream</code> maintains a main memory cache.
158 *
159 * @return <code>true</code>.
160 *
161 * @see #isCached
162 * @see #isCachedFile
163 */
164 public boolean isCachedMemory() {
165 return true;
166 }
167
168 /**
169 * Closes this <code>MemoryCacheImageInputStream</code>, freeing
170 * the cache. The source <code>InputStream</code> 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 protected void finalize() throws Throwable {
183 // Empty finalizer: for performance reasons we instead use the
184 // Disposer mechanism for ensuring that the underlying
185 // MemoryCache is reset prior to garbage collection
186 }
187
188 private static class StreamDisposerRecord implements DisposerRecord {
189 private MemoryCache cache;
190
191 public StreamDisposerRecord(MemoryCache cache) {
192 this.cache = cache;
193 }
194
195 public synchronized void dispose() {
196 if (cache != null) {
197 cache.reset();
198 cache = null;
199 }
200 }
201 }
202 }