Coverage Report - org.apache.commons.io.output.DeferredFileOutputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
DeferredFileOutputStream
87%
47/54
72%
16/22
2,333
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.io.output;
 18  
 
 19  
 import java.io.File;
 20  
 import java.io.FileInputStream;
 21  
 import java.io.FileOutputStream;
 22  
 import java.io.IOException;
 23  
 import java.io.OutputStream;
 24  
 
 25  
 import org.apache.commons.io.FileUtils;
 26  
 import org.apache.commons.io.IOUtils;
 27  
 
 28  
 
 29  
 /**
 30  
  * An output stream which will retain data in memory until a specified
 31  
  * threshold is reached, and only then commit it to disk. If the stream is
 32  
  * closed before the threshold is reached, the data will not be written to
 33  
  * disk at all.
 34  
  * <p>
 35  
  * This class originated in FileUpload processing. In this use case, you do
 36  
  * not know in advance the size of the file being uploaded. If the file is small
 37  
  * you want to store it in memory (for speed), but if the file is large you want
 38  
  * to store it to file (to avoid memory issues).
 39  
  *
 40  
  */
 41  
 public class DeferredFileOutputStream
 42  
     extends ThresholdingOutputStream
 43  
 {
 44  
     // ----------------------------------------------------------- Data members
 45  
 
 46  
 
 47  
     /**
 48  
      * The output stream to which data will be written prior to the threshold
 49  
      * being reached.
 50  
      */
 51  
     private ByteArrayOutputStream memoryOutputStream;
 52  
 
 53  
 
 54  
     /**
 55  
      * The output stream to which data will be written at any given time. This
 56  
      * will always be one of <code>memoryOutputStream</code> or
 57  
      * <code>diskOutputStream</code>.
 58  
      */
 59  
     private OutputStream currentOutputStream;
 60  
 
 61  
 
 62  
     /**
 63  
      * The file to which output will be directed if the threshold is exceeded.
 64  
      */
 65  
     private File outputFile;
 66  
 
 67  
     /**
 68  
      * The temporary file prefix.
 69  
      */
 70  
     private final String prefix;
 71  
 
 72  
     /**
 73  
      * The temporary file suffix.
 74  
      */
 75  
     private final String suffix;
 76  
 
 77  
     /**
 78  
      * The directory to use for temporary files.
 79  
      */
 80  
     private final File directory;
 81  
 
 82  
 
 83  
     /**
 84  
      * True when close() has been called successfully.
 85  
      */
 86  260
     private boolean closed = false;
 87  
 
 88  
     // ----------------------------------------------------------- Constructors
 89  
 
 90  
 
 91  
     /**
 92  
      * Constructs an instance of this class which will trigger an event at the
 93  
      * specified threshold, and save data to a file beyond that point.
 94  
      * The initial buffer size will default to 1024 bytes which is ByteArrayOutputStream's default buffer size.
 95  
      *
 96  
      * @param threshold  The number of bytes at which to trigger an event.
 97  
      * @param outputFile The file to which data is saved beyond the threshold.
 98  
      */
 99  
     public DeferredFileOutputStream(final int threshold, final File outputFile)
 100  
     {
 101  26
         this(threshold,  outputFile, null, null, null, ByteArrayOutputStream.DEFAULT_SIZE);
 102  26
     }
 103  
 
 104  
     /**
 105  
      * Constructs an instance of this class which will trigger an event at the
 106  
      * specified threshold, and save data to a file beyond that point.
 107  
      *
 108  
      * @param threshold  The number of bytes at which to trigger an event.
 109  
      * @param initialBufferSize The initial size of the in memory buffer.
 110  
      * @param outputFile The file to which data is saved beyond the threshold.
 111  
      *
 112  
      * @since 2.5
 113  
      */
 114  
     public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final File outputFile)
 115  
     {
 116  130
         this(threshold, outputFile, null, null, null, initialBufferSize);
 117  130
         if (initialBufferSize < 0) {
 118  0
             throw new IllegalArgumentException("Initial buffer size must be atleast 0.");
 119  
         }
 120  130
     }
 121  
 
 122  
     /**
 123  
      * Constructs an instance of this class which will trigger an event at the
 124  
      * specified threshold, and save data to a temporary file beyond that point.
 125  
      * The initial buffer size will default to 32 bytes which is ByteArrayOutputStream's default buffer size.
 126  
      *
 127  
      * @param threshold  The number of bytes at which to trigger an event.
 128  
      * @param prefix Prefix to use for the temporary file.
 129  
      * @param suffix Suffix to use for the temporary file.
 130  
      * @param directory Temporary file directory.
 131  
      *
 132  
      * @since 1.4
 133  
      */
 134  
     public DeferredFileOutputStream(final int threshold, final String prefix, final String suffix, final File directory)
 135  
     {
 136  26
         this(threshold, null, prefix, suffix, directory, ByteArrayOutputStream.DEFAULT_SIZE);
 137  26
         if (prefix == null) {
 138  26
             throw new IllegalArgumentException("Temporary file prefix is missing");
 139  
         }
 140  0
     }
 141  
 
 142  
     /**
 143  
      * Constructs an instance of this class which will trigger an event at the
 144  
      * specified threshold, and save data to a temporary file beyond that point.
 145  
      *
 146  
      * @param threshold  The number of bytes at which to trigger an event.
 147  
      * @param initialBufferSize The initial size of the in memory buffer.
 148  
      * @param prefix Prefix to use for the temporary file.
 149  
      * @param suffix Suffix to use for the temporary file.
 150  
      * @param directory Temporary file directory.
 151  
      *
 152  
      * @since 2.5
 153  
      */
 154  
     public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final String prefix,
 155  
                                     final String suffix, final File directory)
 156  
     {
 157  78
         this(threshold, null, prefix, suffix, directory, initialBufferSize);
 158  78
         if (prefix == null) {
 159  0
             throw new IllegalArgumentException("Temporary file prefix is missing");
 160  
         }
 161  78
         if (initialBufferSize < 0) {
 162  0
             throw new IllegalArgumentException("Initial buffer size must be atleast 0.");
 163  
         }
 164  78
     }
 165  
 
 166  
     /**
 167  
      * Constructs an instance of this class which will trigger an event at the
 168  
      * specified threshold, and save data either to a file beyond that point.
 169  
      *
 170  
      * @param threshold  The number of bytes at which to trigger an event.
 171  
      * @param outputFile The file to which data is saved beyond the threshold.
 172  
      * @param prefix Prefix to use for the temporary file.
 173  
      * @param suffix Suffix to use for the temporary file.
 174  
      * @param directory Temporary file directory.
 175  
      * @param initialBufferSize The initial size of the in memory buffer.
 176  
      */
 177  
     private DeferredFileOutputStream(final int threshold, final File outputFile, final String prefix,
 178  
                                      final String suffix, final File directory, final int initialBufferSize) {
 179  260
         super(threshold);
 180  260
         this.outputFile = outputFile;
 181  260
         this.prefix = prefix;
 182  260
         this.suffix = suffix;
 183  260
         this.directory = directory;
 184  
 
 185  260
         memoryOutputStream = new ByteArrayOutputStream(initialBufferSize);
 186  260
         currentOutputStream = memoryOutputStream;
 187  260
     }
 188  
 
 189  
 
 190  
     // --------------------------------------- ThresholdingOutputStream methods
 191  
 
 192  
 
 193  
     /**
 194  
      * Returns the current output stream. This may be memory based or disk
 195  
      * based, depending on the current state with respect to the threshold.
 196  
      *
 197  
      * @return The underlying output stream.
 198  
      *
 199  
      * @throws IOException if an error occurs.
 200  
      */
 201  
     @Override
 202  
     protected OutputStream getStream() throws IOException
 203  
     {
 204  754
         return currentOutputStream;
 205  
     }
 206  
 
 207  
 
 208  
     /**
 209  
      * Switches the underlying output stream from a memory based stream to one
 210  
      * that is backed by disk. This is the point at which we realise that too
 211  
      * much data is being written to keep in memory, so we elect to switch to
 212  
      * disk-based storage.
 213  
      *
 214  
      * @throws IOException if an error occurs.
 215  
      */
 216  
     @Override
 217  
     protected void thresholdReached() throws IOException
 218  
     {
 219  130
         if (prefix != null) {
 220  52
             outputFile = File.createTempFile(prefix, suffix, directory);
 221  
         }
 222  130
         FileUtils.forceMkdirParent(outputFile);
 223  130
         final FileOutputStream fos = new FileOutputStream(outputFile);
 224  
         try {
 225  130
             memoryOutputStream.writeTo(fos);
 226  0
         } catch (IOException e){
 227  0
             fos.close();
 228  0
             throw e;
 229  130
         }
 230  130
         currentOutputStream = fos;
 231  130
         memoryOutputStream = null;
 232  130
     }
 233  
 
 234  
 
 235  
     // --------------------------------------------------------- Public methods
 236  
 
 237  
 
 238  
     /**
 239  
      * Determines whether or not the data for this output stream has been
 240  
      * retained in memory.
 241  
      *
 242  
      * @return {@code true} if the data is available in memory;
 243  
      *         {@code false} otherwise.
 244  
      */
 245  
     public boolean isInMemory()
 246  
     {
 247  286
         return !isThresholdExceeded();
 248  
     }
 249  
 
 250  
 
 251  
     /**
 252  
      * Returns the data for this output stream as an array of bytes, assuming
 253  
      * that the data has been retained in memory. If the data was written to
 254  
      * disk, this method returns {@code null}.
 255  
      *
 256  
      * @return The data for this output stream, or {@code null} if no such
 257  
      *         data is available.
 258  
      */
 259  
     public byte[] getData()
 260  
     {
 261  156
         if (memoryOutputStream != null)
 262  
         {
 263  52
             return memoryOutputStream.toByteArray();
 264  
         }
 265  104
         return null;
 266  
     }
 267  
 
 268  
 
 269  
     /**
 270  
      * Returns either the output file specified in the constructor or
 271  
      * the temporary file created or null.
 272  
      * <p>
 273  
      * If the constructor specifying the file is used then it returns that
 274  
      * same output file, even when threshold has not been reached.
 275  
      * <p>
 276  
      * If constructor specifying a temporary file prefix/suffix is used
 277  
      * then the temporary file created once the threshold is reached is returned
 278  
      * If the threshold was not reached then {@code null} is returned.
 279  
      *
 280  
      * @return The file for this output stream, or {@code null} if no such
 281  
      *         file exists.
 282  
      */
 283  
     public File getFile()
 284  
     {
 285  442
         return outputFile;
 286  
     }
 287  
 
 288  
 
 289  
     /**
 290  
      * Closes underlying output stream, and mark this as closed
 291  
      *
 292  
      * @throws IOException if an error occurs.
 293  
      */
 294  
     @Override
 295  
     public void close() throws IOException
 296  
     {
 297  234
         super.close();
 298  234
         closed = true;
 299  234
     }
 300  
 
 301  
 
 302  
     /**
 303  
      * Writes the data from this output stream to the specified output stream,
 304  
      * after it has been closed.
 305  
      *
 306  
      * @param out output stream to write to.
 307  
      * @throws IOException if this stream is not yet closed or an error occurs.
 308  
      */
 309  
     public void writeTo(final OutputStream out) throws IOException
 310  
     {
 311  
         // we may only need to check if this is closed if we are working with a file
 312  
         // but we should force the habit of closing wether we are working with
 313  
         // a file or memory.
 314  104
         if (!closed) {
 315  52
             throw new IOException("Stream not closed");
 316  
         }
 317  
 
 318  52
         if (isInMemory()) {
 319  26
             memoryOutputStream.writeTo(out);
 320  
         } else {
 321  26
             try (FileInputStream fis = new FileInputStream(outputFile)) {
 322  26
                 IOUtils.copy(fis, out);
 323  26
             }
 324  
         }
 325  52
     }
 326  
 }