Creating a Custom Java Console

Thursday Dec 14th 2000 by Greg Travis
Share:

This article will tell you how to create your own browser console, some of the pitfalls you may encounter, and a few other interesting tidbits.

Introduction

Call me old fashioned, but I still debug using print statements. Despite the rise of the integrated development environment (IDE), I never really got over the thrill of watching my program tell me everything it's doing, as it does it, in real time.

Unfortunately, under Java, you don't always get to do this. Sure, Java has

stdout
and
stderr
(the unix-y names for the two output streams that print to the shell you started the program from), but depending on the environment, you might not be able to see their output.

Popular Web browsers give you access to these streams — for example, Netscape has a menu item ("Window/Tools/Java Console") which brings up a window that shows everything written to

stdout
and
stderr
.

But this is not the case for all development environments. I recently did a programming project for a client that wanted it specifically to run under a custom browser. This browser either didn't provide a Java console or they made it too hard to find, because I gave up looking for it. But since I had a bug that only happened under this browser (har-har), I simply had to find a way to see the output of the program. So I created my own console.

This article will tell you how to do this, some of the pitfalls I encountered, and a few other tidbits to keep you interested.

The Ideal Solution

If you're unfortunate enough to be like me, in that you debug using print statements, then you're probably using

System.out.println()
(or
System.err.println()
). So the ideal solution is to cause all of the output sent to these two streams to show up in our console window.

In a plain ol' command-line Java application, the trick is an easy one — you can simply replace the

OutputStream
that the system provides with another one that you have created yourself. Once you can do this, the sky's the limit in terms of how you can redirect your output.

The Practical Solution

Sadly, this isn't generally possible. As I said above, one situation in which a custom console is necessary is under a Web browser that doesn't provide one. However, browsers also usually don't let you replace the output streams. This is presumably a security precaution, because a page might contain applets from different servers, written by different people with different information to protect, it wouldn't be a good idea to let one applet take over the system

OutputStreams
, as it would only be a matter of time before someone hacked up a Web page that used a rogue applet to steal sensitive information from another applet. (Of course, applets shouldn't be writing sensitive information to
System.out
, but that's another issue.)

If you can't hook

System.out
, then you're going to have to use another output stream to do your debugging. This is annoying if you're like me, and type "
System.out.println
" without thinking about it, and it's annoying to have to run through all of your code replacing "
System.out.println
" with "
SomethingElse.println
"; but that's exactly what we'll have to do in some situations. We'll also provide a mechanism for hooking the system
OutputStreams
, in case your environment permits it.

How It Works

We're going to make the use of this console as simple as possible. There have certainly been other systems written in Java that give you very flexible ways to spew out tons of information from your program, with multiple windows, the ability to turn various debugging lines on and off, but that's beyond the scope of this project. What we want is something very much like

System.out.println()
, only a little better.

Our replacement is this:

Console.println()
. Yup, that's it. And the way it works is that when you print to this stream, the output goes into a window. And you don't even have to open the window yourself — the window opens automatically the first time you print something.

Note also that this means there is only one console.

Console.println()
is a static method, so you don't get to create multiple consoles and print different things. This is because we are keeping it simple. (More about this later.)

Take a look at the code for Console.java

. The first thing you should see is that there are a lot of methods called

print()
and
println()
, and they are all very similar:

  // print methods for each type
  static public void println( int i ) {
    println( ""+i );
  }

  // print methods for each type
  static public void println( long l ) {
    println( ""+l );
  }

We need to have these because we want to be able to pass anything to

Console.println()
, not just strings. This makes
Console.println()
just like
System.out.println()
— you can pass it an integer, and it converts it first to a string, and then prints this string.

You'll also notice that there are equivalent

Console.print()
methods. These are just like the
System.out.print()
methods. That is, they're just like
println()
, only they don't add a new line at the end. We won't mention these again — they are just like the
println()
methods, except for this one difference.

Each of the type-specific methods converts the argument to a string and passes it to

Console.println( String s )
, which then passes it to
Console.println( Object o )
. This latter method is the one that does the actual work for all the other
println()
routines. Let's look at it:

  // this is the print method called by the others
  static public void println( Object o ) {
    // Create (and open) a new Console object,
    // if we haven't already
    if (dw==null)
      dw = new Console();

    // Send the object to it
    dw.showString( o.toString()+"\n" );
  }

Remember that we open a console window automatically the first time this system is used — well, here's where it happens.

Console.println( Object o )
is a static method. If this is the first time it's been called, it creates a
Console
object and keeps a reference to it in a member variable called
dw
. Then it tells
dw 
to show the string. The next time around, it doesn't need to create another one, because it already has one, so it just sends the string along with no further ado.

Here's where the string actually gets shown:

  // non-static method for actually displaying new text
  private void showString( Object o ) {
    ta.append( o.toString() );
  }

This non-static method is called on the unique

Console
object that we created above, and it simply adds the string to the end of the scrolling text window.

Voila! Console!

Hooking the System
OutputStreams

Here's our prize feature. If our runtime environment allows it, we'd like to redirect
System.out
and
System.err
to print directly to our console.

Since this is something we don't necessarily want to in every case, we've created a method you have to call to get it going, called

Console.hookStandards()
:

  // hook stdout and stderr, and redirect output to the console
  // keep the originals around, of course
  static public void hookStandards() {
    synchronized( hookLock ) {
      if (out!=null)
        return;

      out = System.out;
      err = System.err;
      PrintStream dwout =
        new PrintStream( new ConsoleOutputStream() );
      System.setOut( dwout );
      System.setErr( dwout );
    }
  }

Since we're going to want to provide the user with a way of restoring the original

System.out
and
System.err
(namely,
Console.unhookStandards()
), we keep them around in variables called
out
and
err
. And to make sure nothing goes awry in a multithreaded situation, we put a
synchronized()
block around it.

We replace both

System.out
and
System.err
with a new OutputStream object called a ConsoleOutputStream

. This is a class we've created that redirects all output back to our console. Take a look at the source

to see how we've done it — it's quite simple.

Trying It Out

We've provided two ways to try this out: an applet and an application. The applet demonstrates the use of
Console.println()
directly, while the application also shows the use of
Console.hookStandards()
and
Console.unhookStandards()
. You can try the applet out right here

. You can run the application by typing

java TestApp
at the command-line.

Bonus Feature

Another nice thing you can do in the console is add your own custom debugging utilities. Some browsers provide useful things like memory dumps — there's no reason we can't do that too.

If you press 'c' in the console window, you'll see that it clears the window. And if you press 'd', you'll see a nice stack dump of all running threads. Well, you might see it — not all runtime environments will allow it.

Further Ideas

There are a few tidbits you might want to consider if you create a class like this one.

Typing

Console.println()
(or
System.out.println()
, for that matter) is a lot of typing if you type it many, many times. If you don't mind a bit of obfuscation in your code, it might be good to rename
Console
to
C
, because
C.println()
is shorter.

I mentioned above that this system only has one

Console
, and all output goes to this same console. This might not be ideal for your situation — consider the possibility of allowing the creation of multiple consoles and allowing your code to send different messages to different consoles. It gets away from our original idea of trying to create a drop-in replacement for the system console, but that doesn't mean it's not a good idea.

Our console doesn't have a close button. It doesn't really need one, since the console is closed when the applet dies, but there's no reason not to allow the user to hide the console if they are sick of it. Remember, however, that you'll need to provide a way to get it back.

Source Code

About the Author


Greg Travis is a freelance programmer in New York City. His interest in computers can probably be traced back to the episode of "The Bionic Woman" where Jamie runs around trying to escape a building whose lights and doors are controlled by an evil artificial intelligence, which mocks her through loudspeakers. He's a devout believer in the religious idea that when a computer program works, it's a complete coincidence. He can be reached at mito@panix.com.

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved