This project has retired. For details please refer to its Attic page.
ThresholdOutputStream xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.server.shared;
20  
21  import java.io.BufferedInputStream;
22  import java.io.BufferedOutputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.FileOutputStream;
27  import java.io.FilterInputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  
32  /**
33   * An OutputStream that stores the data in main memory until it reaches a
34   * threshold. If the threshold is passed the data is written to a temporary
35   * file.
36   * 
37   * It it is important to close this OutputStream before
38   * {@link #getInputStream()} is called or call {@link #destroy()} if the
39   * InputStream isn't required!
40   */
41  public class ThresholdOutputStream extends OutputStream {
42      private static final int MAX_GROW = 10 * 1024 * 1024;
43      private static final int DEFAULT_THRESHOLD = 4 * 1024 * 1024;
44  
45      private File tempDir;
46      private int memoryThreshold;
47  
48      private byte[] buf = null;
49      private int bufSize = 0;
50      private long size;
51      private File tempFile;
52      private OutputStream tmpStream;
53  
54      public ThresholdOutputStream(File tempDir, int memoryThreshold) {
55          this(64 * 1024, tempDir, memoryThreshold);
56      }
57  
58      public ThresholdOutputStream(int initSize, File tempDir, int memoryThreshold) {
59          if (initSize < 0) {
60              throw new IllegalArgumentException("Negative initial size: " + initSize);
61          }
62  
63          this.tempDir = tempDir;
64          this.memoryThreshold = (memoryThreshold < 0 ? DEFAULT_THRESHOLD : memoryThreshold);
65  
66          buf = new byte[initSize];
67      }
68  
69      private void expand(int nextBufferSize) throws IOException {
70          if (bufSize + nextBufferSize <= buf.length) {
71              return;
72          }
73  
74          if (bufSize + nextBufferSize > memoryThreshold) {
75              if (tmpStream == null) {
76                  tempFile = File.createTempFile("opencmis", null, tempDir);
77                  tmpStream = new BufferedOutputStream(new FileOutputStream(tempFile));
78              }
79              tmpStream.write(buf, 0, bufSize);
80  
81              if (buf.length != memoryThreshold) {
82                  buf = new byte[memoryThreshold];
83              }
84              bufSize = 0;
85  
86              return;
87          }
88  
89          int newSize = ((bufSize + nextBufferSize) * 2 < MAX_GROW ? (bufSize + nextBufferSize) * 2 : buf.length
90                  + nextBufferSize + MAX_GROW);
91          byte[] newbuf = new byte[newSize];
92          System.arraycopy(buf, 0, newbuf, 0, bufSize);
93          buf = newbuf;
94      }
95  
96      public long getSize() {
97          return size;
98      }
99  
100     @Override
101     public void write(byte[] buffer) throws IOException {
102         write(buffer, 0, buffer.length);
103     }
104 
105     @Override
106     public void write(byte[] buffer, int offset, int len) throws IOException {
107         if (len == 0) {
108             return;
109         }
110 
111         expand(len);
112         System.arraycopy(buffer, offset, buf, bufSize, len);
113         bufSize += len;
114         size += len;
115     }
116 
117     @Override
118     public void write(int oneByte) throws IOException {
119         if (bufSize == buf.length) {
120             expand(1);
121         }
122 
123         buf[bufSize++] = (byte) oneByte;
124         size++;
125     }
126 
127     @Override
128     public void flush() throws IOException {
129         if (tmpStream != null) {
130             if (bufSize > 0) {
131                 tmpStream.write(buf, 0, bufSize);
132                 bufSize = 0;
133             }
134             tmpStream.flush();
135         }
136     }
137 
138     @Override
139     public void close() throws IOException {
140         flush();
141 
142         if (tmpStream != null) {
143             tmpStream.close();
144         }
145     }
146 
147     /**
148      * Destroys the object before it has been read.
149      */
150     public void destroy() {
151         try {
152             close();
153         } catch (Exception e) {
154             // ignore
155         }
156 
157         if (tempFile != null) {
158             tempFile.delete();
159         }
160 
161         buf = null;
162     }
163 
164     /**
165      * Returns the data as an InputStream.
166      */
167     public InputStream getInputStream() throws Exception {
168         if (tmpStream != null) {
169             close();
170             buf = null;
171 
172             return new InternalTempFileInputStream();
173         } else {
174             return new InternalBufferInputStream();
175         }
176     }
177 
178     /**
179      * Provides information about the input stream.
180      */
181     public interface ThresholdInputStream {
182 
183         /**
184          * Returns <code>true</code> if the data is in memory. Returns
185          * <code>false</code> if the data resides in a temporary file.
186          */
187         boolean isInMemory();
188 
189         /**
190          * Returns the temporary file if the data stored in a file. Returns
191          * <code>null</code> is the data is stored in memory.
192          */
193         File getTemporaryFile();
194     }
195 
196     /**
197      * InputStream for in-memory data.
198      */
199     private class InternalBufferInputStream extends InputStream implements ThresholdInputStream {
200 
201         private int pos = 0;
202 
203         public boolean isInMemory() {
204             return true;
205         }
206 
207         public File getTemporaryFile() {
208             return null;
209         }
210 
211         @Override
212         public boolean markSupported() {
213             return false;
214         }
215 
216         @Override
217         public int available() {
218             return bufSize - pos;
219         }
220 
221         @Override
222         public int read() {
223             return (pos < bufSize) && (buf != null) ? (buf[pos++] & 0xff) : -1;
224         }
225 
226         @Override
227         public int read(byte[] b) throws IOException {
228             return read(b, 0, b.length);
229         }
230 
231         @Override
232         public int read(byte[] b, int off, int len) {
233             if ((pos >= bufSize) || (buf == null)) {
234                 return -1;
235             }
236 
237             if ((pos + len) > bufSize) {
238                 len = (bufSize - pos);
239             }
240 
241             System.arraycopy(buf, pos, b, off, len);
242             pos += len;
243 
244             return len;
245         }
246 
247         @Override
248         public long skip(long n) {
249             if ((pos + n) > bufSize) {
250                 n = bufSize - pos;
251             }
252 
253             if (n < 0) {
254                 return 0;
255             }
256 
257             pos += n;
258 
259             return n;
260         }
261 
262         @Override
263         public void close() throws IOException {
264             buf = null;
265         }
266     }
267 
268     /**
269      * InputStream for file data.
270      */
271     private class InternalTempFileInputStream extends FilterInputStream implements ThresholdInputStream {
272 
273         private boolean isDeleted = false;
274 
275         public InternalTempFileInputStream() throws FileNotFoundException {
276             super(new BufferedInputStream(new FileInputStream(tempFile), memoryThreshold));
277         }
278 
279         public boolean isInMemory() {
280             return false;
281         }
282 
283         public File getTemporaryFile() {
284             return tempFile;
285         }
286 
287         @Override
288         public boolean markSupported() {
289             return false;
290         }
291 
292         @Override
293         public int read() throws IOException {
294             int b = super.read();
295 
296             if (b == -1 && !isDeleted) {
297                 super.close();
298                 isDeleted = tempFile.delete();
299             }
300 
301             return b;
302         }
303 
304         @Override
305         public int read(byte[] b) throws IOException {
306             return read(b, 0, b.length);
307         }
308 
309         @Override
310         public int read(byte[] b, int off, int len) throws IOException {
311             int n = super.read(b, off, len);
312 
313             if (n == -1 && !isDeleted) {
314                 super.close();
315                 isDeleted = tempFile.delete();
316             }
317 
318             return n;
319         }
320 
321         @Override
322         public void close() throws IOException {
323             if (!isDeleted) {
324                 super.close();
325                 isDeleted = tempFile.delete();
326             }
327         }
328     }
329 }