001 /*
002 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.imageio.stream;
027
028 import java.io.DataInput;
029 import java.io.File;
030 import java.io.FileNotFoundException;
031 import java.io.IOException;
032 import java.io.OutputStream;
033 import java.io.RandomAccessFile;
034 import com.sun.imageio.stream.StreamCloser;
035
036 /**
037 * An implementation of <code>ImageOutputStream</code> that writes its
038 * output to a regular <code>OutputStream</code>. A file is used to
039 * cache data until it is flushed to the output stream.
040 *
041 * @version 0.5
042 */
043 public class FileCacheImageOutputStream extends ImageOutputStreamImpl {
044
045 private OutputStream stream;
046
047 private File cacheFile;
048
049 private RandomAccessFile cache;
050
051 // Pos after last (rightmost) byte written
052 private long maxStreamPos = 0L;
053
054 /**
055 * Constructs a <code>FileCacheImageOutputStream</code> that will write
056 * to a given <code>outputStream</code>.
057 *
058 * <p> A temporary file is used as a cache. If
059 * <code>cacheDir</code>is non-<code>null</code> and is a
060 * directory, the file will be created there. If it is
061 * <code>null</code>, the system-dependent default temporary-file
062 * directory will be used (see the documentation for
063 * <code>File.createTempFile</code> for details).
064 *
065 * @param stream an <code>OutputStream</code> to write to.
066 * @param cacheDir a <code>File</code> indicating where the
067 * cache file should be created, or <code>null</code> to use the
068 * system directory.
069 *
070 * @exception IllegalArgumentException if <code>stream</code>
071 * is <code>null</code>.
072 * @exception IllegalArgumentException if <code>cacheDir</code> is
073 * non-<code>null</code> but is not a directory.
074 * @exception IOException if a cache file cannot be created.
075 */
076 public FileCacheImageOutputStream(OutputStream stream, File cacheDir)
077 throws IOException {
078 if (stream == null) {
079 throw new IllegalArgumentException("stream == null!");
080 }
081 if ((cacheDir != null) && !(cacheDir.isDirectory())) {
082 throw new IllegalArgumentException("Not a directory!");
083 }
084 this .stream = stream;
085 this .cacheFile = File.createTempFile("imageio", ".tmp",
086 cacheDir);
087 this .cache = new RandomAccessFile(cacheFile, "rw");
088 StreamCloser.addToQueue(this );
089 }
090
091 public int read() throws IOException {
092 checkClosed();
093 bitOffset = 0;
094 int val = cache.read();
095 if (val != -1) {
096 ++streamPos;
097 }
098 return val;
099 }
100
101 public int read(byte[] b, int off, int len) throws IOException {
102 checkClosed();
103
104 if (b == null) {
105 throw new NullPointerException("b == null!");
106 }
107 if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {
108 throw new IndexOutOfBoundsException(
109 "off < 0 || len < 0 || off+len > b.length || off+len < 0!");
110 }
111
112 bitOffset = 0;
113
114 if (len == 0) {
115 return 0;
116 }
117
118 int nbytes = cache.read(b, off, len);
119 if (nbytes != -1) {
120 streamPos += nbytes;
121 }
122 return nbytes;
123 }
124
125 public void write(int b) throws IOException {
126 flushBits(); // this will call checkClosed() for us
127 cache.write(b);
128 ++streamPos;
129 maxStreamPos = Math.max(maxStreamPos, streamPos);
130 }
131
132 public void write(byte[] b, int off, int len) throws IOException {
133 flushBits(); // this will call checkClosed() for us
134 cache.write(b, off, len);
135 streamPos += len;
136 maxStreamPos = Math.max(maxStreamPos, streamPos);
137 }
138
139 public long length() {
140 try {
141 checkClosed();
142 return cache.length();
143 } catch (IOException e) {
144 return -1L;
145 }
146 }
147
148 /**
149 * Sets the current stream position and resets the bit offset to
150 * 0. It is legal to seek past the end of the file; an
151 * <code>EOFException</code> will be thrown only if a read is
152 * performed. The file length will not be increased until a write
153 * is performed.
154 *
155 * @exception IndexOutOfBoundsException if <code>pos</code> is smaller
156 * than the flushed position.
157 * @exception IOException if any other I/O error occurs.
158 */
159 public void seek(long pos) throws IOException {
160 checkClosed();
161
162 if (pos < flushedPos) {
163 throw new IndexOutOfBoundsException();
164 }
165
166 cache.seek(pos);
167 this .streamPos = cache.getFilePointer();
168 maxStreamPos = Math.max(maxStreamPos, streamPos);
169 this .bitOffset = 0;
170 }
171
172 /**
173 * Returns <code>true</code> since this
174 * <code>ImageOutputStream</code> caches data in order to allow
175 * seeking backwards.
176 *
177 * @return <code>true</code>.
178 *
179 * @see #isCachedMemory
180 * @see #isCachedFile
181 */
182 public boolean isCached() {
183 return true;
184 }
185
186 /**
187 * Returns <code>true</code> since this
188 * <code>ImageOutputStream</code> maintains a file cache.
189 *
190 * @return <code>true</code>.
191 *
192 * @see #isCached
193 * @see #isCachedMemory
194 */
195 public boolean isCachedFile() {
196 return true;
197 }
198
199 /**
200 * Returns <code>false</code> since this
201 * <code>ImageOutputStream</code> does not maintain a main memory
202 * cache.
203 *
204 * @return <code>false</code>.
205 *
206 * @see #isCached
207 * @see #isCachedFile
208 */
209 public boolean isCachedMemory() {
210 return false;
211 }
212
213 /**
214 * Closes this <code>FileCacheImageOututStream</code>. All
215 * pending data is flushed to the output, and the cache file
216 * is closed and removed. The destination <code>OutputStream</code>
217 * is not closed.
218 *
219 * @exception IOException if an error occurs.
220 */
221 public void close() throws IOException {
222 maxStreamPos = cache.length();
223
224 seek(maxStreamPos);
225 flushBefore(maxStreamPos);
226 super .close();
227 cache.close();
228 cache = null;
229 cacheFile.delete();
230 cacheFile = null;
231 stream.flush();
232 stream = null;
233 StreamCloser.removeFromQueue(this );
234 }
235
236 public void flushBefore(long pos) throws IOException {
237 long oFlushedPos = flushedPos;
238 super .flushBefore(pos); // this will call checkClosed() for us
239
240 long flushBytes = flushedPos - oFlushedPos;
241 if (flushBytes > 0) {
242 int bufLen = 512;
243 byte[] buf = new byte[bufLen];
244 cache.seek(oFlushedPos);
245 while (flushBytes > 0) {
246 int len = (int) Math.min(flushBytes, bufLen);
247 cache.readFully(buf, 0, len);
248 stream.write(buf, 0, len);
249 flushBytes -= len;
250 }
251 stream.flush();
252 }
253 }
254 }
|