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 2
Page 2 of 5
List 4. /src/com/holub/tools/Tester.java
   1: package com.holub.tools;
   2: 
   3: import com.holub.tools.debug.Assert;
   4: import java.io.*;
   5: 
   6: /****************************************************************
   7:  * A simple class to help in testing. Various check() methods
   8:  * are passed a test id, an expected value, and an actual value.
   9:  * The test prints appropriate messages and keeps track of the
  10:  * total error count for you.
  11:  * For example:
  12:  * 
  13:  * class Test
  14:  * {    public static void main(String[] args);
  15:  *      {
  16:  *          // Create a tester that sends output to standard error, which
  17:  *          // operates in verbose mode if there are any command-line
  18:  *          // arguments.
  19:  *
  20:  *          Tester t = new Tester( args.length > 0,
  21:  *                                      com.holub.tools.Std.out() );
  22:  *          //...
  23:  *
  24:  *          t.check("test.1", 0,     foo());        // check that foo() returns 0.
  25:  *          t.check("test.2", "abc", bar());        // check that bar() returns "abc".
  26:  *          t.check("test.3", true , cow());        // check that cow() returns true
  27:  *          t.check("test.4", true , dog()==null);  // check that dog() returns null
  28:  *
  29:  *          //...
  30:  *          System.exit( t.total_errors() );
  31:  *      }
  32:  *  }
  33:  * 
  34:  */
  35: 
  36: public class Tester
  37: {
  38:   private int               errors = 0;
  39:   private final boolean     verbose;
  40:   private final PrintWriter log;
  41: 
  42:     /**
  43:      * Create a tester that has the specified behavior and output stream:
  44:      *
  45:      * @param verbose   Print messages even if test succeeds. (Normally,
  46:      *                  only failures are indicated.)
  47:      * @param log       If not null, all output is sent here; otherwise
  48:      *                  output is sent to standard error.
  49:      */
  50:   public Tester( boolean verbose, PrintWriter log )
  51:     {   this.verbose = verbose;
  52:         this.log     = log;
  53:     }
  54:     /******************************************************************
  55:      * Check that the expected result of type String is equal to the
  56:      * actual result.
  57:      *
  58:      * @param test_id   String that uniquely identifies this test.
  59:      * @param expected  The expected result
  60:      * @param actual    The value returned from the function under test
  61:      * @return true if the expected and actual parameters matched.
  62:      */
  63: 
  64:   public boolean check( String test_id, String expected, String actual)
  65:     {
  66:         Assert.is_true( log != null    , "Tester.check(): log is null"      );
  67:         Assert.is_true( test_id != null, "Tester.check(): test_id is null"  );
  68: 
  69:         boolean okay = expected.equals( actual );
  70: 
  71:         if( !okay || verbose )
  72:         {   
  73:             log.println (  (okay ? "   okay " : "** FAIL ")
  74:                          + "("  + test_id + ")"
  75:                          + " expected: " + expected
  76:                          + " got     : " + actual
  77:                     );
  78:         }
  79:         return okay;
  80:     }
  81:     /******************************************************************
  82:      * Convenience method, compares a string against a StringBuffer.
  83:      */
  84:   public boolean check( String test_id, String expected, StringBuffer actual)
  85:     {   return check( test_id, expected, actual.toString());
  86:     }
  87:     /******************************************************************
  88:      * Convenience method, compares two doubles.
  89:      */
  90:   public boolean check( String test_id, double expected, double actual)
  91:     {   return check( test_id, "" + expected, "" + actual );
  92:     }
  93:     /******************************************************************
  94:      * Convenience method, compares two longs.
  95:      */
  96:   public boolean check( String test_id, long expected, long actual)
  97:     {   return check( test_id, "" + expected, "" + actual );
  98:     }
  99:     /******************************************************************
 100:      * Convenience method, compares two booleans.
 101:      */
 102:   public boolean check( String test_id, boolean expected, boolean actual)
 103:     {   return check( test_id, expected?"true":"false", actual?"true":"false" );
 104:     }
 105:     /******************************************************************
 106:      * Exit the program, using the total error count as the exit status.
 107:      */
 108:   public void exit()
 109:     {   System.exit( errors );
 110:     }
 111: }
         

The Bit Bucket

Moving on to I/O-related methods, we find that it is sometimes useful to send output to nowhere. Consider a method that takes as its argument a stream to which diagnostic messages are to be sent:

PrintWriter diagnostic_stream = new PrintWriter(System.err, true);
//...
do_something( diagnostic_stream );

Now let's also assume that you don't happen to want these diagnostics to be displayed at all in some context. You could pass in a null argument instead of an output stream, but you might have to test for null in several places in the method, and many methods might have to implement identical tests. A general solution seems in order, and I've provided one with the Bit_bucket class, which is a PrintWriter that simply discards any requested output. You can discard diagnostic output in the previous example like this:

do_something( new Bit_bucket() );

The implementation of Bit_bucket is in List 5. As you can see, it's pretty trivial: it just extends PrintWriter, overriding every method with an empty one.

Singletons and standard input and output

I discussed the Singleton pattern in the context of threads in the April Java Toolbox. A singleton is an object, only one instance of which will ever exist. Don't confuse a singleton with a similar entity: the Booch Utility, a class made up solely of static methods. The Assert class we just looked at is a utility, not a singleton.

Singletons are real objects manufactured at runtime using new. The creation might have to be deferred until runtime for various reasons. For instance:

  1. The class might be quite large and you might not want to load it unless you need it

  2. The class file might not exist in some environments (an NT version of a class might not exist on a Solaris box, for example)

  3. You might not have the information that you need to instantiate the object until runtime

Java's Toolkit class exemplifies all those possibilities.

Since you want to assure that only one instance of a singleton exists, you never create a singleton by calling new. Rather, you call a static method of the singleton that brings the one and only instance into existence the first time the method is called. For example, the first time it is called, Toolkit.getDefaultToolkit() creates (and returns) the actual Toolkit object, an instance of a derived class. Toolkit is an abstract class, so it can't be instantiated. The second call to getDefaultToolkit() just returns the instance created by the first call.

A Singleton class always has nothing but private constructors to assure that nobody can create an instance directly by using new.

The general (much simplified) form of the Singleton class is:

class Singleton
{   static Singleton the_instance;
   private Singleton(){}
    Singleton getInstance()
    {   if( the_instance == null )
            the_instance = new Singleton();
        return the_instance;
    }

This simple structure works only in single-threaded environments. Refer to the April Java Toolbox for a discussion of Singleton in a multithreaded situation. The April column also discusses the JDK_11_unloading_bug_fix class, used below. (This class fixes a bug in the 1.1 JVM, which was too aggressive about garbage-collecting classes.)

The singleton of current interest is the Std class (List 6, line 44), an evolved form of the April version. The main problem I'm solving is the need to continually wrap System.in, System.out, and System.err in the appropriate Reader or Writer object to use them safely in a Unicode world. For example, you should really use:

new PrintWriter(System.out, true).println("Hello World");

rather than:

System.out.println("Hello World")

Creating a PrintWriter every time you want to write to the console is a waste of both system resources and your own time, however. (As an aside, the real problem here is that in, out, and err are public fields. They shouldn't be. Had Sun made them methods that returned an object that implemented a well-defined interface, the implementation of the interface could have been changed without affecting your code at all.)

The Std class provides a solution by creating singletons that represent the wrapped stream. For example, the earlier code can be restated as:

Std.out().println("Hello World");

Each time that the out() method is called, it returns the PrintWriter that it created the first time out() was called.

The following methods are supported:

Std.in() Returns a BufferedReader that wraps a InputStreamReader that wraps System.in.
Std.out() Returns a PrintWriter that wraps a System.out.
Std.err() Returns a PrintWriter that wraps a System.err.
Std.bit_bucket() Returns a PrintWriter (a Bit_bucket, actually) that discards all output.

Each creates the indicated object the first time it's called and returns the same object on subsequent calls. The implementation is in

List 6

.

List 6. /src/com/holub/io/Std.java
   1: package com.holub.io;
   2: import java.io.*;
   3: import com.holub.asynch.JDK_11_unloading_bug_fix;
   4: import com.holub.io.Bit_bucket;
   5: 
   6: /** Convenience wrappers that takes care of the complexity of creating
   7:  *  Readers and Writers simply to access standard input and output.
   8:  *  For example, a call to
   9:  *  
  10:  *  Std.out().println("hello world");
  11:  *  
  12:  *  is identical in function to:
  13:  *  
  14:  *  new PrintWriter(System.out, true).println("hello world");
  15:  *  
  16:  *  and
  17:  *  
  18:  *  String line = Std.in().readLine();
  19:  *  
  20:  *  is identical in function to:
  21:  *  
  22:  *  String line;
  23:  *  try
  24:  *  {   line = new BufferedReader(new InputStreamReader(System.in)).readLine();
  25:  *  }
  26:  *  catch( Exception e )
  27:  *  {   throw new Error( e.getMessage() );
  28:  *  }
  29:  *  
  30:  *  Equivalent methods provide access to standard error
  31:  *  and a "bit bucket" that just absorbs output without printing it.
  32:  *
  33:  *  <p>All these methods create "singleton" objects. For example, the
  34:  *  same PrintWriter object that is created the first time
  35:  *  you call Std.out() is returned by all subsequent calls.
  36:  *  This way you don't incur the overhead of a new with
  37:  *  each I/O request.
  38:  *
  39:  *  @see com.holub.tools.P
  40:  *  @see com.holub.tools.R
  41:  *  @see com.holub.tools.E
  42:  */
  43: 
  44: public final class Std
  45: {
  46:   static{ new JDK_11_unloading_bug_fix(Std.class); }
  47: 
  48:   private static BufferedReader input;        //= null
  49:   private static PrintWriter    output;       //= null
  50:   private static PrintWriter    error;        //= null
  51:   private static PrintWriter    bit_bucket;   //= null
  52: 
  53:     /*******************************************************************
  54:      * A private constructor, prevents anyone from manufacturing an
  55:      * instance.
  56:      */
  57:   private Std(){}
  58: 
  59:     /*******************************************************************
  60:      * Get a BufferedReader that wraps System.in.
  61:      */
  62:   public static BufferedReader in()
  63:     {   if( input == null )
  64:             synchronized( Std.class )
  65:             {   if( input == null )
  66:                     try
  67:                     {   input = new BufferedReader(
  68:                                         new InputStreamReader(System.in));
  69:                     }
  70:                     catch( Exception e )
  71:                     {   throw new Error( e.getMessage() );
  72:                     }
  73:             }
  74:         return input;
  75:     }
  76: 
  77:     /*******************************************************************
  78:      * Get a PrintWriter that wraps System.out.
  79:      */
  80:   public static PrintWriter out()
  81:     {   if( output == null )
  82:             synchronized( Std.class )
  83:             {   if( output == null )
  84:                     output = new PrintWriter( System.out, true );
  85:             }
  86:         return output;
  87:     }
  88: 
  89:     /*******************************************************************
  90:      * Get a PrintWriter that wraps System.err.
  91:      */
  92: 
  93:   public static PrintWriter err()
  94:     {   if( error == null )
  95:             synchronized( Std.class )
  96:             {   if( error == null )
  97:                     error = new PrintWriter( System.err, true );
  98:             }
  99:         return error;
 100:     }
 101: 
 102:     /*******************************************************************
 103:      * Get an output stream that just discards the characters that are
 104:      * sent to it. This convenience class makes it easy to write methods
 105:      * that are passed a "Writer" to which error messages or status information
 106:      * is logged. You could log output to standard output like this:
 107:      *  
 108:      *  x.method( Std.out() );  // pass in the stream to which messages are logged
 109:      *  
 110:      *  but you could cause the logged messages to simply disappear
 111:      *  by calling:
 112:      *  
 113:      *  x.method( Std.bit_bucket() );   // discard normal logging messages
 114:      *  
 115:      */
 116: 
 117:   public static PrintWriter bit_bucket()
 118:     {   if( bit_bucket == null )
 119:             synchronized( Std.class )
 120:             {   if( bit_bucket == null )
 121:                     bit_bucket = new Bit_bucket();
 122:             }
 123:         return bit_bucket;
 124:     }
 125: 
 126:     /** A small test class, reads a line from standard input and
 127:      *  echoes it to standard output and standard error. Run it
 128:      *  with: java com.holub.tools.Std\$Test
 129:      *  (Don't type in the \ when using a Microsoft-style shell).
 130:      */
 131: 
 132:   static public class Test
 133:     {
 134:       static public void main( String[] args ) throws IOException
 135:         {   String s;
 136:             while( (s = Std.in().readLine()) != null )
 137:             {   Std.out().println( s );
 138:                 Std.err().println( s );
 139:                 Std.bit_bucket().println( s );
 140:             }
 141:         }
 142:     }
 143: }
         

Logging diagnostics

The next problem is the logging of diagnostic messages. The Log class differs from a normal output stream in a couple of useful ways. First, it encapsulates (not extends) the Writer that's used to create output. I've done this because it's such a nuisance to catch the IOException that's thrown from the Writer's write method. The Log's version of write() throws a Log.Failure on error, but this class is a RuntimeException, so it doesn't have to be caught explicitly. It's also handy to be able to extract the underlying PrintWriter.

For example, you might want to pass it to an object of the Tester class, discussed earlier. With that in mind, I've provided an accessor method. (I had to jump through hoops to make the accessor safe to use from an object-oriented perspective. I'll say more on this in a moment.)

You use the class like this:

1 2 3 4 5 Page 2
Page 2 of 5