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 4
Page 4 of 4
Listing 6. /src/com/holub/io/DelayedOutputStream.java
   1: package com.holub.io;
   2: import  java.io.*;
   3: 
   4: import com.holub.tools.Tester;  // for testing
   5: import com.holub.io.Std;        // for testing
   6: import com.holub.tools.Assert;  // for testing
   7: 
   8: // import com.holub.tools.debug.D;      // for testing
   9: import com.holub.tools.D;       // for testing
  10: 
  11: /*  A DelayedOutputStream works like a FileOutputStream, except
  12:  *  that the file is not opened until the first write occurs.
  13:  *  Note that, though you'd like this class to extend
  14:  *  FileOutputStream rather than OutputStream,
  15:  *  that approach won't work here because all of the former's
  16:  *  constructors actually open the file, and the whole point of
  17:  *  the current exercise is not to do that.
  18:  *
  19:  *  This class is not thread safe---two threads cannot safely
  20:  *  write simultaneously to the same DelayedOutputStream without
  21:  *  some sort of external synchronization.
  22:  */
  23: 
  24: public class DelayedOutputStream extends OutputStream
  25: {   
  26:   private String              file_name = null;
  27:   private String              extension = null;
  28:   private boolean             temporary = false;
  29:   private File                file      = null;
  30:   private FileDescriptor      descriptor= null;
  31:   private boolean             append    = false;
  32:   private boolean             closed    = false;
  33:   private FileOutputStream    out       = null;
  34: 
  35:     //======================================================
  36: 
/** 
Creates a temporary file on first write. The file name is the concatenation of the root, an arbitrary number, and the extension. @see #temporary_file
*/
  37:   public DelayedOutputStream( String root, String extension )
  38:     {   Assert.is_true( root != null && extension != null );
  39:         this.temporary = true;
  40:         this.file_name = root;
  41:         this.extension = extension;
  42:     }
  43: 
  44:   public DelayedOutputStream( String file_name )
  45:     {   Assert.is_true( file_name != null );
  46: 
  47:         this.file_name = file_name;
  48:     }
  49: 
  50:   public DelayedOutputStream( String file_name, boolean append )
  51:     {   Assert.is_true( file_name != null );
  52: 
  53:         this.file_name = file_name;
  54:         this.append    = append;
  55:     }
  56: 
  57:   public DelayedOutputStream( File file )
  58:     {   Assert.is_true( file != null );
  59: 
  60:         this.file = file;
  61:     }
  62: 
  63:   public DelayedOutputStream( FileDescriptor descriptor )
  64:     {   Assert.is_true( descriptor != null );
  65: 
  66:         this.descriptor = descriptor;
  67:     }
  68: 
  69:     //======================================================
  70: 
  71:   public void write(int the_byte) throws IOException
  72:     {   open_file();
  73:         out.write( the_byte );
  74:     }
  75: 
  76:   public void write(byte[] bytes, int offset, int length)
  77:                                                 throws IOException
  78:     {   Assert.is_true( bytes != null );
  79: 
  80:         open_file();
  81:         out.write(bytes, offset, length);
  82:     }
  83: 
  84:   public void write(byte[] bytes) throws IOException
  85:     {   open_file();
  86:         out.write(bytes, 0, bytes.length);
  87:     }
  88: 
/**
Close the stream. This method can be called even if the underlying file has not been opened (because nobody's written anything to it). It just silently does nothing in this case.
*/
  89:   public void close() throws IOException
  90:     {   
  91:         if( !closed )
  92:         {
  93:             closed = true;
  94: 
  95:             D.ebug("\t\tDelayedOutputStream closing "
  96:                   + ( file!=null      ? file.getPath() :
  97:                       file_name!=null ? file_name : "???"
  98:                     )
  99:                   );
 100: 
 101:             if( out != null )
 102:             {
 103:                 out.close();
 104:                 D.ebug("\t\t\tclose accomplished");
 105:             }
 106:             else D.ebug("\t\t\tno-op (file never opened)");
 107: 
 108:             // Null out all references *except* the "file"
 109:             // reference for temporary files. (which might be
 110:             // needed by a subsequent call to delete_temporary).
 111: 
 112:             file_name = null;
 113:             extension = null;
 114:             descriptor= null;
 115:             out       = null;
 116: 
 117:             if( !temporary )
 118:                 file = null;
 119:         }
 120:     }
 121: 
 122:   public void flush() throws IOException
 123:     {   open_file();
 124:         out.flush();
 125:     }
 126: 
 127:   public FileDescriptor getFD() throws IOException
 128:     {   if( out==null )
 129:             throw new IOException("No FD yet in DelayedOutputStream");
 130:         return out.getFD();
 131:     }
 132: 
/**
< Return a File object that represents the temporary file created by the DelayedOutputStream(String,String) constructor. @return the File reference or null if the temporary file hasn't been created (either because nobody's written to the current stream or because the current object doesn't represent a temporary file).
*/
 133:   public File temporary_file() throws IOException
 134:     {   if( temporary )
 135:             return file;
 136:         return null;
 137:     }
 138: 
/** 
If a temporary file has been created by a write operation, delete it, otherwise do nothing. @return true if the temporary file existed and was successfully deleted, false if it didn't exist or wasn't successfully deleted. @throws IllegalStateException if the file hasn't been closed.
*/
 139:   public boolean delete_temporary()
 140:     {   if( !closed )
 141:             throw new IllegalStateException(
 142:                           "Stream must be closed before underlying"
 143:                         + " File can be deleted");
 144: 
 145:         boolean it_exists = temporary && (file != null);
 146: 
 147:         if( it_exists )
 148:         {   D.ebug("\t\tDelayedOutputStream deleting " +file.getPath());
 149: 
 150:             if( !file.delete() )
 151:                 return false;
 152:         }
 153: 
 154:         return it_exists;
 155: 
 156:     }
 157: 
/**
If a temporary file has been created by a write operation, rename it, otherwise do nothing. If a file with the same name as the target exists, that file is deleted first. @return true if the temporary file existed and was successfully renamed, false if it didn't exist or the rename attempt failed. @throws IllegalStateException if the file hasn't been closed.
*/
 158:   public boolean rename_temporary_to( File new_name )
 159:     {   if( !closed )
 160:         {   throw new IllegalStateException(
 161:                           "Stream must be closed before underlying"
 162:                         + " File can be renamed");
 163:         }
 164:     
 165:         boolean it_exists = temporary && (file != null);
 166: 
 167:         if( it_exists )
 168:         {   D.ebug("\t\tDelayedOutputStream renaming "
 169:                     +   file.getPath()
 170:                     + " to "
 171:                     +   new_name.getPath()
 172:                   );
 173: 
 174:             if( new_name.exists() )
 175:                 if( !new_name.delete() )
 176:                     return false;
 177: 
 178:             if( !file.renameTo(new_name) )
 179:                 return false;
 180:         }
 181: 
 182:         return it_exists;
 183:     }
 184: 
/** Workhorse function called by write() variants before they
do any I/O in order to bring the file into existence.
*/
 185:   private void open_file() throws IOException
 186:     {   
 187:         if( closed )
 188:             throw new IOException(
 189:                         "Tried to access closed DelayedOutputStream");
 190:         if( out == null )
 191:         {
 192:             if( temporary )
 193:             {   file = File.createTempFile(file_name,extension);
 194:                 file_name = null;
 195:                 extension = null;
 196:             }
 197: 
 198:             if( file_name != null )
 199:             {   out = new FileOutputStream(file_name, append);
 200:                 D.ebug("\t\tDelayedOutputStream created " + file_name );
 201:             }
 202:             else if( file != null )
 203:             {   out = new FileOutputStream(file);
 204:                 D.ebug("\t\tDelayedOutputStream created " + file.getPath());
 205:             }
 206:             else if( descriptor != null )
 207:             {   out = new FileOutputStream(descriptor);
 208:                 D.ebug("\t\tDelayedOutputStream created file from fd "
 209:                                                     + descriptor.toString());
 210:             }
 211:             else
 212:                 Assert.failure(
 213:                     "DelayedOutputStream internal error: nothing to open");
 214:         }
 215:     }
 216: 
 217:   static public class Test
 218:   {   static public void main(String[] args)
 219:         {
 220:             // Note that the temporary-file creation stuff is tested
 221:             // in the FastBufferedOutputStream class's test method,
 222:             // so you should run that test too.
 223: 
 224:             Tester  t = new Tester( args.length > 0, Std.out() );
 225:             try
 226:             {   
 227:                 File f = File.createTempFile("DelayedOutputStream",".test");
 228:                 OutputStream out = new DelayedOutputStream(f);
 229: 
 230:                 for( char c = 'a'; c <= 'x'; ++c )
 231:                     out.write( (byte)c );
 232: 
 233:                 out.write( new byte[]{ (byte)'y', (byte)'z' } );
 234:                 out.close();
 235: 
 236:                 t.verbose(Tester.OFF);
 237:                 char got;
 238:                 FileInputStream in = new FileInputStream(f);
 239:                 for( char c = 'a'; c <= 'z'; ++c )
 240:                     t.check("DelayedOutputStream.1", c,  in.read() );
 241:                 t.verbose(Tester.RESTORE);
 242:                 t.check("DelayedOutputStream.1",
                           !t.errors_were_found(), "Read/Write test" );
 243: 
 244:                 t.check("DelayedOutputStream.2", -1, in.read() );
 245:                 in.close();
 246: 
 247:                 if( !t.errors_were_found() )
 248:                     f.delete();
 249:                 else
 250:                     Std.out().println("Test file not deleted: f.getPath()" );
 251:             }
 252:             catch( Exception e )
 253:             {   t.println("DelayedOutputStream.3:  Exception Toss");
 254:                 e.printStackTrace();
 255:             }
 256: 
 257:             t.exit();
 258:         }
 259:     }
 260: }

Conclusion

That's it for now. The four classes I presented here -- D, Tester, FastBufferedOutputStream, and BufferedOutputStream -- are useful in their own right. Indeed, the D class lets you insert disappearing debugging diagnostics into your code, while Tester eases the process of writing automated unit tests. The FastBufferedOutputStream can speed up your programs considerably (by eliminating the unnecessary synchronization of the BufferedOutputStream) and also give you the ability to read back the output without having to go to the disk. Finally, DelayedOutputStream lets you open a file that you may need to use, but not actually create the file unless you actually use it.

In Part 2, I'll put these classes to work with an Archive class that makes it easy to read, write, or modify an existing zip or jar file.

Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, and MSJ, among others) and is a contributing editor for JavaWorld. He has eight books to his credit, the latest of which covers the traps and pitfalls of Java threading. (Taming Java Threads [Apress, 2000]). He's been designing and building object-oriented software for longer than he cares to remember. After eight years as a C++ programmer, Allen abandoned C++ for Java in early 1996. He now looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now object-oriented design and Java) both on his own and for the University of California Berkeley Extension since 1982. Allen offers both public classes and in-house training in Java and object-oriented design topics. He also does object-oriented design consulting and contract Java programming. Get information, and contact Allen, via his Website (http://www.holub.com).

Learn more about this topic

Related:
1 2 3 4 Page 4
Page 4 of 4