Build an application that puts user-interface principles into practice
No theory is worth anything if it can’t be applied effectively, so this month and next, I’ll build a small but nontrivial application that demonstrates the object-oriented user-interface principles I’ve covered over the last few months: an RPN (Reverse Polish Notation) calculator that can work as either a tape calculator or a keypad calculator. (One of the main things I want to demonstrate is the flexibility of the UI; thus the two presentations.) Since this application is a real one, I need to do some groundwork before covering the calculator itself.
Consequently, this month’s Java Toolbox takes the name of the column literally and presents the set of generic tools we’ll need to build the calculator — little tools of general utility that should be of use for more than the current application. Specifically, I’ll cover the following:
Assert |
An implementation of an “assertion” that lets you implement pre- and postconditions. |
Tester |
A class that aids in putting together automated unit tests. |
Bit_bucket |
The Java equivalent of /dev/null, it throws away characters. |
Std |
A “singleton” wrapper around standard input and output that makes it easy to access these two streams. |
Log |
A convenient class for logging diagnostic messages. |
Align |
A utility containing various methods for aligning text. |
Scrollable_text_area |
A JTextArea that has scroll bars around it. |
Most of those classes are unrelated to one another, and none of them is significant enough to merit a article devoted exclusively to it, so lumping them all together in one place seems sensible. We’ve taken a quick look at a few of the tools in the past, but a review should prove helpful. Now, on to the tools.
TEXTBOX: TEXTBOX_HEAD: Build user interfaces for object-oriented systems: Read the whole series!
:END_TEXTBOX
Assertions
Bertrand Meyer, the author of the Eiffel language, came up with the notion of pre- and postconditions, commonly called assertions. Eiffel, described in Object-Oriented Software Construction (see Resources) supports those concepts directly within the syntax of the language. Java, unfortunately, provides no such support.
The basic idea of a precondition or postcondition is that every method expects the world (the object that it’s working on, the global state of the program, the arguments to the method, and so on) to be in a particular state when the method is called, and that every method will leave the world in a particular state. An assertion is a body of code that guarantees that those conditions hold and typically terminates the program with an exception toss if they do not. Assertions are typically implemented as debug-time tests and removed from the code entirely when the code goes into production. (The removal can be risky since you are modifying tested code, but on the other hand, the assertions can slow down the execution of a program noticeably.)
I’ve implemented assertions using the two classes defined in Lists 1 and 2. The first version, in the com.holub.tools
package, is made up of empty methods. Import this version into your code when you’ve finished debugging. The second version, in the com.holub.tools.debug
package (List 2), actually does something. Import this version when you’re still working on the code.
When you use the class like this:
Assert.is_true( condition );
the program terminates by throwing an Assert.Failed
object if the condition is not true. A second version lets you add a reasonable error message to the thrown object:
Assert.is_true( condition, "Error that caused the problem" );
I’ve also provided is_false()
variants as a programming convenience.
The main problem with my strategy of using an empty method to eliminate the overhead of the assertion is that the resulting efficiency is somewhat JVM dependent. Hotspot, for example, does everything right. It notices that the method is empty, so expands it inline, effectively removing the call from the .class
file. Hotspot even removes the argument to the assertion (effectively dead code). The last behavior can be important if you use string concatenation in an argument to is_true()
or its equivalent. You don’t want to go through the considerable overhead of building a string with a series of concatenations if the string isn’t used for anything. Earlier JVMs were not as good about this optimization: they would correctly eliminate the call itself but typically wouldn’t eliminate the argument evaluation.
Finally, note that method calls in assertions are risky. Hotspot will evidently inline them up to a point, but it won’t chase down the call graph indefinitely. Generally, method calls should appear in assertions only if they do nothing other than return constant values. If you want to test a method’s return value, call the method first, then test the resulting value in a subsequent assertion.
Of course, methods that have side effects, which modify the state of the program, should never be invoked from within an assertion because they’ll go away when you move from the debugging to the production version of the code.
Let’s look at List 1:
List 1. /src/com/holub/tools/Assert.java
1: package com.holub.tools;
2:
3: <a id="Assert" name="Assert">public class Assert</a>
4: {
5: <a id="Assert.is_true(boolean)" name="Assert.is_true(boolean)"> public final static void is_true( boolean expression )</a>
6: {}
7:
8: <a id="Assert.is_false(boolean)" name="Assert.is_false(boolean)"> public final static void is_false( boolean expression )</a>
9: {}
10:
11: <a id="Assert.is_true(boolean,String)" name="Assert.is_true(boolean,String)"> public final static void is_true( boolean expression, String message)</a>
12: {}
13:
14: <a id="Assert.is_false(boolean,String)" name="Assert.is_false(boolean,String)"> public final static void is_false( boolean expression, String message)</a>
15: {}
16:
17: <a id="Assert.Failed" name="Assert.Failed"> static public class Failed extends RuntimeException</a>
18: <a id="Assert.Failed.Failed()" name="Assert.Failed.Failed()"> { public Failed( ){ super("Assert Failed"); }</a>
19: <a id="Assert.Failed.Failed(String)" name="Assert.Failed.Failed(String)"> public Failed( String msg ){ super(msg); }</a>
20: }
21: }
|
|
Next, we take a look at List 2:
List 2. /src/com/holub/tools/debug/Assert.java
1: package com.holub.tools.debug;
2:
3: <a id="Assert" name="Assert">public class Assert</a>
4: {
5: <a id="Assert.is_true(boolean)" name="Assert.is_true(boolean)"> public final static void is_true( boolean expression )</a>
6: { if( !expression )
7: throw( new Assert.Failed() );
8: }
9:
10: <a id="Assert.is_false(boolean)" name="Assert.is_false(boolean)"> public final static void is_false( boolean expression )</a>
11: { if( expression )
12: throw( new Assert.Failed() );
13: }
14:
15: <a id="Assert.is_true(boolean,String)" name="Assert.is_true(boolean,String)"> public final static void is_true( boolean expression, String message)</a>
16: { if( !expression )
17: throw( new Assert.Failed(message) );
18: }
19:
20: <a id="Assert.is_false(boolean,String)" name="Assert.is_false(boolean,String)"> public final static void is_false( boolean expression, String message)</a>
21: { if( expression )
22: throw( new Assert.Failed(message) );
23: }
24:
25: <a id="Assert.Failed" name="Assert.Failed"> static public class Failed extends RuntimeException</a>
26: <a id="Assert.Failed.Failed()" name="Assert.Failed.Failed()"> { public Failed( ){ super("Assert Failed"); }</a>
27: <a id="Assert.Failed.Failed(String)" name="Assert.Failed.Failed(String)"> public Failed( String msg ){ super(msg); }</a>
28: }
29: }
|
|
Help in unit testing
The next helper class of interest is the Tester
, which makes it easier to do unit testing (the standalone testing of a single class).
Test
shows a typical use of this class. I typically put my unit tests in a Test
inner class of the class I’m testing. (I use an inner class so that the code that makes up the test won’t be part of the class file that represents the class I’m testing. Inner classes create their own class files, which in this case do not have to be shipped with the production code.)
I have several criteria for what constitutes proper test behavior. A formal test must:
- Not print anything unless something is wrong
- Have a unique ID
- Test reasonable boundary conditions and “impossible” inputs
The first item is particularly important because, in an automated testing environment, you don’t want to clutter up reports with worthless information. A test typically tries to find out when something goes wrong, so that’s the only time that output should be generated. On the other hand, I occasionally do get paranoid, so I’ve provided a hook that modifies the behavior of my class to print both success and failure messages.
The tester is initialized with a normal constructor call that has passed two arguments: first, a Boolean flag that indicates whether the output is verbose (success messages are printed in addition to failures) or not (only failure messages are printed); second, a PrintWriter
that represents the stream to which output is sent. Thereafter, you run a test by calling the check()
method, which has several overloads.
The arguments are:
- The test ID
- The expected output of a method call
- The method call to test
The overloads change the types of the second and third arguments to handle various possible return values. Supported return values are String
, StringBuffer
, double
(which will handle float
via the normal type conversion), long
(which will also handle char
, int
, and so on, via the normal type conversions), and boolean
. There is only one requirement: the second argument’s type must be the same as the return type of the method call in the third argument.
The final method of interest in the Tester
is the exit()
method, which causes the program to exist with the current error count as its exit status. This mechanism lets you write shell scripts that run tests and detect when something goes wrong.
List 3. Using the Tester class
1: <a id="Test" name="Test">private static class Test</a>
2: {
3: <a id="Test.main(String[])" name="Test.main(String[])"> public static void main(String[] args)</a>
4: {
5: com.holub.tools.Tester t =
6: new com.holub.tools.Tester( args.length > 0,
7: com.holub.io.Std.out());
8: //...
9: t.check("align.1", "01234", Align.left("01234", 5));
10: t.check("align.2", " ", Align.left("", 5));
11: t.check("align.3", "X ", Align.left("X", 5));
12: //...
13:
14: t.exit(); // exits with a status equal to the error count
15: }
16: }
|
|
The code, which is largely self-explanatory, is in List 4:
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: <a id="Tester" name="Tester">public class Tester</a>
37: {
38: <a id="Tester.errors" name="Tester.errors"> private int errors = 0;</a>
39: <a id="Tester.verbose" name="Tester.verbose"> private final boolean verbose;</a>
40: <a id="Tester.log" name="Tester.log"> private final PrintWriter log;</a>
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: <a id="Tester.Tester(boolean,PrintWriter)" name="Tester.Tester(boolean,PrintWriter)"> public Tester( boolean verbose, PrintWriter log )</a>
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: <a id="Tester.check(String,String,String)" name="Tester.check(String,String,String)"> public boolean check( String test_id, String expected, String actual)</a>
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: <a id="Tester.check(String,String,StringBuffer)" name="Tester.check(String,String,StringBuffer)"> public boolean check( String test_id, String expected, StringBuffer actual)</a>
85: { return check( test_id, expected, actual.toString());
86: }
87: /******************************************************************
88: * Convenience method, compares two doubles.
89: */
90: <a id="Tester.check(String,double,double)" name="Tester.check(String,double,double)"> public boolean check( String test_id, double expected, double actual)</a>
91: { return check( test_id, "" + expected, "" + actual );
92: }
93: /******************************************************************
94: * Convenience method, compares two longs.
95: */
96: <a id="Tester.check(String,long,long)" name="Tester.check(String,long,long)"> public boolean check( String test_id, long expected, long actual)</a>
97: { return check( test_id, "" + expected, "" + actual );
98: }
99: /******************************************************************
100: * Convenience method, compares two booleans.
101: */
102: <a id="Tester.check(String,boolean,boolean)" name="Tester.check(String,boolean,boolean)"> public boolean check( String test_id, boolean expected, boolean actual)</a>
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: <a id="Tester.exit()" name="Tester.exit()"> public void exit()</a>
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.
List 5. /src/com/holub/io/Bit_bucket.java
1: package com.holub.io;
2: import java.io.*;
3:
4: /**
5: * The Bit_bucket class overrides all methods of PrintWriter to
6: * do nothing.
7: */
8:
9: <a id="Bit_bucket" name="Bit_bucket">public final class Bit_bucket extends PrintWriter</a>
10: {
11: <a id="Bit_bucket.Bit_bucket()" name="Bit_bucket.Bit_bucket()"> public Bit_bucket(){ super( System.err ); }</a>
12:
13: <a id="Bit_bucket.close()" name="Bit_bucket.close()"> public void close() {}</a>
14: <a id="Bit_bucket.flush()" name="Bit_bucket.flush()"> public void flush() {}</a>
15: <a id="Bit_bucket.print(boolean)" name="Bit_bucket.print(boolean)"> public void print(boolean b) {}</a>
16: <a id="Bit_bucket.print(char)" name="Bit_bucket.print(char)"> public void print(char c) {}</a>
17: <a id="Bit_bucket.print(char[])" name="Bit_bucket.print(char[])"> public void print(char[] s) {}</a>
18: <a id="Bit_bucket.print(double)" name="Bit_bucket.print(double)"> public void print(double d) {}</a>
19: <a id="Bit_bucket.print(float)" name="Bit_bucket.print(float)"> public void print(float f) {}</a>
20: <a id="Bit_bucket.print(int)" name="Bit_bucket.print(int)"> public void print(int i) {}</a>
21: <a id="Bit_bucket.print(long)" name="Bit_bucket.print(long)"> public void print(long l) {}</a>
22: <a id="Bit_bucket.print(Object)" name="Bit_bucket.print(Object)"> public void print(Object o) {}</a>
23: <a id="Bit_bucket.print(String)" name="Bit_bucket.print(String)"> public void print(String s) {}</a>
24: <a id="Bit_bucket.println()" name="Bit_bucket.println()"> public void println() {}</a>
25: <a id="Bit_bucket.println(boolean)" name="Bit_bucket.println(boolean)"> public void println(boolean b) {}</a>
26: <a id="Bit_bucket.println(char)" name="Bit_bucket.println(char)"> public void println(char c) {}</a>
27: <a id="Bit_bucket.println(char[])" name="Bit_bucket.println(char[])"> public void println(char[] s) {}</a>
28: <a id="Bit_bucket.println(double)" name="Bit_bucket.println(double)"> public void println(double d) {}</a>
29: <a id="Bit_bucket.println(float)" name="Bit_bucket.println(float)"> public void println(float f) {}</a>
30: <a id="Bit_bucket.println(int)" name="Bit_bucket.println(int)"> public void println(int i) {}</a>
31: <a id="Bit_bucket.println(long)" name="Bit_bucket.println(long)"> public void println(long l) {}</a>
32: <a id="Bit_bucket.println(Object)" name="Bit_bucket.println(Object)"> public void println(Object o) {}</a>
33: <a id="Bit_bucket.write(char[])" name="Bit_bucket.write(char[])"> public void write(char[] buf) {}</a>
34: <a id="Bit_bucket.write(char[],int,int)" name="Bit_bucket.write(char[],int,int)"> public void write(char[] buf, int off, int len) {}</a>
35: <a id="Bit_bucket.write(int)" name="Bit_bucket.write(int)"> public void write(int c) {}</a>
36: <a id="Bit_bucket.write(String)" name="Bit_bucket.write(String)"> public void write(String buf) {}</a>
37: <a id="Bit_bucket.write(String,int,int)" name="Bit_bucket.write(String,int,int)"> public void write(String buf, int off, int len) {}</a>
38: }
|
|
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:
-
The class might be quite large and you might not want to load it unless you need it
-
The class file might not exist in some environments (an NT version of a class might not exist on a Solaris box, for example)
- 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:
<a id="Singleton" name="Singleton">class Singleton</a>
{ static Singleton the_instance;
<a id="Singleton.Singleton()" name="Singleton.Singleton()"> private Singleton(){}</a>
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: * 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: <a id="Std" name="Std">public final class Std</a>
45: {
46: <a id="Std.java.bug_fix" name="Std.java.bug_fix"> static{ new JDK_11_unloading_bug_fix(Std.class); }</a>
47:
48: <a id="Std.input" name="Std.input"> private static BufferedReader input; //= null</a>
49: <a id="Std.output" name="Std.output"> private static PrintWriter output; //= null</a>
50: <a id="Std.error" name="Std.error"> private static PrintWriter error; //= null</a>
51: <a id="Std.bit_bucket" name="Std.bit_bucket"> private static PrintWriter bit_bucket; //= null</a>
52:
53: /*******************************************************************
54: * A private constructor, prevents anyone from manufacturing an
55: * instance.
56: */
57: <a id="Std.Std()" name="Std.Std()"> private Std(){}</a>
58:
59: /*******************************************************************
60: * Get a BufferedReader that wraps System.in.
61: */
62: <a id="Std.in()" name="Std.in()"> public static BufferedReader in()</a>
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: <a id="Std.out()" name="Std.out()"> public static PrintWriter out()</a>
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: <a id="Std.err()" name="Std.err()"> public static PrintWriter err()</a>
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: <a id="Std.bit_bucket()" name="Std.bit_bucket()"> public static PrintWriter bit_bucket()</a>
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: <a id="Std.Test" name="Std.Test"> static public class Test</a>
133: {
134: <a id="Std.Test.main(String[])" name="Std.Test.main(String[])"> static public void main( String[] args ) throws IOException</a>
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:
Log log = new Log( "log.test", Std.err() );
log.write( "hello worldn" );
log.write( "xxxhello worldn", 3, 12 );
log.timestamp(); // Turn on per-write-call time stamping.
log.write( "timestamp now onn" );
log.write( "xxxhello worldn", 3, 12 );
try
{
Writer writer = log.writer(); // Get encapsulated Writer.
writer.write("output directly to writern");
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:
- Thread safety (I expect a log to be accessed by multiple threads)
- 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.
List 7. /src/com/holub/io/Log.java
1: package com.holub.io;
2:
3: import java.io.*;
4: import java.util.*;
5: import com.holub.io.Std;
6: import com.holub.tools.debug.Assert;
7: // Import com.holub.tools.Assert;
8:
9: /** Encapsulates I/O to a log file. Contains simple wrappers to do
10: * line-level buffering and suck up exceptions that are not errors
11: * in the current context. The Log class encapsulates a Writer
12: * rather than extending it in order to provide more reasonable
13: * exception handling. A Log.Failure object (which is a
14: * RuntimeException) is thrown when something goes wrong
15: * rather than an IOException. This way you don't have to
16: * litter up your code with try blocks. The {@link #writer} method is
17: * provided to allow access to the underlying Writer should
18: * you need it, however.
19: *
20: * This class is thread safe.
21: **/
22:
23: <a id="Log" name="Log">public class Log</a>
24: {
25: <a id="Log.log_file" name="Log.log_file"> private Writer log_file = null;</a>
26: <a id="Log.log_file_name" name="Log.log_file_name"> private static /*final*/ String log_file_name = "log";</a>
27: <a id="Log.timestamp" name="Log.timestamp"> private boolean timestamp = false;</a>
28:
29: <a id="Log.Failure" name="Log.Failure"> public class Failure extends RuntimeException</a>
30: <a id="Log.Failure.Failure(String)" name="Log.Failure.Failure(String)"> { public Failure(String s){super(s);}</a>
31: }
32:
33: /*******************************************************************
34: * Create a new Log with the specified name. If a file with that
35: * name exists, then new lines are just appended to it. The current
36: * time and date are written into the file as well.
37: *
38: * An error message is printed if any exceptions are thrown from
39: * the I/O system, but the program is permitted to run. (In this
40: * case, any subsequent attempts to write to the log are silently
41: * ignored.)
42: *
43: * @param name file to which log messages are sent. If null,
44: * log messages go to standard error.
45: * @param error_message_stream. Any error messages that indicate
46: * problems opening the file are sent here.
47: * Use Std.bit_bucket() to suppress error messages.
48: */
49:
50: <a id="Log.Log(String,PrintWriter)" name="Log.Log(String,PrintWriter)"> public Log( String name, PrintWriter error_message_stream )</a>
51: { Assert.is_true( name != null );
52: Assert.is_true( error_message_stream != null );
53:
54: log_file_name = name;
55: try
56: {
57: error_message_stream.println("Logging to " + name);
58:
59: log_file = ( name == null )
60: ? (Writer)( Std.err() )
61: : (Writer)( new BufferedWriter(
62: new FileWriter(log_file_name, true /*append*/)) )
63: ;
64:
65: write( "n#----" + new Date().toString() + "----#n" );
66: }
67: catch( IOException e )
68: {
69: error_message_stream.println("Couldn't open log file: "
70: + log_file_name
71: + "(" + e.getMessage() + ")" );
72:
73: error_message_stream.println("Input will not be loggedn");
74: }
75: }
76:
77: /*******************************************************************
78: * Writes to a previously opened log file.
79: */
80:
81: <a id="Log.Log(Writer)" name="Log.Log(Writer)"> public Log( Writer log_file )</a>
82: { Assert.is_true( log_file != null );
83: this.log_file = log_file;
84: this.log_file_name = "???";
85: write( "n#----" + new Date().toString() + "----#n" );
86: }
87:
88: /*******************************************************************
89: * Convenience method. Creates a log called "log" in the directory
90: * specified by the log.file system property. If the log.file
91: * System property (which must be specified with the -D command-line
92: * switch) can't be found or if the file can't be opened, then log output
93: * is silently discarded.
94: */
95:
96: <a id="Log.Log()" name="Log.Log()"> public Log()</a>
97: { this( System.getProperty("log.file"), Std.err() );
98: }
99:
100: /*******************************************************************
101: * All messages logged after this call is made will be prefixed by
102: * the current date and time. (This stamp is in addition to the one
103: * that's output at the top of the file, which effectively indicates
104: * when the log file was opened.)
105: */
106:
107: <a id="Log.timestamp()" name="Log.timestamp()"> public void timestamp()</a>
108: { timestamp = true;
109: }
110:
111: /*******************************************************************
112: * Append a string of the indicated length, starting at the indicated
113: * offset, to the log file. All internal buffers are flushed
114: * after writing so that the log file itself will be up-to-date.
115: * An error message is printed if any exceptions are thrown from
116: * the I/O system.
117: *
118: * A Failure is thrown if any exceptions are thrown from any internal
119: * Writer calls.
120: */
121:
122: <a id="Log.write(String,int,int)" name="Log.write(String,int,int)"> public void write( String text, int offset, int length )</a>
123: { Assert.is_true( text != null );
124: Assert.is_true( offset >= 0 );
125: Assert.is_true( 0 <= length && length <= text.length() );
126:
127: if( log_file == null ) return; // do nothing if create failed
128:
129: try
130: { if( timestamp )
131: log_file.write( new Date().toString() + ": " );
132:
133: log_file.write( text, offset, length );
134: log_file.flush();
135: }
136: catch( IOException e )
137: { throw new Failure( log_file_name + ": " +
138: e.getMessage() );
139: }
140: }
141: /*******************************************************************
142: * Convenience method. Logs the entire string.
143: */
144: <a id="Log.write(String)" name="Log.write(String)"> public synchronized void write( String text )</a>
145: { this.write( text, 0, text.length() );
146: }
147:
148: /*******************************************************************
149: * Close the log file.
150: * A Failure is thrown if any exceptions are thrown from
151: * the I/O system.
152: */
153: <a id="Log.close()" name="Log.close()"> public synchronized void close( )</a>
154: { if( log_file == null ) return; // do nothing if create failed
155:
156: try
157: { log_file.close();
158: }
159: catch( IOException e )
160: { throw new Failure( log_file_name + ": " +
161: e.getMessage() );
162: }
163: }
164:
165: /********************************************************************
166: * Objects of the Log.Writer_accessor are returned from the {@link #writer}
167: * method. They implement the Writer interface and chain most operations
168: * through to the underlying writer. They provide synchronized access to the
169: * Writer as well. (That is, multiple threads can safely and simultaneously
170: * access both the Log object itself and also any
171: * Writers returned from various writer()
172: * calls on that Log object). The only surprise is the
173: * {@link #close} method, which throws a
174: * java.lang.UnsupportedOperationException. You must close the
175: * encapsulating Log object, not the object returned from {@link #writer}.
176: *
177: * Note that:
178: *
179: * (1) The log file is flushed after every write operation (including
180: * single-character writes) to make sure that it's as up-to-date as possible.
181: * (2) If the timestamp() method of the encapsulating Log
182: * has been called, output sent to the Log using either String
183: * version of write() is time stamped, but output sent using
184: * the character versions of write() are not time stamped.
185: *
186: */
187:
188: <a id="Log.Writer_accessor" name="Log.Writer_accessor"> private class Writer_accessor extends Writer</a>
189: {
190: <a id="Log.Writer_accessor.write(int)" name="Log.Writer_accessor.write(int)"> public void write( int c ) throws IOException</a>
191: { synchronized(Log.this)
192: { log_file.write(c);
193: log_file.flush();
194: }
195: }
196: <a id="Log.Writer_accessor.write(char[])" name="Log.Writer_accessor.write(char[])"> public void write( char[] cbuf ) throws IOException</a>
197: { synchronized(Log.this)
198: { log_file.write(cbuf);
199: log_file.flush();
200: }
201: }
202: <a id="Log.Writer_accessor.write(char[],int,int)" name="Log.Writer_accessor.write(char[],int,int)"> public void write( char[] cbuf, int off, int len ) throws IOException</a>
203: { synchronized(Log.this)
204: { log_file.write(cbuf,off,len);
205: log_file.flush();
206: }
207: }
208: <a id="Log.Writer_accessor.write(String)" name="Log.Writer_accessor.write(String)"> public void write( String str ) throws IOException</a>
209: { Log.this.write(str);
210: }
211: <a id="Log.Writer_accessor.write(String,int,int)" name="Log.Writer_accessor.write(String,int,int)"> public void write( String str, int off, int len ) throws IOException</a>
212: { Log.this.write(str,off,len);
213: }
214: <a id="Log.Writer_accessor.flush()" name="Log.Writer_accessor.flush()"> public void flush() throws IOException</a>
215: { synchronized(Log.this)
216: { log_file.flush();
217: }
218: }
219: <a id="Log.Writer_accessor.close()" name="Log.Writer_accessor.close()"> public void close()</a>
220: { throw new java.lang.UnsupportedOperationException
221: ("Must close encasulating Log object");
222: }
223: }
224:
225: /** Return a Writer that writes to the current log. This writer is a
226: * singleton in that, for a given Log object, only one
227: * Writer is created. All calls to writer() return
228: * references to the same object.
229: */
230:
231: <a id="Log.writer()" name="Log.writer()"> public Writer writer()</a>
232: { if( the_writer == null )
233: { synchronized( this )
234: { if( the_writer == null )
235: the_writer = new Writer_accessor();
236: }
237: }
238: return the_writer;
239: }
240:
241: <a id="Log.the_writer" name="Log.the_writer"> private static Writer_accessor the_writer;</a>
242:
243:
244: /**********************************************************************
245: * A Unit test class.
246: */
247:
248: <a id="Log.Test" name="Log.Test"> public static class Test</a>
249: <a id="Log.Test.main(String[])" name="Log.Test.main(String[])"> { public static void main( String[] args )</a>
250: {
251: Log log = new Log( "log.test", Std.err() );
252:
253: log.write( "hello worldn" );
254: log.write( "xxxhello worldn", 3, 12 );
255:
256: log.timestamp();
257:
258: log.write( "timestamp now onn" );
259: log.write( "xxxhello worldn", 3, 12 );
260:
261: try
262: {
263: Writer writer = log.writer();
264: writer.write( "output directly to writern" );
265: writer.write( new char[]{ 'c', 'h', 'a', 'r', ' ', 'o', 'u', 't', 'n'} );
266: }
267: catch( IOException exception )
268: { exception.printStackTrace();
269: }
270: }
271: }
272: }
|
|
String alignment
String manipulation is one of Java’s major weaknesses, both because it’s so inefficient and because much of the string-manipulation methods needed by real programs are simply missing. If you’re doing hard-core string work, Daniel F. Savarese has generously made his Perl-style regular-expression package available (see Resources), but often a full-blown regular-expression system is overkill. I’ve created a package of small string-manipulation utilities that might be the subject of a future column, but the one I need for the current calculator application is the Align
utility, whose methods align strings within columns.
For example, Align.right( "123", 10 )
returns a 10-character string with "123"
right-aligned within it. (The first seven characters of the string are space characters.) The Align
utility also supports the Align.left( "123", 10 )
and Align.center( "123", 10 )
methods, which do the expected.
One additional alignment method — Align.align( "123.45", 10, 5, '.', ' ' );
— outputs a 10-character string and places the period in “123.45” in column 5. Align
uses spaces (specified in the last argument) as fill characters.
The following code:
Std.out().println( "[0123456789]" );
Std.out().println( "[" + Align.align("123.45", 10, 5, '.', ' ') + "]");
Std.out().println( "[" + Align.align("1.2", 10, 5, '.', ' ') + "]");
Std.out().println( "[" + Align.align("12.345", 10, 5, '.', ' ') + "]");
prints:
[0123456789]
[ 123.45 ]
[ 1.2 ]
[ 12.345 ]
The periods are aligned vertically.
The code is in List 8.
List 8. /src/com/holub/string/Align.java
1: package com.holub.string;
2:
3: <a id="Align" name="Align">public class Align</a>
4: {
5: /*****************************************************************
6: * Aligns the text so that the first instance of the align_on character
7: * is positioned at align_column, with the pad_character added
8: * to both the left and right of the string to make it work.
9: * If the align_on character isn't found in the input string, then
10: * the output is right adjusted at column alignment_column - 1.
11: * For example:
12: *
13: * align_column
14: * |
15: * V
16: * 01234
17: * align( "1.2" , 5, 2, '.', '_'); // returns _1.2_
18: * align( ".23" , 5, 2, '.', '_'); // returns __.23
19: * align( "12.3" , 5, 2, '.', '_'); // returns 12.3_
20: * align( "2.34" , 5, 2, '.', '_'); // returns _2.34
21: * align( "12.34", 5, 2, '.', '_'); // returns 12.34
22: * align( "1", 5, 2, '.', '_'); // returns _1___
23: *
24: *
25: * @param input The String to align.
26: * @param column_width The width of the output string.
27: * @param align_column The column at which the align_on character
28: * is to be positioned.
29: * @param align_on The leftmost instance of this character in
30: * the input string is aligned at this column.
31: * If Align.LAST, then the rightmost character in the
32: * string is used. If Align.FIRST, then the leftmost
33: * character in the string is used.
34: * @param pad_character The character to use for padding.
35: * @return The input string, with left and right padding added as needed.
36: */
37:
38: <a id="Align.FIRST" name="Align.FIRST"> static public int FIRST = 0;</a>
39: <a id="Align.LAST" name="Align.LAST"> static public int LAST = -1;</a>
40:
41: <a id="Align.align" name="Align.align"> static public String align( String input, int column_width</a>
42: , int align_column
43: , int align_on
44: , char pad_character )
45: {
46: int align_character_at = (align_on == FIRST) ? 0 :
47: (align_on == LAST ) ? input.length()-1 :
48: input.indexOf(align_on);
49: int left_padding = align_character_at >= 0
50: ? Math.max( 0, align_column - align_character_at )
51: : Math.max( 0, align_column - input.length() )
52: ;
53:
54: int right_padding = column_width - left_padding - input.length();
55:
56: return do_alignment( left_padding, input, right_padding, pad_character );
57: }
58:
59: /**********************************************************************
60: * Build the output string with the required padding.
61: */
62: <a id="Align.do_alignment" name="Align.do_alignment"> static private String do_alignment(int left_padding, String input,</a>
63: int right_padding, char pad_character)
64: {
65: StringBuffer work = new StringBuffer();
66:
67: while( --left_padding >= 0 )
68: work.append( pad_character );
69:
70: work.append( input );
71:
72: while( --right_padding >= 0 )
73: work.append( pad_character );
74:
75: return work.toString();
76: }
77:
78: /**********************************************************************
79: * Convenience method -- pads with spaces, left-adjusting the text.
80: */
81: <a id="Align.left(String,int)" name="Align.left(String,int)"> static public String left( String input, int column_width )</a>
82: { return align( input, column_width, 0, FIRST, ' ' );
83: }
84:
85: /**
86: * Convenience method -- pads with spaces, right-adjusting the text.
87: */
88: <a id="Align.right(String,int)" name="Align.right(String,int)"> static public String right( String input, int column_width )</a>
89: { return align( input, column_width, column_width-1, LAST, ' ' );
90: }
91:
92: /**
93: * Pads the input string with the pad_character so that it
94: * is column_width characters wide and centered in the column.
95: * If it can't be centered exactly, the extra padding is placed
96: * on the left. For example, if the input string is "abc" and
97: * you center it in a 6-character-wide column with '.' as the
98: * pad_character, then the output string is "..abc.".
99: */
100: <a id="Align.center(String,int,char)" name="Align.center(String,int,char)"> static public String center( String input, int column_width, char pad_character )</a>
101: { int need = column_width - input.length();
102: return ( need > 0 )
103: ? do_alignment( need - (need/2), input, need/2, pad_character )
104: : input
105: ;
106: }
107:
108: /** Convenience method, pads with spaces.
109: */
110:
111: <a id="Align.center(String,int)" name="Align.center(String,int)"> static public String center( String input, int column_width )</a>
112: { return center( input, column_width, ' ' );
113: }
114:
115:
116: <a id="Align.Test" name="Align.Test"> private static class Test</a>
117: {
118: <a id="Align.Test.main(String[])" name="Align.Test.main(String[])"> public static void main(String[] args)</a>
119: {
120: com.holub.tools.Tester t =
121: new com.holub.tools.Tester( args.length > 0,
122: com.holub.io.Std.out() );
123:
124: t.check( "align.1", "+1.2+", align( "1.2" , 5, 2, '.', '+') );
125: t.check( "align.2", "++.23", align( ".23" , 5, 2, '.', '+') );
126: t.check( "align.3", "12.3+", align( "12.3" , 5, 2, '.', '+') );
127: t.check( "align.4", "+2.34", align( "2.34" , 5, 2, '.', '+') );
128: t.check( "align.5", "12.34", align( "12.34", 5, 2, '.', '+') );
129: t.check( "align.6", "+1+++", align( "1", 5, 2, '.', '+') );
130: t.check( "align.7", "+1.++", align( "1.", 5, 2, '.', '+') );
131:
132: t.check( "align.8", "01234", left ( "01234", 5 ) );
133: t.check( "align.9", " ", left ( "", 5 ) );
134: t.check( "align.a", "X ", left ( "X", 5 ) );
135:
136: t.check( "align.b", "01234", right( "01234", 5 ) );
137: t.check( "align.c", " X", right( "X", 5 ) );
138:
139: t.check( "align.d", " X ", center("X", 5 ) );
140: t.check( "align.e", " XX ", center("XX", 5 ) );
141: t.check( "align.e", "XXXXX", center("XXXXX", 5 ) );
142: t.exit();
143: }
144: }
145: }
|
|
Adding scroll bars to a text area.
The final workhorse class of interest to the calculator app is the Scrollable_JTextArea
(List 9, line 13).
This is a simple convenience class that compensates for what I consider to be a flaw of Swing’s JTextArea
: it doesn’t create scroll bars when it’s too small to fully display its contents. I’ve provided a small class that extends JScrollPane
to automatically create an internal JTextArea
. Although the Scrollable_JTextArea
is a JScrollPane
, it implements many of the methods of JTextArea
, which are simple pass-throughs to the contained text widget. That is, you can just pass the Scrollable_JTextArea
object messages such as setText()
, append()
, and getText()
, as if it were a JTextArea
.
The main problem with making JScrollPane
the base class is that, from a design point of view, you really want this thing to be a JTextArea
with scroll bars, not a scroll pane. Implementing JScrollPane
is awkward. The alternative would be to extend JTextArea
and literally override everything, but that solution is both tedious and ugly.
The code is in List 9.
List 9. /src/com/holub/ui/Scrollable_JTextArea.java
1: package com.holub.ui;
2:
3: import javax.swing.*;
4: import java.awt.*;
5: import java.awt.event.*;
6:
7: /**
8: * A convenience class that creates a JTextArea and wraps it into
9: * a JScrollPane for you. A Scrollable_JTextArea is a JScrollPane
10: * that contains a JTextArea.
11: */
12:
13: <a id="Scrollable_JTextArea" name="Scrollable_JTextArea">public class Scrollable_JTextArea extends JScrollPane</a>
14: {
15: /******************************************************************
16: * Create an empty Scrollable_JTextArea with scroll bars created as
17: * needed, and word-wrapping on in the encapsulate JTextArea. (The
18: * latter means that you won't ever get a horizontal scroll bar.
19: * You can change this behavior by sending a setLineWrap(false)
20: * message to the Scrollable_JTextArea object.) The default preferred
21: * size is 250 x 250.
22: */
23:
24: <a id="Scrollable_JTextArea.text" name="Scrollable_JTextArea.text"> private final JTextArea text = new JTextArea();</a>
25:
26: <a id="Scrollable_JTextArea.Scrollable_JTextArea(boolean)" name="Scrollable_JTextArea.Scrollable_JTextArea(boolean)"> public Scrollable_JTextArea( boolean write_only )</a>
27: {
28: // Set up for line wrap and a vertical scroll bar. Other
29: // alternatives could be horizontal and vertical
30: // scroll bars and no line wrap.
31:
32: text.setLineWrap(true);
33: text.setWrapStyleWord(true);
34:
35: setViewportView( text );
36:
37: setPreferredSize( new Dimension(250, 250) );
38: setSize ( new Dimension(250, 250) );
39: setBorder ( BorderFactory.createEtchedBorder() );
40:
41: if( write_only )
42: {
43: text.setEditable ( false );
44: text.addFocusListener
45: ( new FocusAdapter()
46: <a id="Scrollable_JTextArea.focusGained(FocusEvent)" name="Scrollable_JTextArea.focusGained(FocusEvent)"> { public void focusGained(FocusEvent e)</a>
47: { Scrollable_JTextArea.this.requestFocus();
48: }
49: }
50: );
51: }
52: }
53:
54: /******************************************************************
55: * Convenience method, sets the initial text to initial_text.
56: */
57: <a id="Scrollable_JTextArea.Scrollable_JTextArea(String,boolean)" name="Scrollable_JTextArea.Scrollable_JTextArea(String,boolean)"> public Scrollable_JTextArea( String initial_text, boolean write_only )</a>
58: { this(write_only);
59: text.setText( initial_text );
60: }
61:
62: /******************************************************************
63: * Convenience method, sets the initial text to initial_text, one
64: * String per line. The initial font is 12-point Monospaced.
65: * The size is controlled by the size of the text. The number of
66: * columns is the maximum number of characters on a line in
67: * initial-text; the number of rows is the number of lines.
68: * Word wrapping is off.
69: */
70:
71: <a id="Scrollable_JTextArea.Scrollable_JTextArea(String[],boolean)" name="Scrollable_JTextArea.Scrollable_JTextArea(String[],boolean)"> public Scrollable_JTextArea( String[] initial_text, boolean write_only )</a>
72: { this(write_only);
73: text.setLineWrap( false );
74: text.setFont ( new Font("Monospaced", Font.PLAIN, 12) );
75: text.setRows ( initial_text.length );
76:
77: int width = 0;
78: for( int i = 0 ; i < initial_text.length; ++i )
79: { String current = initial_text[i];
80: width = Math.max( width, current.length() );
81: text.append( current + "n" );
82: }
83:
84: text.setColumns( width );
85:
86: Dimension size = text.getPreferredSize();
87: size.height += 5; // Add some slop so that scroll bars won't
88: size.width += 5; // be displayed initially.
89:
90: setPreferredSize( size );
91: setSize ( size );
92: }
93:
94: /******************************************************************
95: * Convenience methods, just chain through to the
96: * setText() method of the contained JTextArea();
97: */
98:
99: <a id="Scrollable_JTextArea.setText(String)" name="Scrollable_JTextArea.setText(String)"> public final void setText( String str )</a>
100: { text.setText( str );
101: }
102:
103: <a id="Scrollable_JTextArea.append(String)" name="Scrollable_JTextArea.append(String)"> public final void append( String str )</a>
104: { text.append( str );
105: }
106:
107: <a id="Scrollable_JTextArea.setEditable(boolean)" name="Scrollable_JTextArea.setEditable(boolean)"> public final void setEditable(boolean on)</a>
108: { text.setEditable(on);
109: }
110:
111: <a id="Scrollable_JTextArea.setLineWrap(boolean)" name="Scrollable_JTextArea.setLineWrap(boolean)"> public final void setLineWrap(boolean on)</a>
112: { text.setLineWrap(on);
113: }
114:
115: <a id="Scrollable_JTextArea.getText()" name="Scrollable_JTextArea.getText()"> public final String getText()</a>
116: { return text.getText();
117: }
118:
119: <a id="Scrollable_JTextArea.setBackground(Color)" name="Scrollable_JTextArea.setBackground(Color)"> public final void setBackground(Color c)</a>
120: { if(text!=null) text.setBackground(c);
121: }
122:
123: <a id="Scrollable_JTextArea.setForeground(Color)" name="Scrollable_JTextArea.setForeground(Color)"> public final void setForeground(Color c)</a>
124: { if(text!=null) text.setForeground(c);
125: }
126:
127: <a id="Scrollable_JTextArea.setFont(Font)" name="Scrollable_JTextArea.setFont(Font)"> public final void setFont(Font f)</a>
128: { if(text!=null) text.setFont(f);
129: }
130:
131: /******************************************************************
132: * Accessor method, provides access to the contained JTextArea
133: * if you need it.
134: */
135: <a id="Scrollable_JTextArea.getTextArea()" name="Scrollable_JTextArea.getTextArea()"> public final JTextArea getTextArea( ){ return text; }</a>
136:
137: /******************************************************************
138: * Container of the unit-test
139: */
140: <a id="Scrollable_JTextArea.Test" name="Scrollable_JTextArea.Test"> static public class Test</a>
141: {
142: <a id="Scrollable_JTextArea.Test.main(String[])" name="Scrollable_JTextArea.Test.main(String[])"> static public void main( String[] args )</a>
143: { test1();
144: test2();
145: }
146:
147: static void test1()
148: {
149: Scrollable_JTextArea text = new Scrollable_JTextArea(
150: "This is an editable JTextArea " +
151: "that has been initialized with the setText method. " +
152: "A text area is a "plain" text component, " +
153: "which means that although it can display text " +
154: "in any font, all of the text is in the same font.",
155: false
156: );
157:
158: JFrame frame = new JFrame();
159: frame.getContentPane().add( text );
160: frame.setVisible( true );
161:
162: try{ Thread.currentThread().sleep(10000); } catch (Exception e){}
163:
164: text.setText( "Here is a completely newn"+
165: "chunk of textn"+
166: "this timen" +
167: "with newlines."
168: );
169: }
170:
171: static void test2()
172: { String[] contents = { "This is a read-only ->",
173: "multiline text control ->",
174: "that should display->",
175: "four lines.->"
176: };
177:
178: Scrollable_JTextArea text = new Scrollable_JTextArea( contents,true );
179: JFrame frame = new JFrame();
180: frame.getContentPane().add( text );
181: frame.pack();
182: frame.setVisible( true );
183: }
184: }
185: }
|
|
Loose ends
The following classes were covered in depth in previous articles, but they’re used either in the foregoing code or by the calculator itself, so I’ve reprinted them here for the sake of completeness.
List 10. /src/com/holub/asynch/JDK_11_unloading_bug_fix.java
1: package com.holub.asynch;
2:
3: /**
4: * <table class="legacyTable" border=1 cellspacing=0 cellpadding=5><tr><td><font size=-1> 5: * <center>(c) 1999, Allen I. Holub.</center>
6: *
7: * This code may not be distributed by yourself except in binary form,
8: * incorporated into a java .class file. You may use this code freely
9: * for personal purposes, but you may not incorporate it into any
10: * commercial product without express permission of Allen I. Holub in
11: * writing.
12: * </td></tr></table>
13: *
14: *
15: * This class provides a workaround for a bug in the JDK 1.1 VM that
16: * unloads classes too aggressively. The problem is that if the only
17: * reference to an object is held in a static member of the object, the
18: * class is subject to unloading and the static member will be discarded.
19: * This behavior causes a lot of grief when you're implementing a
20: * Singleton. Use it like this:
21: *
22: *
23: * class Singleton
24: * { private Singleton()
25: * { new JDK_11_unloading_bug_fix(Singleton.class);
26: * }
27: * // ...
28: * }
29: *
30: * In either event, once the "JDK_11_unloading_bug_fix" object is
31: * created, the class (and its static fields) won't be unloaded for
32: * the life of the program.
33: */
34:
35: <a id="JDK_11_unloading_bug_fix" name="JDK_11_unloading_bug_fix">public class JDK_11_unloading_bug_fix</a>
36: {
37: <a id="JDK_11_unloading_bug_fix.JDK_11_unloading_bug_fix(Class)" name="JDK_11_unloading_bug_fix.JDK_11_unloading_bug_fix(Class)"> public JDK_11_unloading_bug_fix( final Class the_class )</a>
38: {
39: if( System.getProperty("java.version").startsWith("1.1") )
40: {
41: Thread t =
42: new Thread()
43: <a id="JDK_11_unloading_bug_fix.singleton_class" name="JDK_11_unloading_bug_fix.singleton_class"> { private Class singleton_class = the_class;</a>
44: <a id="JDK_11_unloading_bug_fix.run()" name="JDK_11_unloading_bug_fix.run()"> public void run()</a>
45: { synchronized(this)
46: { try{ wait(); }catch(InterruptedException e){}
47: }
48: }
49: };
50:
51: // I'm not exactly sure why synchronization is necessary, below,
52: // but without it, some VMs complain of an illegal-monitor
53: // state. The effect is to stop the wait() from executing
54: // until the thread is fully started.
55:
56: synchronized( t )
57: { t.setDaemon(true); // so program can shut down
58: t.start();
59: }
60: }
61: }
62: }
|
|
List 11. /src/com/holub/ui/AncestorAdapter.java
1: package com.holub.ui;
2:
3: import javax.swing.event.*;
4:
5: /** Corrects a flaw in Swing, provides an AncestorListener
6: * implementation made up of empty methods.
7: */
8:
9: <a id="AncestorAdapter" name="AncestorAdapter">public class AncestorAdapter implements AncestorListener</a>
10: <a id="AncestorAdapter.ancestorAdded(AncestorEvent)" name="AncestorAdapter.ancestorAdded(AncestorEvent)">{ public void ancestorAdded ( AncestorEvent event ){}</a>
11: <a id="AncestorAdapter.ancestorMoved(AncestorEvent)" name="AncestorAdapter.ancestorMoved(AncestorEvent)"> public void ancestorMoved ( AncestorEvent event ){}</a>
12: <a id="AncestorAdapter.ancestorRemoved(AncestorEvent)" name="AncestorAdapter.ancestorRemoved(AncestorEvent)"> public void ancestorRemoved ( AncestorEvent event ){}</a>
13: }
|
|
Allen Holub runs Holub Associates in the San
Francisco Bay Area. The firm designs software and guides you
through its implementation in much the same way that traditional
architects design buildings and help contractors construct them.
Holub Associates also provides training in object-oriented design
and Java, provides design-review and mentoring services, and does
occasional Java implementations. Allen has been working in the
computer industry since 1979. He is widely published in magazines
(Dr. Dobb’s Journal, Programmers Journal,
Byte, and MSJ, among others). He is working on
his eighth book, which will present the complete sources for a Java
compiler written in Java. A former C++ programmer, Allen abandoned
it for Java in early 1996. He now looks at C++ as a bad dream, the
memory of which is mercifully fading. He has built two operating
systems from scratch, several compilers, and various application
programs ranging in scope from robotics controllers to children’s
software. He has been teaching programming, both on his own and for
the University of California Berkeley Extension, since 1982. Get
information and contact Allen via his Website
Source: www.infoworld.com