001    package org.rakeshv.io;
002    
003    import java.io.BufferedOutputStream;
004    import java.io.File;
005    import java.io.FileOutputStream;
006    import java.io.FileNotFoundException;
007    import java.io.IOException;
008    import java.io.InputStream;
009    import java.io.OutputStream;
010    
011    /**
012     * A decorator class that logs all the data it reads from the underlying
013     * <code>InputStream</code> to a specified <code>OutputStream</code>.
014     *
015     * <p>The following code shows a way of using this class.</p>
016     * <pre>
017     * import org.rakeshv.io.LoggingInputStream;
018     * import java.io.BufferedInputStream;
019     * import java.io.FileInputStream;
020     * 
021     * public class test
022     * {
023     *   public static void main( String[] args )
024     *   {
025     *     try
026     *     {
027     *       String logFile = "/tmp/log.txt";
028     *       BufferedInputStream inputStream = new BufferedInputStream(
029     *           new FileInputStream( "/tmp/test.java" ) );
030     *       LoggingInputStream lis = new LoggingInputStream( inputStream,
031     *           logFile );
032     *       byte[] buffer = new byte[ 2048 ];
033     *       int length = 0;
034     *       while ( ( length = lis.read( buffer, 0, buffer.length ) ) != -1 )
035     *       {
036     *       }
037     *       lis.close();
038     *     }
039     *     catch ( Throwable t )
040     *     {
041     *       t.printStackTrace();
042     *     }
043     *   }
044     * }
045     * </pre>
046     *
047     * @see LoggingReader
048     * <p>&copy; Copyright 2005, Rakesh Vidyadharan</p>
049     * @author Rakesh Vidyadharan 19<sup><small>th</small></sup> September 2005
050     *
051     * @version $Id: LoggingInputStream.java,v 1.1 2005/09/20 11:59:15 rakesh Exp $
052     */
053    public class LoggingInputStream extends InputStream 
054    {
055      /**
056       * The <code>InputStream</code> from which the data that is to be
057       * logged will be read.
058       */
059      private InputStream inputStream;
060    
061      /**
062       * The stream to which the contents read from the stream are to be
063       * logged.
064       */
065      private OutputStream outputStream;
066    
067      /**
068       * A flag used to indicate that the {@link #outputStream} must be
069       * flushed prior to closing this input stream.
070       */
071      private boolean flush = false;
072    
073      /**
074       * Create a new instance of the class that decorates the specified
075       * <code>InputStream</code>, and the specified <code>OutputStream
076       * </code>
077       *
078       * @param inputStream The stream from which data will be read.  This
079       *   is the stream that will be decorated.
080       * @param outputStream The stream to which the data read is to be
081       *   logged.
082       */
083      public LoggingInputStream( InputStream inputStream,
084          OutputStream outputStream )
085      {
086        setInputStream( inputStream );
087        setOutputStream( outputStream );
088      }
089    
090      /**
091       * Create a new instance of the class that decorates the specified
092       * <code>InputStream</code>, and the specified <code>fileName
093       * </code>
094       *
095       * @see FileUtilities#mkdirs
096       * @param inputStream The stream from which data will be read.  This
097       *   is the stream that will be decorated.
098       * @param fileName The fully qualified name of the file to which the
099       *   data is to be logged.
100       * @throws FileNotFoundException If the specified file could not be
101       *   created or accessed.
102       */
103      public LoggingInputStream( InputStream inputStream, String fileName )
104        throws FileNotFoundException
105      {
106        setInputStream( inputStream );
107    
108        File file = new File( fileName );
109        FileUtilities.mkdirs( file );
110        BufferedOutputStream outputStream = new BufferedOutputStream(
111            new FileOutputStream( file ) );
112    
113        setFlush( true );
114        setOutputStream( outputStream );
115      }
116    
117      /**
118       * Create a new instance of the class that decorates the specified
119       * <code>InputStream</code>, and the specified <code>file</code>
120       *
121       * @see FileUtilities#mkdirs
122       * @param inputStream The stream from which data will be read.  This
123       *   is the stream that will be decorated.
124       * @param file The file to which the data is to be logged.
125       * @throws FileNotFoundException If the specified file could not be
126       *   created or accessed.
127       */
128      public LoggingInputStream( InputStream inputStream, File file )
129        throws FileNotFoundException
130      {
131        setInputStream( inputStream );
132    
133        FileUtilities.mkdirs( file );
134        BufferedOutputStream outputStream = new BufferedOutputStream(
135            new FileOutputStream( file ) );
136    
137        setFlush( true );
138        setOutputStream( outputStream );
139      }
140    
141      /**
142       * Reads the next byte of data from the input stream. The value byte 
143       * is returned as an int in the range <code>0</code> to <code>255
144       * </code>. If no byte is available because the end of the stream has 
145       * been reached, the value <code>-1</code> is returned. This method 
146       * blocks until input data is available, the end of the stream is 
147       * detected, or an exception is thrown.
148       *
149       * @return int - The next byte of data, or <code>-1</code> if the 
150       *   end of the stream is reached.
151       * @throws IOException If errors are encountered while reading the
152       *   byte of data.
153       */
154      public int read() throws IOException
155      {
156        int data = inputStream.read();
157        if ( data != -1 )
158        {
159          outputStream.write( data );
160        }
161    
162        return data;
163      }
164    
165      /**
166       * Reads some number of bytes from the input stream and stores them 
167       * into the buffer array <code>buffer</code>. The method behaves
168       * similar to the implementation in the decorated underlying {@link 
169       * #inputStream}.
170       *
171       * @see #read( byte[], int, int )
172       * @param buffer The buffer into which the data will be read.
173       * @return int - The total number of bytes read into the buffer, or 
174       *   <code>-1</code> is there is no more data because the end of the 
175       *   stream has been reached.
176       * @throws IOException If errors are encountered while reading the
177       *   data.
178       * @throws NullPointerException If the <code>buffer</code> is
179       *   <code>null</code>.
180       */
181      public int read( byte[] buffer ) 
182        throws IOException, NullPointerException
183      {
184        return read( buffer, 0, buffer.length );
185      }
186    
187      /**
188       * Reads up to <code>length</code> bytes of data from the input 
189       * stream into an array of bytes. An attempt is made to read as many 
190       * as <code>length</code> bytes, but a smaller number may be read, 
191       * possibly <code>zero</code>. The number of bytes actually read is 
192       * returned as an integer.  This method behaves similar to the
193       * implementation in the decorated underlying {@link #inputStream}.
194       *
195       * @param buffer The buffer into which the data will be read.
196       * @param offset The start offset in array <code>buffer</code> at 
197       *   which the data is written.
198       * @param length The maximum number of bytes to read.
199       * @return int - The total number of bytes read into the buffer, or 
200       *   <code>-1</code> is there is no more data because the end of the 
201       *   stream has been reached.
202       * @throws IOException If errors are encountered while reading the
203       *   data.
204       * @throws NullPointerException If the <code>buffer</code> is
205       *   <code>null</code>.
206       */
207      public int read( byte[] buffer, int offset, int length ) 
208        throws IOException, NullPointerException
209      {
210        int size = inputStream.read( buffer, offset, length );
211        if ( size != -1 )
212        {
213          outputStream.write( buffer, offset, size );
214        }
215    
216        return size;
217      }
218    
219      /**
220       * Skips over and discards <code>number</code> bytes of data from 
221       * this input stream.  Delegates to the decorated {@link
222       * #inputStream} implementation.
223       *
224       * @param number The number of bytes to be skipped.
225       * @return long - The actual number of bytes skipped.
226       * @throws IOException If errors are encountered while skipping.
227       */
228      public long skip( long number ) throws IOException
229      {
230        return inputStream.skip( number );
231      }
232    
233      /**
234       * Returns the number of bytes that can be read (or skipped over) 
235       * from this input stream without blocking by the next caller of a 
236       * method for this input stream. The next caller might be the same 
237       * thread or or another thread.
238       *
239       * @return int - The number of bytes that can be read from this 
240       *   input stream without blocking.
241       * @throws IOException If errors are encountered while interacting
242       *   with the input stream.
243       */
244      public int available() throws IOException
245      {
246        return inputStream.available();
247      }
248    
249      /**
250       * Closes this input stream and releases any system resources 
251       * associated with the stream.  Closes the {@link #inputStream} that
252       * this stream decorates.  Also closes the {@link #outputStream}
253       * to which the data is logged.  If the {@link #flush} is 
254       * <code>true</code>, then the {@link #outputStream} is flushed
255       * prior to closing.
256       *
257       * @throws IOException If errors are encountered while interacting
258       *   with the input stream.
259       */
260      public void close() throws IOException
261      {
262        inputStream.close();
263    
264        if ( flush )
265        {
266          outputStream.flush();
267        }
268    
269        outputStream.close();
270      }
271    
272      /**
273       * Marks the current position in this input stream. A subsequent call 
274       * to the reset method repositions this stream at the last marked 
275       * position so that subsequent reads re-read the same bytes.
276       *
277       * @param readlimit The maximum limit of bytes that can be read 
278       *   before the mark position becomes invalid.
279       */
280      public void mark( int readlimit )
281      {
282        inputStream.mark( readlimit );
283      }
284    
285      /**
286       * Repositions this stream to the position at the time the mark 
287       * method was last called on this input stream.
288       *
289       * @see #mark
290       * @throws IOException If this stream has not been marked or if the 
291       *   mark has been invalidated.
292       */
293      public void reset() throws IOException
294      {
295        inputStream.reset();
296      }
297    
298      /**
299       * Tests if this input stream supports the mark and reset methods. 
300       * Whether or not mark and reset are supported is an invariant 
301       * property of a particular {@link #inputStream} used to initialise
302       * this stream.
303       *
304       * @return boolean - Returns <code>true</code> if this stream 
305       *   instance supports the {@link #mark} and {@link #reset} methods; 
306       *   <code>false</code> otherwise.
307       */
308      public boolean markSupported()
309      {
310        return inputStream.markSupported();
311      }
312    
313      /**
314       * Flushes the {@link #outputStream}.  Invoke this method if the
315       * <code>OutputStream</code> you used to initialise this stream
316       * requires to be flushed prior to a <code>close</code> invocation.
317       * You <b>must</b> invoke this method prior to invoking the
318       * {@link #close} method if the <code>OutputStream</code> needs to
319       * be flushed.
320       */
321      public void flush() throws IOException
322      {
323        outputStream.flush();
324      }
325      
326      /**
327       * Returns {@link #inputStream}.
328       *
329       * @return InputStream The value/reference of/to inputStream.
330       */
331      public final InputStream getInputStream()
332      {
333        return inputStream;
334      }
335      
336      /**
337       * Set {@link #inputStream}.
338       *
339       * @param inputStream The value to set.
340       */
341      protected final void setInputStream( InputStream inputStream )
342      {
343        this.inputStream = inputStream;
344      }
345      
346      /**
347       * Returns {@link #outputStream}.
348       *
349       * @return OutputStream The value/reference of/to outputStream.
350       */
351      public final OutputStream getOutputStream()
352      {
353        return outputStream;
354      }
355      
356      /**
357       * Set {@link #outputStream}.
358       *
359       * @param outputStream The value to set.
360       */
361      protected final void setOutputStream( OutputStream outputStream )
362      {
363        this.outputStream = outputStream;
364      }
365      
366      /**
367       * Returns {@link #flush}.
368       *
369       * @return boolean The value/reference of/to flush.
370       */
371      public final boolean getFlush()
372      {
373        return flush;
374      }
375      
376      /**
377       * Set {@link #flush}.
378       *
379       * @param flush The value to set.
380       */
381      protected final void setFlush( boolean flush )
382      {
383        this.flush = flush;
384      }
385    }