Saturday May 10, 2008

Coding a Swing Explorer Plugin for NetBeans IDE

One of the cool encounters at JavaOne was with Maxim Zakharenkov, who is from Latvia, and his https://swingexplorer.dev.java.net/ project. The tool lets you introspect Swing applications and is discussed on Javalobby as well. Max told me that there's already an Eclipse plugin for his tool, but nothing for NetBeans IDE. We spent some time together yesterday and made quite some progress.

First, you need to be able to launch the Swing Explorer together with your own application. You can simply use Ant to do so:

    <target name="swingexplorer-run" description="swingexplorer-run" depends="jar">
        <property file="nbproject/project.properties"/>
        <java classpath="${run.classpath}" classname="org.swingexplorer.Launcher" fork="true">
            <arg value="${main.class}"/>
            <jvmarg line="-Dswex.mport=1099 -Dcom.sun.management.jmxremote"/>
        </java>
    </target>

Put the swexpl.jar from the Swing Explorer project on your classpath and you're good to go. When you then run the above script, your own app will start as well as the Swing Explorer, where you can then inspect the insides of your application and play its creation back to you (which needs to be seen to be believed).

So, that's how it works from Ant. However, what do we need to do to create a plugin so that a menu item can be selected on the project which would then launch the above?

In addition, the Eclipse plugin lets you, when you play an application, jump from each played line to the line in the editor. So, as each method is played, you can click in the Swing Explorer and then the file that is being played is opened, with the cursor landing on the appropriate line. This is made possible via JMX. For diagnostics purposes, Max and I set up VisualVM and subscribed to notifications, so that we could see how this part of the application worked and to see the properties that are exposed:

(Would be nice if the columns in the table above could be resized and that a tooltip would appear displaying a column's content.)

Therefore, what the NetBeans plugin needs to do is listen for changes on the related JMX notifications and then open the editor when the "src" button is clicked in the Swing Explorer.

  1. First create an action on project nodes, from which you'll run the Swing Explorer in combination with your own applicaation:

    <folder name="Projects">
         <folder name="Actions">
             <file name="org-netbeans-swingexplorerplugin-LaunchSE.instance"/>
             <attr name="position" intvalue="0"/>
         </folder>
    </folder>

    Open issue: How to make the action available only to Java applications, but no other project type? Currently, the menu item is displayed on all project types, which is inappropriate.

  2. In the above action's performAction, create and run the Ant script:

    protected void performAction(Node[] activatedNodes) {
        Project project = activatedNodes[0].getLookup().lookup(Project.class);
        String script = "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>" +
                "<project name=\\"SwingExplorerRun\\" default=\\"swingexplorer-run\\" basedir=\\".\\">" +
                "<import file=\\"nbproject/build-impl.xml\\"/>" +
                "<target name=\\"swingexplorer-run\\" description=\\"swingexplorer-run\\" depends=\\"jar\\">" +
                "<property file=\\"nbproject/project.properties\\"/>" +
                "<java classpath=\\"${run.classpath}\\" classname=\\"org.swingexplorer.Launcher\\" fork=\\"true\\">" +
                "<arg value=\\"${main.class}\\"/>" +
                "<jvmarg line=\\"-Dswex.mport=1099 -Dcom.sun.management.jmxremote\\"/>" +
                "</java>" +
                "</target>" +
                "</project>";
        try {
            FileObject zfO = FileUtil.createData(project.getProjectDirectory(), "ant-swe.xml");
            File zf = FileUtil.toFile(zfO);
            BufferedWriter out = new BufferedWriter(new FileWriter(zf.getAbsoluteFile()));
            out.write(script);
            out.close();
            FileObject zfo = FileUtil.toFileObject(FileUtil.normalizeFile(zf));
            ActionUtils.runTarget(zfo, new String[]{"swingexplorer-run"}, null);
            zf.deleteOnExit();
        } catch (IOException e) {
            System.out.println("IO error: " + e);
        }
    }

    Open issue: Maybe use ContextualAwareAction instead of CookieAction, to somehow prevent the menu item from being shown when the current project is not a Java project? But what distinguishes Java projects from other projects?

    Another open issue is that ideally the JAR files would be put on the classpath in the background. Currently I'm putting them there manually. They should be hidden from the user, though, so that they're not visible in the Libraries node and are definitely not packaged into the application's JAR.

  3. Implement an Ant logger that listens for the above target and then connects to JMX:

    public class SwingExplorerListener extends org.apache.tools.ant.module.spi.AntLogger {
    
        @Override
        public boolean interestedInSession(AntSession session) {
            return true;
        }
    
        @Override
        public boolean interestedInAllScripts(AntSession session) {
            return true;
        }
    
        @Override
        public String[] interestedInTargets(AntSession session) {
            return AntLogger.ALL_TARGETS;
        }
    
        @Override
        public void targetStarted(AntEvent event) {
            String targetName = event.getTargetName();
            if (targetName.equals("swingexplorer-run")) {
                Thread thread = new Thread() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(5000);
                            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:" + "1099" + "/server");
                            JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
                            MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
                            ObjectName name = new ObjectName("org.swingexplorer:name=IDESupport");
                            ActOpenSourceLine actOpenSourceLine = new ActOpenSourceLine();
                            mbsc.addNotificationListener(name, actOpenSourceLine, null, null);
                        } catch (Exception ex) {
                            Logger.getLogger(SwingExplorerListener.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                };
                thread.start();
            }
        }
    }

    For the above to work, you need a file called 'org.apache.tools.ant.module.spi.AntLogger' in META-INF/Services. In that file, simply put the FQN of the above class: 'org.netbeans.swingexplorerplugin.SwingExplorerListener'.

  4. Define the NotificationListener as follows:

    public class ActOpenSourceLine implements NotificationListener {
        public void handleNotification(Notification notification, Object handback) {
            HashMap map = (HashMap) notification.getUserData();
            String className = (String) map.get("className");
            int lineNumber = (Integer) map.get("lineNumber");
            OutputWriter writer;
            InputOutput io = IOProvider.getDefault().getIO("Hello Output", false);
            writer = io.getOut();
            writer.println("Swing Explorer Output: " + className + " at " + lineNumber);
        }
    }

    Result:

    Open issue: Now that I have the class name and the line number, how do I open the class into the IDE's Java editor?

Of course, once this is all working, it would be cool to get it to work for NetBeans Platform applications too. Then we'd be able to replay the creation of the entire NetBeans IDE...

About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« May 2008 »
SunMonTueWedThuFriSat
    
2
5
7
16
20
22
25
28
       
Today