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 }