Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/mortbay/util/TempByteHolder.java


1   // ========================================================================
2   // Copyright (c) 2002 Mort Bay Consulting, Sydney
3   // $Id: TempByteHolder.java,v 1.6 2003/06/27 18:49:43 hlavac Exp $
4   // ========================================================================
5   package org.mortbay.util;
6   
7   import java.io.File;
8   import java.io.IOException;
9   import java.io.RandomAccessFile;
10  
11  
12  
13  /**
14   * Temporary buffer for bytes to be used in situations where bytes need to be buffered 
15   * but total size of data is not known in advance and may potentially be very large.
16   * Provides easy way to access small buffered data as byte[] or String.
17   * Enables efficient memory-only handling of small data while automatically switching
18   * to temporary file storage when data gets too big to fit in memory buffer.
19   * It is highly efficient for both byte-per-byte and block I/O.
20   * This class is not a FIFO - you can't mix reading and writing infinitely as all data
21   * keep being buffered, not just unread data.
22   * Mixing reads and writes may be inefficient in some situations but is fully supported.
23   * <br>
24   * Overall usage strategy: You first write data to the buffer using OutputStream 
25   * returned by getOutputStream(), then examine data size using getLength() 
26   * and isLarge() and either call getBytes() to get byte[],
27   * getString() to get data as String or getInputStream() to read data using stream.
28   * Instance of TempByteHolder can be safely and efficiently reused by calling clear().
29   * When TempByteHolder is no longer needed you must call close() to ensure underlying
30   * temporary file is closed and deleted.
31   * <br><br>
32   * <i>NOTE:</i> For performance, this class is not synchronized. If you need thread safety, 
33   * use synchronized wrapper.<br>
34   * This class can hold up to 2GB of data.
35   * <br><br>
36   * <i>SECURITY NOTE:</i> As data may be written to disk, don't use this for sensitive information.
37   * @author  Jan Hlavatý &lt;hlavac AT code.cz&gt;
38   */
39  public class TempByteHolder {
40      
41      byte[] _memory_buffer = null;    /** buffer to use */
42      
43      boolean _file_mode = false;  /** false: memory buffer mode (small data)
44                                      true: temp file mode (large data) */
45  
46      int _window_size = 0;    /** size of memory buffer */
47      int _window_low = 0;     /** offset of first byte in memory buffer */
48      int _window_high = 0;    /** offset of first byte after memory buffer */
49      int _file_high = 0;      /** offset of fist byte not yet written to temp file */
50      int _write_pos = 0;      /** offset of next byte to be writen; number of bytes written */
51      int _read_pos = 0;       /** offset of fist byte to be read */
52      int _file_pos = -1;      /** current temp file seek offset; -1 = unknown */
53      int _mark_pos = 0;       /** mark */
54      
55  
56      /** Instance of OutputStream is cached and reused. */
57      TempByteHolder.OutputStream _output_stream = new TempByteHolder.OutputStream();
58      /** Instance of InputStream is cached and reused. */
59      TempByteHolder.InputStream _input_stream = null; //input_stream = new TempByteHolder.InputStream();
60      
61      /** Temporary directory to be used, or null for system default */
62      File _temp_directory = null;
63      /** File object representing temporary file. */
64      File _tempfilef = null;
65      /** Temporary file or null when none is used yet */
66      RandomAccessFile _tempfile = null;
67  
68      
69      //----- constructors -------
70      
71      /**
72       * Creates a new instance of TempByteHolder allocating memory buffer of given capacity.
73       * You should use reasonably large buffer for potentionally large data to improve
74       * effect of caching for file operations (about 512 bytes).
75       * @param in_memory_capacity Size in bytes of memory buffer to allocate.
76       */
77      public TempByteHolder(int in_memory_capacity) {
78          this(new byte[in_memory_capacity],0,0);
79      }
80      
81      /**
82       * Creates a new instance of TempByteHolder using passed byte[] as memory buffer.
83       * @param byte_array byte array to be used as memory buffer.
84       */
85      public TempByteHolder(byte[] byte_array) {
86          this(byte_array,0,0);
87      }
88  
89      /**
90       * Creates a new instance of TempByteHolder using passed byte[] which
91       * contains prefilled data as memory buffer.
92       * @param byte_array byte array to be used as memory buffer.
93       * @param offset offset of prefilled data in buffer.
94       * @param prefilled_data_size number of bytes that contain valid data.
95       */
96      public TempByteHolder(byte[] byte_array, int offset, int prefilled_data_size) {
97          if (byte_array == null) throw new NullPointerException();
98          _window_size = byte_array.length;
99          if ((offset < 0) || (offset > _window_size)) throw new IllegalArgumentException("Bad prefilled data offset");
100         if ((offset+prefilled_data_size > _window_size)||(prefilled_data_size < 0)) throw new IllegalArgumentException("Bad prefilled data size");
101         _memory_buffer = byte_array;
102         _write_pos = prefilled_data_size;
103         _window_low = -offset;
104         _window_high = _window_size-offset;
105     }
106     
107     public void finalize() {
108         try {
109             close();
110         } catch (IOException e) {
111         }
112     }
113 
114     /**
115      * Erases all unread buffered data and prepares for next use cycle.
116      * If temporary file was used, it is not closed/deleted yet as it may be needed again.
117      */
118     public void clear() {
119         _file_mode = false;
120         _write_pos = 0;
121         _read_pos = 0;
122         _window_low = 0;
123         _window_high = _window_size;
124         _file_high = 0;
125         _mark_pos = 0;
126     }
127     
128     /**
129      * Clears all data and closes/deletes backing temporary file if used.
130      * @throws IOException when something goes wrong.
131      */
132     public void close() throws IOException {
133         clear();
134         if (_tempfile != null) {
135             _tempfile.close();
136             _tempfile = null;
137             _tempfilef.delete();
138             _tempfilef = null;
139         }
140     }
141 
142     /**
143      * Repositions InputStream at given offset within buffered data.
144      * @throws IOException when something goes wrong.
145      */
146     public void seek(int offset) throws IOException {
147         if ((offset <= _write_pos)&&(offset>=0)) {
148             _read_pos = offset;
149         } else throw new IOException("bad seek offset");
150     }
151     
152     /**
153      * Truncates buffered data to specified size. Can not be used to extend data.
154      * Repositions OutputStream at the end of truncated data.
155      * If current read offset or mark is past the new end of data, it is moved at the new end.
156      */
157     public void truncate(int offset) throws IOException {
158         if ((offset < 0)||(offset > _write_pos)) throw new IOException("bad truncate offset");
159         if (_read_pos > offset) _read_pos = offset;
160         if (_mark_pos > offset) _mark_pos = offset;
161         _write_pos = offset;
162         if (_file_high > offset) _file_high = offset;
163         moveWindow(_write_pos);
164     }
165     
166 
167     /**
168      * Override directory to create temporary file in.
169      * Does not affect already open temp file.
170      * @param dir File object representing temporary directory.
171      * May be null which means that system default
172      * (java.io.tmpdir system property) should be used.
173      * @throws IOException
174      */
175     public void setTempDirectory(File dir) throws IOException {
176         File td = dir.getCanonicalFile();
177         if (td.isDirectory()) {
178             _temp_directory = td;
179         }
180     }
181     
182     
183     
184     /**
185      * Returns number of bytes buffered so far.
186      * @return total number of bytes buffered. If you need number of bytes
187      * to be read, use InputStream.available() .
188      */
189     public int getLength() {
190         return _write_pos;
191     }
192     
193     /**
194      * Tells whether buffered data is small enough to fit in memory buffer
195      * so that it can be returned as byte[]. Data is considered large 
196      * when it will not fit into backing memory buffer.
197      * @return true when data is only accessible through InputStream interface;
198      * false when data can be also retrieved directly as byte[] or String.
199      * @see #getBytes()
200      * @see #getString(String)
201      */
202     public boolean isLarge() {
203         return _file_mode;
204     }
205     
206 
207     /**
208      * Returns byte[] that holds all buffered data in its first getLength() bytes.
209      * If this instance was created using (byte[]) constructor, this is the same
210      * array that has been passed to the constructor. If buffered data don't fit into
211      * memory buffer, IllegalStateException is thrown.
212      * @return byte[] with data as its first getLength() bytes.
213      * @throws IllegalStateException when data is too big to be read this way.
214      * @see #isLarge()
215      * @see #getLength()
216      * @see #getString(String)
217      * @see #getInputStream()
218      */
219     public byte[] getBytes() {
220         if (_file_mode) throw new IllegalStateException("data too large");
221         return _memory_buffer;
222     }
223 
224     /**
225      * Returns buffered data as String using given character encoding.
226      * @param character_encoding Name of character encoding to use for
227      * converting bytes to String.
228      * @return Buffered data as String.
229      * @throws IllegalStateException when data is too large to be read this way.
230      * @throws java.io.UnsupportedEncodingException when this encoding is not supported.
231      */
232     public String getString(String character_encoding) throws java.io.UnsupportedEncodingException {
233         if (_file_mode) throw new IllegalStateException("data too large");
234         return new String(_memory_buffer,0,_write_pos,character_encoding);
235     }
236     
237     /**
238      * Returns OutputStream filling this buffer.
239      * @return OutputStream for writing in the buffer.
240      */
241     
242     public java.io.OutputStream getOutputStream() {
243         return _output_stream;
244     }
245     
246     
247     /**
248      * Returns InputSream for reading buffered data.
249      * @return InputSream for reading buffered data.
250      */    
251     public java.io.InputStream getInputStream() {
252         if (_input_stream == null) {
253             _input_stream = new TempByteHolder.InputStream();
254         }
255         return _input_stream;
256     }
257 
258     
259     /**
260      * Writes efficiently whole content to output stream.
261      * @param os OutputStream to write to
262      * @throws IOException
263      */
264     public void writeTo(java.io.OutputStream os) throws IOException {
265         writeTo(os, 0, getLength());
266     }
267     
268     
269     /**
270      * Writes efficiently part of the content to output stream.
271      * @param os OutputStream to write to
272      * @param start_offset  Offset of data fragment to be written
273      * @param length        Length of data fragment to be written
274      * @throws IOException
275      */
276     public void writeTo(java.io.OutputStream os, int start_offset, int length) throws IOException {
277         int towrite = min(length, _write_pos-start_offset);
278         int writeoff = start_offset;
279         if (towrite > 0) {
280             while (towrite >= _window_size) {
281                 moveWindow(writeoff);
282                 os.write(_memory_buffer,0,_window_size);
283                 towrite -= _window_size;
284                 writeoff += _window_size;
285             }
286             if (towrite > 0) {
287                 moveWindow(writeoff);
288                 os.write(_memory_buffer,0,towrite);
289             }
290         }
291     }
292     
293     
294     /**
295      * Reads all available data from input stream.
296      * @param is
297      * @throws IOException
298      */    
299     public void readFrom(java.io.InputStream is) throws IOException {
300         int howmuch = 0;
301         do {
302             _write_pos += howmuch;
303             moveWindow(_write_pos);
304             howmuch = is.read(_memory_buffer);
305         } while (howmuch != -1);
306     }
307     
308     
309     // ----- helper methods -------
310     
311     /**
312      * Create tempfile if it does not already exist
313      */
314     private void createTempFile() throws IOException {
315         _tempfilef = File.createTempFile("org.mortbay.util.TempByteHolder-",".tmp",_temp_directory).getCanonicalFile();
316         _tempfilef.deleteOnExit();
317         _tempfile = new RandomAccessFile(_tempfilef,"rw");
318     }
319 
320     /**
321      * Write chunk of data at specified offset in temp file.
322      * Marks data as big.
323      * Updates high water mark on tempfile content.
324      */
325     private void writeToTempFile(int at_offset, byte[] data, int offset, int len) throws IOException {
326         if (_tempfile == null) {
327             createTempFile();
328             _file_pos = -1;
329         }
330         _file_mode = true;
331         if (at_offset != _file_pos) {
332             _tempfile.seek((long)at_offset);
333         }
334         _tempfile.write(data,offset,len);
335         _file_pos = at_offset + len;
336         _file_high = max(_file_high,_file_pos);
337     }
338     
339     /**
340      * Read chunk of data from specified offset in tempfile
341      */
342     private void readFromTempFile(int at_offset, byte[] data, int offset, int len) throws IOException {
343         if (_file_pos != at_offset) {
344             _tempfile.seek((long)at_offset);
345         }
346         _tempfile.readFully(data,offset,len);
347         _file_pos = at_offset+len;
348     }
349     
350     
351     /**
352      * Move file window, synchronizing data with file.
353      * Works somewhat like memory-mapping a file.
354      * This one was nightmare to write :-)
355      */
356     private void moveWindow(int start_offset) throws IOException {
357         if (start_offset != _window_low) { // only when we have to move
358 
359             int end_offset = start_offset + _window_size;
360             // new window low/high = start_offset/end_offset
361             int dirty_low = _file_high;
362             int dirty_high = _write_pos;
363             int dirty_len = _write_pos - _file_high;
364             if (dirty_len > 0) {   // we need to be concerned at all about dirty data.
365                 // will any part of dirty data be moved out of window?
366                 if ( (dirty_low < start_offset) || (dirty_high > end_offset) ) {
367                     // yes, dirty data need to be saved.
368                     writeToTempFile(dirty_low, _memory_buffer, dirty_low - _window_low, dirty_len);
369                 }
370             }
371             
372             // reposition any data from old window that will be also in new window:
373             
374             int stay_low = max(start_offset,_window_low);
375             int stay_high = min(_write_pos, _window_high, end_offset);
376             // is there anything to preserve?
377             int stay_size = stay_high - stay_low;
378             if (stay_size > 0) {
379                 System.arraycopy(_memory_buffer, stay_low-_window_low, _memory_buffer, stay_low-start_offset, stay_size);
380             }
381             
382             // read in available data that were not in old window:
383             if (stay_low > start_offset) {
384                 // read at the start of buffer
385                 int toread_low = start_offset;
386                 int toread_high = min(stay_low,end_offset);
387                 int toread_size = toread_high - toread_low;
388                 if (toread_size > 0) {
389                     readFromTempFile(toread_low, _memory_buffer, toread_low-start_offset, toread_size);
390                 }
391             }
392             if (stay_high < end_offset) {
393                 // read at end of buffer
394                 int toread_low = max(stay_high,start_offset);
395                 int toread_high = min(end_offset,_file_high);
396                 int toread_size = toread_high-toread_low;
397                 if (toread_size > 0) {
398                     readFromTempFile(toread_low, _memory_buffer, toread_low-start_offset, toread_size);
399                 }
400             }
401             _window_low = start_offset;
402             _window_high = end_offset;
403         }
404     }
405 
406     /** Simple minimum for 2 ints */    
407     private static int min(int a, int b) {
408         return (a<b?a:b);
409     }
410     
411     /** Simple maximum for 2 ints */    
412     private static int max(int a, int b) {
413         return (a>b?a:b);
414     }
415     
416     /** Simple minimum for 3 ints */    
417     private static int min(int a, int b, int c) {
418         int r = a;
419         if (r > b) r = b;
420         if (r > c) r = c;
421         return r;
422     }
423 
424     /**
425      * @return true when range 1 is fully contained in range 2
426      */
427     private static boolean contained(int range1_low, int range1_high, int range2_low, int range2_high) {
428         return ((range1_low >= range2_low)&&(range1_high <= range2_high));
429     }
430     
431     /**
432      * Internal implementation of java.io.OutputStream used to fill the byte buffer.
433      */
434     class OutputStream extends java.io.OutputStream {
435         
436         /**
437          * Write whole byte array into buffer.
438          * @param data byte[] to be written
439          * @throws IOException when something goes wrong.
440          */        
441         public void write(byte[] data) throws IOException {
442             write(data,0,data.length);
443         }
444 
445         /**
446          * Write segment of byte array to the buffer.
447          * @param data Byte array with data
448          * @param off Starting offset within the array.
449          * @param len Number of bytes to write
450          * @throws IOException when something goes wrong.
451          */        
452         public void write(byte[] data, int off, int len) throws IOException {
453             int new_write_pos = _write_pos + len;
454             boolean write_pos_in_window = (_write_pos >= _window_low)&&(_write_pos < _window_high);
455             
456             if (!write_pos_in_window) {
457                 // either current window is full of dirty data or it is somewhere low
458                 moveWindow(_write_pos);  // flush buffer if necessary, move window at end
459             }
460             
461             boolean end_of_data_in_window = (new_write_pos <= _window_high);
462             
463             if ( end_of_data_in_window ) {
464                 // if there is space in window for all data, just put it in buffer.
465                 // 0 writes, window unchanged
466                 System.arraycopy(data, off, _memory_buffer, _write_pos-_window_low, len);
467                 _write_pos = new_write_pos;
468             } else {
469                 int out_of_window = new_write_pos - _window_high;
470                 if (out_of_window < _window_size) {
471                     // start of data in window, rest will fit in a new window:
472                     // 1 write, window moved at window_high, filled with rest of data
473                     
474                     // fill in rest of the current window with first part of data
475                     int part1_len = _window_high - _write_pos;
476                     int part2_len = len - part1_len;
477                     
478                     System.arraycopy(data, off, _memory_buffer, _write_pos-_window_low, part1_len);
479                     _write_pos = _window_high;
480                     
481                     moveWindow(_write_pos);  // flush data to file
482                     
483                     System.arraycopy(data, off+part1_len, _memory_buffer, 0, part2_len);
484                     _write_pos = new_write_pos;
485                     
486                 } else {
487                     // start of data in window, rest will not fit in window (and leave some space):
488                     // 2 writes; window moved at end, empty
489                     
490                     int part1_size = _window_high - _write_pos;
491                     int part2_size = len - part1_size;
492                     
493                     if (part1_size == _window_size) {
494                         // buffer was empty - no sense in splitting the write
495                         // write data directly to file in one chunk
496                         writeToTempFile(_write_pos, data, off, len);
497                         _write_pos = new_write_pos;
498                         moveWindow(_write_pos);
499                         
500                     } else {
501                         // copy part 1 to window
502                         if (part1_size > 0) {
503                             System.arraycopy(data, off, _memory_buffer, _write_pos-_window_low, part1_size);
504                             _write_pos += part1_size;
505                             moveWindow(_write_pos);  // flush buffer
506                         }
507                         // flush window to file
508                         // write part 2 directly to file
509                         writeToTempFile(_write_pos, data, off+part1_size, part2_size);
510                         _write_pos = new_write_pos;
511                         moveWindow(_write_pos);
512                     }
513                 }
514             }
515         }
516         
517         /**
518          * Write single byte to the buffer.
519          * @param b
520          * @throws IOException
521          */        
522         public void write(int b) throws IOException {
523             if ((_write_pos >= _window_high) || (_write_pos < _window_low)) {
524                 moveWindow(_write_pos);
525             }
526             // we now have space for one byte in window.
527             _memory_buffer[_write_pos - _window_low] = (byte)(b &0xFF);
528             _write_pos++;
529         }
530         
531         public void flush() throws IOException {
532             moveWindow(_write_pos); // or no-op? not needed
533         }
534         
535         public void close() throws IOException {
536             // no-op: this output stream does not need to be closed.
537         }
538     }
539     
540     
541     
542     
543     
544     /**
545      * Internal implementation of InputStream used to read buffered data.
546      */    
547     class InputStream extends java.io.InputStream {
548         
549         public int read() throws IOException {
550             int ret = -1;
551             // if window does not contain read position, move it there
552             if (!contained(_read_pos,_read_pos+1, _window_low, _window_high)) {
553                 moveWindow(_read_pos);
554             }
555             if (_write_pos > _read_pos) {
556                 ret = (_memory_buffer[_read_pos - _window_low])&0xFF;
557                 _read_pos++;
558             }
559             return ret;
560         }
561         
562         public int read(byte[] buff) throws IOException {
563             return read(buff,0, buff.length);
564         }
565         
566         public int read(byte[] buff, int off, int len) throws IOException {
567             // clip read to available data:
568             int read_size = min(len,_write_pos-_read_pos);
569             if (read_size > 0) {
570                 if (read_size >= _window_size) {
571                     // big chunk: read directly from file
572                     moveWindow(_write_pos);
573                     readFromTempFile(_read_pos, buff, off, read_size);
574                 } else {
575                     // small chunk:
576                     int read_low = _read_pos;
577                     int read_high = read_low + read_size;
578                     // if we got all data in current window, read it from there
579                     if (!contained(read_low,read_high, _window_low, _window_high)) {
580                         moveWindow(_read_pos);
581                     }
582                     System.arraycopy(_memory_buffer, _read_pos - _window_low, buff, off, read_size);
583                 }
584                 _read_pos += read_size;
585             }
586             return read_size;
587         }
588         
589         public long skip(long bytes) throws IOException {
590             if (bytes < 0 || bytes > Integer.MAX_VALUE) throw new IllegalArgumentException();
591             int len = (int)bytes;
592             if ( (len+_read_pos) > _write_pos ) len = _write_pos - _read_pos;
593             _read_pos+=len;
594             moveWindow(_write_pos);  // invalidate window without reading data by moving it at the end
595             return (long)len;
596         }
597         
598         public int available() throws IOException {
599             return _write_pos - _read_pos;
600         }
601         
602         
603         public void mark(int readlimit) {
604             // readlimit is ignored, we store all the data anyway
605             _mark_pos = _read_pos;
606         }
607         
608         public void reset() throws IOException {
609             _read_pos = _mark_pos;
610         }
611         
612         public boolean markSupported() {
613             return true;
614         }
615         
616         
617     }
618 }