BookNBPlatformCookbookCH0302

Contents

NetBeans Platform Cookbook Chapter 03 02

Offer an Extension Point

Your application has many features that can be used or can be not used or you can extend any function by 3rd-party plugin. Imagine you add export/import plugin to other formats, 3D shaders, new objects for modelling, other visualisation, other algorithms, image filters, drawing tools, new validation for business objects.

Module defines an API interfaces (or base classes) for some functionality. If they need use their instance they ask the central registry of services if any implementation is installed. If yes then it uses it. If several implementations of the service are installed it uses the first one or lets the user select which provider will be used. This is called as an extension point. Other modules provide an implementation of the service and register it into the central registry - they are called service providers. We will create an extension point in this tip. (And we will implement it in the next tip.)

Preparation

The principle is simple

  • Define an interface.
  • Ask if any implementation is registered (generally no matter how registered, see the discussion later, you must look up any implementations).
  • If there is no implementation do not use the function or use some empty or default simple implementation or disable the module.
  • If there exists any implementation use it or let the user choose which of them will be used (for each action or set it as an application option).
  • Create or find any implementation of this service and register it by used registering management system to run the application.

Here is described how to offer an extension point. How to provide the service provider (the implementation, even the default that can be included into API module) is described in the next section Provide Service Implementation.

How to

We show it on example application visualizing simple tree data like chapter numbers or structured outline.

Create Suite or Application Suite project if you do not have any.

Create a new view_api module. It defines interface of your application objects and provides the default implementation if it is needed.

We define an interface MyData for data in the base package mymodule.pkg.api:

public interface MyData {
    public void addChild(MyData chch);
 
    public MyData [] getChildren();
    public String getText();
    public void setText(String s);
}

Create some MyData implementation directly or as a service - as the same way as we will work with MyView. Or provide the default implementation. Now define the MyView interface for View - object that knows how to visualize MyData data:

/** General interface for view used in MyView-like application.
 * New instances are made by MyViewFactory.
 * If you do not look up its any implementation the default one will be used.
 */
public interface MyView {

    public JComponent getComponent();

    public MyData getMyData();

    public void setMyData(MyData myData);

}

Define an interface MyViewFactory for a factory that can create new MyView instances.

/** General interface for factory creating MyView instances.
 * If you do not look up its any implementation the default one will be used.
 */
public interface MyViewFactory {

    public String getFactoryName();

    public MyView createMyView(); 

}

The method getFactoryName() returns name of the implementation. You can use it when the user selects the implementation for an action or in the Options window.

Describe the extension point in the documentation - what is needed, interface contract and how to register a new service provider.

Create the default implementation if you will provide it. How to create the service provider is described in the Provide Service Implementaiton tip.

Keep in your mind you must handle the case when no service provider is found or you must ensure that at least one service provider is installed.

Open Properties of the view_api module, select API Versioning category and check the base package to set it public. The api.dftview package can stay be hidden. The default implementation is secret.

It's time to create the application module. Set it dependent on view_api module. And on Lookup API and Utilities API.

Create ViewTopComponent as new Window in Module development category. Check Open by application start option.

Add the combo box at the top by name e.g. implementationSelect. Erase the model property in its Property window.

Add a JPanel, expand it into rest of the TopComponent, name it viewsPanel. Set the CardLayout layout to this panel. We will show the concrete MyView panel by chosen implementation.

Switch to the source editor. Set the TopComponent.PERSISTENCE_NEVER in the getPersistenceType() method to disable its state persistence.

Add our members

    private Result<MyViewFactory> lookupResultView;
    private LookupListener lookupViewListener;

    private List<String> viewImpls = new ArrayList<String>(20);

    private MyData myData;

    public ViewTopComponent() {
        ..... 

        implementationSelect.removeAllItems();
        loadMyData();

        lookupResultView = Lookup.getDefault().lookupResult(MyViewFactory.class);
       // handle the state that no service is installed
        lookupViewListener = new LookupViewListener();
    }

The loadMyData() method simulates data loading. The viewImpls list of strings will be used for found implementation names. The Lookup.Result looks up for MyViewFactory registered implementations. Register a LookupListener and when the result changes call the manageViewPanelsByImplementation():

    private class LookupViewListener implements LookupListener
    {
        public LookupViewListener() {
        }
        public void resultChanged(LookupEvent ev) {
            manageViewPanelsByImplementation();
        }
    } // LookupViewListener

Implement the method loading implementations:

    private void manageViewPanelsByImplementation() {
        Collection<? extends MyViewFactory> col = lookupResultView.allInstances(); 
        for (Iterator<? extends MyViewFactory> it = col.iterator(); it.hasNext();) {
            MyViewFactory myViewF = it.next();
            String impName = myViewF.getFactoryName();
            MyView vw;
            if ( !viewImpls.contains(impName) ) {
                vw = myViewF.createMyView();
                vw.setMyData( getMyData() );
                viewsPanel.add( vw.getComponent() , impName );
                viewImpls.add( impName );
                implementationSelect.addItem( impName );
            }
        } // for it : col 
    }

Pass through all found instances of the MyViewFactory. Check if it is used. If not ask the factory for MyView instance. Set the data, add it into viewsPanel, use the factory's name as a constraint. Register it in the list and add new item to combo box.

In the componentActivated() and componentDeactivated() add or remove the LookupListener.

     protected void componentActivated() {
        super.componentActivated();
        manageViewPanelsByImplementation(); 
        lookupResultView.addLookupListener( lookupViewListener );
    }

    protected void componentDeactivated() {
        super.componentDeactivated();
        lookupResultView.removeLookupListener( lookupViewListener );
    }

We have left switch the view if user selects other implementation in the combo:

    private void implementationSelectItemStateChanged(ItemEvent evt) {                                                         
        CardLayout cl = (CardLayout) viewsPanel.getLayout();
        cl.show(viewsPanel, (String)evt.getItem());
    }

The sample loadMyData() method that simulates data loading looks like:

    protected void loadMyData() {
        MyDataFactory fac = Lookup.getDefault().lookup(MyDataFactory.class);
        // suppose there at least one instance exists - the default from view_api module
        // if not you must handle this case
        this.myData = fac.createMyData();
        this.myData.setText("root");
        MyData ch, chch;
        String tx;

        for (int i = 1; i < 4; i++) {
             ch = fac.createMyData();
             tx = "" + i;
             ch.setText( tx );
             tx += ".";
             for (int j = 1; j < 4; j++) {
                  chch = fac.createMyData();
                  chch.setText( tx + j );
                  ch.addChild( chch );
             } // for j
             this.myData.addChild( ch );
        } // for i
    }

Run the application (we suppose there at least one service provider is installed). You see a combo box, where you find one implementation only - the default.

image:Nbpcook_03_01_service_dft.png

Figure 3.1 Extension point - default service

Notes/Tips - Try add the next prepared module

Try add the next implementation. It is prepared to install. Now open examples prepared for this book, find the treenode module (com-packtpub-nbpcook-service-view-treenode.nbm file) in the 03_Lookup/Service/ directory.

Run the application, if it is not running.

Install prepared module com-packtpub-nbpcook-service-view-treenode.nbm.

Choose in menu Tools / Plugins.

Switch to the Downloaded tab.

Press Add Plugin button. The File Chooser is appeared. Find the nbm-file.

Press Install button (the plugin must be selected), pass through installation wizard and close the Plugins frame.

image:Nbpcook_03_02_serviceinst1.png

Figure 3.2 Plugin installation

image:Nbpcook_03_03_serviceinst2.png

Figure 3.3 Installed plugin

Explore content of the combo box - the "Tree view by nodes" provider appeared. Select it. The data structure is shown by Nodes tree.

image:Nbpcook_03_04_serviceplugin.png

Figure 3.4 Usage of the new installed view.

Notes/Tips to run

If you run the application (from the IDE) again and you want clear settings and installed module choose in suite project context menu Clean All before building and running it.

If you run the distributed application (created by Create ZIP distribution and unzipped) again and you want clear settings and installed module goto <user-directory>/Application data/.<appname> and delete this directory. For this example <appname> is service - name of the .exe file.

Text and sources were created under NB 6.8 and will be upgraded.

Navigation

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo