Home | History | Annotate | Download | only in io
      1 /*
      2  * Copyright (C) 2008 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.io;
     18 
     19 import com.google.common.annotations.VisibleForTesting;
     20 
     21 import java.io.ByteArrayInputStream;
     22 import java.io.ByteArrayOutputStream;
     23 import java.io.File;
     24 import java.io.FileInputStream;
     25 import java.io.FileOutputStream;
     26 import java.io.IOException;
     27 import java.io.InputStream;
     28 import java.io.OutputStream;
     29 
     30 /**
     31  * An {@link OutputStream} that starts buffering to a byte array, but
     32  * switches to file buffering once the data reaches a configurable size.
     33  *
     34  * <p>This class is thread-safe.
     35  *
     36  * @author Chris Nokleberg
     37  * @since 2009.09.15 <b>tentative</b>
     38  */
     39 public final class FileBackedOutputStream extends OutputStream {
     40 
     41   private final int fileThreshold;
     42   private final boolean resetOnFinalize;
     43   private final InputSupplier<InputStream> supplier;
     44 
     45   private OutputStream out;
     46   private MemoryOutput memory;
     47   private File file;
     48 
     49   /** ByteArrayOutputStream that exposes its internals. */
     50   private static class MemoryOutput extends ByteArrayOutputStream {
     51     byte[] getBuffer() {
     52       return buf;
     53     }
     54 
     55     int getCount() {
     56       return count;
     57     }
     58   }
     59 
     60   /** Returns the file holding the data (possibly null). */
     61   @VisibleForTesting synchronized File getFile() {
     62     return file;
     63   }
     64 
     65   /**
     66    * Creates a new instance that uses the given file threshold.
     67    * Equivalent to {@code ThresholdOutputStream(fileThreshold, false)}.
     68    *
     69    * @param fileThreshold the number of bytes before the stream should
     70    *     switch to buffering to a file
     71    */
     72   public FileBackedOutputStream(int fileThreshold) {
     73     this(fileThreshold, false);
     74   }
     75 
     76   /**
     77    * Creates a new instance that uses the given file threshold, and
     78    * optionally resets the data when the {@link InputSupplier} returned
     79    * by {@link #getSupplier} is finalized.
     80    *
     81    * @param fileThreshold the number of bytes before the stream should
     82    *     switch to buffering to a file
     83    * @param resetOnFinalize if true, the {@link #reset} method will
     84    *     be called when the {@link InputSupplier} returned by {@link #getSupplier}
     85    *     is finalized
     86    */
     87   public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) {
     88     this.fileThreshold = fileThreshold;
     89     this.resetOnFinalize = resetOnFinalize;
     90     memory = new MemoryOutput();
     91     out = memory;
     92 
     93     if (resetOnFinalize) {
     94       supplier = new InputSupplier<InputStream>() {
     95         public InputStream getInput() throws IOException {
     96           return openStream();
     97         }
     98 
     99         @Override protected void finalize() {
    100           try {
    101             reset();
    102           } catch (Throwable t) {
    103             t.printStackTrace(System.err);
    104           }
    105         }
    106       };
    107     } else {
    108       supplier = new InputSupplier<InputStream>() {
    109         public InputStream getInput() throws IOException {
    110           return openStream();
    111         }
    112       };
    113     }
    114   }
    115 
    116   /**
    117    * Returns a supplier that may be used to retrieve the data buffered
    118    * by this stream.
    119    */
    120   public InputSupplier<InputStream> getSupplier() {
    121     return supplier;
    122   }
    123 
    124   private synchronized InputStream openStream() throws IOException {
    125     if (file != null) {
    126       return new FileInputStream(file);
    127     } else {
    128       return new ByteArrayInputStream(
    129           memory.getBuffer(), 0, memory.getCount());
    130     }
    131   }
    132 
    133   /**
    134    * Calls {@link #close} if not already closed, and then resets this
    135    * object back to its initial state, for reuse. If data was buffered
    136    * to a file, it will be deleted.
    137    *
    138    * @throws IOException if an I/O error occurred while deleting the file buffer
    139    */
    140   public synchronized void reset() throws IOException {
    141     try {
    142       close();
    143     } finally {
    144       if (memory == null) {
    145         memory = new MemoryOutput();
    146       } else {
    147         memory.reset();
    148       }
    149       out = memory;
    150       if (file != null) {
    151         File deleteMe = file;
    152         file = null;
    153         if (!deleteMe.delete()) {
    154           throw new IOException("Could not delete: " + deleteMe);
    155         }
    156       }
    157     }
    158   }
    159 
    160   @Override public synchronized void write(int b) throws IOException {
    161     update(1);
    162     out.write(b);
    163   }
    164 
    165   @Override public synchronized void write(byte[] b) throws IOException {
    166     write(b, 0, b.length);
    167   }
    168 
    169   @Override public synchronized void write(byte[] b, int off, int len) throws IOException {
    170     update(len);
    171     out.write(b, off, len);
    172   }
    173 
    174   @Override public synchronized void close() throws IOException {
    175     out.close();
    176   }
    177 
    178   @Override public synchronized void flush() throws IOException {
    179     out.flush();
    180   }
    181 
    182   /**
    183    * Checks if writing {@code len} bytes would go over threshold, and
    184    * switches to file buffering if so.
    185    */
    186   private void update(int len) throws IOException {
    187     if (file == null && (memory.getCount() + len > fileThreshold)) {
    188       File temp = File.createTempFile("FileBackedOutputStream", null);
    189       if (resetOnFinalize) {
    190         // Finalizers are not guaranteed to be called on system shutdown;
    191         // this is insurance.
    192         temp.deleteOnExit();
    193       }
    194       FileOutputStream transfer = new FileOutputStream(temp);
    195       transfer.write(memory.getBuffer(), 0, memory.getCount());
    196       transfer.flush();
    197 
    198       // We've successfully transferred the data; switch to writing to file
    199       out = transfer;
    200       file = temp;
    201       memory = null;
    202     }
    203   }
    204 }
    205