Path: blob/master/src/java.desktop/share/classes/javax/imageio/stream/FileCacheImageOutputStream.java
41153 views
/*1* Copyright (c) 2000, 2012, 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.IOException;29import java.io.OutputStream;30import java.io.RandomAccessFile;31import java.nio.file.Files;32import com.sun.imageio.stream.StreamCloser;3334/**35* An implementation of {@code ImageOutputStream} that writes its36* output to a regular {@code OutputStream}. A file is used to37* cache data until it is flushed to the output stream.38*39*/40public class FileCacheImageOutputStream extends ImageOutputStreamImpl {4142private OutputStream stream;4344private File cacheFile;4546private RandomAccessFile cache;4748// Pos after last (rightmost) byte written49private long maxStreamPos = 0L;5051/** The CloseAction that closes the stream in52* the StreamCloser's shutdown hook */53private final StreamCloser.CloseAction closeAction;5455/**56* Constructs a {@code FileCacheImageOutputStream} that will write57* to a given {@code outputStream}.58*59* <p> A temporary file is used as a cache. If60* {@code cacheDir} is non-{@code null} and is a61* directory, the file will be created there. If it is62* {@code null}, the system-dependent default temporary-file63* directory will be used (see the documentation for64* {@code File.createTempFile} for details).65*66* @param stream an {@code OutputStream} to write to.67* @param cacheDir a {@code File} indicating where the68* cache file should be created, or {@code null} to use the69* system directory.70*71* @exception IllegalArgumentException if {@code stream}72* is {@code null}.73* @exception IllegalArgumentException if {@code cacheDir} is74* non-{@code null} but is not a directory.75* @exception IOException if a cache file cannot be created.76*/77public FileCacheImageOutputStream(OutputStream stream, File cacheDir)78throws IOException {79if (stream == null) {80throw new IllegalArgumentException("stream == null!");81}82if ((cacheDir != null) && !(cacheDir.isDirectory())) {83throw new IllegalArgumentException("Not a directory!");84}85this.stream = stream;86if (cacheDir == null)87this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile();88else89this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp")90.toFile();91this.cache = new RandomAccessFile(cacheFile, "rw");9293this.closeAction = StreamCloser.createCloseAction(this);94StreamCloser.addToQueue(closeAction);95}9697public int read() throws IOException {98checkClosed();99bitOffset = 0;100int val = cache.read();101if (val != -1) {102++streamPos;103}104return val;105}106107public int read(byte[] b, int off, int len) throws IOException {108checkClosed();109110if (b == null) {111throw new NullPointerException("b == null!");112}113if (off < 0 || len < 0 || off + len > b.length || off + len < 0) {114throw new IndexOutOfBoundsException115("off < 0 || len < 0 || off+len > b.length || off+len < 0!");116}117118bitOffset = 0;119120if (len == 0) {121return 0;122}123124int nbytes = cache.read(b, off, len);125if (nbytes != -1) {126streamPos += nbytes;127}128return nbytes;129}130131public void write(int b) throws IOException {132flushBits(); // this will call checkClosed() for us133cache.write(b);134++streamPos;135maxStreamPos = Math.max(maxStreamPos, streamPos);136}137138public void write(byte[] b, int off, int len) throws IOException {139flushBits(); // this will call checkClosed() for us140cache.write(b, off, len);141streamPos += len;142maxStreamPos = Math.max(maxStreamPos, streamPos);143}144145public long length() {146try {147checkClosed();148return cache.length();149} catch (IOException e) {150return -1L;151}152}153154/**155* Sets the current stream position and resets the bit offset to156* 0. It is legal to seek past the end of the file; an157* {@code EOFException} will be thrown only if a read is158* performed. The file length will not be increased until a write159* is performed.160*161* @exception IndexOutOfBoundsException if {@code pos} is smaller162* than the flushed position.163* @exception IOException if any other I/O error occurs.164*/165public void seek(long pos) throws IOException {166checkClosed();167168if (pos < flushedPos) {169throw new IndexOutOfBoundsException();170}171172cache.seek(pos);173this.streamPos = cache.getFilePointer();174maxStreamPos = Math.max(maxStreamPos, streamPos);175this.bitOffset = 0;176}177178/**179* Returns {@code true} since this180* {@code ImageOutputStream} caches data in order to allow181* seeking backwards.182*183* @return {@code true}.184*185* @see #isCachedMemory186* @see #isCachedFile187*/188public boolean isCached() {189return true;190}191192/**193* Returns {@code true} since this194* {@code ImageOutputStream} maintains a file cache.195*196* @return {@code true}.197*198* @see #isCached199* @see #isCachedMemory200*/201public boolean isCachedFile() {202return true;203}204205/**206* Returns {@code false} since this207* {@code ImageOutputStream} does not maintain a main memory208* cache.209*210* @return {@code false}.211*212* @see #isCached213* @see #isCachedFile214*/215public boolean isCachedMemory() {216return false;217}218219/**220* Closes this {@code FileCacheImageOutputStream}. All221* pending data is flushed to the output, and the cache file222* is closed and removed. The destination {@code OutputStream}223* is not closed.224*225* @exception IOException if an error occurs.226*/227public void close() throws IOException {228maxStreamPos = cache.length();229230seek(maxStreamPos);231flushBefore(maxStreamPos);232super.close();233cache.close();234cache = null;235cacheFile.delete();236cacheFile = null;237stream.flush();238stream = null;239StreamCloser.removeFromQueue(closeAction);240}241242public void flushBefore(long pos) throws IOException {243long oFlushedPos = flushedPos;244super.flushBefore(pos); // this will call checkClosed() for us245246long flushBytes = flushedPos - oFlushedPos;247if (flushBytes > 0) {248int bufLen = 512;249byte[] buf = new byte[bufLen];250cache.seek(oFlushedPos);251while (flushBytes > 0) {252int len = (int)Math.min(flushBytes, bufLen);253cache.readFully(buf, 0, len);254stream.write(buf, 0, len);255flushBytes -= len;256}257stream.flush();258}259}260}261262263