Monday, October 12, 2009

How to have more than one instance of AWT

I have been pondering a problem with the Costello, the test recording tool in Abbot. The one main draw back to this tool is that you can't access the recording window then the application you are testing has a modal dialog showing. This is a sever limitation that we can work around to some extent by modifying the window exclusion type for the Costello window after it is shown.


import abbot.editor.Costello;
import abbot.editor.ScriptEditorFrame;


{
  ...

  Costello.showCostello(ec);


  for (Frame next : Frame.getFrames() )
  {
    if (next instanceof ScriptEditorFrame)
    {
      next.setModalExclusionType(
        Dialog.ModalExclusionType.APPLICATION_EXCLUDE);
    }
  }

  ...
}

This is fine if you can accept that dialogs that used to be modal on Costello are no longer; but this is likely to cause bugs as the developer of Costello wasn't expecting this. (What does it mean to have two file open dialog up at the same time?)

(Anybody who believes in the sanctity of the Event Dispatch Thread please look away now, this means you Alex)

Underneath the covers swing makes use of the AppContext class to maintain the settings of a particular "instance" of AWT. For most cases you only have the one instance; but you need more in the case of applets in order to isolate them from each other. Each AppContext also has its own event thread so a deadlock in one applet cannot affect another. (Useful for helping a user when say JDeveloper deadlocks)

The actual AppContext is contained within a kinda inheritable-ThreadGroup-local variable. By default all thread groups will default to default "main" AppContext; but if you create a new ThreadGroup and an AppContext every thread and group under this will make use of the new context. Consider the following code that puts up a frame with a modal child window both on the "default" AppContext and a new AppContext created for just this occasion:


import sun.awt.AppContext;
import sun.awt.SunToolkit;

...

public static void main(String[] args) throws Exception {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    new Example("Main Event Thread");

    ThreadGroup group = new ThreadGroup("SomeOtherGroup");
    new Thread(group, "Start Another Frame") {
            public void run() {
                // Create a new appContext
                AppContext appContext = SunToolkit.createNewAppContext();
                // Create a second frame, independent from the first
                new Example("Other Event Thread");
            }
        }.start();
}

You end up with two windows each able to show a modal dialog at the same time. You can just about make out the different look and feels:

A quick look at the debugger shows that indeed we do have two independent event dispatch threads:

The only thing that is shared between the different AppContext is java.awt.Component.LOCK which is a bit of a pain. I am thinking about logged a bug saying that the tree lock should be on the AppContext to prevent one from blocking another. You can see this would be quite a trivial DOS applet attack. The most obvious work around is to re-load the classes in a separate class loader; but that presents some interaction issues so I will leave that as an exercise to the reader.

No comments: