001    package org.rakeshv.io;
002    
003    import java.io.BufferedWriter;
004    import java.io.File;
005    import java.io.FileWriter;
006    import java.io.IOException;
007    import java.io.Reader;
008    import java.io.Writer;
009    
010    /**
011     * A decorator class that logs all the data it reads from the underlying
012     * <code>Reader</code> to a specified <code>Writer</code>.
013     *
014     * <p>The following code shows a way of using this class.</p>
015     * <pre>
016     * import org.rakeshv.io.LoggingReader;
017     * import java.io.BufferedReader;
018     * import java.io.FileReader;
019     * 
020     * public class test
021     * {
022     *   public static void main( String[] args )
023     *   {
024     *     try
025     *     {
026     *       String logFile = "/tmp/log.txt";
027     *       BufferedReader reader = new BufferedReader(
028     *           new LoggingReader( new FileReader( "/tmp/test.java" ), 
029     *             logFile ) );
030     *       String line = "";
031     *       while ( ( line = reader.readLine() ) != null )
032     *       {
033     *       }
034     *       reader.close();
035     *     }
036     *     catch ( Throwable t )
037     *     {
038     *       t.printStackTrace();
039     *     }
040     *   }
041     * }
042     * </pre>
043     *
044     * @see LoggingInputStream
045     * <p>&copy; Copyright 2005, Rakesh Vidyadharan</p>
046     * @author Rakesh Vidyadharan 19<sup><small>th</small></sup> September 2005
047     *
048     * @version $Id: LoggingReader.java,v 1.2 2005/09/20 13:15:11 rakesh Exp $
049     */
050    public class LoggingReader extends Reader 
051    {
052      /**
053       * The <code>Reader</code> from which the data that is to be
054       * logged will be read.
055       */
056      private Reader reader;
057    
058      /**
059       * The writer to which the contents read from the reader are to be
060       * logged.
061       */
062      private Writer writer;
063    
064      /**
065       * A flag used to indicate that the {@link #writer} must be
066       * flushed prior to closing this reader.
067       */
068      private boolean flush = false;
069    
070      /**
071       * Create a new instance of the class that decorates the specified
072       * <code>Reader</code>, and the specified <code>Writer
073       * </code>
074       *
075       * @param reader The reader from which data will be read.  This
076       *   is the reader that will be decorated.
077       * @param writer The writer to which the data read is to be
078       *   logged.
079       */
080      public LoggingReader( Reader reader, Writer writer )
081      {
082        setReader( reader );
083        setWriter( writer );
084      }
085    
086      /**
087       * Create a new instance of the class that decorates the specified
088       * <code>Reader</code>, and the specified <code>fileName
089       * </code>
090       *
091       * @see FileUtilities#mkdirs
092       * @param reader The reader from which data will be read.  This
093       *   is the reader that will be decorated.
094       * @param fileName The fully qualified name of the file to which the
095       *   data is to be logged.
096       * @throws IOException If the file exists but is a directory rather 
097       *   than a regular file, does not exist but cannot be created, or 
098       *   cannot be opened for any other reason
099       */
100      public LoggingReader( Reader reader, String fileName )
101        throws IOException
102      {
103        setReader( reader );
104    
105        File file = new File( fileName );
106        FileUtilities.mkdirs( file );
107        BufferedWriter writer = new BufferedWriter(
108            new FileWriter( file ) );
109    
110        setFlush( true );
111        setWriter( writer );
112      }
113    
114      /**
115       * Create a new instance of the class that decorates the specified
116       * <code>Reader</code>, and the specified <code>file</code>
117       *
118       * @see FileUtilities#mkdirs
119       * @param reader The reader from which data will be read.  This
120       *   is the reader that will be decorated.
121       * @param file The file to which the data is to be logged.
122       * @throws IOException If the file exists but is a directory rather 
123       *   than a regular file, does not exist but cannot be created, or 
124       *   cannot be opened for any other reason
125       */
126      public LoggingReader( Reader reader, File file )
127        throws IOException
128      {
129        setReader( reader );
130    
131        FileUtilities.mkdirs( file );
132        BufferedWriter writer = new BufferedWriter(
133            new FileWriter( file ) );
134    
135        setFlush( true );
136        setWriter( writer );
137      }
138    
139      /**
140       * Read a single character. This method will block until a character 
141       * is available, an I/O error occurs, or the end of the stream is 
142       * reached.
143       *
144       * @return int - The character read, as an integer in the range 
145       *   <code>0</code> to <code>65535 (0x00-0xffff)</code>, or 
146       *   <code>-1</code> if the end of the stream has been reached
147       * @throws IOException If errors are encountered while reading the
148       *   byte of data.
149       */
150      public int read() throws IOException
151      {
152        int data = reader.read();
153        if ( data != -1 )
154        {
155          writer.write( data );
156        }
157    
158        return data;
159      }
160    
161      /**
162       * Read characters into an array. This method will block until some 
163       * input is available, an I/O error occurs, or the end of the stream 
164       * is reached.
165       *
166       * @see #read( char[], int, int )
167       * @param buffer The buffer into which the characters will be read.
168       * @return int - The number of characters read, or <code>-1</code> 
169       *   if the end of the stream has been reached
170       * @throws IOException If errors are encountered while reading the
171       *   data.
172       */
173      public int read( char[] buffer ) throws IOException
174      {
175        return read( buffer, 0, buffer.length );
176      }
177    
178      /**
179       * Read characters into a portion of an array. This method will block 
180       * until some input is available, an I/O error occurs, or the end of 
181       * the stream is reached.
182       *
183       * @param buffer The buffer into which the characters will be read.
184       * @param offset The start offset in array <code>buffer</code> at 
185       *   which the characters are written.
186       * @param length The maximum number of characters to read.
187       * @return int - The number of characters read, or <code>-1</code>
188       *   if the end of the stream has been reached
189       * @throws IOException If errors are encountered while reading the
190       *   data.
191       */
192      public int read( char[] buffer, int offset, int length ) 
193        throws IOException
194      {
195        int size = reader.read( buffer, offset, length );
196        if ( size != -1 )
197        {
198          writer.write( buffer, offset, size );
199        }
200    
201        return size;
202      }
203    
204      /**
205       * Skip characters. This method will block until some characters are 
206       * available, an I/O error occurs, or the end of the stream is 
207       * reached.
208       *
209       * @param number The number of characters to be skipped.
210       * @return long - The actual number of characters skipped.
211       * @throws IllegalArgumentException  If <code>number</code> is 
212       *   negative.
213       * @throws IOException If errors are encountered while skipping.
214       */
215      public long skip( long number ) 
216        throws IllegalArgumentException, IOException
217      {
218        return reader.skip( number );
219      }
220    
221      /**
222       * Tell whether this stream is ready to be read.
223       *
224       * @return boolean - <code>true</code> if the next {@link #read()} is 
225       *   guaranteed not to block for input, <code>false</code> otherwise. 
226       *   Note that returning <code>false</code> does not guarantee that 
227       *   the next read will block.
228       * @throws IOException If errors are encountered while interacting
229       *   with the input stream.
230       */
231      public boolean ready() throws IOException
232      {
233        return reader.ready();
234      }
235    
236      /**
237       * Tell whether this stream supports the {@link #mark} operation. 
238       *
239       * @return boolean - Returns <code>true</code> if and only if this 
240       *   stream supports the mark operation.
241       */
242      public boolean markSupported()
243      {
244        return reader.markSupported();
245      }
246    
247      /**
248       * Mark the present position in the stream. Subsequent calls to 
249       * {@link #reset} will attempt to reposition the stream to this 
250       * point. Not all character-input streams support the {@link #mark}
251       * operation.
252       *
253       * <p><b>Note:</b> Logging will remain true to the read sequence.
254       * In other words, marking and resetting the {@link #reader} will
255       * cause duplicate characters to be written to the {@link #writer}.
256       * </p>
257       *
258       * @param readlimit Limit on the number of characters that may be 
259       *   read while still preserving the mark. After reading this many 
260       *   characters, attempting to reset the stream may fail.
261       * @throws IOException If the stream does not support <code>mark()
262       *   </code>, or if some other I/O error occurs
263       */
264      public void mark( int readlimit ) throws IOException
265      {
266        reader.mark( readlimit );
267      }
268    
269      /**
270       * Reset the stream. If the stream has been marked, then attempt to 
271       * reposition it at the mark. If the stream has not been marked, then 
272       * attempt to reset it in some way appropriate to the particular 
273       * stream, for example by repositioning it to its starting point. Not 
274       * all character-input streams support the <code>reset()</code>
275       * operation, and some support <code>reset()</code> without 
276       * supporting <code>mark()</code>.
277       *
278       * @see #mark
279       * @throws IOException If the stream has not been marked, or if the 
280       *   mark has been invalidated, or if the stream does not support 
281       *   <code>reset()</code>, or if some other I/O error occurs
282       */
283      public void reset() throws IOException
284      {
285        reader.reset();
286      }
287    
288      /**
289       * Close the stream. Once a stream has been closed, further {@link
290       * #read()}, {@link #ready}, {@link #mark}, or {@link #reset} 
291       * invocations will throw an <code>IOException</code>. Closing a 
292       * previously-closed stream, however, has no effect.
293       * Closes the {@link #reader} that this stream decorates.  Also 
294       * closes the {@link #writer} to which the data is logged.  If the 
295       * {@link #flush} is <code>true</code>, then the {@link #writer} is 
296       * flushed prior to closing.
297       *
298       * @throws IOException If errors are encountered while interacting
299       *   with the input stream.
300       */
301      public void close() throws IOException
302      {
303        reader.close();
304    
305        if ( flush )
306        {
307          writer.flush();
308        }
309    
310        writer.close();
311      }
312    
313      /**
314       * Flushes the {@link #writer}.  Invoke this method if the
315       * <code>Writer</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>Writer</code> needs to
319       * be flushed.
320       */
321      public void flush() throws IOException
322      {
323        writer.flush();
324      }
325      
326      /**
327       * Returns {@link #reader}.
328       *
329       * @return Reader The value/reference of/to reader.
330       */
331      public final Reader getReader()
332      {
333        return reader;
334      }
335      
336      /**
337       * Set {@link #reader}.
338       *
339       * @param reader The value to set.
340       */
341      protected final void setReader( Reader reader )
342      {
343        this.reader = reader;
344      }
345      
346      /**
347       * Returns {@link #writer}.
348       *
349       * @return Writer The value/reference of/to writer.
350       */
351      public final Writer getWriter()
352      {
353        return writer;
354      }
355      
356      /**
357       * Set {@link #writer}.
358       *
359       * @param writer The value to set.
360       */
361      protected final void setWriter( Writer writer )
362      {
363        this.writer = writer;
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    }