Chapter 23

Advanced Debugging and Troubleshooting


CONTENTS


Sometimes your code will compile just fine but will not run properly. As you begin to develop advanced applets and applications, this will happen more frequently. Instead of spending days poring over thousands of lines of code trying to pinpoint problem areas, you should spend an hour with a tool called the Java debugger. Use it whenever you have problems in your source code that you cannot solve without a little help.

Debugging code after a successful compile is quite different from debugging code that will not compile, primarily because of the type of errors you will come across at this stage of troubleshooting. When your program will not compile, the errors mostly relate to syntax, such as missing semicolons or bad references to objects. However, when your program does not run the way you expect or displays errors during execution of the code, the errors mostly relate to the program's logic, such as calling the wrong method or using a variable set to the wrong value.

Although the Java debugger is quite useful for debugging code interactively, sometimes you do not want to do that. For those times, a debugging utility called HelpDebug can be used to help debug your code using a noninteractive means. For this reason, this chapter covers both interactive and noninteractive debugging.

Advanced troubleshooting requires more logic than concept. Therefore, this chapter takes you step by step through the debugging process as it demonstrates how to use the Java debugger.

Starting the Java Debugger

Anyone who has ever programmed in C/C++ knows that a good debugger can quite literally save the day. The Java debugger works much like the other debuggers you may have used to debug your C or C++ code. You invoke it from the command line. The name of the command-line program is jdb, which stands for Java debugger. When invoked, the debugger uses the Debugger API-sun.tools.debug-to help you troubleshoot problems in your code.

You can invoke the debugger from the command line in one of two ways: The first way to start a jdb session is to pass it the name of your Java class file without the .class extension, and the second is to attach it to a Java interpreter or applet viewer.

As with other tools in the Java Developer's Kit, the debugger allows you to use command-line options. These options depend on the way you use the debugger. If you use it directly, any options you specify on the command line are passed on to the interpreter the debugger invokes, so you can use any of the interpreter options when you start the debugger directly.

When you attach the debugger to an interpreter session, you are limited to three options: -host, -password, and -classpath. The -host option sets the name of the host machine on which the interpreter session is running. The -password option sets the password and enables you to attach the debugger to a particular session. The -classpath option lets you specify a path for your class files that overrides the default or current CLASSPATH setting. Because this option takes precedence, you should set it to include the current directory, the location of your personal or third-party class files, and the location of the Java API classes.

Tip
To aid the debugging process, compile your code with the -g option of the Java compiler. This option tells the Java compiler to generate debugging tables. Although debugging tables are not useful for troubleshooting syntax problems, they are useful for troubleshooting logic problems. Generally, you will use these tables to browse local variables on the stack.

Starting the Debugger Directly

To start a debugging session directly, usually you would pass it the name of your Java class file without the .class extension by typing the following at the command line:

jdb classname

where classname is the name of the class file that starts the program you want to debug.

When you start the debugger directly, it in turn starts the Java interpreter with any parameters you passed on the command line. Afterward, the debugger loads the specified class file.

Note
For the JDK version 1.0 or later, the debugger expects you to be on a networked host. If you are not, you may see the following error message or a similar one:
java.net.UnknownHostException
    at java.net.InetAddress.getLocalHost(InetAddress.java:276)
    at sun.tools.debug.RemoteDebugger.<init>(RemoteDebugger.java:61)
    at sun.tools.ttydebug.TTY.<init>(TTY.java:1263)
    at sun.tools.ttydebug.TTY.main(TTY.java:1387)
To clear up this problem, start a browser session that connects your computer to the Internet. Then you should be able to start the debugger.

Attaching the Debugger to a Current Interpreter Session

Although most programmers prefer to invoke the debugger directly, you can also attach a debugging session to an interpreter that is already running. To do this, start the Java interpreter with the -debug option. When you use this option, the interpreter displays a password that must be used when starting the debugging session.

Note
Because the password is randomly allocated by session, you can use it only for the applet or application currently running on your system.

you should follow to attach the debugger to an interpreter:

  1. Start the interpreter with the -debug option, such as
    java -debug classname
    where classname is the name of the class file that starts the application.
  2. Invoke the debugger with the session password:
    jdb -host hostname -password session_password
    where hostname is the hostname of your computer and session_password is the password provided by the interpreter.

Note
When you start the Java interpreter or applet viewer with the -debug option under JDK version 1.0, they both check for the hostname and local Internet address of your system. If you are not on a networked host, you may see the following error message or a similar one:
java.net.UnknownHostException
    at java.net.InetAddress.getLocalHost(InetAddress.java:276)
    at sun.tools.debug.RemoteDebugger.<init>(RemoteDebugger.java:61)
    at sun.tools.ttydebug.TTY.<init>(TTY.java:1263)
    at sun.tools.ttydebug.TTY.main(TTY.java:1387)
To clear up this problem, start a browser session that connects your computer to the Internet. Then you should be able to start the interpreter or applet viewer with the -debug option.

Attaching the Debugger to an Applet Viewer Session

The final way to use the debugger is to attach it to an applet viewer session by starting the applet viewer with the -debug option.

Here are the steps you would follow to attach the debugger to the applet viewer:

  1. Start the applet viewer with the -debug option, such as
    appletviewer -debug document.html
    where document.html is the name of the HTML document with a valid <APPLET> tag.
  2. Run the applet using the run command of the debugger.

Troubleshooting the Code with the Debugger

After you start the debugger, an input prompt appears. You can enter commands directly to the debugger at this prompt. One of the most useful commands is help, which lists all the commands the debugger recognizes. The help listing for the debugger follows:

Initializing jdb…
> help

** command list **
threads [threadgroup]     -- list threads
thread <thread id>        -- set default thread
suspend [thread id(s)]    -- suspend threads (default: all)
resume [thread id(s)]     -- resume threads (default: all)
where [thread id] | all   -- dump a thread's stack
threadgroups              -- list threadgroups
threadgroup <name>        -- set current threadgroup

print <id> [id(s)]        -- print object or field
dump <id> [id(s)]         -- print all object information

locals                    -- print all local variables in current stack frame

classes                   -- list currently known classes
methods <class id>        -- list a class's methods

stop in <class id>.<method> -- set a breakpoint in a method
stop at <class id>:<line> -- set a breakpoint at a line
up [n frames]             -- move up a thread's stack
down [n frames]           -- move down a thread's stack
clear <class id>:<line>   -- clear a breakpoint
step                      -- execute current line
cont                      -- continue execution from breakpoint

catch <class id>          -- break for the specified exception
ignore <class id>         -- ignore when the specified exception

list [line number]        -- print source code
use [source file path]    -- display or change the source path

memory                    -- report memory usage
gc                        -- free unused objects

load classname            -- load Java class to be debugged
run <class> [args]        -- start execution of a loaded Java class
!!                        -- repeat last command
help (or ?)               -- list commands
exit (or quit)            -- exit debugger

The sections that follow examine how many of these options are used to debug your code.

Examining Threads

The debugger includes a group of useful commands for examining threads. All Java programs have at least one thread of execution.

The threads command lists all the threads in use and their status. The type and number of threads running on the system vary according to the threads used by your system, how you start the debugger, and the threads used by your Java program. When you start the debugger directly, no Java program is running, and few threads are running on your system.

To follow along with this debugging session, start the debugger for use with the Jompanion editor developed in Chapter 21, "Designing and Implementing Advanced Applications." To do this, change to the directory containing the class files for Jompanion and type the following:

jdb Jompanion

Your system should display output similar to this:

Initializing jdb...
0x13a41e0:class(Jompanion)
>

Note
The greater-than sign (>) is the input prompt for the debugger.

At this point in the debugging session, the Jompanion application is not running; I have merely invoked a session with the debugger. To examine the threads currently running on your system, type threads at the debugger input prompt. On a Windows 95 system using JDK 1.0, the following threads run whenever you start the debugger:

> threads
Group system:
 1. (java.lang.Thread)0x13931f8                  Finalizer thread cond. waiting
 2. (java.lang.Thread)0x1393918                  Debugger agent     running
 3. (sun.tools.debug.BreakpointHandler)0x13a2668 Breakpoint
Âhandler cond. waiting
Group main:
 4. (java.lang.Thread)0x13930a0 main running

The output from the threads command tells you a lot about the threads running on your system. As you can see, each thread is listed by threadgroup and is numbered consecutively. This number is the ID of the thread. Following the thread ID is the class associated with the thread, its name, and its status.

Using the ID number, you can set the default thread using the thread command. To switch to the main thread, type the following:

>thread 4

When you do this, the input prompt changes to this:

main[1]

The new prompt, main[1], tells you the current thread is the thread named main. All commands typed at the input prompt are taken in context of this thread. However, because no Java program is running on your system now, the debugger essentially behaves the same. To switch to the debugger agent thread, type its ID at the input prompt, like this:

main[1] thread 2

When you do this, the input prompt changes to this:

Debugger agent[1]

To start the application from within the debugging session, type run at the debugger input prompt. The following output should display in your command area:

> run
run Jompanion
running ...
main[1]

Now that a program is running, the main[1] prompt references the main thread of your program-in this case, Jompanion's main thread. Using the threads command, you can check the threads running in the context of the main thread. As you can see from the following listing, the threadgroup and threads are different when a program is running:

main[1] threads
Group Jompanion.main:
 1. (java.lang.Thread)0x13a6738      AWT-Win32          running
 2. (java.lang.Thread)0x13a6790      AWT-Callback-Win32 running
 3. (sun.awt.ScreenUpdater)0x13a6a38 Screen Updater     cond. waiting
main[1]

The three threads listed previously are all the threads running in the context of the current thread. Obviously, there are more threads running on your system, and you may be wondering what happened to the threads the debugger is running. To see all the threads running on your system, change to the system threadgroup and then type the threads command:

main[1] threadgroup system
main[1] threads
Group system:
 1. (java.lang.Thread)0x13931f8                Finalizer thread   cond. waiting
 2. (java.lang.Thread)0x1393918                  Debugger agent     running
 3. (sun.tools.debug.BreakpointHandler)0x13a2668
ÂBreakpoint handler cond. waiting
Group main:
 4. (java.lang.Thread)0x13930a0 main cond. waiting
Group Jompanion.main:
 5. (java.lang.Thread)0x13a6738      AWT-Win32          running
 6. (java.lang.Thread)0x13a6790      AWT-Callback-Win32 running
 7. (sun.awt.ScreenUpdater)0x13a6a38 Screen Updater     cond. waiting
main[1]

To analyze the behavior of your program when certain threads are stopped, use the suspend command. Although this command is more useful when you have multiple background threads, it is used here to stop the Screen Updater thread of the Jompanion application. After stopping a thread, you can check its status using the threads command. Here's how:

main[1] suspend 7
main[1] threads
Group system:
 1. (java.lang.Thread)0x13931f8                Finalizer thread   cond. waiting
 2. (java.lang.Thread)0x1393918                  Debugger agent     running
 3. (sun.tools.debug.BreakpointHandler)0x13a2668
ÂBreakpoint handler cond. waiting
Group main:
 4. (java.lang.Thread)0x13930a0 main cond. waiting
Group Jompanion.main:
 5. (java.lang.Thread)0x13a6738      AWT-Win32          running
 6. (java.lang.Thread)0x13a6790      AWT-Callback-Win32 running
 7. (sun.awt.ScreenUpdater)0x13a6a38 Screen Updater     suspended
main[1]

After testing the behavior of your application with the thread suspended, you will usually want to restart the thread and go on to test the behavior of your application with other threads suspended. To restart a thread, use the resume command.

Examining Methods and Classes

By examining the methods and classes used in your program, you can learn more about the behavior of your program, verify that sections of the code are being executed as you expect, and pinpoint problem areas in the code. The debugger provides many ways to examine methods and classes. However, before you can do so, you need to start a debugging session.

This section demonstrates how to attach the debugger to a running application. To follow along, change to the directory containing the Jompanion class files and start the Jompanion application using the interpreter as follows:

java -debug Jompanion

Your system should display a password to be used with the debugger, such as the following:

Agent password=52d2zj

If your password is 52d2zj, you now open a new command window and type this:

jdb -password 52d2zj

Your system should display output similar to the following:

Initializing jdb...
>

Now that your debugging session is started, you can examine the classes and methods. To list the classes the debugger currently knows about, use the debugger's classes command. If you use this command for Jompanion, the output from the debugger is similar to the following abbreviated listing:

> classes
** classes list **
0x1393008:class(java.lang.Thread)
0x1393018:class(java.lang.Object)
0x1393098:class(java.lang.Class)
0x1393028:class(java.lang.String)
0x1393038:class(java.lang.ThreadDeath)
0x1393048:class(java.lang.Error)
0x13932f0:class(Jompanion)
0x1393300:class(java.awt.Frame)
0x1393310:class(java.awt.Window)
0x1393320:class(java.awt.Container)
0x1393330:class(java.awt.Component)
0x1393ab0:class(java.awt.BorderLayout)
0x1393af8:class(java.awt.Color)
0x1393b70:class(java.awt.Font)
0x1393bd0:class(java.awt.MenuBar)
0x1393be0:class(java.awt.MenuComponent)
0x1393c08:class(sun.tools.java.ClassPath)
0x1393c18:class(java.io.File)
0x1393c60:class(sun.tools.java.ClassPathEntry)
0x1393cd0:class(sun.tools.zip.ZipFile)
0x1393d00:class(java.io.RandomAccessFile)
0x1393d70:class(java.util.Vector)
0x1393d90:class(java.awt.Menu)
0x1393da0:class(java.awt.MenuItem)
0x1393e90:interface(sun.tools.zip.ZipConstants)
0x1393ee0:class(sun.tools.zip.ZipEntry)
0x1393f30:interface(java.awt.peer.MenuBarPeer)
0x1394088:interface(java.awt.peer.FramePeer)
0x1394098:class(java.awt.Choice)
0x13940c0:interface(java.awt.peer.ChoicePeer)
0x13942d0:class(java.awt.FlowLayout)
0x13949a0:class(java.awt.TextArea)
0x13949b0:class(java.awt.TextComponent)
0x13949e0:interface(java.awt.peer.TextComponentPeer)
0x1394a08:class(java.awt.FileDialog)
0x1394a18:class(java.awt.Dialog)
0x1394a88:class(java.awt.Toolkit)
0x1395878:class(sun.awt.win32.MToolkit)
0x1398038:class(java.awt.Dimension)
0x1398050:class(sun.awt.win32.MFramePeer)
0x1398db8:class(sun.awt.win32.MPanelPeer)
0x1398dc8:class(sun.awt.win32.MCanvasPeer)
0x1398dd8:class(sun.awt.win32.MComponentPeer)
0x139df48:class(java.awt.Rectangle)
0x139df60:class(java.awt.Insets)
0x139e148:class(sun.awt.win32.MMenuBarPeer)
0x139e168:class(sun.tools.debug.BreakpointHandler)
0x139e1a0:class(sun.tools.debug.BreakpointQueue)
0x139e1e8:class(java.net.Socket)
0x139e220:class(sun.awt.win32.MMenuPeer)
0x139e230:class(sun.awt.win32.MMenuItemPeer)
0x139e2d0:class(sun.awt.win32.MChoicePeer)
0x139e300:class(sun.awt.win32.MTextAreaPeer)
0x139e330:interface(java.awt.peer.ContainerPeer)
0x139e340:interface(java.awt.peer.WindowPeer)
0x139e350:class(sun.awt.win32.Win32FontMetrics)
0x139e360:class(java.awt.FontMetrics)
0x139e3a8:interface(java.awt.peer.TextAreaPeer)
0x139e3d8:class(sun.awt.ScreenUpdater)
0x139e410:class(sun.awt.ScreenUpdaterEntry)
0x139e468:class(sun.awt.win32.Win32Graphics)
0x139e478:class(java.awt.Graphics)
>

As you can see, the list of classes known to the debugger includes all classes used by both the debugger and the program you are debugging. You can use the methods command to see the methods in any class shown in the classes list. To see a list of methods in the Jompanion class, type the following:

> methods Jompanion

The debugger output should be similar to the following:

void AddDialog()
boolean handleEvent(Event)
void cut()
void copy()
void paste()
void openFile()
void saveAsFile()
void saveOpenedFile()
void startNewText()
void showFindDialog()
void showRepDialog()
void showReplaceAllDialog()
void showAboutDialog()
void showErrorDialog(String)
boolean read(String)
boolean write(String)
String check(String)
void setFindString(String)
void setReplaceString(String)
void findSelectedText()
void findText()
void replaceSelectText()
boolean replaceText()
void main(String[])

When you know the methods for a particular class, you can debug the method using breakpoints and steps. When the program you are debugging reaches a breakpoint, execution of the program stops. You can then step through the program line by line to examine what happens when the program executes specific lines within the method.

Warning
When your program reaches a breakpoint, all execution of the program stops. If the program occupies the entire screen when you hit the breakpoint, your workstation may lock up. To avoid this, resize the application or applet's frame so you can access the command area with the active debugging session.

To set a breakpoint, use the stop command and reference the method you want to put the breakpoint in at the class level. For example, to place a breakpoint in the copy method of the Jompanion class, you would reference the method as Jompanion.copy, like this:

> stop in Jompanion.copy
Breakpoint set in Jompanion.copy

When the breakpoint is reached, program execution stops. You can then examine the stack of the current thread using the where command or step through the method line by line using the step command. A useful command to use as you step through the program is list, which shows the current execution point in the program.

To cause Jompanion to reach the breakpoint set in the copy method, use the copy command to copy selected text in the text area. The debugger should print out the following:

Breakpoint hit: Jompanion.copy (Jompanion:244)
AWT-Callback-Win32[1]

This output tells you the method that reached the breakpoint is called Jompanion.copy, the parent program is called Jompanion, and the line number where the breakpoint was reached is 244. Notice that on the Windows system used in the example, the prompt changed to AWT-Callback-Win32[1], which is the current thread. All commands issued to the debugger with this new prompt are in the context of the AWT-Callback-Win32 thread.

Using the where command, you can see what the stack looks like:

AWT-Callback-Win32[1] where
  [1] Jompanion.copy (Jompanion:244)
  [2] Jompanion.handleEvent (Jompanion:187)
  [3] java.awt.Component.postEvent (Component:838)
  [4] java.awt.MenuComponent.postEvent (MenuComponent:94)
  [5] java.awt.MenuComponent.postEvent (MenuComponent:94)
  [6] java.awt.MenuComponent.postEvent (MenuComponent:94)
  [7] sun.awt.win32.MMenuItemPeer.action (MMenuItemPeer:50)
AWT-Callback-Win32[1]

The stack shows you a picture of the steps the program took to get to the breakpoint. In this case, you selected a menu item, which posted an event that must be handled by the event handler. The event handler in turn called the copy method, and the breakpoint was reached. You can move up and down the stack using the up and down commands.

Other breakpoint-related commands include cont, used to continue execution of the program after the breakpoint; resume, used to resume execution of the current thread; and clear, used to clear a breakpoint.

Note
Keep in mind that the Java stack is organized according to the last in, first out methodology (LIFO). Because the last item in the stack is the first to pop off it when execution resumes, you need to read the stack in reverse order. Therefore, to trace the path to the current event in time order, start at the bottom of the stack and work to the top.

Using the list command, you can see exactly where you are in the source code:

AWT-Callback-Win32[1] list
240            textArea.requestFocus();
241            }
242
243          //copies selected text to the buffer
244     =>   public void copy() {
245            copyString = textArea.getSelectedText();
246            textArea.requestFocus();
247          }
248
AWT-Callback-Win32[1]

The output of the list command tells you where you are in the program. In the previous listing, the program stopped on line number 244. You can then step through the current method line by line using the step command.

Examining Instance Variables

By examining the instance variables in your program, you can check their values and ensure that they are set within the ranges you expect. Generally, you will want to check the status of instance variables at a specific point during the program's execution, so it's a good idea to set a breakpoint in a method whose instance variables you want to check. Before you can examine methods and classes used in your application, you need to start a debugging session.

This section demonstrates how to attach the debugger to an applet. If you want to follow along, change to the directory containing the ScrollText class files. (The ScrollText applet was created in Chapter 15, "Creating Java-Powered Web Presentations with Applets.") Next, create a new HTML document or use an existing one that references the ScrollText applet, such as the following:

<HTML>
<HEAD>
<TITLE>ScrollText Applet</TITLE>
</HEAD>
<BODY>
<APPLET CODE="ScrollText.class" WIDTH=500 HEIGHT=300>
</APPLET>
</BODY>
</HTML>

Then start the ScrollText applet with the applet viewer:

appletviewer -debug ScrollText

Your system should display output similar to the following:

appletviewer -debug test.html
Initializing jdb...
0x13a41e0:class(sun.applet.AppletViewer)
>

Finally, start the ScrollText applet using the run command:

> run

Your system should display output similar to the following:

run sun.applet.AppletViewer test.html
running ...
main[1]

Now that the applet is started, you can use the dump command to check the status of a specified object's instance variables:

main[1] dump ScrollText
ScrollText = 0x13a7ea8:class(ScrollText) {
    superclass = 0x13a7eb8:class(java.applet.Applet)
    loader = null
    interfaces:
        0x13a2f70:interface(java.lang.Runnable)

    static final LayoutManager panelLayout = (java.awt.FlowLayout)0x13a7738
}

The dump listing tells you many things about the state of the class or object to which it relates. For the ScrollText class, the dump tells you the superclass is java.applet.Applet, the loader is null, the applet uses the runnable interface, and the layout setting is for FlowLayout. Provided that local variables are used in your program and are available, you can also check the status of local variables using the locals command:

main[1] locals
Local variables and arguments:
  this = ScrollText[3000,3000,0x0,layout=java.awt.FlowLayout]

You can set a breakpoint in the applet to check instance variables related to a specific method at a given point in time. Because the scroll method plays a key role in how the ScrollText applet works, it's a good idea to check variables in this method. First, place the breakpoint; then dump the variable you want to check, as in the following:

main[1] stop in ScrollText.scroll
Breakpoint set in ScrollText.scroll
main[1]
Breakpoint hit: ScrollText.scroll (ScrollText:83)
Thread-2[1] dump dist
this.dist = -480

Using the cont command, you can resume execution of the program. Then, when the next breakpoint is reached, you can dump the variable you want to check again, as in the following:

Thread-2[1] cont
Thread-2[1]
Breakpoint hit: ScrollText.scroll (ScrollText:83)
Thread-2[1] dump dist
this.dist = -481

Identifying Trouble with the Code

Debugging your code is not a straightforward process, so there are many ways to do it. There are two key methods used by Java programmers; one is highly interactive and one is mostly noninteractive. Just as you use the debugger to provide a highly interactive way to find out the status of variables and what the program is doing at a particular line of code, you can use the program itself to tell you what it's doing at any given time in a primarily noninteractive way simply by inserting print statements at key points in the logic of the program.

By adding print statements to key methods, you can ensure that the methods are executing. You can check the sequence of events that lead to the method being called and which methods are called after the method executes. Usually, this type of print statement simply echoes the name of the method being executed, as in the following:

System.out.println("In the run method.");

By placing print statements in conditional loops, you can see what conditions are met on the basis of the current state of the program. You can then alter the program's state and see if other conditions of the loop are met. Usually, this type of print statement simply echoes the condition you are checking for, as in the following:

System.out.println("No parameter, using default.");

By placing print statements before or after values associated with a variable are changed, you can ensure the value associated with the variable is what you expect it to be when called and after the change occurs. Usually, this type of print statement describes the variable and displays the value, such as

System.out.println("The location parameter is set to: " + x);

After you use this mostly noninteractive method of debugging for a while, you may see why many programmers prefer it. The key concept is that you insert the print statements to help you debug and then either comment them out or delete them before you provide the finished version of the program.

The only bad thing about this method of debugging is that tracking the print statements that display to your screen is sometimes difficult, especially if your command window doesn't have a scroll feature. To make this style of debugging easier, you may want to create a utility to aid the debugging process, such as a window that displays all your debugging messages in reverse time order so you always see the most current messages but also have access to all previous messages.

Putting your debugging messages in a more usable and trackable format is exactly what the HelpDebug application shown in Listing 23.1 does. The application creates a window to which you can pass input and thus log your debugging information. The heart of the application is an overloaded method called out. The first version of the out method enables you to pass strings that you want displayed in the debugging window, the second enables you to display values associated with objects, and the final version enables you to pass values associated with integers.


Listing 23.1. The HelpDebug application.
/**
 * The Complete Guide to HelpDebug Application
 * This application creates a window to log debugging information.
 */

import java.applet.Applet;
import java.awt.*;
import java.util.*;


public class HelpDebug extends Frame {
     TextArea textArea;

     void out(String str) {
          textArea.insertText(str + '\n',0);
     }

     void out(Object obj) {
          textArea.insertText(String.valueOf(obj) + '\n',0);
     }

     void out(int x) {
          textArea.insertText("The value of the integer is:" + x + '\n',0);
     }


     HelpDebug() {

           super("HelpDebug Output Window");
           pack();
           reshape(200,0,300,300);
           show();

           textArea = new TextArea();
           textArea.setText("[New Debug Session]");
           setLayout(new BorderLayout());
           add("Center",textArea);
     }

     public boolean handleEvent(Event e) {

         if (e.id == Event.WINDOW_DESTROY) {
        dispose();
         return true;
         }
         else return super.handleEvent(e);

     }
}

Before you can use the HelpDebug application, type in and compile the code or access the source code for the application on the CD-ROM. Then you need to let your application or applet know the HelpDebug application exists by doing the following:

  1. Import the HelpDebug class:
    import HelpDebug;
  2. Create a container for the debug object:
    HelpDebug debug;
  3. Create an instance of the HelpDebug object in the main() method of an application or the init() method of an applet:
    debug = new HelpDebug();

When your program can access HelpDebug, you can direct your debugging output to the HelpDebug application itself. You do this by calling the out() method of the object you created in step 3, like this:

debug.out("The height parameter as read in is:" + hs);

To show you how to use this method of debugging and gain practice with the HelpDebug class, I'll modify the ScrollText applet. I'll do this by directing debugging statements to HelpDebug's out() method. As you examine the modified applet shown in Listing 23.2, look at how the debugging statements are added, where they are added, and what they output.


Listing 23.2. The modified ScrollText applet with debugging.
import java.awt.Graphics;
import java.awt.Font;
import HelpDebug;

/**
 * The Complete Guide to Java ScrollText Applet with Debugging information
 * This applet is used to scroll a text banner across the screen
 * The applet takes TEXT, WIDTH, and HEIGHT as parameters.
 */

public class ScrollText extends java.applet.Applet implements Runnable {

     int h;                    // Height of applet in pixels
     int w;                    // Width of applet in pixels
     char separated[];         // Output string in array form
     String s = null;          // Input string containing display text
     String hs = null;         // Input string containing height
     String ws = null;         // Input string containing width
     Thread ScrollThread = null;     // Thread to control processing
     int speed=35;             // Length of delay in milliseconds
     boolean threadSuspended = false;
     int dist;
     HelpDebug debug;     //container for the debug object


/* Setup width, height, and display text */
public void init() {
     debug = new HelpDebug();
     ws = getParameter ("width");
     debug.out("The width parameter as read in is:" + ws);
     hs = getParameter ("height");
     debug.out("The height parameter as read in is:" + hs);
     if (ws == null){         // Read width as input
          w = 150;            // If not found use default
     } else {
            w = Integer.parseInt(ws); // Convert input string to integer
     }
     if (hs == null){         // Read height as input
          h = 50;             // If not found use default
     } else {
          h = Integer.parseInt (hs); // Convert input string to integer
     }
     resize(w,h);             // Set font based on height
     setFont(new Font("TimesRoman",Font.BOLD,h - 6));
     s = getParameter("text");// Read input text, if null use default
     if (s == null) {
         s = " The Java ScrollText Applet at work.";
         debug.out("No text parameter, using default");
     }

     separated =  new char [s.length()];
     s.getChars(0,s.length(),separated,0);
 }

/* Start new thread to run applet */
public void start() {
     if(ScrollThread == null)
     {
     debug.out("Thread is null");
        ScrollThread = new Thread(this);
        ScrollThread.start();
     }
}

/* End thread containing applet */
public void stop() {
     debug.out("In the stop method.");
     ScrollThread = null;
 }

// While applet is running pause then scroll text
public void run() {
     debug.out("In the run method.");
     while (ScrollThread != null) {
     try {Thread.sleep(speed);} catch (InterruptedException e){}
     scroll();
     }
     ScrollThread = null;
 }

// Scroll text by determining new location to draw text and redrawing
synchronized void scroll () {
     debug.out("In the scroll method.");
     dist--;               // Move string to left
     debug.out(dist);
     // If string has disappeared to the left, move back to right edge
     if (dist + ((s.length()+1)*(h *5 / 11)) == 0){
     dist=w;
}
     repaint();
}

// Redraw string at given location
public void paint(Graphics g) {
     g.drawChars(separated, 0, s.length(), dist,4 *h / 5);
 }

// Suspend thread when mouse is pushed, resume when pushed again
public boolean mouseDown(java.awt.Event evt, int x, int y) {
     debug.out("Detected mouseDown event");
        if (threadSuspended) {
            debug.out("Resuming Thread…");
            ScrollThread.resume();
        }
        else {
            debug.out("Stopping Thread…");
            ScrollThread.suspend();
        }
        threadSuspended = !threadSuspended;
    return true;
    }
}

Before you can use the modified applet, you have to type in and compile the source code or access the source code for the modified applet on the CD-ROM. You also need to create an HTML document that displays the applet, such as the one shown in Listing 23.3. This document is also available on the CD-ROM.


Listing 23.3. The HTML document used to display the applet.
<HTML>
<HEAD>
<TITLE>Applet for Debugging</TITLE>
</HEAD>
<BODY>
<APPLET CODE="ScrollText.class" WIDTH=500 HEIGHT=300>
<PARAM NAME=TEXT VALUE="Applet Debugging Example…">
</APPLET>
</BODY>
</HTML>

After creating the necessary files, you can run the applet using the applet viewer or your Web browser. Figure 23.1 shows the applet and the debugging window. Note that the debugging output displayed in the HelpDebug window is in reverse time order, which ensures that the most current debugging information is always displayed at the top of the window.

Figure 23.1 : Using the HelpDebug application.

Summary

Debugging logic problems in your code is more difficult than debugging syntax problems. Therefore, you need a set of advanced tools to aid the debugging process. When you want to debug your code in a highly interactive way, use the Java debugger. The Java debugger is one of the most advanced tools in the Java Developer's Kit and, as such, has many useful features for debugging your code. When you want to debug your code in a mostly noninteractive way, you may want to use print statements and possibly a utility that helps you track them. The HelpDebug utility presented in this chapter is designed to help you do just that.