Modify archives, Part 1

Supplement Java's util.zip package to make it easy to write or modify existing archives

1 2 3 4 Page 3
Page 3 of 4
  50:   public void write(int the_byte) throws IOException
  51:     {   
  52:         if( current >= buffer.length )
  53:             flush();    // resets current to 0
  54: 
  55:         D.ebug(   "\t\twrite(" + the_byte 
  56:                 + "): current=" + current 
  57:                 + ", buffer.length=" + buffer.length );
  58: 
  59:         buffer[current++] = (byte)the_byte;
  60:         ++bytes_written;
  61:     }
  62: 
  63:   public void write(byte[] bytes, int offset, int length)
  64:                                                 throws IOException
  65:     {   while( --length >= 0 )
  66:             write( bytes[offset++] );
  67:     }
  68: 
/******************************************************
Return the total number of bytes written to this stream.
*/
  69:   public int bytes_written(){ return bytes_written; }
  70: 
/******************************************************
Return the object wrapped by the FastBufferedOutputStream. (I don't consider this to be a violation of encapsulation because that object is passed into the Decorator, so is externally accessible anyway.) The internal buffer is flushed so it is safe to write directly to the "contents" object.
*/
  71:   public OutputStream contents() throws IOException
  72:     {   flush();
  73:         return out;
  74:     }
  75: 
/******************************************************
Return true if the buffer has been flushed to the underlying stream.
*/
  76:   public boolean has_flushed(){ return flushed; }
  77:     
/******************************************************
If the buffer has never been flushed to the wrapped stream, copy it to destination stream and return true (without sending the characters to the wrapped stream), otherwise return false; in any event, close the stream. @see #has_flushed
*/
  78:   public boolean export_buffer_and_close(OutputStream destination)
  79:                                                 throws IOException
  80:     {   
  81:         if( !flushed )
  82:         {   destination.write( buffer, 0, current );
  83:             current = 0;
  84:         }
  85:         close();
  86:         return !flushed;
  87:     }
  88: 
/******************************************************
If the buffer has never been flushed to the wrapped stream, return it; otherwise return null. In any event, close the stream; @see #has_flushed
*/
  89:   public byte[] export_buffer_and_close() throws IOException
  90:     {   byte[] buffer = null;
  91: 
  92:         if( !flushed )
  93:         {   buffer = this.buffer;
  94:             current = 0;
  95:         }
  96:         close();
  97:         return buffer ;
  98:     }
  99: 
/******************************************************
A test class.
*/
 100:   static public class Test
 101:   {   static public void main(String[] args) throws Exception
 102:         {   Tester  t = new Tester( args.length > 0, Std.out() );
 103:             try
 104:             {
 105:                 File f = File.createTempFile( "FastBufferedOutputStream", ".test");
 106:                 FastBufferedOutputStream out = new FastBufferedOutputStream
 107:                                                 ( new FileOutputStream(f), 10 );
 108: 
 109:                 for( char c = 'a'; c <= 'x'; ++c )
 110:                     out.write( (byte)c );
 111: 
 112:                 out.write( new byte[]{ (byte)'y', (byte)'z' } );
 113:                 out.close();
 114: 
 115:                 t.check("FastBufferedOutputStream.1.0", 'z'-'a'+1, out.bytes_written() );
 116: 
 117:                 t.verbose(Tester.OFF);
 118:                 char got;
 119:                 FileInputStream in = new FileInputStream(f);
 120:                 for( char c = 'a'; c <= 'z'; ++c )
 121:                     t.check("FastBufferedOutputStream.1.1", c,  in.read() );
 122:                 t.verbose(Tester.RESTORE);
 123:                 t.check("FastBufferedOutputStream.1.1", !t.errors_were_found(),
 124:                                                             "read/write test");
 125: 
 126:                 t.check("FastBufferedOutputStream.1.2", -1, in.read() );
 127:                 in.close();
 128: 
 129:                 if( !t.errors_were_found() )
 130:                     f.delete();
 131:                 else
 132:                     Std.out().println("Test file not deleted: f.getName()" );
 133: 
 134:                 //----------------------------------------------------------------
 135:                 File temp = new File("Fast.2.test");
 136: 
 137:                 t.check( "FastBufferedOutputStream.2.0", false, temp.exists() );
 138: 
 139:                 DelayedOutputStream stream  = new DelayedOutputStream(temp);
 140: 
 141:                 t.check( "FastBufferedOutputStream.2.1", false, temp.exists() );
 142: 
 143:                 out = new FastBufferedOutputStream( stream, 2 );
 144: 
 145:                 t.check( "FastBufferedOutputStream.2.2", false, temp.exists() );
 146: 
 147:                 out.write( (byte)'x' );
 148: 
 149:                 t.check( "FastBufferedOutputStream.2.3", false, temp.exists() );
 150: 
 151:                 out.write( (byte)'x' );
 152: 
 153:                 t.check( "FastBufferedOutputStream.2.4", false, temp.exists() );
 154: 
 155:                 out.write( (byte)'x' );
 156:                 t.check( "FastBufferedOutputStream.2.5", true, temp.exists() );
 157: 
 158:                 out.close();
 159:                 boolean deleted = temp.delete();
 160:                 t.check( "FastBufferedOutputStream.2.6", deleted,
 161:                                                         "Deleting temporary file");
 162: 
 163:                 //------------------------------------------------------
 164: 
 165:                 stream = new DelayedOutputStream("Fast",".3.test");
 166:                 out = new FastBufferedOutputStream( stream, 2 );
 167: 
 168:                 out.write( 'a' );
 169:                 out.write( 'b' );
 170: 
 171:                 byte[] buffer = out.export_buffer_and_close();
 172:                 t.check("FastBufferedOutputStream.3.1", buffer != null, "Expected non null");
 173:                 t.check("FastBufferedOutputStream.3.2", buffer[0]=='a' && buffer[1]=='b',
 174:                                                     "Expected \"ab\"");
 175: 
 176:                 t.check("FastBufferedOutputStream.3.3", stream.temporary_file()==null,
 177:                                                     "Expected no temporary-file reference");
 178: 
 179:                 //------------------------------------------------------
 180: 
 181:                 stream = new DelayedOutputStream("Fast",".4.test");
 182:                 out = new FastBufferedOutputStream( stream, 2 );
 183: 
 184:                 out.write( 'a' );
 185:                 out.write( 'b' );
 186: 
 187:                 ByteArrayOutputStream bytes = new ByteArrayOutputStream();
 188: 
 189:                 t.check("FastBufferedOutputStream.4.1",
                           true, out.export_buffer_and_close(bytes) );
 190:                 t.check("FastBufferedOutputStream.4.2", bytes.toString().equals("ab"),
 191:                                                     "Expected \"ab\"");
 192:                 t.check("FastBufferedOutputStream.4.3", stream.temporary_file()==null,
 193:                                                     "Expected no temporary-file reference");
 194: 
 195:                 //------------------------------------------------------
 196: 
 197:                 stream = new DelayedOutputStream("Fast",".5.test");
 198:                 out = new FastBufferedOutputStream( stream, 2 );
 199: 
 200:                 for( char c = 'a'; c <= 'z'; ++c )
 201:                     out.write( (byte)c );
 202:                 out.close();
 203: 
 204:                 buffer = out.export_buffer_and_close();
 205:                 t.check("FastBufferedOutputStream.5.1", buffer==null, "Expected null");
 206:                 t.check("FastBufferedOutputStream.5.2",
                           false, out.export_buffer_and_close(bytes) );
 207:                 t.check("FastBufferedOutputStream.5.3", stream.temporary_file()!=null,
 208:                                                     "Expected temporary-file reference");
 209: 
 210:                 in = new FileInputStream(stream.temporary_file());
 211:                 for( char c = 'a'; c <= 'z'; ++c )
 212:                     t.check("FastBufferedOutputStream.1.1", c,  in.read() );
 213:                 t.check("FastBufferedOutputStream.1.2", -1, in.read() );
 214:                 in.close();
 215: 
 216:                 String name = stream.temporary_file().getName();
 217:                 stream.delete_temporary();
 218:                 t.check("FastBufferedOutputStream.5.3", !(new File(name).exists()),
 219:                                                     "Temporary file destroyed" );
 220:             }
 221:             catch( Exception e )
 222:             {   t.check( "FastBufferedOutputStream.Abort", false,
 223:                                 "Terminated by Exception toss" );
 224:                 e.printStackTrace();
 225:             }
 226:             finally
 227:             {   t.exit();
 228:             }
 229:         }
 230:     }
 231: }

The DelayedOutputStream class

The main difficulty with the earlier example is that I don't want the temporary file to be created until the flush occurs. In other words, I want no disk I/O to be performed at all if the incoming dataset is small enough. I solve this problem with another

OutputStream variant called DelayedOutputStream (Listing 6). The DelayedOutputStream -- a real class, not a wrapper -- works just like FileOutputStream with one important exception: the file doesn't open until the first write() operation is performed. The constructors just cache the information needed to open the file, and the open_file() method (Listing 6, line 185), called from the various write overloads (starting on line 71), actually opens the file.

Replacing the temporary_file declared in the earlier example with:

FastBufferedOutputStream temporary_file
        = new FastBufferedOutputStream( new DelayedOutputStream("file.tmp") );

ensures that file.tmp will not be created if the buffer is never flushed to disk.

The only implementation problem relates to temporary files. In Java, you create a temporary file with a call to File.createTempFile("root", ".ext");, which creates a file named something like root123456.ext in the default temporary-file directory (typically specified in the TEMP or TMP environment variable). The generated number ensures that the file name is unique. The only problem with this method is that it actually does create the file, as compared to just creating the name, and the whole point of this exercise is to avoid unnecessary disk I/O. Consequently, something like:

new DelayedOutputStream( File.createTempFile("root", ".ext") );

won't do what I want, which is to create the file only when (or if) it's actually needed.

I've solved this problem by providing a few methods not found in OutputStream itself. The DelayedOutputStream(String,String) constructor (Listing 6, line 37) creates a uniquely named temporary file if necessary.

I've also provided two methods to make it easy to delete or rename this temporary file. The delete_temporary() method (Listing 6, line 139) deletes the temporary file if it exists, and the rename_temporary_to(...) method (Listing 6, line 158) renames it. Neither of these last two methods may be called if the stream hasn't been closed. (A java.lang.IllegalStateException object is thrown in this situation.)

1 2 3 4 Page 3
Page 3 of 4