Coverage Summary for Class: FileBackedOutputStream (com.google.common.io)

Class Method, % Line, %
FileBackedOutputStream 0% (0/14) 0% (0/51)
FileBackedOutputStream$1 0% (0/3) 0% (0/6)
FileBackedOutputStream$2 0% (0/2) 0% (0/2)
FileBackedOutputStream$MemoryOutput 0% (0/3) 0% (0/3)
Total 0% (0/22) 0% (0/62)


1 /* 2  * Copyright (C) 2008 The Guava Authors 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5  * in compliance with the License. You may obtain a copy of the License at 6  * 7  * http://www.apache.org/licenses/LICENSE-2.0 8  * 9  * Unless required by applicable law or agreed to in writing, software distributed under the License 10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11  * or implied. See the License for the specific language governing permissions and limitations under 12  * the License. 13  */ 14  15 package com.google.common.io; 16  17 import static java.util.Objects.requireNonNull; 18  19 import com.google.common.annotations.Beta; 20 import com.google.common.annotations.GwtIncompatible; 21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.errorprone.annotations.concurrent.GuardedBy; 23 import java.io.ByteArrayInputStream; 24 import java.io.ByteArrayOutputStream; 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.OutputStream; 31 import javax.annotation.CheckForNull; 32  33 /** 34  * An {@link OutputStream} that starts buffering to a byte array, but switches to file buffering 35  * once the data reaches a configurable size. 36  * 37  * <p>Temporary files created by this stream may live in the local filesystem until either: 38  * 39  * <ul> 40  * <li>{@link #reset} is called (removing the data in this stream and deleting the file), or... 41  * <li>this stream (or, more precisely, its {@link #asByteSource} view) is finalized during 42  * garbage collection, <strong>AND</strong> this stream was not constructed with {@linkplain 43  * #FileBackedOutputStream(int) the 1-arg constructor} or the {@linkplain 44  * #FileBackedOutputStream(int, boolean) 2-arg constructor} passing {@code false} in the 45  * second parameter. 46  * </ul> 47  * 48  * <p>This class is thread-safe. 49  * 50  * @author Chris Nokleberg 51  * @since 1.0 52  */ 53 @Beta 54 @GwtIncompatible 55 @ElementTypesAreNonnullByDefault 56 public final class FileBackedOutputStream extends OutputStream { 57  private final int fileThreshold; 58  private final boolean resetOnFinalize; 59  private final ByteSource source; 60  @CheckForNull private final File parentDirectory; 61  62  @GuardedBy("this") 63  private OutputStream out; 64  65  @GuardedBy("this") 66  @CheckForNull 67  private MemoryOutput memory; 68  69  @GuardedBy("this") 70  @CheckForNull 71  private File file; 72  73  /** ByteArrayOutputStream that exposes its internals. */ 74  private static class MemoryOutput extends ByteArrayOutputStream { 75  byte[] getBuffer() { 76  return buf; 77  } 78  79  int getCount() { 80  return count; 81  } 82  } 83  84  /** Returns the file holding the data (possibly null). */ 85  @VisibleForTesting 86  @CheckForNull 87  synchronized File getFile() { 88  return file; 89  } 90  91  /** 92  * Creates a new instance that uses the given file threshold, and does not reset the data when the 93  * {@link ByteSource} returned by {@link #asByteSource} is finalized. 94  * 95  * @param fileThreshold the number of bytes before the stream should switch to buffering to a file 96  */ 97  public FileBackedOutputStream(int fileThreshold) { 98  this(fileThreshold, false); 99  } 100  101  /** 102  * Creates a new instance that uses the given file threshold, and optionally resets the data when 103  * the {@link ByteSource} returned by {@link #asByteSource} is finalized. 104  * 105  * @param fileThreshold the number of bytes before the stream should switch to buffering to a file 106  * @param resetOnFinalize if true, the {@link #reset} method will be called when the {@link 107  * ByteSource} returned by {@link #asByteSource} is finalized. 108  */ 109  public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) { 110  this(fileThreshold, resetOnFinalize, null); 111  } 112  113  private FileBackedOutputStream( 114  int fileThreshold, boolean resetOnFinalize, @CheckForNull File parentDirectory) { 115  this.fileThreshold = fileThreshold; 116  this.resetOnFinalize = resetOnFinalize; 117  this.parentDirectory = parentDirectory; 118  memory = new MemoryOutput(); 119  out = memory; 120  121  if (resetOnFinalize) { 122  source = 123  new ByteSource() { 124  @Override 125  public InputStream openStream() throws IOException { 126  return openInputStream(); 127  } 128  129  @Override 130  protected void finalize() { 131  try { 132  reset(); 133  } catch (Throwable t) { 134  t.printStackTrace(System.err); 135  } 136  } 137  }; 138  } else { 139  source = 140  new ByteSource() { 141  @Override 142  public InputStream openStream() throws IOException { 143  return openInputStream(); 144  } 145  }; 146  } 147  } 148  149  /** 150  * Returns a readable {@link ByteSource} view of the data that has been written to this stream. 151  * 152  * @since 15.0 153  */ 154  public ByteSource asByteSource() { 155  return source; 156  } 157  158  private synchronized InputStream openInputStream() throws IOException { 159  if (file != null) { 160  return new FileInputStream(file); 161  } else { 162  // requireNonNull is safe because we always have either `file` or `memory`. 163  requireNonNull(memory); 164  return new ByteArrayInputStream(memory.getBuffer(), 0, memory.getCount()); 165  } 166  } 167  168  /** 169  * Calls {@link #close} if not already closed, and then resets this object back to its initial 170  * state, for reuse. If data was buffered to a file, it will be deleted. 171  * 172  * @throws IOException if an I/O error occurred while deleting the file buffer 173  */ 174  public synchronized void reset() throws IOException { 175  try { 176  close(); 177  } finally { 178  if (memory == null) { 179  memory = new MemoryOutput(); 180  } else { 181  memory.reset(); 182  } 183  out = memory; 184  if (file != null) { 185  File deleteMe = file; 186  file = null; 187  if (!deleteMe.delete()) { 188  throw new IOException("Could not delete: " + deleteMe); 189  } 190  } 191  } 192  } 193  194  @Override 195  public synchronized void write(int b) throws IOException { 196  update(1); 197  out.write(b); 198  } 199  200  @Override 201  public synchronized void write(byte[] b) throws IOException { 202  write(b, 0, b.length); 203  } 204  205  @Override 206  public synchronized void write(byte[] b, int off, int len) throws IOException { 207  update(len); 208  out.write(b, off, len); 209  } 210  211  @Override 212  public synchronized void close() throws IOException { 213  out.close(); 214  } 215  216  @Override 217  public synchronized void flush() throws IOException { 218  out.flush(); 219  } 220  221  /** 222  * Checks if writing {@code len} bytes would go over threshold, and switches to file buffering if 223  * so. 224  */ 225  @GuardedBy("this") 226  private void update(int len) throws IOException { 227  if (memory != null && (memory.getCount() + len > fileThreshold)) { 228  File temp = File.createTempFile("FileBackedOutputStream", null, parentDirectory); 229  if (resetOnFinalize) { 230  // Finalizers are not guaranteed to be called on system shutdown; 231  // this is insurance. 232  temp.deleteOnExit(); 233  } 234  try { 235  FileOutputStream transfer = new FileOutputStream(temp); 236  transfer.write(memory.getBuffer(), 0, memory.getCount()); 237  transfer.flush(); 238  // We've successfully transferred the data; switch to writing to file 239  out = transfer; 240  } catch (IOException e) { 241  temp.delete(); 242  throw e; 243  } 244  245  file = temp; 246  memory = null; 247  } 248  } 249 }