Jeeb Extension Manager

Why use an extension manager ?

Applications capabilities may be augmented at any time when they can manage extensions (or plugins). Such extensions may be managed through an extension manager for convenience. The extension manager can return lists of extensions of given types or extensions compatible with some contexts.

The Jeeb extension manager is a reusable implementation of such a tool, it can be used as a superclass for extension managers in various applications. It makes it possible to use extensions of different libraries in a single application with a single instance of extension manager. The extension manager subclasses implement the Singleton pattern.

The Jeeb extension manager is part of the jeeb.lib.util.extensionmanager package.

Examples: CapsisExtensionManager, SketchExtensionManager, XploExtensionManager, SimeoExtensionManager.

How to build an extension manager for an application

For an application called "App", you have to build a class AppExtensionManager extending ExtensionManager following this pattern:

public class AppExtensionManager extends ExtensionManager {

    // App extension typology:
    public static final String ITEM_CHOOSER = "ItemChooser";
    public static final String ITEM_PATTERN = "ItemPattern";

    protected static boolean initOk = false;

    /** Singleton pattern
    */
    static synchronized public ExtensionManager getInstance () {    
        if (instance == null) {
            instance = new AppExtensionManager();
        } 
        return instance;
    }

    /**    Constructor
    */
    protected AppExtensionManager() {
        super("");
        init(this);
    }

    /**    Inits the extension manager
    */
    static public void init(ExtensionManager e) {

        // init() must be executed only once
        if (initOk) {return;}
        initOk = true;

        // Add the App extension types
        initTypes(e);

        // Load the App extensions
        try {
            e.readExtensionPropertiesResource("app/directory/app.extension.list");
        } catch(Exception ex) {    }

    }

    /**    Declaration of the extension types
     */
    public static void initTypes(ExtensionManager em) {

        em.declareType(ITEM_CHOOSER, ItemChooser.class);
        em.declareType(ITEM_PATTERN, ItemPattern.class);

    }

}

The extension manager for the "App" application extends the Jeeb extension manager. The constructor is protected and the getInstance () methods can be used to get an instance of the AppExtensionManager according to the Singleton design pattern.

A static init() method must be called in the constructor to:
  • declare the types for this extension manager,
  • load the extensions found in an extension file.

The extension types are String constants, they are declared in the initTypes () method with declareType (String, Class) where the class is the common superclass of the given type of extensions.

How to write an extension

Interface

The extension manager deals with extension families. A given family is built for a given purpose (e.g. viewer of something, filter of something else, renderer...) and has a common superinterface. These superinterfaces may be grouped in a package, e.g. app.kernel.extensiontype.

package capsis.kernel.extensiontype;

import jeeb.lib.defaulttype.Extension;
import capsis.gui.StepButton;
import capsis.kernel.GModel;
import capsis.kernel.Step;

/** StandViewer - Superclass of Capsis Viewers.
 */
public interface StandViewer extends Extension {

    public void init(GModel model, Step s, StepButton but) throws Exception;

}

Abstract class

For convenience, each superinterface can be completed with an abstract class implementing the common methods.

abstract public class AbstractStandViewer extends JPanel 
        implements StandViewer, Embedded, Repositionable, SMListener, Printable {

        private static final long serialVersionUID = 1L;
    ...
    @Override
    public void init(GModel model, Step s, StepButton but) throws Exception {
        this.step = s;
        this.stepButton = but;

        ProjectManager sm = Pilot.getProjectManager ();
        sm.addListener (this);
    }
    ...

A subclass for each extension in the family

Then each extension of this type extends the abstract class and redefines the key methods. For example, this standviewer (extract) shows a forestry stand as a text representation. It can be "inited" like all the other extensions of the StandViewer family.

public class SVText extends AbstractStandViewer implements ActionListener, Pilotable {

    static public String AUTHOR = "F. de Coligny";
    static public String VERSION = "1.2";

    private JButton helpButton;
    private int maxNumberOfTrees = 100;
    private JScrollPane scrollpane;
    private JViewport viewport;

    static {
        Translator.addBundle("capsis.extension.standviewer.SVText");
    } 

    @Override
    public void init(GModel model, Step s, StepButton but) throws Exception {

        super.init (model, s, but);
        setLayout (new GridLayout (1, 1));        // important for viewer appearance

        try {            

            createUI ();
            update ();
        } catch (Exception e) {
            Log.println (Log.ERROR, "SVText.c ()", "Error in constructor", e);
            throw e;    // propagate
        }
    }

Builtin compatibility

For a given family of extensions, the compatibility is defined for a given type of referent object. Each extension contains a static matchWith (Object referent) method returning true if the extension can be used with the given object.

MatchWith may write to a Log and anyway should return false in case of trouble during its evaluation.

    /**    Extension dynamic compatibility mechanism.
    *    This matchwith method checks if the extension can deal (i.e. is compatible) with the referent.
    */
    static public boolean matchWith (Object referent) {
        try {
            if (!(referent instanceof GModel)) {return false;}

        } catch (Exception e) {
            Log.println (Log.ERROR, "SVText.matchWith ()", "Error in matchWith () (returned false)", e);
            return false;
        }
        return true;
    }

Extensions management information, translations

It is possible to ask some information to the extension manager concerning extensions before their instanciation. These information can be used to build lists to help the user chose in a graphical user interface.

  1. Extension name: extMan.getName (className)
  2. Extension author: extMan.getAuthor (className)
  3. Extension version: extMan.getVersion (className)
  4. Extension description: extMan.getDescription (className)

These methods rely on static fields that must be provided in each extension:

  1. static public String NAME = "SVText";
  2. static public String AUTHOR = "F. de Coligny";
  3. static public String VERSION = "1.7";
  4. static public String DESCRIPTION = "SVText.description";

Note: NAME and DESCRIPTION are optional. If not found, they will be deducted from the class simple name: getClass ().getSimpleName (), here for the capsis.extension.standViewer.SVText class: "SVText".

For the applications using the jeeb.lib.util.Translator to have a locale-dependent graphical user interface, translations must be provided for these extensions management information. A file must be writen for each supported language (e.g. french and english: SVText_fr.properties and SVText_en.properties), containing the translations for the extension.

# SVText_fr.properties: SVText labels - French

SVText = Visu Texte
SVText.description = Visualisateur de peuplements en mode texte
... other translations
# SVText_en.properties: SVText labels - English

SVText = Text viewer
SVText.description = Text mode stand viewer
... other translations

Each extension must take care of loading its own translations into the Translator in a static initializer (_fr / _en.properties will be loaded by the translator depending on the current language of the application:

    static {
        Translator.addBundle("capsis.extension.standviewer.SVText");
    } 

How to use extensions

Lets consider an application with a graphical user interface. Some parts of this gui can show lists of extensions of a given type, compatible with a current context. These lists may be entries in menus, in popup menus, list widgets or comboboxes. The components managing these lists deal with the extension manager to get the information about the extensions to be listed. They also rely on the extension manager to open the extension when chosen by the user.

Note: for the extensions that should be usable in batch mode, it is possible to add specific constructors and methods and to call them directly without asking the extension manager.

    // Get the extension manager
    extMan = CapsisExtensionManager.getInstance ();

    // Get the StandViewers compatible with the referent (model)
    Collection<String> extensionClassNames = 
            extMan.getExtensionClassNames (CapsisExtensionManager.STAND_VIEWER, model);

    // Show the list in some widget
    for (String className :  extensionClassNames) {
        String name = ExtensionManager.getName (className);  // translated if translation was found
        extensions.put (name, className);  // to get the className when the user selects a name
        ...
    }

    // When the user chooses an extension in the list, 
    // instanciate it from its className
    StandViewer viewer = (StandViewer) extMan.instantiate(className);

    // Call the init method to tell the viewer what to show (here a given step in a simulation)
    viewer.init(model, step, sb);

How to link extension managers

It is possible to build a single extension manager dealing with extensions of several applications. For example, the CapsisExtensionManager can provide extensions of the Sketch library in a transparent way. This is possible because the CapsisExtensionManager was designed to link with the SketchExtensionManager at initialisation time.

The connection is made in the init () method. Before declaring the Capsis extension types and loading them, a call is made to the init () method of the SketchExtensionManager, passing it the instance of the CapsisExtensionManager. Thus, the Sketch extension types will be also added and the Sketch extensions loaded.

The result is a single extension manager: CapsisExtensionManager.getInstance () with all the extension of Capsis and Sketch usable in the Capsis application.

    /**    Inits the extension manager
     */
    static public void init(ExtensionManager e) {

        // init() must be executed only once
        if (initOk) {return;}
        initOk = true;

        // Add the Sketch extensions
        SketchExtensionManager.init(e);

        // Declare the Capsis extension types
        initTypes(e);

        // Load the Capsis extensions
        String extFile = getExtensionFileName();
        try {
            e.readExtensionPropertiesFile(extFile);
        } catch(Exception ex) {
            e.findNewExtension(ModelManager.getInstance().getPackageNames(), extFile);
        }

    }