KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mortbay > util > TempByteHolder


1 // ========================================================================
2
// $Id: TempByteHolder.java,v 1.8 2004/10/23 09:03:22 gregwilkins Exp $
3
// Copyright 2002-2004 Mort Bay Consulting Pty. Ltd.
4
// ------------------------------------------------------------------------
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
// ========================================================================
15
package org.mortbay.util;
16
17 import java.io.File JavaDoc;
18 import java.io.IOException JavaDoc;
19 import java.io.RandomAccessFile JavaDoc;
20
21
22
23 /**
24  * Temporary buffer for bytes to be used in situations where bytes need to be buffered
25  * but total size of data is not known in advance and may potentially be very large.
26  * Provides easy way to access small buffered data as byte[] or String.
27  * Enables efficient memory-only handling of small data while automatically switching
28  * to temporary file storage when data gets too big to fit in memory buffer.
29  * It is highly efficient for both byte-per-byte and block I/O.
30  * This class is not a FIFO - you can't mix reading and writing infinitely as all data
31  * keep being buffered, not just unread data.
32  * Mixing reads and writes may be inefficient in some situations but is fully supported.
33  * <br>
34  * Overall usage strategy: You first write data to the buffer using OutputStream
35  * returned by getOutputStream(), then examine data size using getLength()
36  * and isLarge() and either call getBytes() to get byte[],
37  * getString() to get data as String or getInputStream() to read data using stream.
38  * Instance of TempByteHolder can be safely and efficiently reused by calling clear().
39  * When TempByteHolder is no longer needed you must call close() to ensure underlying
40  * temporary file is closed and deleted.
41  * <br><br>
42  * <i>NOTE:</i> For performance, this class is not synchronized. If you need thread safety,
43  * use synchronized wrapper.<br>
44  * This class can hold up to 2GB of data.
45  * <br><br>
46  * <i>SECURITY NOTE:</i> As data may be written to disk, don't use this for sensitive information.
47  * @author Jan Hlavatý &lt;hlavac AT code.cz&gt;
48  */

49 public class TempByteHolder {
50     
51     byte[] _memory_buffer = null; /** buffer to use */
52     
53     boolean _file_mode = false; /** false: memory buffer mode (small data)
54                                     true: temp file mode (large data) */

55
56     int _window_size = 0; /** size of memory buffer */
57     int _window_low = 0; /** offset of first byte in memory buffer */
58     int _window_high = 0; /** offset of first byte after memory buffer */
59     int _file_high = 0; /** offset of fist byte not yet written to temp file */
60     int _write_pos = 0; /** offset of next byte to be writen; number of bytes written */
61     int _read_pos = 0; /** offset of fist byte to be read */
62     int _file_pos = -1; /** current temp file seek offset; -1 = unknown */
63     int _mark_pos = 0; /** mark */
64     
65
66     /** Instance of OutputStream is cached and reused. */
67     TempByteHolder.OutputStream _output_stream = new TempByteHolder.OutputStream();
68     /** Instance of InputStream is cached and reused. */
69     TempByteHolder.InputStream _input_stream = null; //input_stream = new TempByteHolder.InputStream();
70

71     /** Temporary directory to be used, or null for system default */
72     File JavaDoc _temp_directory = null;
73     /** File object representing temporary file. */
74     File JavaDoc _tempfilef = null;
75     /** Temporary file or null when none is used yet */
76     RandomAccessFile JavaDoc _tempfile = null;
77
78     
79     //----- constructors -------
80

81     /**
82      * Creates a new instance of TempByteHolder allocating memory buffer of given capacity.
83      * You should use reasonably large buffer for potentionally large data to improve
84      * effect of caching for file operations (about 512 bytes).
85      * @param in_memory_capacity Size in bytes of memory buffer to allocate.
86      */

87     public TempByteHolder(int in_memory_capacity) {
88         this(new byte[in_memory_capacity],0,0);
89     }
90     
91     /**
92      * Creates a new instance of TempByteHolder using passed byte[] as memory buffer.
93      * @param byte_array byte array to be used as memory buffer.
94      */

95     public TempByteHolder(byte[] byte_array) {
96         this(byte_array,0,0);
97     }
98
99     /**
100      * Creates a new instance of TempByteHolder using passed byte[] which
101      * contains prefilled data as memory buffer.
102      * @param byte_array byte array to be used as memory buffer.
103      * @param offset offset of prefilled data in buffer.
104      * @param prefilled_data_size number of bytes that contain valid data.
105      */

106     public TempByteHolder(byte[] byte_array, int offset, int prefilled_data_size) {
107         if (byte_array == null) throw new NullPointerException JavaDoc();
108         _window_size = byte_array.length;
109         if ((offset < 0) || (offset > _window_size)) throw new IllegalArgumentException JavaDoc("Bad prefilled data offset");
110         if ((offset+prefilled_data_size > _window_size)||(prefilled_data_size < 0)) throw new IllegalArgumentException JavaDoc("Bad prefilled data size");
111         _memory_buffer = byte_array;
112         _write_pos = prefilled_data_size;
113         _window_low = -offset;
114         _window_high = _window_size-offset;
115     }
116     
117     protected void finalize() {
118         try {
119             close();
120         } catch (IOException JavaDoc e) {
121         }
122     }
123
124     /**
125      * Erases all unread buffered data and prepares for next use cycle.
126      * If temporary file was used, it is not closed/deleted yet as it may be needed again.
127      */

128     public void clear() {
129         _file_mode = false;
130         _write_pos = 0;
131         _read_pos = 0;
132         _window_low = 0;
133         _window_high = _window_size;
134         _file_high = 0;
135         _mark_pos = 0;
136     }
137     
138     /**
139      * Clears all data and closes/deletes backing temporary file if used.
140      * @throws IOException when something goes wrong.
141      */

142     public void close() throws IOException JavaDoc {
143         clear();
144         if (_tempfile != null) {
145             _tempfile.close();
146             _tempfile = null;
147             _tempfilef.delete();
148             _tempfilef = null;
149         }
150     }
151
152     /**
153      * Repositions InputStream at given offset within buffered data.
154      * @throws IOException when something goes wrong.
155      */

156     public void seek(int offset) throws IOException JavaDoc {
157         if ((offset <= _write_pos)&&(offset>=0)) {
158             _read_pos = offset;
159         } else throw new IOException JavaDoc("bad seek offset");
160     }
161     
162     /**
163      * Truncates buffered data to specified size. Can not be used to extend data.
164      * Repositions OutputStream at the end of truncated data.
165      * If current read offset or mark is past the new end of data, it is moved at the new end.
166      */

167     public void truncate(int offset) throws IOException JavaDoc {
168         if ((offset < 0)||(offset > _write_pos)) throw new IOException JavaDoc("bad truncate offset");
169         if (_read_pos > offset) _read_pos = offset;
170         if (_mark_pos > offset) _mark_pos = offset;
171         _write_pos = offset;
172         if (_file_high > offset) _file_high = offset;
173         moveWindow(_write_pos);
174     }
175     
176
177     /**
178      * Override directory to create temporary file in.
179      * Does not affect already open temp file.
180      * @param dir File object representing temporary directory.
181      * May be null which means that system default
182      * (java.io.tmpdir system property) should be used.
183      * @throws IOException
184      */

185     public void setTempDirectory(File JavaDoc dir) throws IOException JavaDoc {
186         File JavaDoc td = dir.getCanonicalFile();
187         if (td.isDirectory()) {
188             _temp_directory = td;
189         }
190     }
191     
192     
193     
194     /**
195      * Returns number of bytes buffered so far.
196      * @return total number of bytes buffered. If you need number of bytes
197      * to be read, use InputStream.available() .
198      */

199     public int getLength() {
200         return _write_pos;
201     }
202     
203     /**
204      * Tells whether buffered data is small enough to fit in memory buffer
205      * so that it can be returned as byte[]. Data is considered large
206      * when it will not fit into backing memory buffer.
207      * @return true when data is only accessible through InputStream interface;
208      * false when data can be also retrieved directly as byte[] or String.
209      * @see #getBytes()
210      * @see #getString(String)
211      */

212     public boolean isLarge() {
213         return _file_mode;
214     }
215     
216
217     /**
218      * Returns byte[] that holds all buffered data in its first getLength() bytes.
219      * If this instance was created using (byte[]) constructor, this is the same
220      * array that has been passed to the constructor. If buffered data don't fit into
221      * memory buffer, IllegalStateException is thrown.
222      * @return byte[] with data as its first getLength() bytes.
223      * @throws IllegalStateException when data is too big to be read this way.
224      * @see #isLarge()
225      * @see #getLength()
226      * @see #getString(String)
227      * @see #getInputStream()
228      */

229     public byte[] getBytes() {
230         if (_file_mode) throw new IllegalStateException JavaDoc("data too large");
231         return _memory_buffer;
232     }
233
234     /**
235      * Returns buffered data as String using given character encoding.
236      * @param character_encoding Name of character encoding to use for
237      * converting bytes to String.
238      * @return Buffered data as String.
239      * @throws IllegalStateException when data is too large to be read this way.
240      * @throws java.io.UnsupportedEncodingException when this encoding is not supported.
241      */

242     public String JavaDoc getString(String JavaDoc character_encoding) throws java.io.UnsupportedEncodingException JavaDoc {
243         if (_file_mode) throw new IllegalStateException JavaDoc("data too large");
244         return new String JavaDoc(_memory_buffer,0,_write_pos,character_encoding);
245     }
246     
247     /**
248      * Returns OutputStream filling this buffer.
249      * @return OutputStream for writing in the buffer.
250      */

251     
252     public java.io.OutputStream JavaDoc getOutputStream() {
253         return _output_stream;
254     }
255     
256     
257     /**
258      * Returns InputSream for reading buffered data.
259      * @return InputSream for reading buffered data.
260      */

261     public java.io.InputStream JavaDoc getInputStream() {
262         if (_input_stream == null) {
263             _input_stream = new TempByteHolder.InputStream();
264         }
265         return _input_stream;
266     }
267
268     
269     /**
270      * Writes efficiently whole content to output stream.
271      * @param os OutputStream to write to
272      * @throws IOException
273      */

274     public void writeTo(java.io.OutputStream JavaDoc os) throws IOException JavaDoc {
275         writeTo(os, 0, getLength());
276     }
277     
278     
279     /**
280      * Writes efficiently part of the content to output stream.
281      * @param os OutputStream to write to
282      * @param start_offset Offset of data fragment to be written
283      * @param length Length of data fragment to be written
284      * @throws IOException
285      */

286     public void writeTo(java.io.OutputStream JavaDoc os, int start_offset, int length) throws IOException JavaDoc {
287         int towrite = min(length, _write_pos-start_offset);
288         int writeoff = start_offset;
289         if (towrite > 0) {
290             while (towrite >= _window_size) {
291                 moveWindow(writeoff);
292                 os.write(_memory_buffer,0,_window_size);
293                 towrite -= _window_size;
294                 writeoff += _window_size;
295             }
296             if (towrite > 0) {
297                 moveWindow(writeoff);
298                 os.write(_memory_buffer,0,towrite);
299             }
300         }
301     }
302     
303     
304     /**
305      * Reads all available data from input stream.
306      * @param is
307      * @throws IOException
308      */

309     public void readFrom(java.io.InputStream JavaDoc is) throws IOException JavaDoc {
310         int howmuch = 0;
311         do {
312             _write_pos += howmuch;
313             moveWindow(_write_pos);
314             howmuch = is.read(_memory_buffer);
315         } while (howmuch != -1);
316     }
317     
318     
319     // ----- helper methods -------
320

321     /**
322      * Create tempfile if it does not already exist
323      */

324     private void createTempFile() throws IOException JavaDoc {
325         _tempfilef = File.createTempFile("org.mortbay.util.TempByteHolder-",".tmp",_temp_directory).getCanonicalFile();
326         _tempfilef.deleteOnExit();
327         _tempfile = new RandomAccessFile JavaDoc(_tempfilef,"rw");
328     }
329
330     /**
331      * Write chunk of data at specified offset in temp file.
332      * Marks data as big.
333      * Updates high water mark on tempfile content.
334      */

335     private void writeToTempFile(int at_offset, byte[] data, int offset, int len) throws IOException JavaDoc {
336         if (_tempfile == null) {
337             createTempFile();
338             _file_pos = -1;
339         }
340         _file_mode = true;
341         if (at_offset != _file_pos) {
342             _tempfile.seek((long)at_offset);
343         }
344         _tempfile.write(data,offset,len);
345         _file_pos = at_offset + len;
346         _file_high = max(_file_high,_file_pos);
347     }
348     
349     /**
350      * Read chunk of data from specified offset in tempfile
351      */

352     private void readFromTempFile(int at_offset, byte[] data, int offset, int len) throws IOException JavaDoc {
353         if (_file_pos != at_offset) {
354             _tempfile.seek((long)at_offset);
355         }
356         _tempfile.readFully(data,offset,len);
357         _file_pos = at_offset+len;
358     }
359     
360     
361     /**
362      * Move file window, synchronizing data with file.
363      * Works somewhat like memory-mapping a file.
364      * This one was nightmare to write :-)
365      */

366     private void moveWindow(int start_offset) throws IOException JavaDoc {
367         if (start_offset != _window_low) { // only when we have to move
368

369             int end_offset = start_offset + _window_size;
370             // new window low/high = start_offset/end_offset
371
int dirty_low = _file_high;
372             int dirty_high = _write_pos;
373             int dirty_len = _write_pos - _file_high;
374             if (dirty_len > 0) { // we need to be concerned at all about dirty data.
375
// will any part of dirty data be moved out of window?
376
if ( (dirty_low < start_offset) || (dirty_high > end_offset) ) {
377                     // yes, dirty data need to be saved.
378
writeToTempFile(dirty_low, _memory_buffer, dirty_low - _window_low, dirty_len);
379                 }
380             }
381             
382             // reposition any data from old window that will be also in new window:
383

384             int stay_low = max(start_offset,_window_low);
385             int stay_high = min(_write_pos, _window_high, end_offset);
386             // is there anything to preserve?
387
int stay_size = stay_high - stay_low;
388             if (stay_size > 0) {
389                 System.arraycopy(_memory_buffer, stay_low-_window_low, _memory_buffer, stay_low-start_offset, stay_size);
390             }
391             
392             // read in available data that were not in old window:
393
if (stay_low > start_offset) {
394                 // read at the start of buffer
395
int toread_low = start_offset;
396                 int toread_high = min(stay_low,end_offset);
397                 int toread_size = toread_high - toread_low;
398                 if (toread_size > 0) {
399                     readFromTempFile(toread_low, _memory_buffer, toread_low-start_offset, toread_size);
400                 }
401             }
402             if (stay_high < end_offset) {
403                 // read at end of buffer
404
int toread_low = max(stay_high,start_offset);
405                 int toread_high = min(end_offset,_file_high);
406                 int toread_size = toread_high-toread_low;
407                 if (toread_size > 0) {
408                     readFromTempFile(toread_low, _memory_buffer, toread_low-start_offset, toread_size);
409                 }
410             }
411             _window_low = start_offset;
412             _window_high = end_offset;
413         }
414     }
415
416     /** Simple minimum for 2 ints */
417     private static int min(int a, int b) {
418         return (a<b?a:b);
419     }
420     
421     /** Simple maximum for 2 ints */
422     private static int max(int a, int b) {
423         return (a>b?a:b);
424     }
425     
426     /** Simple minimum for 3 ints */
427     private static int min(int a, int b, int c) {
428         int r = a;
429         if (r > b) r = b;
430         if (r > c) r = c;
431         return r;
432     }
433
434     /**
435      * @return true when range 1 is fully contained in range 2
436      */

437     private static boolean contained(int range1_low, int range1_high, int range2_low, int range2_high) {
438         return ((range1_low >= range2_low)&&(range1_high <= range2_high));
439     }
440     
441     /**
442      * Internal implementation of java.io.OutputStream used to fill the byte buffer.
443      */

444     class OutputStream extends java.io.OutputStream JavaDoc {
445         
446         /**
447          * Write whole byte array into buffer.
448          * @param data byte[] to be written
449          * @throws IOException when something goes wrong.
450          */

451         public void write(byte[] data) throws IOException JavaDoc {
452             write(data,0,data.length);
453         }
454
455         /**
456          * Write segment of byte array to the buffer.
457          * @param data Byte array with data
458          * @param off Starting offset within the array.
459          * @param len Number of bytes to write
460          * @throws IOException when something goes wrong.
461          */

462         public void write(byte[] data, int off, int len) throws IOException JavaDoc {
463             int new_write_pos = _write_pos + len;
464             boolean write_pos_in_window = (_write_pos >= _window_low)&&(_write_pos < _window_high);
465             
466             if (!write_pos_in_window) {
467                 // either current window is full of dirty data or it is somewhere low
468
moveWindow(_write_pos); // flush buffer if necessary, move window at end
469
}
470             
471             boolean end_of_data_in_window = (new_write_pos <= _window_high);
472             
473             if ( end_of_data_in_window ) {
474                 // if there is space in window for all data, just put it in buffer.
475
// 0 writes, window unchanged
476
System.arraycopy(data, off, _memory_buffer, _write_pos-_window_low, len);
477                 _write_pos = new_write_pos;
478             } else {
479                 int out_of_window = new_write_pos - _window_high;
480                 if (out_of_window < _window_size) {
481                     // start of data in window, rest will fit in a new window:
482
// 1 write, window moved at window_high, filled with rest of data
483

484                     // fill in rest of the current window with first part of data
485
int part1_len = _window_high - _write_pos;
486                     int part2_len = len - part1_len;
487                     
488                     System.arraycopy(data, off, _memory_buffer, _write_pos-_window_low, part1_len);
489                     _write_pos = _window_high;
490                     
491                     moveWindow(_write_pos); // flush data to file
492

493                     System.arraycopy(data, off+part1_len, _memory_buffer, 0, part2_len);
494                     _write_pos = new_write_pos;
495                     
496                 } else {
497                     // start of data in window, rest will not fit in window (and leave some space):
498
// 2 writes; window moved at end, empty
499

500                     int part1_size = _window_high - _write_pos;
501                     int part2_size = len - part1_size;
502                     
503                     if (part1_size == _window_size) {
504                         // buffer was empty - no sense in splitting the write
505
// write data directly to file in one chunk
506
writeToTempFile(_write_pos, data, off, len);
507                         _write_pos = new_write_pos;
508                         moveWindow(_write_pos);
509                         
510                     } else {
511                         // copy part 1 to window
512
if (part1_size > 0) {
513                             System.arraycopy(data, off, _memory_buffer, _write_pos-_window_low, part1_size);
514                             _write_pos += part1_size;
515                             moveWindow(_write_pos); // flush buffer
516
}
517                         // flush window to file
518
// write part 2 directly to file
519
writeToTempFile(_write_pos, data, off+part1_size, part2_size);
520                         _write_pos = new_write_pos;
521                         moveWindow(_write_pos);
522                     }
523                 }
524             }
525         }
526         
527         /**
528          * Write single byte to the buffer.
529          * @param b
530          * @throws IOException
531          */

532         public void write(int b) throws IOException JavaDoc {
533             if ((_write_pos >= _window_high) || (_write_pos < _window_low)) {
534                 moveWindow(_write_pos);
535             }
536             // we now have space for one byte in window.
537
_memory_buffer[_write_pos - _window_low] = (byte)(b &0xFF);
538             _write_pos++;
539         }
540         
541         public void flush() throws IOException JavaDoc {
542             moveWindow(_write_pos); // or no-op? not needed
543
}
544         
545         public void close() throws IOException JavaDoc {
546             // no-op: this output stream does not need to be closed.
547
}
548     }
549     
550     
551     
552     
553     
554     /**
555      * Internal implementation of InputStream used to read buffered data.
556      */

557     class InputStream extends java.io.InputStream JavaDoc {
558         
559         public int read() throws IOException JavaDoc {
560             int ret = -1;
561             // if window does not contain read position, move it there
562
if (!contained(_read_pos,_read_pos+1, _window_low, _window_high)) {
563                 moveWindow(_read_pos);
564             }
565             if (_write_pos > _read_pos) {
566                 ret = (_memory_buffer[_read_pos - _window_low])&0xFF;
567                 _read_pos++;
568             }
569             return ret;
570         }
571         
572         public int read(byte[] buff) throws IOException JavaDoc {
573             return read(buff,0, buff.length);
574         }
575         
576         public int read(byte[] buff, int off, int len) throws IOException JavaDoc {
577             // clip read to available data:
578
int read_size = min(len,_write_pos-_read_pos);
579             if (read_size > 0) {
580                 if (read_size >= _window_size) {
581                     // big chunk: read directly from file
582
moveWindow(_write_pos);
583                     readFromTempFile(_read_pos, buff, off, read_size);
584                 } else {
585                     // small chunk:
586
int read_low = _read_pos;
587                     int read_high = read_low + read_size;
588                     // if we got all data in current window, read it from there
589
if (!contained(read_low,read_high, _window_low, _window_high)) {
590                         moveWindow(_read_pos);
591                     }
592                     System.arraycopy(_memory_buffer, _read_pos - _window_low, buff, off, read_size);
593                 }
594                 _read_pos += read_size;
595             }
596             return read_size;
597         }
598         
599         public long skip(long bytes) throws IOException JavaDoc {
600             if (bytes < 0 || bytes > Integer.MAX_VALUE) throw new IllegalArgumentException JavaDoc();
601             int len = (int)bytes;
602             if ( (len+_read_pos) > _write_pos ) len = _write_pos - _read_pos;
603             _read_pos+=len;
604             moveWindow(_write_pos); // invalidate window without reading data by moving it at the end
605
return (long)len;
606         }
607         
608         public int available() throws IOException JavaDoc {
609             return _write_pos - _read_pos;
610         }
611         
612         
613         public void mark(int readlimit) {
614             // readlimit is ignored, we store all the data anyway
615
_mark_pos = _read_pos;
616         }
617         
618         public void reset() throws IOException JavaDoc {
619             _read_pos = _mark_pos;
620         }
621         
622         public boolean markSupported() {
623             return true;
624         }
625         
626         
627     }
628 }
629
Popular Tags