diff options
Diffstat (limited to 'guava/src/com/google/common/io/FileBackedOutputStream.java')
-rw-r--r-- | guava/src/com/google/common/io/FileBackedOutputStream.java | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/guava/src/com/google/common/io/FileBackedOutputStream.java b/guava/src/com/google/common/io/FileBackedOutputStream.java new file mode 100644 index 0000000..ce593b2 --- /dev/null +++ b/guava/src/com/google/common/io/FileBackedOutputStream.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An {@link OutputStream} that starts buffering to a byte array, but + * switches to file buffering once the data reaches a configurable size. + * + * <p>This class is thread-safe. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +public final class FileBackedOutputStream extends OutputStream { + + private final int fileThreshold; + private final boolean resetOnFinalize; + private final InputSupplier<InputStream> supplier; + + private OutputStream out; + private MemoryOutput memory; + private File file; + + /** ByteArrayOutputStream that exposes its internals. */ + private static class MemoryOutput extends ByteArrayOutputStream { + byte[] getBuffer() { + return buf; + } + + int getCount() { + return count; + } + } + + /** Returns the file holding the data (possibly null). */ + @VisibleForTesting synchronized File getFile() { + return file; + } + + /** + * Creates a new instance that uses the given file threshold, and does + * not reset the data when the {@link InputSupplier} returned by + * {@link #getSupplier} is finalized. + * + * @param fileThreshold the number of bytes before the stream should + * switch to buffering to a file + */ + public FileBackedOutputStream(int fileThreshold) { + this(fileThreshold, false); + } + + /** + * Creates a new instance that uses the given file threshold, and + * optionally resets the data when the {@link InputSupplier} returned + * by {@link #getSupplier} is finalized. + * + * @param fileThreshold the number of bytes before the stream should + * switch to buffering to a file + * @param resetOnFinalize if true, the {@link #reset} method will + * be called when the {@link InputSupplier} returned by {@link + * #getSupplier} is finalized + */ + public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) { + this.fileThreshold = fileThreshold; + this.resetOnFinalize = resetOnFinalize; + memory = new MemoryOutput(); + out = memory; + + if (resetOnFinalize) { + supplier = new InputSupplier<InputStream>() { + @Override + public InputStream getInput() throws IOException { + return openStream(); + } + + @Override protected void finalize() { + try { + reset(); + } catch (Throwable t) { + t.printStackTrace(System.err); + } + } + }; + } else { + supplier = new InputSupplier<InputStream>() { + @Override + public InputStream getInput() throws IOException { + return openStream(); + } + }; + } + } + + /** + * Returns a supplier that may be used to retrieve the data buffered + * by this stream. + */ + public InputSupplier<InputStream> getSupplier() { + return supplier; + } + + private synchronized InputStream openStream() throws IOException { + if (file != null) { + return new FileInputStream(file); + } else { + return new ByteArrayInputStream( + memory.getBuffer(), 0, memory.getCount()); + } + } + + /** + * Calls {@link #close} if not already closed, and then resets this + * object back to its initial state, for reuse. If data was buffered + * to a file, it will be deleted. + * + * @throws IOException if an I/O error occurred while deleting the file buffer + */ + public synchronized void reset() throws IOException { + try { + close(); + } finally { + if (memory == null) { + memory = new MemoryOutput(); + } else { + memory.reset(); + } + out = memory; + if (file != null) { + File deleteMe = file; + file = null; + if (!deleteMe.delete()) { + throw new IOException("Could not delete: " + deleteMe); + } + } + } + } + + @Override public synchronized void write(int b) throws IOException { + update(1); + out.write(b); + } + + @Override public synchronized void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override public synchronized void write(byte[] b, int off, int len) + throws IOException { + update(len); + out.write(b, off, len); + } + + @Override public synchronized void close() throws IOException { + out.close(); + } + + @Override public synchronized void flush() throws IOException { + out.flush(); + } + + /** + * Checks if writing {@code len} bytes would go over threshold, and + * switches to file buffering if so. + */ + private void update(int len) throws IOException { + if (file == null && (memory.getCount() + len > fileThreshold)) { + File temp = File.createTempFile("FileBackedOutputStream", null); + if (resetOnFinalize) { + // Finalizers are not guaranteed to be called on system shutdown; + // this is insurance. + temp.deleteOnExit(); + } + FileOutputStream transfer = new FileOutputStream(temp); + transfer.write(memory.getBuffer(), 0, memory.getCount()); + transfer.flush(); + + // We've successfully transferred the data; switch to writing to file + out = transfer; + file = temp; + memory = null; + } + } +} |