Advanced Java Lab 7 - Final Touches

This week you will touch up a few features of both your client and your server. On the client side, you will work to make the user interface more user-friendly and less frustrating to use. On the server side, you will incorporate the Log4j logging framework to provide simple tunable logging to the server.

And, these will be the last programming features for the Boggle project! Once these features are done, the next steps (next week) will be to finalize the structure of the project, and to package everything up into a JAR file for distribution.

Here is what you need to do.

Client UI Updates

One of the problems with last week's client is that the user interface freezes when the user hits the "Start Game" button. This is because the client is making a long-running RMI call from the Swing event-dispatch thread, and this prevents any user-interface repainting from occurring while the RMI call is in progress.

The solution to this is to use the javax.swing.SwingWorker class to make the long-running RMI call to the server from a separate thread. SwingWorker is a very easy class to incorporate into your program. As discussed in the Lecture 7 slides, you need to create a subclass of SwingWorker that implements the doInBackground() method and the done() method. This SwingWorker subclass should be an inner class of your Boggle client application, since it will need to access both the game state and the user-interface components of your client.

However, we also need to update the client to use dialog windows to indicate more clearly to the user what is going on. For most of these situations you can use the javax.swing.JOptionPane class to display simple informational or error dialogs. However, there are a couple of situations that are more complicated, and in those cases you can use dialog classes provided with this assignment.

Here is an overview of how the client will work this week:

  1. When the user presses the "Start Game" button, your program will do these steps:
  2. The "start game" Swing-worker's doInBackground() method will be called automatically. This method can simply call the remote server's startGame() method and return the result from the method. The doInBackground() method also should not handle any exceptions from the RMI call. Simply declare them and let them propagate!
  3. The Swing-worker's done() method will also be called automatically, when the doInBackground() method returns (or throws). This method is called on the Swing event-dispatch thread, so you can update your user interface from this method. Here is what the done() method does:
  4. The RMI call to gameOver() doesn't need to be performed on a SwingWorker thread because the call goes very quickly. Typically it only takes a fraction of a second to complete, and users simply won't notice any delay.

    However, you should also add dialogs to the gameOver() code to display an error message when the RMI call throws an exception. This should be a very simple addition to your client code.

End-Of-Game Results

Once you have made the above changes, you can spruce up your client UI by adding in a JGameResultsDialog to display the results returned from the Boggle server. (Here are the sources for the JGameResultsDialog class, and the JClientInfoView class that the dialog uses internally.)

However, you will need to make a few changes to your source code to use this dialog:

Once you have done these things, you should be able to get the JGameResultsDialog class working. It is a modal dialog, and you can display it in your "game-over" handler code. Remember that modal dialogs are special when it comes to Swing: Even though the dialog.setVisible(true) operation doesn't return until the modal dialog is closed, the call will not block any Swing event handling. So, you can do this within your "game over" code without causing any problems.

Server Logging

Logging is a simple and powerful way to tell what is going on in a large and complex program. Virtually all widely-used operating systems provide logging services for programs to use, and there are logging APIs for many different programming languages.

One of the most popular logging frameworks for Java is called Log4j. It is very easy to get up and running, and it provides many options for tuning where logs are stored, and what logs are enabled or disabled. It has also been around for quite a few years, so it is stable and mature.

The JAR file for Log4j version 1.2.13 is available on the CS cluster for your convenience, at:

    /cs/courses/cs11/software/log4j/log4j-1.2.13.jar
Copy it into your project's libraries-directory, and update your build file to include that in the classpath for compilation. You will also want to add this file to your project's source-code repository.

Review the "Short Introduction to Log4j," available here, to learn the basics of how to use Log4j. (The article goes into far more depth than you will need, but at least skim the more esoteric parts so you know what is available.) You will see that Log4j is very simple to use in your code. In classes that do logging, you will have something like this:

    import org.apache.log4j.Logger;

    public class SomeClass {
        /** The logging object for our class to use. **/
        private static Logger logger = Logger.getLogger("logging.category");

        public doStuff() {
            logger.info("Doing stuff!");
            ...
        }
    }

Of course, it can get much more complicated than this, but this level of usage should be more than adequate.

One important note is that you can get a Logger object by specifying a class, or by specifying a String. The important detail here is that the class or string is being used to determine the logger's category. Different categories can then be configured or filtered out in the Log4j configuration. You can use the class approach if you would like, but typically, specifying actual category strings allows you to choose much more intuitive category names, rather than having to put up with whatever package and class names you might have in your code. You might as well make your logging categories intuitive and easy to understand.

Go through the Boggle server code and update it to use the Log4j API to report information using logging messages. Make sure that you choose an appropriate logging level for each message, based on what is being reported. For example, you might log a message when a user connects, that can be reported as an INFO message, but if a communication error occurs, report that as an ERROR level. If the RMI registration process fails then you should report that as a FATAL error.

Also, your logging messages should not unnecessarily waste CPU cycles. Use the isDebugEnabled() and isInfoEnabled() methods on the Logger object to guard your logging operations, like this:

    public doStuff() {
        if (logger.isInfoEnabled()) {
            logger.info("Doing stuff!");
        }
        ...
    }

This will reduce the CPU requirements of your program when the INFO and/or DEBUG levels of logging are turned off in the logging-config. You don't need to do the same thing for the WARN, ERROR, or FATAL levels, because these kinds of logging messages are much rarer, and should have far less impact on the CPU requirements of logging. (This is why there aren't any isErrorEnabled() kinds of methods on the Logger class!)

Logging Configuration

Speaking of configuration, you should also update your Boggle Server's main() method to use a org.apache.log4j.PropertyConfigurator to configure logging from a config-file. You should call that config-file logging.props (or something similar), and have the server's main() method configure Log4j using that file right at start-up, before any logging occurs.

You don't need to do anything fancy for the log configuration; just set up Log4j to report log messages to the console. (There is an example configuration file that does this in the "Short Introduction" document.) You can use this config file to change what level of logging is reported, while you test your code.

Useful Links

Here are some useful links about Log4j:


Last updated May 21, 2008. Copyright (C) 2008, California Institute of Technology.