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>© 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 }