blob: f63871254998b5f83ada6a78a1e17b9f23ee7fca [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package javax.imageio.stream;
27
28import java.io.File;
29import java.io.IOException;
30import java.io.OutputStream;
31import java.io.RandomAccessFile;
32import com.sun.imageio.stream.StreamCloser;
33
34/**
35 * An implementation of <code>ImageOutputStream</code> that writes its
36 * output to a regular <code>OutputStream</code>. A file is used to
37 * cache data until it is flushed to the output stream.
38 *
39 */
40public class FileCacheImageOutputStream extends ImageOutputStreamImpl {
41
42 private OutputStream stream;
43
44 private File cacheFile;
45
46 private RandomAccessFile cache;
47
48 // Pos after last (rightmost) byte written
49 private long maxStreamPos = 0L;
50
51 /**
52 * Constructs a <code>FileCacheImageOutputStream</code> that will write
53 * to a given <code>outputStream</code>.
54 *
55 * <p> A temporary file is used as a cache. If
56 * <code>cacheDir</code>is non-<code>null</code> and is a
57 * directory, the file will be created there. If it is
58 * <code>null</code>, the system-dependent default temporary-file
59 * directory will be used (see the documentation for
60 * <code>File.createTempFile</code> for details).
61 *
62 * @param stream an <code>OutputStream</code> to write to.
63 * @param cacheDir a <code>File</code> indicating where the
64 * cache file should be created, or <code>null</code> to use the
65 * system directory.
66 *
67 * @exception IllegalArgumentException if <code>stream</code>
68 * is <code>null</code>.
69 * @exception IllegalArgumentException if <code>cacheDir</code> is
70 * non-<code>null</code> but is not a directory.
71 * @exception IOException if a cache file cannot be created.
72 */
73 public FileCacheImageOutputStream(OutputStream stream, File cacheDir)
74 throws IOException {
75 if (stream == null) {
76 throw new IllegalArgumentException("stream == null!");
77 }
78 if ((cacheDir != null) && !(cacheDir.isDirectory())) {
79 throw new IllegalArgumentException("Not a directory!");
80 }
81 this.stream = stream;
82 this.cacheFile =
83 File.createTempFile("imageio", ".tmp", cacheDir);
84 this.cache = new RandomAccessFile(cacheFile, "rw");
85 StreamCloser.addToQueue(this);
86 }
87
88 public int read() throws IOException {
89 checkClosed();
90 bitOffset = 0;
91 int val = cache.read();
92 if (val != -1) {
93 ++streamPos;
94 }
95 return val;
96 }
97
98 public int read(byte[] b, int off, int len) throws IOException {
99 checkClosed();
100
101 if (b == null) {
102 throw new NullPointerException("b == null!");
103 }
104 if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
105 throw new IndexOutOfBoundsException
106 ("off < 0 || len < 0 || off+len > b.length || off+len < 0!");
107 }
108
109 bitOffset = 0;
110
111 if (len == 0) {
112 return 0;
113 }
114
115 int nbytes = cache.read(b, off, len);
116 if (nbytes != -1) {
117 streamPos += nbytes;
118 }
119 return nbytes;
120 }
121
122 public void write(int b) throws IOException {
123 flushBits(); // this will call checkClosed() for us
124 cache.write(b);
125 ++streamPos;
126 maxStreamPos = Math.max(maxStreamPos, streamPos);
127 }
128
129 public void write(byte[] b, int off, int len) throws IOException {
130 flushBits(); // this will call checkClosed() for us
131 cache.write(b, off, len);
132 streamPos += len;
133 maxStreamPos = Math.max(maxStreamPos, streamPos);
134 }
135
136 public long length() {
137 try {
138 checkClosed();
139 return cache.length();
140 } catch (IOException e) {
141 return -1L;
142 }
143 }
144
145 /**
146 * Sets the current stream position and resets the bit offset to
147 * 0. It is legal to seek past the end of the file; an
148 * <code>EOFException</code> will be thrown only if a read is
149 * performed. The file length will not be increased until a write
150 * is performed.
151 *
152 * @exception IndexOutOfBoundsException if <code>pos</code> is smaller
153 * than the flushed position.
154 * @exception IOException if any other I/O error occurs.
155 */
156 public void seek(long pos) throws IOException {
157 checkClosed();
158
159 if (pos < flushedPos) {
160 throw new IndexOutOfBoundsException();
161 }
162
163 cache.seek(pos);
164 this.streamPos = cache.getFilePointer();
165 maxStreamPos = Math.max(maxStreamPos, streamPos);
166 this.bitOffset = 0;
167 }
168
169 /**
170 * Returns <code>true</code> since this
171 * <code>ImageOutputStream</code> caches data in order to allow
172 * seeking backwards.
173 *
174 * @return <code>true</code>.
175 *
176 * @see #isCachedMemory
177 * @see #isCachedFile
178 */
179 public boolean isCached() {
180 return true;
181 }
182
183 /**
184 * Returns <code>true</code> since this
185 * <code>ImageOutputStream</code> maintains a file cache.
186 *
187 * @return <code>true</code>.
188 *
189 * @see #isCached
190 * @see #isCachedMemory
191 */
192 public boolean isCachedFile() {
193 return true;
194 }
195
196 /**
197 * Returns <code>false</code> since this
198 * <code>ImageOutputStream</code> does not maintain a main memory
199 * cache.
200 *
201 * @return <code>false</code>.
202 *
203 * @see #isCached
204 * @see #isCachedFile
205 */
206 public boolean isCachedMemory() {
207 return false;
208 }
209
210 /**
211 * Closes this <code>FileCacheImageOututStream</code>. All
212 * pending data is flushed to the output, and the cache file
213 * is closed and removed. The destination <code>OutputStream</code>
214 * is not closed.
215 *
216 * @exception IOException if an error occurs.
217 */
218 public void close() throws IOException {
219 maxStreamPos = cache.length();
220
221 seek(maxStreamPos);
222 flushBefore(maxStreamPos);
223 super.close();
224 cache.close();
225 cache = null;
226 cacheFile.delete();
227 cacheFile = null;
228 stream.flush();
229 stream = null;
230 StreamCloser.removeFromQueue(this);
231 }
232
233 public void flushBefore(long pos) throws IOException {
234 long oFlushedPos = flushedPos;
235 super.flushBefore(pos); // this will call checkClosed() for us
236
237 long flushBytes = flushedPos - oFlushedPos;
238 if (flushBytes > 0) {
239 int bufLen = 512;
240 byte[] buf = new byte[bufLen];
241 cache.seek(oFlushedPos);
242 while (flushBytes > 0) {
243 int len = (int)Math.min(flushBytes, bufLen);
244 cache.readFully(buf, 0, len);
245 stream.write(buf, 0, len);
246 flushBytes -= len;
247 }
248 stream.flush();
249 }
250 }
251}