One of the main benefits of the NetBeans platform is the module system. Regardless of which module system is best ( my guess is there will soon be a version of NeBeans that can also run as OSGi bundles ) it’s important to have a system that enables you to create a modular architecture for your application. It’s an invitation to create a clean and maintainable architecture with defined dependencies and to create nice APIs with clearly defined and easy to use extension points. If you follow these principles others can extend your application easily. Maybe the easiest way to provide extension points in NetBeans is via the layer.xml.
In NetBeans modules the layer file is the central configuration file. NetBeans IDE uses the layer a lot to provide extension points for APIs. Objects can be created declaratively there and you can use the lookup to listen for changes. You will use it whenever you create new Actions or TopComponents. This quick tutorial shows how you can provide your own extension points via the layer:
Prerequisites:
- NetBeans (I’m using 6.1, but this will also work with older versions)
- Create a new ModuleSuite :
- Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Plug-in Modules. Under projects, select “NetBeans Platform Application” or ( “Module Suite Project” on older versions ) and click Next.
- In the Name and Location panel, type “layerextensionpoints” in Project Name. Change the Project Location to any directory on your computer, such as c:\mymodules. Click Finish.
- Now create four modules inside the suite “extensionpointinterface”, “messagereader”, “messageprovider1″ and “messageprovider2″:
- Choose File > New Project (Ctrl-Shift-N) again. Under Categories, select NetBeans Plug-in Modules. Under projects, select Module Project and click Next.
- In the Name and Location panel, type the name in Project Name. The default in the wizard should be to create the module underneath the directory where you just created the suite, which is fine. Click Next.
- In the Basic Module Configuration panel, replace the Code Name Base with de.eppleton.<modulename>. Click Finish.
Create a Service interface
We will use module “extensionpointinterface” to define an interface that will be used by the Service Providers as well as by the module that uses the extension point. Inside that module create interface “MessageProviderInterface” with the single method getMessage() that returns a String:
[sourcecode language='java']
public interface MessageProviderInterface {
public String getMessage();
}
[/sourcecode]
To make this part of the public API right click the project node, select API Versioning and select the check box of de.eppleton.extensionpointinterface. Now this package is accessible by other modules.
Create Service Provider implementations
Now we need some modules that implement the ExtensionPointInterface. We will use the modules “messageprovider1″ and “messageprovider2″ to do that. To implement the interface they both need a dependency on module “extensionpointinterface”. For each of them do the following:
- Right click the project node, select the “Libraries” category.
- Click “Add Dependency”.
- Select “extensionpointinterface”, and click “OK”.
Now that we have access to the interface in our modules we can implement it. Again for both modules do the following:
- Select “New” > “Java Class”.
- In the Wizard type “MessageProvider1″ or “MessageProvider2″ in the name field respectively.
- Implement the interface. Each of them should provide a different String e.g. “Hello ” in MessageProvider1 and “World!” in MessageProvider2:
8<———–MessageProvider1——————->8
[sourcecode language='java']
import de.eppleton.extensionpointinterface.MessageProviderInterface;
public class MessageProvider1 implements MessageProviderInterface {
public String getMessage() {
return “Hello “;
}
}
[/sourcecode]
8<——————————>8
8<———–MessageProvider2——————->8
[sourcecode language='java']
import de.eppleton.extensionpointinterface.MessageProviderInterface;
public class MessageProvider2 implements MessageProviderInterface {
public String getMessage() {
return “World!”;
}
}
[/sourcecode]
8<——————————>8
In order to make our MessageProviders available as services add these entries in the layer of the two modules. In 6.0 and earlier version the layer should be there anyway. In 6.1 you will need to create the layer.xml yourself:
<ol>
<li>In “Important files” open “Module Manifest”.</li>
<li>In the manifest add this line: OpenIDE-Module-Layer: de/eppleton/messageprovider2/layer.xml or OpenIDE-Module-Layer: de/eppleton/messageprovider1/layer.xml respectively.
That indicates the location where you put the layer.xml later on.</li>
<li>Create the layer.xml files in their respective package. (“New” > “XML” > “XML Document”) , use “layer” in the name field and add:</li>
</ol>
[sourcecode language='xml']
[/sourcecode]
In the layer files <filesystem> add:
[sourcecode language='xml']
[/sourcecode]
and
[sourcecode language='xml']
[/sourcecode]
respectively. This will create an instance of our MessageProviders using the standard constructor. The trick is that those instances will be accessible from outside via the SystemFileSystem. The next step shows how you can access these Services without a module dependency.
<h3 id=”section-VisualDatabaseExplorer-CreateACookieAction”>Find and use Service Providers</h3>
We will use module “messagereader” to display messages from all MessageProviders. To do so we will create a TopComponent:
<ol>
<li>Set a dependency on “extensionpointinterface” as shown above.</li>
<li>Click “New” > “WindowComponent”. “Output” is ok for the location. Make it show on startup by ticking the box.</li>
<li>Enter “MessageReader” for the class name prefix and click “Finish”.</li>
<li>The TopComponent class will open in Design View. Drop a JScrollPane from the palette in the window, make it fill the whole area and add a JTextPane to it.</li>
<li>In the source view add this to the end of the constructor:</li>
</ol>
[sourcecode language='java']
Lookup lkp = Lookups.forPath(“MessageProviders”);
Collection <MessageProviderInterface> coll = (Collection<MessageProviderInterface>) lkp.lookupAll(MessageProviderInterface.class);
for (Iterator<MessageProviderInterface> it = coll.iterator(); it.hasNext();) {
MessageProviderInterface messageProviderInterface = it.next();
jTextArea1.append(messageProviderInterface.getMessage());
}
[/sourcecode]
This will lookup the folder you created via the layer file ( Lookups.forPath(“MessageProviders”) ), search for classes implementing the interface ( lookupAll(MessageProviderInterface.class) ) and call the interface method on all instances. Let’s try it out:
Run the ModuleSuite. You will see the window either displaying “Hello World!” or “World!Hello “. As we can see in this very simple example, the order in which the ServiceProviders are called can be important for the result. So in the next step I will show you a trick to guarantee the correct order:
Sort Service Providers
NetBeans provides a way to sort layer entries. This mechanism is e. g. used to provide an order for actions in menus and toolbars. Since 6.0 (I think) this is done via the position attribute. So this probably won’t work in older versions:
In the layer of the two modules add these entries:
[sourcecode language='xml']
[/sourcecode]
and
[sourcecode language='xml']
[/sourcecode]
respectively.
Now the messages will always be displayed in the correct order “Hello world!”. Simply swap these values to reverse the order of the messages. When you do something like this make sure that you choose your values big enough to add Services in between the initial ones so there’s room for your application to grow. Try what happens when you set the same value for both attributes!
Summary
The intention of this tutorial is to illustrate how simple it is to implement loose coupling via the layer.xml in a NetBeans Platform application. Note that the module that uses the services has no dependencies on the modules that provide the services. And not only does NetBeans provide a very simple mechanism to provide and lookup Services, but also an easy way to declaratively order them.
Until the 1.4/1.5 timeframe we said it was okay to create your UI objects off of the EDT in certain cases (main frame not initalized). This was our mistake. We should have said this because it depends on particular implementation details of Swing (the order in which things are initialized internally), and it turns out we were wrong in some cases. Therefore we now say that creating or manipulating any UI object off of the EDT is a bug. Doing so will probably work fine in 99% of the cases, but you have now introduced potential race conditions in your app that may give you strange behavior later on.
One more detail, and this is important. As we work to further improve startup time of the VM and the Swing infrastructure, we will likely change the internals of Swing. This may increase the likely hood that code which used to work fine off of the GUI thread will now break. Therefore it is *very* important that you stay on the GUI thread; and that’s why I say: yes, it’s a bug!”
Thanks a lot Josh! Not only did you give a clear answer, but also an explanation as to why It’s a bug and not only a bad practice. I really do appreciate this!