Friday, March 8, 2013

Why does Netbeans beep? Debugging with a custom AWT Toolkit



I use Javascript to remote control Netbeans using a modified version of JRUNSCRIPTIN (http://kenai.com/projects/jrunscriptin). Most of the time my code works, but sometimes I was not getting the results I expected. When this happened, Netbeans would beep at me while performing certain editor actions. I was wondering if the beeping was related to why my code didn’t work? I was pretty sure the beeping was from java.awt.Toolkit.getDefaultToolkit().beep(). So I thought, let’s attach a debugger, see who is calling that. Well, it was a bit tricky to get Netbeans to debug itself. Possibly, I could have started two instances of Netbeans and used one to debug another – Netbeans won’t do this by default, but there is a way (http://wiki.netbeans.org/FaqAlternateUserdir). However, my code which remote controls Netbeans would get confused if more than one Netbeans was running, so I decided to look for another debugger.

I could have tried Eclipse, I could have tried JDeveloper. I’ve got both on my system, but possibly not the latest versions, so I might have had to muck around to get them to work with JDK7. Then I thought – there is a debugger that comes with the JDK – jdb. Let me try that. First I start Netbeans with debug on:

C:\Program Files\NetBeans 7.3 Beta 2\bin>netbeans64 -J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1044

Use “jps –v” to find out pid of netbeans. In another window, attach to it with JDB:

jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=1044

Now, using Windows, I know the beep() method is actually sun.awt.windows.WToolkit.beep(). You can find out the toolkit class your platform uses with “System.println(Toolkit.getDefaultToolkit().getClass().getName())”. The platform default is overridden by “-Dawt.toolkit=com.example.MyAWTToolkit”. So I try to set a breakpoint with jdb on “sun.awt.windows.WToolkit.beep”:

> stop in sun.awt.windows.WToolkit.beep
Internal exception:
com.sun.jdi.NativeMethodException: Cannot set breakpoints on native methods
        at com.sun.tools.jdi.EventRequestManagerImpl.createBreakpointRequest(EventRequestManagerImpl.java:814)

This seems to be some JDB limitation – Netbeans debugger can put a breakpoint on a native method, jdb can't. Hmm, what should I do.

So I decided upon the following somewhat unorthodox approach – I wrote my own AWT toolkit. My custom AWT Toolkit actually delegates everything to the platform one, except the beep() method also does “new Throwable().printStackTrace()”. It solved my problem. My toolkit isn’t perfect – I didn’t bother to delegate every method, it probably still has some StackOverflowError bugs (any method in java.awt.Toolkit which tests if the current Toolkit is the default one will tend to cause this) – but it seems to be enough for Netbeans to run – the fonts look ugly, but when it beeps, it tells me why!

Code is attached. Run it like so:

netbeans64 -J-Dawt.toolkit= awtbug.AWTBugToolkit -J-Xbootclasspath/a:
awtbug.jar -J-Dawtbug.realToolkit=sun.awt.windows.WToolkit

Here is a sample of the output:

java.lang.Throwable: AWTBugToolkit.beep()
at awtbug.AWTBugToolkit.beep(AWTBugToolkit.java:393)
at org.netbeans.modules.java.editor.imports.JavaFixAllImports.performFixImports(JavaFixAllImports.java:260)
at org.netbeans.modules.java.editor.imports.JavaFixAllImports.access$200(JavaFixAllImports.java:104)
at org.netbeans.modules.java.editor.imports.JavaFixAllImports$1.run(JavaFixAllImports.java:148)
at org.netbeans.modules.java.editor.imports.JavaFixAllImports$1.run(JavaFixAllImports.java:125)
at org.netbeans.api.java.source.JavaSource$1.run(JavaSource.java:644)
at org.netbeans.api.java.source.JavaSource$1.run(JavaSource.java:634)
at org.netbeans.api.java.source.JavaSource$MultiTask.run(JavaSource.java:488)
at org.netbeans.modules.parsing.impl.TaskProcessor.callUserTask(TaskProcessor.java:584)
at org.netbeans.modules.parsing.api.ParserManager$UserTaskAction.run(ParserManager.java:153)
at org.netbeans.modules.parsing.api.ParserManager$UserTaskAction.run(ParserManager.java:137)
at org.netbeans.modules.parsing.impl.TaskProcessor$2.call(TaskProcessor.java:201)
at org.netbeans.modules.parsing.impl.TaskProcessor$2.call(TaskProcessor.java:198)
at org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager.priorityIO(FileChangedManager.java:176)
at org.netbeans.modules.masterfs.providers.ProvidedExtensions.priorityIO(ProvidedExtensions.java:360)
at org.netbeans.modules.parsing.impl.Utilities.runPriorityIO(Utilities.java:74)
at org.netbeans.modules.parsing.impl.TaskProcessor.runUserTask(TaskProcessor.java:198)
at org.netbeans.modules.parsing.api.ParserManager.parse(ParserManager.java:104)
at org.netbeans.api.java.source.JavaSource.runUserActionTaskImpl(JavaSource.java:438)
at org.netbeans.api.java.source.JavaSource.runUserActionTask(JavaSource.java:409)
at org.netbeans.api.java.source.JavaSource.runModificationTask(JavaSource.java:655)
at org.netbeans.modules.java.editor.imports.JavaFixAllImports$2.run(JavaFixAllImports.java:164)
at org.netbeans.modules.progress.ui.RunOffEDTImpl$1.run(RunOffEDTImpl.java:150)
at org.openide.util.RequestProcessor$Task.run(RequestProcessor.java:1454)
at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:2036)

Now off to read the Netbeans source code, to find out why. (Having done so, I’m ironically left with the conclusion, that I don’t think the beeps have anything to do with why my script doesn’t work.)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The code:

package awtbug;

import java.awt.AWTError;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Checkbox;
import java.awt.CheckboxMenuItem;
import java.awt.Choice;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dialog;
import java.awt.Dialog.ModalExclusionType;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Insets;
import java.awt.JobAttributes;
import java.awt.KeyboardFocusManager;
import java.awt.Label;
import java.awt.List;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.PageAttributes;
import java.awt.Panel;
import java.awt.Point;
import java.awt.PopupMenu;
import java.awt.PrintJob;
import java.awt.ScrollPane;
import java.awt.Scrollbar;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.InvalidDnDOperationException;
import java.awt.dnd.peer.DragSourceContextPeer;
import java.awt.event.AWTEventListener;
import java.awt.font.TextAttribute;
import java.awt.im.InputMethodHighlight;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.awt.peer.ButtonPeer;
import java.awt.peer.CanvasPeer;
import java.awt.peer.CheckboxMenuItemPeer;
import java.awt.peer.CheckboxPeer;
import java.awt.peer.ChoicePeer;
import java.awt.peer.DesktopPeer;
import java.awt.peer.DialogPeer;
import java.awt.peer.FileDialogPeer;
import java.awt.peer.FontPeer;
import java.awt.peer.FramePeer;
import java.awt.peer.KeyboardFocusManagerPeer;
import java.awt.peer.LabelPeer;
import java.awt.peer.LightweightPeer;
import java.awt.peer.ListPeer;
import java.awt.peer.MenuBarPeer;
import java.awt.peer.MenuItemPeer;
import java.awt.peer.MenuPeer;
import java.awt.peer.MouseInfoPeer;
import java.awt.peer.PanelPeer;
import java.awt.peer.PopupMenuPeer;
import java.awt.peer.ScrollPanePeer;
import java.awt.peer.ScrollbarPeer;
import java.awt.peer.TextAreaPeer;
import java.awt.peer.TextFieldPeer;
import java.awt.peer.WindowPeer;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.Properties;
import sun.awt.HeadlessToolkit;
import sun.awt.KeyboardFocusManagerPeerProvider;

/**
 * AWTBUG is a dummy AWT toolkit implementation used for debugging. It delegates
 * all calls to the standard AWT toolkit, but it intercepts beep() and prints
 * the stack trace.
 *
 * @author Simon Kissane
 */
public final class AWTBugToolkit extends Toolkit implements
        KeyboardFocusManagerPeerProvider {

   private Toolkit toolkit = getNamedToolkit();

   private static synchronized Toolkit getNamedToolkit() {
      final String nm = System.getProperty("awtbug.realToolkit");
      return (Toolkit) java.security.AccessController.doPrivileged(
              new java.security.PrivilegedAction() {
         @Override
         public Object run() {
            Toolkit toolkit = null;
            Class cls = null;
            try {
               try {
                  cls = Class.forName(nm);
               } catch (ClassNotFoundException e) {
                  ClassLoader cl = ClassLoader.getSystemClassLoader();
                  if (cl != null)
                     try {
                        cls = cl.loadClass(nm);
                     } catch (ClassNotFoundException ee) {
                        throw new AWTError("Toolkit not found: " + nm);
                     }
               }
               if (cls != null) {
                  toolkit = (Toolkit) cls.newInstance();
                  if (GraphicsEnvironment.isHeadless())
                     toolkit = new HeadlessToolkit(toolkit);
               }
            } catch (InstantiationException e) {
               throw new AWTError("Could not instantiate Toolkit: " + nm);
            } catch (IllegalAccessException e) {
               throw new AWTError("Could not access Toolkit: " + nm);
            }
            return toolkit;
         }
      });
   }

   @Override
   public KeyboardFocusManagerPeer createKeyboardFocusManagerPeer(
           KeyboardFocusManager kfm) {
      return ((KeyboardFocusManagerPeerProvider) toolkit).
              createKeyboardFocusManagerPeer(kfm);
   }

   private Object call(String method) {
      return call(method, new Class[0], new Object[0]);
   }

   private Object call(final String method,
           final Class[] types, final Object[] args) {
      return AccessController.doPrivileged(new PrivilegedAction() {
         @Override
         public Object run() {
            try {
               Class c = Toolkit.class;
               Method m = c.getDeclaredMethod(method, types);
               m.setAccessible(true);
               return m.invoke(toolkit, args);
            } catch (NoSuchMethodException | SecurityException |
                    IllegalAccessException | IllegalArgumentException |
                    InvocationTargetException ex) {
               throw new RuntimeException(ex);
            }
         }
      });
   }

   @Override
   public DesktopPeer createDesktopPeer(Desktop target) throws HeadlessException {
      return (DesktopPeer) call("createDesktopPeer",
              new Class[]{Desktop.class}, new Object[]{target});
   }

   @Override
   public void setDynamicLayout(boolean dynamic) throws HeadlessException {
      toolkit.setDynamicLayout(dynamic);
   }

   @Override
   public boolean isDynamicLayoutActive() throws HeadlessException {
      return toolkit.isDynamicLayoutActive();
   }

   @Override
   public Insets getScreenInsets(GraphicsConfiguration gc) throws
           HeadlessException {
      return toolkit.getScreenInsets(gc);
   }

   @Override
   public Image createImage(byte[] imagedata) {
      return toolkit.createImage(imagedata);
   }

   @Override
   public PrintJob getPrintJob(Frame frame, String jobtitle,
           JobAttributes jobAttributes, PageAttributes pageAttributes) {
      return toolkit.getPrintJob(frame, jobtitle, jobAttributes, pageAttributes);
   }

   @Override
   public int getMenuShortcutKeyMask() throws HeadlessException {
      return toolkit.getMenuShortcutKeyMask();
   }

   @Override
   public boolean getLockingKeyState(int keyCode) throws
           UnsupportedOperationException {
      return toolkit.getLockingKeyState(keyCode);
   }

   @Override
   public void setLockingKeyState(int keyCode, boolean on) throws
           UnsupportedOperationException {
      toolkit.setLockingKeyState(keyCode, on);
   }

   @Override
   public Cursor createCustomCursor(Image cursor, Point hotSpot, String name)
           throws IndexOutOfBoundsException, HeadlessException {
      return toolkit.createCustomCursor(cursor, hotSpot, name);
   }

   @Override
   public Dimension getBestCursorSize(int preferredWidth, int preferredHeight)
           throws HeadlessException {
      return toolkit.getBestCursorSize(preferredWidth, preferredHeight);
   }

   @Override
   public int getMaximumCursorColors() throws HeadlessException {
      return toolkit.getMaximumCursorColors();
   }

   @Override
   public boolean isFrameStateSupported(int state) throws HeadlessException {
      return toolkit.isFrameStateSupported(state);
   }

   @Override
   public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
           Class<T> abstractRecognizerClass,
           DragSource ds, Component c, int srcActions, DragGestureListener dgl) {
      return toolkit.createDragGestureRecognizer(abstractRecognizerClass, ds, c,
              srcActions, dgl);
   }

   @Override
   public void addPropertyChangeListener(String name, PropertyChangeListener pcl) {
      toolkit.addPropertyChangeListener(name, pcl);
   }

   @Override
   public void removePropertyChangeListener(String name,
           PropertyChangeListener pcl) {
      toolkit.removePropertyChangeListener(name, pcl);
   }

   @Override
   public PropertyChangeListener[] getPropertyChangeListeners() {
      return toolkit.getPropertyChangeListeners();
   }

   @Override
   public PropertyChangeListener[] getPropertyChangeListeners(
           String propertyName) {
      return toolkit.getPropertyChangeListeners(propertyName);
   }

   @Override
   public boolean isAlwaysOnTopSupported() {
      return toolkit.isAlwaysOnTopSupported();
   }

   @Override
   public void addAWTEventListener(AWTEventListener listener, long eventMask) {
      toolkit.addAWTEventListener(listener, eventMask);
   }

   @Override
   public void removeAWTEventListener(AWTEventListener listener) {
      toolkit.removeAWTEventListener(listener);
   }

   @Override
   public AWTEventListener[] getAWTEventListeners() {
      return toolkit.getAWTEventListeners();
   }

   @Override
   public AWTEventListener[] getAWTEventListeners(long eventMask) {
      return toolkit.getAWTEventListeners(eventMask);
   }

   @Override
   public boolean areExtraMouseButtonsEnabled() throws HeadlessException {
      return toolkit.areExtraMouseButtonsEnabled();
   }

   @Override
   public Dimension getScreenSize() throws HeadlessException {
      return toolkit.getScreenSize();
   }

   @Override
   public int getScreenResolution() throws HeadlessException {
      return toolkit.getScreenResolution();
   }

   @Override
   public ColorModel getColorModel() throws HeadlessException {
      return toolkit.getColorModel();
   }

   @Override
   public String[] getFontList() {
      return toolkit.getFontList();
   }

   @Override
   public FontMetrics getFontMetrics(Font font) {
      return toolkit.getFontMetrics(font);
   }

   @Override
   public void sync() {
      toolkit.sync();
   }

   @Override
   public Image getImage(String filename) {
      return toolkit.getImage(filename);
   }

   @Override
   public Image getImage(URL url) {
      return toolkit.getImage(url);
   }

   @Override
   public Image createImage(String filename) {
      return toolkit.createImage(filename);
   }

   @Override
   public Image createImage(URL url) {
      return toolkit.createImage(url);
   }

   @Override
   public boolean prepareImage(Image image, int width, int height,
           ImageObserver observer) {
      return toolkit.prepareImage(image, width, height, observer);
   }

   @Override
   public int checkImage(Image image, int width, int height,
           ImageObserver observer) {
      return toolkit.checkImage(image, width, height, observer);
   }

   @Override
   public Image createImage(ImageProducer producer) {
      return toolkit.createImage(producer);
   }

   @Override
   public Image createImage(byte[] imagedata, int imageoffset, int imagelength) {
      return toolkit.createImage(imagedata, imageoffset, imagelength);
   }

   @Override
   public PrintJob getPrintJob(Frame frame, String jobtitle, Properties props) {
      return toolkit.getPrintJob(frame, jobtitle, props);
   }

   @Override
   public void beep() {
      new Throwable("AWTBugToolkit.beep()").printStackTrace();
      toolkit.beep();
   }

   @Override
   public Clipboard getSystemClipboard() throws HeadlessException {
      return toolkit.getSystemClipboard();
   }

   @Override
   public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge)
           throws InvalidDnDOperationException {
      return toolkit.createDragSourceContextPeer(dge);
   }

   @Override
   public boolean isModalityTypeSupported(ModalityType modalityType) {
      return toolkit.isModalityTypeSupported(modalityType);
   }

   @Override
   public boolean isModalExclusionTypeSupported(
           ModalExclusionType modalExclusionType) {
      return toolkit.isModalExclusionTypeSupported(modalExclusionType);
   }

   @Override
   public Map<TextAttribute, ?> mapInputMethodHighlight(
           InputMethodHighlight highlight)
           throws HeadlessException {
      return toolkit.mapInputMethodHighlight(highlight);
   }

   @Override
   protected MouseInfoPeer getMouseInfoPeer() {
      return (MouseInfoPeer) call("getMouseInfoPeer",
              new Class[0], new Object[0]);
   }

   @Override
   protected LightweightPeer createComponent(Component target) {
      return (LightweightPeer) call("createComponent", new Class[]{
                 Component.class}, new Object[]{target});
   }

   @Override
   protected void loadSystemColors(int[] systemColors) throws HeadlessException {
      call("loadSystemColors", new Class[]{new int[0].getClass()},
              new Object[]{systemColors});
   }

   @Override
   protected boolean isDynamicLayoutSet() throws HeadlessException {
      return (Boolean) call("isDynamicLayoutSet", new Class[0], new Object[0]);
   }

   @Override
   protected Object lazilyLoadDesktopProperty(String name) {
      return call("lazilyLoadDesktopProperty", new Class[]{String.class},
              new Object[]{name});
   }

   @Override
   protected void initializeDesktopProperties() {
      call("initializeDesktopProperties");
   }

   @Override
   protected ButtonPeer createButton(Button target) throws HeadlessException {
      return (ButtonPeer) call("createButton",
              new Class[]{Button.class}, new Object[]{target});
   }

   @Override
   protected TextFieldPeer createTextField(TextField target) throws
           HeadlessException {
      return (TextFieldPeer) call("createTextField",
              new Class[]{TextField.class},
              new Object[]{target});
   }

   @Override
   protected LabelPeer createLabel(Label target) throws HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected ListPeer createList(List target) throws HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected CheckboxPeer createCheckbox(Checkbox target) throws
           HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected ScrollbarPeer createScrollbar(Scrollbar target) throws
           HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected ScrollPanePeer createScrollPane(ScrollPane target) throws
           HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected TextAreaPeer createTextArea(TextArea target) throws
           HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected ChoicePeer createChoice(Choice target) throws HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected FramePeer createFrame(Frame target) throws HeadlessException {
      return (FramePeer) call("createFrame",
              new Class[]{Frame.class},
              new Object[]{target});
   }

   @Override
   protected CanvasPeer createCanvas(Canvas target) {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected PanelPeer createPanel(Panel target) {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected WindowPeer createWindow(Window target) throws HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected DialogPeer createDialog(Dialog target) throws HeadlessException {
      return (DialogPeer) call("createDialog",
              new Class[]{Dialog.class},
              new Object[]{target});
   }

   @Override
   protected MenuBarPeer createMenuBar(MenuBar target) throws HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected MenuPeer createMenu(Menu target) throws HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   protected PopupMenuPeer createPopupMenu(PopupMenu target) throws
           HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public MenuItemPeer createMenuItem(MenuItem target) throws
           HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public FileDialogPeer createFileDialog(FileDialog target) throws
           HeadlessException {
      throw new UnsupportedOperationException("Not supported yet.");
   }

   @Override
   public CheckboxMenuItemPeer createCheckboxMenuItem(CheckboxMenuItem target)
           throws HeadlessException {
      return (CheckboxMenuItemPeer) call("createCheckboxMenuItem",
              new Class[]{CheckboxMenuItem.class},
              new Object[]{target});
   }

   @Override
   public FontPeer getFontPeer(String name, int style) {
      return (FontPeer) call("getFontPeer",
              new Class[]{String.class, Integer.TYPE},
              new Object[]{name, style});
   }

   @Override
   public EventQueue getSystemEventQueueImpl() {
      return (EventQueue) call("getSystemEventQueueImpl");
   }
}

No comments:

Post a Comment