Monday Sep 29, 2008

Spring RCP Application Initialized From Griffon

To get them to co-exist, somehow Spring RCP's application class should replace the Griffon application class, or the other way round. Maybe I need to create an ApplicationBuilder for Spring RCP? There's also threading issues to figure out. Anyway, right now I'm able to show both applications: the Griffon application and the Spring RCP application (at least, their main windows):

Here's the application context, containing one single bean only, for the application class:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="application" class="org.springframework.richclient.application.Application">
    </bean>

</beans>

Now the question is from where to initialize the above. In "Startup.groovy", I have this:

def rootController = app.controllers.root
rootController.startApplication()

...which points to this in the above referenced controller:

def appContextPath = "richclient-application-context.xml"

def startApplication = { evt = null ->
    edt {
        withWorker( start: true ) {
            onInit {
            }
            work {
                try {
                    new ApplicationLauncher(appContextPath)
                } catch (RuntimeException e) {
                    println 'Failure'
                }
            }
            onDone {
            }
        }
    }
}

Without SwingWorker, I couldn't get it to work. Then when the application is run, first the Griffon view is shown, then you see Spring RCP output (all about initialization of Spring RCP internals), and then the application class is initialized and displayed. Whether the above is a step forward or not I don't know. But at least it shows the Spring RCP application, which is nice to see in this context.

Sunday Sep 28, 2008

Spring's MethodInvokingFactoryBean & Griffon

Here's my Spring application context:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetClass">
            <value>java.lang.System</value>
        </property>
        <property name="targetMethod">
            <value>getProperties</value>
        </property>
    </bean>

    <bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetObject">
            <ref local="sysProps"/>
        </property>
        <property name="targetMethod">
            <value>getProperty</value>
        </property>
        <property name="arguments">
            <list>
                <value>java.version</value>
            </list>
        </property>
    </bean>

</beans>

And here's my Griffon "Initialize.groovy":

import org.springframework.richclient.application.ApplicationLauncher

def appContextPath = "richclient-application-context.xml"

try {
    new ApplicationLauncher(appContextPath)
} catch (RuntimeException e) {
    println 'Failure'
}

Next, in "Startup.groovy", I initialize the controller:

def rootController = app.controllers.root
rootController.gotoPage()

And now I can get the application context and inject the Spring bean defined at the start of this blog entry:

class GriffonDemo1Controller {

    def view

    def gotoPage() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("richclient-application-context.xml")
        String jVersion = ctx.getBean("javaVersion")
        view.versionLabel.text = "Java Version: " + jVersion
    }

}

The final line above refers to my view, which is exactly this:

application(title:'GriffonDemo1',  size:[250,50], location:[50,50], locationByPlatform:true) {

    label(id:'versionLabel')

}

Having invoked the method on the Spring bean, the code above shows the result in a label in the view, which therefore now looks like this:

One improvement might be to set the application context in the model, via "Initialize.groovy", and then retrieve it in the view, so that everything is nicely centralized, instead of repeating the location/name of the application context, which is what I'm doing above.

It follows from the above that if you use Spring to create your Swing applications, you can code in Groovy and structure your sources according to the strict MVC pattern encouraged by Griffon. I also believe that this could be the basis for Spring RCP integration with Griffon, which implies docking framework support (VLDocking, JIDE, etc) for Griffon.

Saturday Sep 27, 2008

Case Study: Customizing the NetBeans Window System

The NetBeans window system can be used "as is" in, I guess, 90% of cases. However, it's that final 10% that's always the problem! That's when the NetBeans window system becomes, potentially, a little bit painful. Less so, though, when you become aware of the MANY different ways in which you can tweak it. I want to illustrate this point by means of a concrete example.

You're working on a welcome screen with the following requirements:

  1. It should be undocked when the application starts.
  2. It should be modal so that when the user clicks on the application behind it, the welcome screen shouldn't disappear behind it.
  3. It should have a certain default size.
  4. It should have no buttons or right-click menu items or anything else in its titlebar, except for 'Close'.
  5. When 'Close' is clicked, it should dock into the right sliding side.
  6. When it is 'slided in', i.e., when the mouse hovers over the button in the right sliding side, the full welcome screen should be shown, instead of the default slided in size, which is much smaller.
  7. The application must run on NetBeans Platform 6.1, which means that the previous requirement cannot be solved in the new 6.5 way of doing so (via a client property in the TopComponent constructor).
  8. When the 'Pin' event is invoked, i.e., when the user clicks the 'Pin' button while the welcome screen is slided in from its position in the right sliding side, it should be docked back into its original undocked mode.

Now, in case you don't realize it, the above puts a lot of strain on the NetBeans window system. In terms of the concepts made available by the NetBeans window system, the above requirements are extremely obtuse. Below I will describe one solution to this case study. I think if you read what follows closely enough, anyone wrestling with the NetBeans window system should gain a lot from it. Just to give a clear impression of the intended end result, below the screenshots show a mock up of the solution. In the first screenshot, you see the welcome screen (empty of content, but that's not the point), exactly as it should appear when the user starts the application:

When the user clicks behind the welcome screen, the welcome screen does not disappear behind the application, i.e., the welcome screen is modal. Next, here's a screenshot that shows the welcome screen when it is closed, i.e., it is never actually closed; it 'closes' into the right sliding side:

When it is opened from its closed state, it should be exactly as shown in the first screenshot.

And the reason for all of the above? The users of the application are either not finding the welcome screen or find that it gets in their way. They can't find the 'undock' functionality very easily and so, whenever the 'editor' mode is occupied by documents (or whatever else is docked there) the welcome screen (which is in the 'editor' mode by default) is hidden and thereby unusable. By making the welcome screen more prominent (as described in the list of requirements above), the user is more likely to find it and also to find it useful. The easy switching between undocked and slided out also aids in this.

A summary of everything that you'd need to do to obtain the above result:

  • Create a new mode for the undocked welcome screen. When the application starts, the welcome screen should be modal and undocked. So you need a new mode (i.e., a new position) in the window system, because none of the default modes provide these features. Modes are defined in XML. Here's the new mode for the above:
    <?xml version="1.0" encoding="UTF-8"?>
    <mode version="2.3">
        <name unique="welcomeScreen" />
        <kind type="view" />
        <state type="separated" />
        <constraints>
            <path orientation="vertical" number="0" weight="0.5"/>
            <path orientation="horizontal" number="1" weight="0.5"/>
        </constraints>
        <bounds x="312" y="237" width="679" height="378" />
        <frame state="0"/>
        <active-tc  id="DemoTopComponent" />
        <empty-behavior permanent="true"/>
    </mode>

    Brief explanation of each element: 'name' is used in the layer to hook the welcome screen to this mode; 'kind' specifies that the mode will cause its TopComponents to be modal (instead of 'editor' and 'slidingSide'); 'state' specifies that the mode supports undocked TopComponents; 'constraints' and 'bounds' set position and size; 'frame' sets whether the frame is normal or iconified (or a few others, see the JFrame documentation); 'active-tc' sets which of the TopComponents within the mode will be active by default; 'empty-behavior' sets whether the mode will be destroyed if it is empty (normally, best to always set it to be 'permanent=true').

  • Connect the mode to the TopComponent. In the layer, you need this, taking careful note of the section in bold:
    <folder name="Windows2">
        <folder name="Components">
            <file name="DemoTopComponent.settings" url="DemoTopComponentSettings.xml"/>
        </folder>
        <folder name="Modes">
            <file name="welcomeScreen.wsmode" url="welcomeScreenWsmode.xml"/>
            <folder name="welcomeScreen">
                <file name="DemoTopComponent.wstcref" url="DemoTopComponentWstcref.xml"/>
            </folder>
        </folder>
    </folder>

  • Use the installer to force the TopComponent into the mode. Despite all of the above, I found that the TopComponent was still not modal. It appeared in the right place, but it wasn't modal. So I forced it via the installer:
    public class Installer extends ModuleInstall {
    
        @Override
        public void restored() {
            WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
    
                public void run() {
                    DemoTopComponent demoTC = DemoTopComponent.findInstance();
                    Mode mode = WindowManager.getDefault().findMode("welcomeScreen");
                    if (mode != null) {
                        mode.dockInto(demoTC);
                        demoTC.open();
                    }
                }
            });
    
        }
    }

  • Override TopComponent.componentClose. Instead of the standard Close behavior, you want the welcome screen to be docked into the right sliding side. The right sliding side is provided by one of the default modes, called 'rightSlidingSide'. You can reuse it as follows:
    @Override
    public void componentClosed() {
        Mode mode = WindowManager.getDefault().findMode("rightSlidingSide");
        if (mode != null) {
            mode.dockInto(this);
            this.open();
        }
    }

    So, whenever Close is clicked, the welcome screen will dock into the right sliding side.

  • Handle the Pin event problem. When the user hovers with the mouse over the slided out welcome screen (i.e., when it is docked into the right sliding side), the welcome screen forms a pop up (called 'slided in' in NetBeans terminology). This slided in mode has some problems for our scenario. Firstly, the default size cannot be controlled by our module. In 6.5 this is changed, i.e., in 6.5 each TopComponent can set its slided in size (client property in constructor specifies that TopComponent preferred size should be used when sliding in). However, if we want the whole welcome screen to be shown in 6.1, we'd need to create a new mode. And that's where the TopComponent should be slided out to, instead of the right sliding side. Secondly, when slided in, the TopComponent has a button which, when you hover over it, says 'Pin'. When clicked 'Pin' behaves oddly, in my opinion. It should return our TopComponent to its original undocked modal state, but instead it puts the welcome screen in the 'explorer' view, where it doesn't make any sense.

    To get around both these problems, override TopComponent.requestVisible, which is called when the welcome screen is slided in. So, whenever the TopComponent slides in, you will force it into your own custom mode, which is the undocked and modal mode with which we started:

    @Override
    public void requestVisible() {
        Mode mode = WindowManager.getDefault().findMode("welcomeScreen");
        if (mode != null) {
            mode.dockInto(this);
            this.open();
        }
    }

    In this way, the user is never confronted with the 'Pin' event. The user also doesn't ever see the 'slided in' mode, so that the size of the popup is irrelevant.

  • Force the menu item in the menu bar to dock the TopComponent into the custom mode. To make sure the custom mode is used when the user chooses the menu item that opens the welcome screen, do something similar to the code shown above, such as the below:
    @Override
    public void actionPerformed(ActionEvent evt) {
        DemoTopComponent demoTC = DemoTopComponent.findInstance();
        Mode mode = WindowManager.getDefault().findMode("welcomeScreen");
        if (mode != null) {
            mode.dockInto(demoTC);
            demoTC.open();
        }
    }

  • Remove the actions from the welcome screen titlebar. When the user right-clicks in the titlebar, there's an undock menu item, among others. These are dangerous because you want to control this particular TopComponent as much as possible. So remove them:
    @Override
    public Action[] getActions() {
        ArrayList actions = new ArrayList();
        Action[] retVal = new Action[actions.size()];
        actions.toArray(retVal);
        return retVal;
    }

And that's "all". Now you have absolute control of your welcome screen and it does exactly what you want it to do. As you can see, modifying the NetBeans window system can be done in various ways—through overriding methods on the TopComponent as well as some carefully thought out XML entries. The trick with the XML is to start up the application, put all the windows where you want them to be at start up, close down the application, and then move the generated XML from the application's user directory into your module's XML files. That, at least, gives a strong starting point, after which your tweaking can begin.

Friday Sep 26, 2008

From ServiceLoader to Lookup for Griffon

In An Approach to Pluggable Griffon Applications, I discussed how the JDK 6 ServiceLoader class is useful in the context of Griffon. In effect, it provides Griffon with plugins. However, the ServiceLoader doesn't have the concept of 'hot deploy'. Changes made on the classpath are only presented to a Griffon application upon restart. Let's take an alternative approach—instead of the ServiceLoader class, we'll use the Lookup class. Just add org-openide-util.jar from the NetBeans IDE distribution (and then, if that's how you feel, simply delete NetBeans IDE, because that one JAR is all you need) to the 'lib' folder of your Griffon application.

Now, as before, to get started, we need to create a JAR that provides a service. It exposes the service via this very simple service provider interface:

package com.example;

public interface ResultSet {

    public int getResult();
    
}

In this case, for this example, the JAR contains nothing other than the above. In reality, there could be a lot of supporting classes. But all that we need, in essence, is an interface to implement in our service provider.

Now, in our Griffon controller, we'll want to invoke the above method on all the implementations of that interface. However, in addition, wouldn't it be cool if we could set a listener on the result? Then, if the result changes, we'd know about it within our Griffon application. This is something that the ServiceLoader cannot do. As before, from our Griffon Startup.groovy, we call a method in the controller, for loading our service:

def rootController = app.controllers.root
rootController.loadService()

However, our service loader method (i.e., in the controller), is now as follows:

def Result res;

def loadService(){
    Lookup lkp = Selection.getSelection()
    res = lkp.lookupResult(ResultSet.class)
    res.addLookupListener(this)
    resultChanged(null)
}

The first line in our service loader method refers to a provider class, called 'Selection', which will define our lookup. We haven't defined this provider class yet. And what is a lookup, anyway? A lookup is "a bag of stuff". It is a map, with its keys being objects, with the values being instances of that object. Look at the code above. The 'key' of our map is called 'ResultSet'. That is the object that the service provider will have implemented, since that is the interface with which this blog entry started. And what is a 'Result' object? Well, for that you'll need to know our import statements for the above snippet:

import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Lookup.Result;

The result is the key 'ResultSet', to which we add a LookupListener. Finally, a method resultChanged will be called. This is for the first time we start up our Griffon application, at which point the result will not have changed. After that, whenever a change occurs to the Result, the resultChanged will be called automatically. Why automatically? Because our controller implements the LookupListener class. The only abstract method (i.e., the only required method) for a class implementing LookupListener is resultChanged. And here is the definition of that method:

StringBuilder sb = new StringBuilder()

@Override
public void resultChanged(LookupEvent arg0) {
    long mills = System.currentTimeMillis()
    Collection instances = res.allInstances()
    instances.each() {
        def nextResult = it.getResult()
        sb.append("At " + mills + ": " + nextResult + "\\n")
        view.encoderList.text = sb.toString()
    }
}

For each iteration through the collection of instances of the interface, we add a timestamp to the StringBuilder, together with the next result. This happens whenever a change is made to the instance. Again, "why?" And again, because the Groovy class implements LookupListener, which calls resultChanged whenever a change is made to any of the implementations of the given interface.

Then, in the final line above, the stringified version of the StringBuilder is put into 'view.encoderList.text'. What's that? Firstly, 'view' is defined as the view class (thanks to one of the Griffon configuration files), 'encoderList' is the id property of the JTextArea in the view, and 'text' is the JTextArea's 'text' property. See it in the penultimate line below, which is the complete definition of the view:

import javax.swing.border.TitledBorder
import javax.swing.border.EtchedBorder
application(title:'Encoder Sales', pack:true) {
    border = new TitledBorder( new EtchedBorder(), 'Incoming Results')
    panel(border:border) {
        textArea( id:'encoderList', rows:10, columns:30, editable:false )
   }    
}

Now we'll create one implementation of the ResultSet interface. We'll do so in a completely new JAR. Create a new Java application, put the JAR that provides the interface on the JAR's classpath and then implement it (and have it created) as follows:

public class Selection {

    private Selection() {
    }
    
    private static MyLookup LKP = new MyLookup();

    //Accessor for the lookup:
    public static Lookup getSelection() {
        return LKP;
    }

    //The lookup, which adds new ResultSetImpls to itself:
    private static final class MyLookup extends ProxyLookup implements Runnable {

        private static ScheduledExecutorService EX = Executors.newSingleThreadScheduledExecutor();

        public MyLookup() {
            EX.schedule(this, 2000, TimeUnit.MILLISECONDS);
        }
        private int i;

        @Override
        public void run() {
            //Add to the Lookup a new ResultSetImpl:
            setLookups(Lookups.singleton(new ResultSetImpl(i++)));
            EX.schedule(this, 2000, TimeUnit.MILLISECONDS);
        }
    }

    //The implementation of the interface:
    private static final class ResultSetImpl implements ResultSet {

        public int result;

        public ResultSetImpl(int i) {
            result = i;
        }

        public int getResult() {
            return result;
        }

    }
    
}

Now, based on the above ScheduledExecutorService, a new instance of ResultSetImpl is added to the lookup, which is exposed via getSelection(). And that's the method that defined the lookup in the controller:

Lookup lkp = Selection.getSelection()

And when you run the above Griffon application, with the two JARs above on the classpath? The view shows the following:

The view is automatically updated, based on the above ScheduledExecutorService, which specifies that every few milliseconds a new instance of the implementation is created and added to the lookup, on which a LookupListener is set in the controller. Clearly, the key to all of this is that LookupListener. Without it, there would have been no way of knowing that the result is changing every few milliseconds and therefore no way of updating the view. In this way, long running processes can be handled by a separate JAR, with output asynchronously loaded into a Griffon application.

Thursday Sep 25, 2008

Injecting a Splash Screen via Spring into Griffon

Assuming the Spring RCP JARs are in the Griffon application's 'lib' folder, I believe it should be possible to put this into 'Startup.groovy':
import org.springframework.richclient.application.ApplicationLauncher

def startupContextPath = "richclient-startup-context.xml"

try {
    new ApplicationLauncher(startupContextPath)
} catch (RuntimeException e) {
    println 'Failure'
}

The XML file referred to above has this content:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="splashScreen" class="org.springframework.richclient.application.SplashScreen">
        <property name="imageResourcePath" value="splash-screen.jpg" />
    </bean>

</beans>

Together with the referenced 'splash-screen.jpg', the above XML file is in the Griffon application's 'griffon-app/resources' folder, which means that when I invoke 'griffon run-app', both these files are included in the JAR. Here's the application structure:

However, sometimes this seems to work and sometimes it doesn't. Sometimes the splash screen is shown and sometimes not. When it is shown, it is always shown very quickly. But often it is not shown. It is these varying results that are the strangest part of it. Possible reason for the problems—I'm using the wrong lifecycle file (though I've tried all of them). Maybe I should be using the controller somehow, but that seems too late, since the splash screen needs to load before the main window of the application. Maybe this in the controller:

def startupContextPath = "richclient-startup-context.xml";

def loadSplash(){
    doLater {
        try {
            new ApplicationLauncher(startupContextPath);
        } catch (RuntimeException e) {
            println 'Failure'
        }
    }
}

And then this in 'Startup.groovy':

def rootController = app.controllers.root
rootController.loadSplash()

Or maybe I should be loading the application context in a different way. However, since the splash screen is shown sometimes, it possibly means that there's something I'm not doing right in the lifecycle files, maybe need to put something in a special thread? But that's all I can think of.

Once the above works, it'll mean that a Spring bean created via Spring RCP has been successfully injected into a Griffon application. After that it should be investigated if/how the docking frameworks supported by Spring RCP can be used within a Griffon application, which would be very cool. One of the benefits from the perspective of Spring RCP users would be that they'd be able to code their Spring RCP application in Groovy.

Wednesday Sep 24, 2008

2nd Attempt at Griffon Support

I got tired of seeing the Grails icon whenever I was working with Griffon, so fashioned my own Griffon icon and then recreated the whole set of modules from scratch, with a completely fresh check out from hg. I.e., the first time I did everything on a hunch: I had the hunch that getting from Grails support to Griffon support would be a matter of branding. This time, I knew that was true, so went about it all a bit more carefully and have come up with a more reliable result. Still a few problems to fix though. Also, the Griffon support should, ideally, work in the same IDE as where Grails is found (though that's not a hard requirement, I think). So, enough things to do.

Meantime, here are the screenshots. You start with the project template:

...which creates your application and opens it inside the IDE. This time, the logical view is much better than before, with more nodes displayed:

There are many menu items on the project node, such as 'Run' and 'Compile' and so on, which all work. Similarly, on the folder-level, you can choose menu items which pop up GUIs for creating the Griffon artifacts, like controller, views, etc, via the actual Griffon scripts in the official Griffon distro that do the work under the hood. I'm hoping to be able to make some kind of distribution available in the Plugin Portal soon.

Monday Sep 22, 2008

Griffon as a Quick and Dirty Learning Tool

It's not like I'm constantly looking for useful scenarios for Griffon and then blogging about it. It's more like I keep tripping over them accidentally, all the time. I just can't help it! Here's the latest one. Let's say you watched Roumen's very cool Javalobby NetBeans Visual Library Presentation. "Cool, you think, let's learn about that." At some point you put together a piece of code like this:
public class GraphSceneImpl extends GraphScene {

    private LayerWidget mainLayer

    public GraphSceneImpl() {

        mainLayer = new LayerWidget (this)
        
        addChild (mainLayer)

        createWidget(mainLayer, LayoutFactory.SerialAlignment.JUSTIFY, 50, 50)
        createWidget(mainLayer, LayoutFactory.SerialAlignment.JUSTIFY, 20, 20)
        
    }

    private static void createWidget(LayerWidget layer, LayoutFactory.SerialAlignment align, int x, int y) {
        Widget widget = new Widget(layer.getScene())
        widget.setBorder(BorderFactory.createResizeBorder(8, Color.BLACK, false))
        widget.setLayout(LayoutFactory.createVerticalFlowLayout(align, 0))
        widget.setPreferredLocation(new Point(x, y))
        widget.setPreferredBounds(new Rectangle(200, 200))
        widget.getActions().addAction(ActionFactory.createResizeAction())
        widget.getActions().addAction(ActionFactory.createMoveAction())
        layer.addChild(widget)
    }

    protected Widget attachNodeWidget(Object node) {return null}

    protected Widget attachEdgeWidget(Object edge) {return null}

    protected void attachEdgeSourceAnchor(Object edge, Object oldSourceNode, Object newSourceNode) {}

    protected void attachEdgeTargetAnchor(Object edge, Object oldTargetNode, Object newTargetNode) {}

}

The widgets, border factory, action factory, and so on, all come from the org.netbeans.api.visual package, which is exactly the point: the Visual Library handles all these concerns for you, you can simply reuse them in your own code. Now, the question is how do you actually try out this code above?

For a start, you're a sloppy programmer, so you've left out all the semi-colons. Hurray. That's Groovy. Normally, to show the above Visual Library scene in a Java application, you'd need to create a Java application, add a container like a JFrame or a JPanel, and then add a JScrollPane, mess around with the ViewportView to which you add the scene's JComponent, via scene.createView(). Phew! That's a lot of work. How about doing it like this instead?:

That's literally the entire view of my application! The frame, the JScrollPane, the JComponent, everything, it's all there... just in a Groovy way, within the context of a simple Griffon application:

Now, when I run the application, I can immediately see, for three lines of code shown above (only one of which I wrote myself, since the other two are part of the output from 'griffon create-app'!), that my code has been correctly hooked up and that it's doing something useful. The resize action is working and so is the move action. The resize border is also how I'd expect it to be:

Furthermore, I can also deploy my small application as an applet, where it works exactly the same way:

In short, Griffon provides a cool prototyping & learning environment which can be used, for example, to focus on the specific APIs you're learning about... because Griffon handles everything else around it for you, from the creation of the application, to its structure, to giving you a placeholder for coding the few Groovy lines needed for hooking the API class into the Griffon application. Pretty cool.

Sunday Sep 21, 2008

Pluggable Applets via ServiceLoaders, Groovy, and Griffon

The first Griffon issue is solved: I made the mistake of including "pack: true", which fails in applets. Now I can start playing a bit with the applet side of Griffon. So, this is the pluggable Griffon application discussed yesterday:

In what way is the application pluggable? Read yesterday's blog entry but, in summary, be aware that none of the items in the list that you see above are defined in the Griffon application. Each item is made available by a different service provider and loaded into the list via the JDK 6 ServiceLoader class. For more details, read yesterday's blog entry. The only change is that I've added a border so that later, when the application is deployed as an applet, I have something to hold on to as I drag the applet out of the browser:

import javax.swing.border.TitledBorder

import javax.swing.border.EtchedBorder

application(title:'Encoder Sales') {

    border = new TitledBorder( new EtchedBorder(), 'Available Encoders')

    panel(border:border) {

        textArea( id:'encoderList', rows:10, columns:30, editable:false )

   }

}

Also note that above I removed "pack:true" from the application's properties. And here it is deployed to the web as an applet:

That's possible because when Griffon creates the applet (when you do "griffon run-app"), the value of the applet's archive element is a list of all the JARs on the application's classpath. Those JARs include the service providers (and the service itself), hence the resources defined in META-INF/services are made available to the applet. As a result, applets are pluggable too!

And, of course, one can drag the applet out of the browser (Firefox3 with JDK 6 Update 10):

By the way, did you know that you can close the browser... and the applet will still exist, running independently on your desktop?

Summary: I created the applet by first creating a Swing application in Groovy via the Griffon framework. I used the JDK 6 ServiceLoader class to plug three service providers into my application. Then I ran "griffon run-app" and, in addition to a Swing application, an applet (and a JNLP application) was created by Griffon. However, for the applet to work, I needed to remove "pack:true" from the view. When I ran the applet, I had the same result as the Swing application, i.e., the ServiceLoader loaded the service provider interface, thus letting me call the exposed method on each of the implementations registered in their JAR's META-INF/services folder. Hence, I did nothing at all to create the applet, Griffon simply generated it for me from my Groovy (in the Griffon application) and Java (in the service providers) source code. Among many other things, this example therefore shows yet another scenario where Groovy and Java work seamlessly together.

Saturday Sep 20, 2008

An Approach to Pluggable Griffon Applications

If I were someone evaluating the existing Swing desktop frameworks, I wouldn't hesitate to choose the NetBeans Platform over Griffon or Spring RCP—for one very specific reason: any application built atop the NetBeans Platform is inherently extensible. John O'Conner's definition of extensibility applies here: "An extensible application is one that you can extend easily without modifying its original code base." In this sense, a plugin is not what Grails understands a plugin to be. A Grails plugin is applicable to a developer making use of the Grails framework. For example, the Grails Wicket plugin lets users of Grails incorporate Wicket view technology into the development of a Grails application. I imagine that the Griffon creators have the same definition of "plugin" in mind. Plugins for users of a framework are incredibly useful—users of Grails and, at some point, Griffon, are not limited to the original code that the Grails (and Griffon) creators originally provided, nor do they need to wait for the next release of the framework before making use of their favorite technology in combination with Grails (or Griffon). They're able to create a plugin that extends the Grails (or Griffon) framework and can then merrily continue creating the application of their dreams using the technologies that they're personally most comfortable with.

Those plugins, though, are not the ones I have in mind here. I mean end-user plugins, such as those that users of Firefox can plug into Firefox to add new functionality to it. (Here's a nice opportunity for me to plug the DZone Voting plugin for Firefox. Try it, try it now! It's great.) Similarly, unlike the Griffon framework (and unlike Spring RCP), the NetBeans Platform lets you extend existing applications without changing the original code base, by creating plugins. Does this current gap in Griffon (and Spring RCP) functionality mean it should not be used? Not at all. Since both Griffon and Spring RCP let you create good old Java applications, you can use the JDK 6 java.util.ServiceLoader class and, without the Griffon (and Spring RCP) creators needing to do anything else, you're able to let users of applications built atop these frameworks extend it too! In addition, in each case there are certain benefits in using the ServiceLoader class in these two frameworks (the benefits being distinct and particular to each) that the NetBeans Platform cannot, at least on the face of it, benefit from. However, the NetBeans Platform doesn't make use of the ServiceLoader class, it has an objectively superior approach (which could be reused in Griffon or Spring RCP, by the way), but that's a different story. The short point of this whole story is that you can already create extensible applications in Griffon (and in Spring RCP, but I'll provide a full scenario around that another day).

Here's a condensed step-by-step generic approach to working with ServiceLoader:

  1. Create a service. A service is a set of classes that exposes features via a public interface.

  2. Create a service provider. A service provider provides one or more implementations of the service. In order to provide implementations of the service, the service provider needs only to have the JAR that defines the service on its classpath. In other words, the service and the service provider can be (but do not have to be) in different JARs. The service provider is sometimes referred to as an 'extension point'. The service provider can also be seen as a 'plugin'.

  3. Publish the service provider. A provider configuration file needs to be placed in the service provider JAR's META-INF/services folder. The name of the file needs to match the FQN of the service. Each service provider made available by the JAR needs to be named in the file, by its FQN.

  4. Distribute the service provider. The service provider JAR needs to be put on the classpath of the application that needs to be extended. The JAR that contains the service needs to be on that classpath too.

  5. Load the service. Within the application that needs to be extended, a ServiceLoader needs to have been defined. The ServiceLoader will load the service. Then methods defined on the service (i.e., the interface) are invoked, which are called on each of the available service providers, if they have been published as described in step 3 above.

Via the above approach, the application has no direct relationship to any of the service providers. If a service provider isn't there, it simply isn't loaded. A default service provider can be created to handle the situation where no service providers are available.

But... can this work with Groovy? If the answer is "Yes", then Griffon applications are extensible, aren't they, since Griffon is nothing more than strictly structured Groovy code? And what could be the answer other than "Yes", given that Groovy is Java? And that right there is the benefit that Griffon has over the NetBeans Platform when it comes to creating extensible applications—you have the additional option of using Groovy to do so. (However, I guess that one could probably also create NetBeans Platform applications in Groovy, but lets leave those ruminations for another day too.)

I reckon the ServiceLoader Javadoc is very good and so I'll use the example described there in my scenario below. So, based on that (maybe read it all, if you haven't yet, before going further) here's a simple scenario of how everything described above fits together concretely in the context of Griffon:

  1. Create the application. Run "griffon create-app" and create an application called "EncoderSales". Here's the application (at least, here's how it looks for me in NetBeans IDE, via my tweaked Grails plugins for NetBeans IDE):

    So, this is the application that we will deliver to our users. Let's say that it will let the user choose an encoder (for something or other) from a list and then (at some further stage in the application, not covered here) somehow purchase it. However, we want to make it possible for the application to be extensible, so that providers of other encoders can add their encoders to the list. The encoder market is large and growing, one assumes, so we need to let the application be supplemented externally with additional encoder offerings. That's a pretty realistic scenario.

  2. Create the service. So, and this is unavoidably step 1 of the whole process, we'll create a service. To that end, create a brand new Java application called 'CodecSetService', with an interface named 'com.example.CodecSet' (which is the name of the example service in the Javadoc). The service will define what the set of encoders will consist of, in order for a new encoder to be allowed to be added to the application.

    The service could also be created in Groovy, that's neither here nor there, whatever you're comfortable with:

    package com.example;
    
    public interface CodecSet {
    
        public String getEncoder();
        
    }

    To really simplify things, we'll have one method instead of two and we'll use strings instead of the Encoder/Decoder return types referred to in the Javadoc example.

  3. Create the service provider. Next, we'll create our first service provider. Remember that a service provider is an implementation of a service. We will create it in a new Java application. Again, we'll follow the example from the Javadoc and call our service provider 'StandardCodecsProvider', with the implementing class being called 'com.example.impl.StandardCodecs'. Again, for now we'll use a Java class for the service provider too.

    To fulfil all the requirements for creating a service provider, the 3 bullets that follow will result in a Java application that looks as follows:

    • First, build the service and put its JAR on the classpath of the service provider's application. Now that the service is available to the service provider, the latter can implement the former.

    • We'll create a very simple implementation (how could it be otherwise, since we're simply returning the name of an encoder):
      package com.example.impl;
      
      import com.example.CodecSet;
      
      public class StandardCodecs implements CodecSet {
      
          @Override
          public String getEncoderName() {
              return "Standard Encoder";
          }
      
      }

    • Finally, in the service provider's application, create a folder structure within 'src', named 'META-INF/services'. Within it, create a file, without any extension, named 'com.example.CodecSet'. Inside that file, write one line and one line only, the content being 'com.example.impl.StandardCodecs' (without the quotes around it).

  4. Put the service provider on the application's classpath. Now, put both JARs that you've created (i.e., the service JAR, as well as the service provider JAR) in the Griffon application's "lib" folder. Your EncoderSales application should now look as follows:

  5. Load the service interface. Now we simply need to load the service interface into our Griffon application! Here we go—we use generics to specify the type and are then able to call the "getEncoderName()" on each service provider that is on our classpath and that has been registered according to the META-INF/services approach, as described above:
    import com.example.CodecSet
    
    class EncoderSalesController {
    
        def view
        def i = 1
    
        def loadService(){
            ServiceLoader<CodecSet> sl = ServiceLoader.load(CodecSet.class)
            sl.each() {
                view.encoderList.text =
                       view.encoderList.text +
                       "\\n" +
                       i++ +
                       ". "+
                       it.getEncoderName()
            }
        }
    
    }

    We call the above from Startup.groovy:

    def rootController = app.controllers.root
    rootController.loadService()

    And 'view.encoderList.text' in the controller? What's that all about? That refers to a JTextArea in the view, which is defined as follows:

    application(title:'Encoder Sales',  pack:true, locationByPlatform:true) {
        textArea( id:'encoderList', rows:10, columns:30 )
    }

    Run the application. Isn't it beautiful? Here it is:

    It's clearly time to distribute your application to all your customers! Do so now.

  6. Extend the distributed application. Good, your wonderful application is now distributed to your users and they're making use of it and telling you how wonderful it is. Then comes the moment when they'd like to extend it and, for whatever reason (you don't want to create the requested features in your original code base, or you don't have the time to do so, or the customer has some private features that need to be added, i.e., features that are germane to the customer and irrelevant to all the other users). In other words, there's a new encoder to be added to the list. Time to create a new service provider:

    • Put the service on your new service provider's classpath.

    • Implement the service:
      package com.example.impl;
      
      import com.example.CodecSet;
      
      public class SpecialCodecs implements CodecSet {
      
          public String getEncoderName() {
              return "Special Encoder";
          }
      
      }

    • Publish the service provider via MET-INF/services. Your service provider should look very similar to the one discussed earlier, only the implementation is different (and that's exactly the point):

    • Distribute the new JAR to to the end users, who need to put it on their EncoderSales application's classpath. Below you can see that I have three service provider JARs, together with the service JAR:

    • When the application restarts (which is just one area where the NetBeans Lookup class is superior, in that it has a handy Listener, unlike the ServiceLoader, which means that if the classpath changes dynamically, you will be able to unload and reload objects, which lets you hot-reload JARs without restarting the application, as described by Tim here on Javalobby), i.e., via the reloaded service the new service providers, i.e., those that are on the classpath and registered correctly, are invoked, and they'll provide new entries in the list, each potentially provided by different service providers, all simply as a result of the ServiceLoader loading the service and then having the methods invoked on the service providers:

    Only the fourth bullet above, i.e., distribution, is slightly inconvenient. (On the other hand, that's how it works for the Lobo Browser too, last time I checked.) Your granny Smith end user isn't very happy receiving JARs and being told to put them in special places. So, why not create a Plugin Manager in your 'EncoderSales' application? Add a menu item that says 'Plugins' and, when selected, a dialog appears that lets the user browse for the JAR. When they click 'Install', the JAR will simply be put into one of the application's folders (a user directory or even in the installation directory itself), so long as it is on the classpath, which is all that is needed for the ServiceLoader in the application to call its 'getEncoderName' method. (Perhaps the Griffon framework could provide this kind of functionality itself, so that the Griffon user could via a few lines of code simply enable the presence of a Plugin Manager, which is something the NetBeans Platform allows you to do too.)

(By the way, in case you're wondering about this, you can also specify the order of instantiation as well.) And that's how Griffon application are, in fact, extensible. (And, as one should be able to see, Spring RCP too.) So, returning to my original (slightly provocative) statement, extensibility is not a reason for choosing the NetBeans Platform over Griffon or Spring RCP. Yes, there's a little bit of extra work involved, at the moment anyway, but isn't that always the case with plugins?

Postscript: The John O'Conner quote at the start of this story comes from his excellent article Creating Extensible Applications With the Java Platform, which you should definitely read if you haven't already!

Friday Sep 19, 2008

Want to Share NetBeans Platform Screenshots?

The NetBeans Platform Screenshots page has been updated! There are several new screenshots of cool existing NetBeans Platform applications that are now listed there for the first time:

Spikeflow - click to enlarge Instant JChem - click to enlarge JFugue Music NotePad - click to enlarge Imagine - click to enlarge

TagsMe GUI - click to enlarge ThinkingRock - click to enlarge VisualVM - click to enlarge Alkitab Bible Study - click to enlarge

These are all applications that have been created on top of the NetBeans Platform, the modular Swing application framework that is the basis of NetBeans IDE, among many other applications, such as the above. For more information on the above applications, see the NetBeans Platform Screenshots page. Is your application missing from that page? Go here to submit yours for addition!

Thursday Sep 18, 2008

Groovy Web Services and org.apache.cxf.endpoint.dynamic.DynamicClientFactory

The title of this blog entry is meaningless... except... if you want to use Groovy Web Services with JDK 6.x (instead of JDK 5.x). I wrote about the embarrassingly easy web services in Groovy before and then today ran into the error that starts with this line:

org.apache.cxf.endpoint.dynamic.DynamicClientFactory outputDebug

Yes, it is an illuminating start to a long stack trace, isn't it? (That's why I put it in the title of this blog entry, hoping to draw frustrated Googlers here, so they can see for themselves what the solution is.) So here's hoping that Google will bring you to this blog entry, because (good news!) ALL YOU NEED TO DO IS PUT JAVA_HOME/BIN ON THE CLASSPATH! (Read more here which is where I found this tip.) IntelliJ people might also benefit from this knowledge, as evidenced here:

http://www.jetbrains.net/jira/browse/GRVY-889

So, to reiterate, in JDK 6.x, you need JAVA_HOME/bin on the classpath (not just JAVA_HOME, as was the case with JDK 5.x), otherwise you'll get a nightmarish stack trace instead of your wonderful web service payload retrieved from the web.

Wednesday Sep 17, 2008

Further Experiments with Griffon

Another small experiment with Griffon, but this time there's a problem, one that I've had a couple of times. First, the code. Here's the model:

import groovy.beans.Bindable

class App1Model {

    @Bindable String name = "James"

}

Here's the controller:

class App1Controller {

    def model
    def view

    def saveName = { evt ->
        model.name = view.nameField.text
    }

}

Here's the view:

build(App1Actions)

application(title: 'App1', size:[320,480],
    location:[50,50], pack:true, locationByPlatform:true) {

    panel(border:emptyBorder(6)) {
        borderLayout()
        hbox(constraints:NORTH) {
            label(text:"Name:")
            hstrut(5)
            textField(id:"nameField",columns:20,"Tom")
            hstrut(5)
            button(action: saveName)
        }
        hbox(constraints:SOUTH) {
            label(text: "Hello ")
            label(text: bind{model.name})
        }
    }

}

And here's the "App1Actions" that's referred to above:

actions {

    action( id: 'saveName',
        name: "Save",
        closure: controller.saveName,
        accelerator: shortcut('S'),
        mnemonic: 'S',
        shortDescription: "Save the entered name",
    )

}

Couldn't be much simpler than that. The text in the label is bound to the domain 'name', which changes when the Save button is clicked. I then deployed both the Swing application and the applet (which, for fun, I dragged out of the browser):

But there's a problem. The Swing application works as expected. But the applet only works the first time the Save button is clicked, after that not at all. Possibly there's a threading issue? Or an initialization issue? I didn't use any of the lifecycle classes. Maybe that's why? Anyway, I'm stumped.

Tuesday Sep 16, 2008

Griffon + JDK 6 Update 10 = 100% Free Draggable Applets

Cay Horstmann's blog today inspired me to try the draggable applet scenario again. JDK 6 Update 10 holds that promise. However, I've never managed to get it to work before on Ubuntu (or anywhere else, but I only use Ubuntu so that's all that matters at the moment). Cay's keymap tip was one step I never knew about. Then I discovered a world of pain (conflicting JDKs, conflicting Java plugins, a myriad of small files that need to be tweaked, half a freakish hour when no applets displayed at all, etc), after which success was sweet indeed. Below you see the applet I created via Griffon as described in Flying with Griffon on Javalobby. However, this time it's dragged out of the web page and into the NetBeans Groovy editor:

Thanks to Griffon and JDK 6 Update 10, the applet that you see above is 100% free. I.e., I did nothing at all to create it and (once everything was set up correctly) I did nothing at all to be able to drag it. Via "griffon run-app" the applet was created (together with the Swing application) and because I opened it into FireFox 3 with JDK 6 Update 10 (b32, which is the recently released RC2 of JDK 6 Update 10), I was automatically able to drag it out of the browser. Griffon is therefore a perfect companion to JDK 6 Update 10. Seriously, if there's a nicer way to produce applets than Griffon (i.e., by not doing anything other than creating a Groovy application and then running "griffon create-app"), I'd like to know about it. Hurray for Griffon: the toolset that makes draggable applets a true breeze.

Monday Sep 15, 2008

Griffon: Identifying the Dark Underbelly of Java

It seems to me that Griffon (brand new Groovy-based Swing MVC-structured framework) has identified the dark underbelly of the Java desktop world—there are no guidelines for how an application's sources should be structured. Two of the bloggers I read today on the excellent http://groovyblogs.org confirm this:
  • "Generally, I find the code very manageable, I look at xyzController in the controllers folder, I know its views will be in a directory xyz under views. Domain objects are again easily located and the dynamic CRUD support makes database interaction very straightforward. Another benefit is anyone who has used grails before has a much better chance of picking up the code, as they know where to look for things."
    From Grails and the power of code by convention

  • "MVC: By far, this was the area that benefited the most from the port. What began as a big clusterf—k all in one file has evolved to a nice separation of MVC. Thanks to @Bindable and changing from a concrete class as a view to a view clousure, I was able to eliminate some useless instance variables and move what needed to referenced from the view to the model."
    From Porting a Groovy app to Griffon

The latter comment is by one of the creators of Griffon, so he should know what he's talking about. Spend half an hour looking at the Griffon samples, try to make some of your own, revel in the coolness of Groovy, and then marvel at how manageable your application's source structure has become. Even better, lead your existing application back onto the path of righteousness (i.e., port it!) by following this article I wrote today on Javalobby. Griffon is to source structure what refactoring is to source code. Maybe a new term needs to be coined for source structure refactoring, because that's Griffon's (and Grails') main benefit, and—until you grasp that aspect—you won't "get" it.

The case for Groovy has been made a whole lot stronger via Griffon. If you want not just your source code, but also your source structure, to be clean and maintainable, then I don't think you've got much choice but to give Griffon a whirl. And you'll enjoy the ride since Groovy is so liberating!

Friday Sep 12, 2008

The 5 Best Things About Griffon

I created the whole NetBeans API Javadoc viewer described in Flying with Griffon in NetBeans IDE 6.5 (Dev), using my tweaked Grails modules. The Swing application and applet are created via the same "griffon run-app" process, with this result:

However, the application (i.e., Swing application, applet, and JNLP) could have been created without NetBeans IDE, of course. Simply follow the steps described there from a command prompt, with a text pad, and you'll end up with the same result. The NetBeans IDE modules do nothing magical for Griffon (same with Grails), other than provide a GUI which replaces the command prompt.

And here's what I like best about Griffon so far:

  1. I can use Groovy.
  2. I can reuse everything I learned from Grails.
  3. I am almost forced to think in an MVC-way about my Swing application.
  4. At the end, for no extra effort whatsoever, I not only have a Swing application, but also an applet and a JNLP application.
  5. Griffon is under active development by people who know what they're doing, having learned from the successes and mistakes of Swing, in a modern context, so I'm expecting the best of all worlds within a single framework.

Thursday Sep 11, 2008

Notes on Converting NetBeans Grails Support to NetBeans Griffon Support

New project template that runs "griffon create-app" and automatically opens the newly created application in the IDE. The newly created application is based on the brand new Grails-based Groovy-Swing framework called Griffon:

For future reference, here's notes on how to convert Grails support to Griffon support, which I will do once Grails support has stabilized:

  1. hg clone http://hg.netbeans.org/main nb_all
  2. cd nb_all
  3. Then search in the Groovy/Grails modules for "grails.bat". (Don't search for "grails", because every class has that in it somewhere.) Once you've found the "grails.bat" references, you'll have found the classes that define what a Grails application consists of. Simply change "grails.bat" to "griffon.bat" and then look for the Unix declarations, which are right above or below the Windows declarations and also change those from "grails" to "griffon".
  4. Simply remove the "web-app-dir" setting, which is part of the definition of what a Grails application is, though doing this may break things elsewhere, so need to investigate this. (Need to be able to create an absolutely minimal download of NetBeans IDE, i.e., one that is Java SE only, plus the Griffon cluster, unlike Grails, which is part of the "Java Web & EE" cluster.)
  5. Make sure to also change the classes that handle the execution of the Groovy scripts, which redefine the above settings.
  6. Check the file-level Grails scripts, also check that the project structure is created correctly in the Projects window.
  7. Also change the Options window, where the location of "Griffon home" should be set instead of "Grails home".
  8. Look in layer.xml and bundle.properties files for strings that say "Grails" and change those to "Griffon". Do the same in the HTML descriptor for the project template.
  9. Change the Grails icon with the Griffon icon. (Probably 16x16 as well as 32x32.)
  10. Add some Griffon samples, JavaHelp for the Griffon support, and a tutorial explaining how to use it in NetBeans IDE.
  11. Set the Griffon home (on Ubuntu anyway) in "etc/environment", not in ".bashrc" (don't know why, but otherwise NetBeans IDE doesn't pick up Griffon home).
  12. ant -Dcluster.config=groovy build-nozip
  13. Then start NetBeans from "nbbuild/netbeans/bin".
  14. Then all should be as described here.

Then maybe create a site on dev.java.net, called "nbgriffonsupport.dev.java.net", probably.

Wednesday Sep 10, 2008

Do you have news about the NetBeans Platform?

Over time, there have been many changes on the NetBeans Platform site, i.e., http://platform.netbeans.org/. And there are a few more coming still, but most of the major changes have now been realized. One cool new thing is that there's a vertical scrolling list for news items (since the last few days) relating to the NetBeans Platform, which currently looks as follows:

The vertical scrollbar is new, meaning that the list of new items can pretty much be endless, and maintainers of the page are not limited to focusing on only a handful of the latest news items. So... if anyone out there has news (old/new/whatever) that they'd like the rest of the NetBeans Platform community to know about... there's an e-mail address listed above where you can send your news items. News items can be anything at all, e.g., new releases of your product, tips & tricks, new FAQ items, new articles somewhere, new blog entries relating to the NetBeans Platform, new screenshots of your application, etc. Consider it free advertizing for your product, for example. Also, be sure to keep in touch with that page if you want to keep track of all the things that are happening around the NetBeans Platform!

Monday Sep 08, 2008

NetBeans Podcast Episode 46

A very crowded NetBeans Podcast this month. As usual, it would have been impossible without Lloyd, who [apart from his interview contributions] strung all the segments together and deleted all the "uhhhs" very professionally!

The focus in this month's podcast is on news from the NetBeans QE team, in light of the release of the upcoming NetBeans IDE 6.5, represented by Lukas Hasik. In addition, Tinu talks about the (great new) NetBeans forums and the NetBeans World Tour (starting October 1!), James informs us about the NetBeans Community Docs Program, and Jaroslav Tulach reveals, in the API design tip of the podcast, why "constructors rule". He also announces the winner of the 'Will Code for Food' competion. Then we introduce a new segment, focusing on an interesting e-mail we've received over the course of the past month (if your e-mail is mentioned you win a NetBeans t-shirt). Finally, of course, there's the plugin of the podcast and... the puzzler!

<script type="text/javascript" src="http://www.netbeans.tv/js/swfobject.js"></script> <script type="text/javascript"> </script>

NetBeans Podcast Episode 46 (35:36 min)

Links:

Send the answer to the puzzler to: nbpodcast@netbeans.org by Friday, 26 September 2008.

If there are topics/people you'd like to hear about in future podcasts, please also let us know at nbpodcast@netbeans.org. If your e-mail is mentioned during the podcast, you'll win a t-shirt!

To subscribe via iTunes or similar players, visit http://feeds.feedburner.com/NetBeansPodcast.

Sunday Sep 07, 2008

Dealing with Helpsets from 3rd Party JARs

Let's say you have an application on top of the NetBeans Platform. One of the application's modules does nothing more than wrap a 3rd party library. However, within that 3rd party library is a helpset that you'd like to integrate into your own application's helpset. How do you integrate that helpset, i.e., one that is within a 3rd party JAR, into your own application?

You could, of course, simply manually copy the help files from the 3rd party helpset and paste them into one of the modules in your suite that already provides a helpset. That solution presents two problems, however. Firstly, you now have the same HTML files in two places—within the 3rd party JAR as well as within the module that integrates them into the application's helpset. That's not very convenient and your application is now potentially much larger than it needs to be. Secondly, what happens when the JAR is updated? The helpset is then potentially modified and you'll need to copy them over again, again manually. Not convenient either.

So the better solution is to add an Ant script to your build process. The Ant script will, at whatever point in your build process that it is convenient to do so, unjar the 3rd pary JAR, create a scratch folder, copy the complete content of the 3rd party JAR into the scratch folder, and then copy the relevant folder (i.e., the 'help' folder) into the module that will provide the helpset. So long as the "helpsetref" tag in your helpset.xml file points to the "hs" file that is expanded into the module, everything will work seamlessly and you'll have a single source for the helpset. In other words, you'll not check any help topics into your CVS. Instead, you'll recreate the helpset on the fly, as needed, and the HTML files will always be within the JAR file:

<target name="create-marvin-help" description="Get help topics from JAR">

    <property name="help.dir" location="../modules/MarvinHelp/javahelp/com/im/ijc/marvinhelp/help"/>
    <property name="scratch.dir" location="../modules/MarvinHelp/scratch"/>

    <delete dir="${help.dir}"/>
    <mkdir dir="${help.dir}"/>

    <mkdir dir="${scratch.dir}"/>
    <unjar  src="../libraries/JChem/release/modules/ext/jchem.jar" dest="${scratch.dir}"/>
    <copy todir="${help.dir}">
        <fileset dir="${scratch.dir}/help">
            <include name="\*\*"/>
        </fileset>    
    </copy>    
    <delete dir="${scratch.dir}"/>

</target>

The downside of this approach is that you now have no control over the helpset at all, since you're simply unjarring it on the fly. In other words, you need to take the settings and content provided by the JAR as given, there's no way for you to change them. For example, if 'expand=false' is not set in the TOC, and you don't want the top node to be expanded, there's nothing you can do since you're using the TOC from the JAR, rather than one you have control over within your module.

The Possibility of a Translation

"Damit Service Provider einem System dynamisch und flexibel hinzugefügt werden können, also auch dann noch, wenn eine Anwendung bereits ausgeliefert wurde, und erst dann geladen werden, wenn sie auch wirklich benötigt werden, werden diese deklarativ, also über Konfigurationsdateien, registriert."

The above sentence encapsulates the wonderfulness of the German language! What I absolutely love about it is the fact that its VERB is "registriert". :-) I.e., the very last word in that whole long sentence. (And the fact that about 5 separate facts are transmitted, and need to be digested, before one gets to that point! The reader's had a lot to chew on at that stage, having gained an unlikely wealth of knowledge before being permitted to draw breath again.) I also like the word "Damit" which is a firm start to a sentence (like "Now" at the start of Richard III), even though it meanders (albeit very strongly and declaratively, like a purposeful guide in a jungle slashing a path resolutely off the beaten track, heading towards a goal that those under his care can do no more than guess at), only to return to its original path right at the end. Translated more or less directly, the above means the below:

"In order for a service provider to be added dynamically and flexibly to an application, even when the application has already been released, and in order for it to be loaded only when it is actually needed, the service provider is registered declaratively, that is, by means of configuration data."

Bear in mind that I cheated quite a bit in the above translation and that I went out of my way to put everything into a single sentence. Had I been 'honest', I would have translated "diese" to "it", instead of "the service provider". It's cool how dangling participles are not a problem in German, apparently. But the above sounds, despite my overcompensations, quite unnatural in English, and having "in order for" twice (even once) in a sentence is clunky and the sentence as a whole misses the modern American-style friendliness common to just about everything nowadays. So maybe it should be translated as follows instead:

"You register service providers declaratively, that is, using configuration data. One advantage of this approach is that you can add service providers dynamically, even after you've distributed the application to your users. Another advantage is that the service provider will only be loaded once your users actually need to make use of it."

The human actors, missing from the original, have been injected back into the sequence of events. The sentence has been broken into manageable chunks. (Even a contraction, to show we're all nice and down to earth!) The reader, the ever-smiling "you" is present again (calmly reading by the fire, a comforting whisky at hand, echoing the rational lord of the manor down the ages, the glow of a laptop screen replacing the hearth, a grubby t-shirt instead of a double-breasted suit). However, the emphasis of the original is lost. But, then again, I'm not sure whether the German original had an emphasis to begin with. Perhaps the original emphasis was the declarative nature of the registration, while my translation above emphasizes the advantages of declarative registration. The latter, though, is what I believe to be the more important point. That, in turn, brings one into the ethical issues relating to translation. Should one try to improve the original? And who is to say that your personal preferences are improvements at all?

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
« September 2008 »
SunMonTueWedThuFriSat
 
3
6
9
13
14
23
30
    
       
Today