1. Writing your own ClassLoader contribution
    1. What are you contributing
    2. Defining Our Contribution
      1. Creating a simple Preferences Page
        1. Preference Constants
        2. Defining Default Values for preferences
        3. Preference Page
      2. Contributing The preference page
      3. Screen shot of Preference Page
      4. Implementing IClassPathModel
        1. BasicClassPathModel
      5. Contributing our IClassPathModel
      6. Screen Shot of Contributed IClassPathModel
    3. Contributing Using help
      1. Providing help HTML
      2. Defining a TOC
      3. Contributing your TOC

Writing your own ClassLoader contribution

This section details the steps involved in creating a contribution that will extend the classpathmodels extension point allowing you to create arbitrarily complex classloader hierarchies. This tutorial will not provide a detailed introduction to eclipse plugins.

What are you contributing

classpathhelper defines an extension point (classpathhelper.eclipse.ui.classpathmodels) that is intended to allow plugins to specify an object that can create its own ClassLoaders.

The core API in classpath helper is the CGClassLoader class from the classpathhelper.api plugin. CGClassLoader is a standard classloader that also provides the detailed dependency and class detail information required by the classpath helper views. CGClassLoader can be configured to model most classloading requirements and can also be used in a hierarchy of CGClassLoaders to simulate complex classloading environments (such as a J2EE application server).

The actual CGClassLoader is provided to the UI via a IClassPathModel instance. The IClassPathModel is the object that is actually contributed to the UI extension point.

Defining Our Contribution

For the purposes of this tutorial we will define a very basic classloading example, that uses a standard classpath string as its input. To complete this example we need to supply the following.

Complete code for this example is included with the classpathhelper.eclipse.basicmodel plugin.

Creating a simple Preferences Page

Creating preferences pages in eclipse is well documented. For our purposes we need to create a page that can accept the classpath string we will be using as our model. Using preferences has the advantage that we can register listeners that can respond to changes to the preference value.

Preference Constants

First we define a shared constants interface to provide a common reference for constants names. (Comments are excluded for brevity but exist in source files).

package classpathhelper.eclipse.basicmodel;

public class PreferencesConstants {
    public final static String BASIC_CLASSPATH_LABEL_KEY = "classpath.label";
    public final static String BASIC_CLASSPATH_PREF  = "BasicClasspath";

    private static final String BUNDLE_NAME = "classpathhelper.eclipse.basicmodel.messages";

    private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
                        .getBundle(BUNDLE_NAME);
    
    public static String getString(String key) {
        try {
            return RESOURCE_BUNDLE.getString(key);
        }
        catch (MissingResourceException e) {
            return '!' + key + '!';
        }
    }
}
The corresponding messages.properties would look as follows:
  classpath.label=Classpath

Defining Default Values for preferences

When creating a preference it is a good practice to provide default values. This is done by overriding the initializeDefaultPluginPreferences method within your plugin class.

    protected void initializeDefaultPluginPreferences() {
        plugin.getPluginPreferences().setDefault(PreferencesConstants.BASIC_CLASSPATH_PREF,
                                                 "");
    }

Preference Page

Second we define the preference page. As is standard with eclipse preferences page. We extend the PreferencePage base class and implement IWorkbenchPreferencePage. We also implement our PreferencesConstants interface to obtain easy references to the constants. (Comments removed for brevity but exist in source files).

package classpathhelper.eclipse.basicmodel;

import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;

public class PreferencesPage extends PreferencePage implements
                                        IWorkbenchPreferencePage {

Next we define the string field editor that will handle the classpath string, as well as hooking this classes preferences store to the preference store for this plugin.

    private StringFieldEditor classpath;
    
    public PreferencesPage() {
        setPreferenceStore(BMPlugin.getDefault().getPreferenceStore());
    }

The createContents method is used to layout the controls. In our case this is just the single text field.

    protected Control createContents(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);          
        composite.setLayout(new GridLayout(1, true));
        GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
        composite.setLayoutData(gd);
        classpath = new StringFieldEditor(PreferencesConstants.BASIC_CLASSPATH_PREF,
                                          PreferencesConstants.getString(PreferencesConstants.BASIC_CLASSPATH_LABEL_KEY),
                                          composite);
        classpath.setPreferenceStore(getPreferenceStore());
        classpath.setPage(this);
        classpath.load();           

        return parent;
    }

The remaining methods init and performOk a straightforward as we just want to store the new value. The PreferenceStore automatically handles firing a preferences change event.

    public void init(IWorkbench workbench) {
    }

    public boolean performOk() {
        classpath.store();
        
        return true;
    }

Contributing The preference page

As is standard in eclipse. To introduce the preference page we just coded we need to define the extension in our plugin.xml. Note the category, which will place our preference page underneith the classpath helper preferences entry.

   <extension
         point="org.eclipse.ui.preferencePages">
      <page
            name="Simple"
            category="classpathhelper.eclipse.ui.preferences.PreferencesPage"
            class="classpathhelper.eclipse.basicmodel.PreferencesPage"
            id="classpathhelper.eclipse.basicmodel.PreferencesPage">
      </page>
   </extension>

Screen shot of Preference Page

After defining the preference page in our plugin, we should have a page similar to the follow: Note how this page is accessible off of the Classpath Helper area.

Implementing IClassPathModel

IClassPathModel is really the core interface that makes the classloader available to views. The basic interface only contains a few methods. (Comments removed for brevity).

package classpathhelper.eclipse.ui;

public interface IClassPathModel {
    public void startClassPathListening();   

    public void stopClassPathListening();
    
    public void reset();
        
    public void addClassPathChangeListener(IClassPathChangeListener changeListener);
        
    public boolean removeClassPathChangeListener(IClassPathChangeListener changeListener);
}

The start and stop methods are lifecycle methods which allow the model to create resources and release them when the model is nolonger active (such as when no views are open).

The remaining two methods are simply standard listener related methods that the views use to attach to the model.

The classpathhelper.eclipse.ui plugin also provides a helper base class AbstractClassPathModel which implements the listener registration methods. The relavent methods within AbstractClassPathModel are as follows:

package classpathhelper.eclipse.ui;

import classpathhelper.CGClassLoader;

public abstract class AbstractClassPathModel implements IClassPathModel {
    protected synchronized void fireClassPathChangeEvent(CGClassLoader classLoader) {
        ...
    }
    
    protected abstract CGClassLoader getCurrentClassLoader();

The method fireClassPathModelChangeEvent is intended to be used by subclasses to notify their listeners of a new ClassLoader.

The abstract method getCurrentClassLoader is used to retrieve the current classloader so that newly registered listeners can receive an update event (without needing to fire a new event to all listeners). The current classloader should be the same object that was last fired as part of a classpath change event.

BasicClassPathModel

Our implementation of IClassPathModel extends AbstractClassPathModel and also implements two listener interfaces.

package classpathhelper.eclipse.basicmodel;

import java.io.File;
import java.util.StringTokenizer;

import classpathhelper.CGClassLoader;
import classpathhelper.eclipse.ui.AbstractClassPathModel;
import classpathhelper.filechangelistener.FileChangeMonitor;
import classpathhelper.filechangelistener.FileChangedEvent;
import classpathhelper.filechangelistener.IFileChangeListener;

import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;

public class BasicClassPathModel extends AbstractClassPathModel implements
                                        IFileChangeListener, IPropertyChangeListener {

The two listener interfaces represent callbacks that we will use to fire a new classloader event. IPropertyChangeListener is a standard eclipse callback for any changes related to preferences. Since we earlier created a preference page we can use this interface to detect a change that would require us to create a new ClassLoader.

IFileChangeListener is a callback from a helper utilitly defined in classpathhelper.api. The underlying utility will inform us of any file change events that could make our classloader obsolete. In this case we re-create the classloader with the same path and fire it as a new classloader. This allows us to respond to environment changes such as a directory getting repopulated due to compilation or a jar being removed or updated.

The bulk of our classes intelligence is handled in the single method updateClassLoader we will tie in calls to this method later. But for now we can see how we create a file monitor to watch all the files on the classpath as well as the basic parsing logic to tokenize the classpath string (formatted in standard classpath format). This method also fires the new classloader event.

    private CGClassLoader     currentClassLoader = null;
    private FileChangeMonitor fileMonitor = null;

    protected void updateClassLoader(String classpath) {
        if ( fileMonitor != null )
            fileMonitor.cancel();
                
        // file monitor looks for changes to any files
        // on the classpath and will fire an event.
        fileMonitor = new FileChangeMonitor(30000, 2);
        fileMonitor.addFileChangeListener(this);
        StringTokenizer tok = new StringTokenizer(classpath, File.pathSeparator);
        while ( tok.hasMoreTokens() ) {
            String element = tok.nextToken();
            File   fileElem = new File(element);
            fileMonitor.addSource(element,
                                  fileElem.exists() && fileElem.isDirectory());
        }
        fileMonitor.startMonitor();
                
        currentClassLoader = new CGClassLoader(classpath);
        fireClassPathChangeEvent( currentClassLoader );                         
    }

With this method in place the remainder of the class can be expressed in terms of simple implementations. The start method simply attaches this class as a property change listener, while calling or previously defined update method to create the initial classloader.

The stop method detaches this class as a preferences listener and clears/cancels the related state.

    public void startClassPathListening() {
        BMPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
        updateClassLoader(BMPlugin.getClassPathPreference());
    }

    public void stopClassPathListening() {
        BMPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
        currentClassLoader = null;
        if ( fileMonitor != null ) {
            fileMonitor.cancel();
            fileMonitor = null;
        }
    }

Our two callback methods from the two listener interfaces are straight forward as well. The fileChanged callback simply stops and starts this listener, which releases any prior classloader and then simply starts monitoring a new classloader.

The propertyChange callback reacts to the new classpath value by calling updateClassLoader which will fire the new class loader event.

    public void fileChanged(FileChangeMonitor monitor, FileChangedEvent[] evts) {
        stopClassPathListening();
        // starting the classpath listening will resend a classpath event.
        startClassPathListening();
    }
        
    public void propertyChange(PropertyChangeEvent evt) {
        Object newValue = evt.getNewValue();
        if ( newValue != null &&
             newValue instanceof String )
            updateClassLoader((String) newValue);
    }

Contributing our IClassPathModel

As is standard with extensions in eclipse we must also indicate in our plugin.xml how we are contributing. The label and description are used in the UI to indicate which ClassPathModel is currently being used.

   <extension
         point="classpathhelper.eclipse.ui.classpathmodels">
      <model class="classpathhelper.eclipse.basicmodel.BasicClassPathModel"
             id="classpathhelper.eclipse.basicmodel.classpathmodel"
             name="classpathhelper.eclipse.basicmodel.classpathmodel"
             label="%model.name"
             description="%model.description" />
   </extension>

Where the tokens model.name and model.description are supplied in a properties file.

  model.name=Simple
  model.description=Classpath provided as string

Screen Shot of Contributed IClassPathModel

After coding the BasicClassPathModel and contributing it, ClassPath Helper will automatically show it in the list of available classpath models.

Contributing Using help

Eclipse provides good documentation on contributing help. This section will merely provide a quick overview of the steps for contributing help.

Providing help HTML

To start contributing help, you define your help contents, typically in a standard HTML file. In the Basic Classpath model, the help HTML is defined in the help/using.html file.

Defining a TOC

To contribute help you must next define a Table-of-Contents file that defines the structure of your help.

<toc label="classpathhelper.eclipse.tomcat.listener.ui JavaDocs" 
     link_to="../classpathhelper.eclipse.ui.help/toc.xml#using">
    <topic label="Using the Simple ClasspathHelper model" 
           href="help/using.html" />
</toc>

Notice the reference to the #using anchor, this allows the help to get properly inserted within the Classpath Helper contents.

Contributing your TOC

To contribute help you must contribute to the org.eclipse.help.toc extension point.

   <extension point="org.eclipse.help.toc">
      <toc file="toc.xml" />
   </extension>
SourceForge.net Logo