BookNBPlatformCookbookCH0301

Contents

NetBeans Platform Cookbook Chapter 03 01

Decouple Dependencies

The Lookup principle allows you to write applications where modules cooperate together but are as independent as possible. Imagine you are going to create generalized support for something (e.g. a project), general solution, hight abstraction functionality. Other specialized modules can utilize its infrastructure.


Preparation

Imagine we create generalized abstract support for some objects. This infrastructure will serve common functions which each implementation would solve by its way, but similar. May be this infrastructure will be important and used by other modules on the higher application layer. Objects MyObject have to have these mandatory properties:

Unique id to distinguish and address instances for storing.

Unique identificator having analytic meaning (contract number, insurance number, task identificator).

Property named someProperty (e.g. directory containing documents). May be some others we will add later.

The infrastructure requires only getId(), getIdentificator() and getSomeProperty() methods of MyObject's interface because it uses them directly (it calls them). Other objects can be utilized under different conditions and by some implementations.

Future implementation (we do not know their character now) can use other objects and functions – it is their matter.

We require existence of a manager of such objects which will manage instances and id-s and can create them.

Suppose our general solution will be used by some general (or specialized) GUI so we do not want dirty the interface by other functionality.

How to

Create new module general_solution and define interface for MyObject and MyObjectManager classes:

public interface MyObject {
    public int getId();
    public String getIdentificator();
    public String getSomeProperty();
}
public interface MyObjectManager {
    public MyObject createMyObject(String identif);
    // other functions - find etc.
}

We prepare MyObject to provide lot of properties we will use in other modules. We would use Map<property-key, object> of course. The Platform offers the Lookup! It enables to store many instances, even many instances of the same type. Their class is the key. So declare it must provide its Lookup object:

public interface MyObject extends Lookup.Provider {

Implement the general solution.

Set available packages of the as public. Other modules can see them.

Create an implementation module Impl1.

Create the manager – see the simple template:

public class Impl1Manager implements MyObjectManager {

    private static final String PREFIX = "MyObject";

    private static Map<String, Impl1Object> objects_by_ident;
    private static Map<Integer, String> id_to_identificator ;

    private MyObjectListener objListener;  // if needed

    public static Impl1Manager getInstance() {
        return Impl1ManagerHolder.INSTANCE;
    }

    private static class Impl1ManagerHolder {
        private static final Impl1Manager INSTANCE = new Impl1Manager();
    }

    private Impl1Manager() {
        this.objListener = new MyObjectListener();   // if needed
    }

    public MyObject createMyObject(String identif) {
         int id = IdGenerator.createId();
         Impl1Object ob = Impl1Object.create( id , identif);
         // create temporary identificator
         String temp_ident = PREFIX + id;
         id_to_identificator.put( Integer.valueOf(id), temp_ident );
         objects_by_ident.put( temp_ident, ob );
         // ob.addPropertyChangeListener( objListener ); // if is needed 
         return ob;
    }

    private static final class IdGenerator {
        private static int last_id = 0;  // load last used
        private static synchronized int createId() {
            return ++last_id;
        }
    } // IdGenerator
    //…
}

Create Impl1Object implementation of the MyObject

public class Impl1Object implements MyObject {

    private int id = 0;
    private String identificator;
    protected String someProperty;
    public static final String PROP_SOMEPROPERTY = "someProperty";

    private Lookup lookup = null;
    private InstanceContent lookupContent;

    private Properties properties;

    // …  PropertyChangeSupport propertyChangeSupport 

    static final Impl1Object create(int id, String identif) {
        return new Impl1Object(id, identif);
    }

    private Impl1Object(int id, String identif) {
        this.id = id;
        this.lookupContent = new InstanceContent();
        this.properties = createProperties();
        this.identificator = identif;
    }

    public int getId() {           return this.id;    }
    
    public String getIdentificator() {         return identificator;    }
 
    // addPropertyChangeListener(PropertyChangeListener l) 
    // removePropertyChangeListener(PropertyChangeListener l) 

    public boolean equals(Object obj) {
        … 
    }

    public int hashCode() {
        ...
    }

    public String toString() {        
         return "Impl1Object{" + identificator + '}';     
    }

    public String getSomeProperty() {
        return someProperty;
    }

    public void setSomeProperty(String someProperty) {
        String oldSomeProperty = this.someProperty;
        this.someProperty = someProperty;
        // PropertyChangeSupport
    }

    public Lookup getLookup() {
        if (lookup == null) { 
           synchronized(this) {
                // if you want mutable content
                lookup = new AbstractLookup(lookupContent);
                lookupContent.add( this );  // mandatory
                // store object which uses this implementation only
                lookupContent.add( new SpecialObjectOfImpl1(this) );
                lookupContent.add( this.properties );
                // or if it can be immutable
                // = Lookups.fixed( list of these objects );
            }
        }
        return lookup;
    }

    private Properties loadProperties() {
        // load properties bundled with this object (e.g. Project)
        Properties props = new Properties();        
        // set defaults or standard values by options
        return props;
    }
}

Store instances of objects into the Lookup that are important for the infrastructure, possibly for other modules (GUI) and for your implementation.

Do not forget implement equals() and hashCode() methods.

Have you noticed the interface left simple? It is not all and only purpose.

You will create GUI module for creating and managing Impl1 objects. You show a list of these objecs and allow the user create, delete and change them and other objects in their background. You need Actions.

Nothing difficult. Create an action map or actions (e.g. EditCookie, SaveCookie, RunCookie) and simply add them into the Lookup instance.

Do you need a Node? Offer it (lazy creation is recommended).

If other implementation needs some object let it store it into Lookup.

If some implementation knows some object it simply asks the Lookup if such one exists.

Where MyObject interface is needed only look up this.

MyObject ob = myobj.getLookup().lookup(MyObject.class);

Where the concrete implemenation is needed ask for it:

Impl1Object ob = myobj.getLookup().lookup(Impl1Object.class);
We haven't mentioned we can register the Impl1Manager as a service provider – see the '''Provide Service Implementation''' section. 

Your implementation can exploit the general infrastructure, e. g. SomeSupportFunction class (see example sources, project DecoupleDep).

Let's Explain!

We will make a summary now. Sorry for repeating.

The general_solution project defines two basic interfaces - abstracted objects:

1) MyObject interface defines properties and functions only needed for the generalized solution that offers an infrastructure for all implementations.

public int getId();
public String getIdentificator();
public String getSomeProperty();
public Lookup getLookup(); from the Lookup.Provider interface

2) MyObjectManager creates MyObject instances and manages their id-s:

public MyObject createMyObject(String identif);

3) SomeSupportFunction provides some support for MyObject instances, generalized common functionality used by all MyObjects.

The Impl1 project implements some kind of the MyObject (and its manager, too).

  1. Impl1Object
  2. Impl1Manager
  3. other classes specific for this implementation e.g. SpecialObjectOfImpl1.

E.g. the SpecialObjectOfImpl1 provides some functionality which general module or other ones need not use and define. This one provides a buffered image, paint method and Graphics object of the image.

The Impl1Object stores into its Lookup mandatory instances and objects which this implementation uses (and modules using it, e.g. Impl1GUI module):

  • this - the Impl1Object instance itself
  • SpecialObjectOfImpl1
  • Properties instance
  • other useful instances

It only stores/publishes them and who knows any object type and uses it it asks for it. If some module works with MyObject objects (explicitly with MyObject interface) asks some action context for MyObject instance:

MyObject ob = selectedObject.getLookup().lookup(MyObject.class);

The selectedObject can be a Node containing MyObject instance or the instance itself.

If some module (e.g. Impl1 module, Impl1GUI module) works with concrete type of the MyObject - Impl1Object here - asks for it:

Impl1Object ob = myobj.getLookup().lookup(Impl1Object.class);

Some module knows which instance types can be stored in the MyObject's Lookup and it uses them. It asks simply if such an object exists:

SpecialObjectOfImpl1 spec = selobj.getLookup().lookup(SpecialObjectOfImpl1.class);
Properties props = selobj.getLookup().lookup(Properties.class);

Note that implementation of the MyObjectManager can be registered as a service.

Notes/Tips - Documentation

Dip into the NetBeans documentation how some general tasks are implemented. This approach is used e.g. in the Project API module.

Notes/Tips - Examples

There is a Decouple project in sample sources. It shows how to create your central registry for any purpose - CentralLookup.

The API_module defines working with Books, Shelves and some storage. It defines Book, Shelf an interface DB for storing data.

The appcontext module provides the CentralLookup - a singleton Lookup object which can store and remove instances dynamically. Each module can store/retrieve into/from it instances which it is interested in.

The DBStore' project implements some DB interface by some way. Its module installer registers the DBStore instance into the CentralLookup where it can be found by other modules - API_module.

The CentralLookup and the EventBus classes are described on internet on


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