User interfaces for object-oriented systems, Part 5: Useful stuff

Build an application that puts user-interface principles into practice

1 2 3 4 5 Page 3
Page 3 of 5
Log log = new Log( "log.test", Std.err() );
log.write( "hello world\n" );
log.write( "xxxhello world\n", 3, 12 );
log.timestamp(); // Turn on per-write-call time stamping.
log.write( "timestamp now on\n" );
log.write( "xxxhello world\n", 3, 12 );
    Writer writer = log.writer();   // Get encapsulated Writer.
    writer.write("output directly to writer\n");
    writer.write(new char[]{'c','h','a','r',' ','o','u','t','\n'});
catch( IOException exception )      // Must catch exceptions when
{   exception.printStackTrace();    // talking directly to underlying
}                                   // writer.

The arguments to the Log constructor specify the name of the file to which you're logging and a stream to which error messages are sent (which can be Std.bit_bucket()) if you encounter any problems when opening that file. A second constructor (not shown) lets you pass in a Writer rather than a file name so that you can send logging messages to a previously opened stream.

The foregoing code creates the following log file:

#----Wed Dec 01 13:20:04 PST 1999----#
hello world
hello world
Wed Dec 01 13:20:04 PST 1999: timestamp now on
Wed Dec 01 13:20:04 PST 1999: hello world
Wed Dec 01 13:20:04 PST 1999: output directly to writer
char out

Note that the per-line timestamp is prefixed only to those calls that take string arguments.

The implementation of Log is in List 7. There are two main issues here:

  1. Thread safety (I expect a log to be accessed by multiple threads)
  2. Making sure that access to the underlying Writer object is both safe from an object-oriented point of view and thread safe with respect to the Log that contains the Writer

The log-level thread-safety issue is straightforward: just synchronize the methods of the Log class. The main problem is the Writer returned by the writer() method. For both practical and theoretical reasons, direct access to the encapsulated Writer should be impossible. Consequently, the writer() method returns an instance of the Writer_accessor inner class (List 7, line 188), which implements Writer with three twists: First, all the methods except close() just chain through to methods of the encapsulated Writer. The close() method (List 7, line 219) just throws an exception. You must close the Log that contains the Writer.

The second twist is that all the methods explicitly synchronize on the outer-class object. That way, one thread can safely write directly to the Log while another writes via the encapsulated Writer.

Third, a flush() is issued after every output operation in an attempt to make the log as up-to-date as possible if you're logging messages during a catastrophic failure.

1 2 3 4 5 Page 3
Page 3 of 5