Frank's Eclipse and Java Blog

Tuesday, March 29, 2005

Typed Extension Point

This seems to be a frequently asked question on the newsgroups (another instance popped up today in the platform group) so I decided to document this in pattern form. I use this pattern all the time myself and it occurs many times in eclipse itself, so it definitely qualifies as a pattern. Please don't beat me up on the pattern form, I'm doing this from the top of my head and in a hurry... Perhaps it is time for an Eclipse pattern repository? Is there one already and did I miss it?

Intent

Your plug-in is providing an extension point with a class attribute allowing other plug-ins to contribute code to your framework, but you want to force those code contributions to implement a particular interface you provide.

Motivation
Obviously you expect the contributions to your plug-in to contribute a known set of behaviors and java interfaces are the natural mechanism to do so. But what is not so obvious to developers new to Eclipse is that the classes from the base plug-in (your plug-in with the extension point) and the plug-ins contributing the implementations are loaded by different class loaders. A frequent mistake is to provide the interface class in both the base plug-in and the contributing plug-in, resulting in these type of postings to the newsgroups:

"My goal is to use a plugin to load an extension which is in a separate plugin(later 1 to many extensions deriving from the same interface) but I am getting a ClassCastException when casting the return object from a createExecutableExtension call."

If the same class is loaded by two different class loaders, then even though the name of the class(es) is the same, there are in fact two distinct classes. Passing an instance of one, to a method that expects the other will cause a ClassCastException.

Structure

The solve this, provide the interface in the base plugin only and make the contributing plugins import the base plug-in in their plugin.xml, which is a natural thing to do since they extent the extension point provided by the base plug-in. By importing the base plug-in, the contributing plug-ins's classloader will find the interface (and any other code exported from the base) and there will be no classloader confusion.

Example


The following code is from my metrics plug-in. The core framework has an extension point that is extended like so:

<extension
point="net.sourceforge.metrics.core.calculators">

<calculator
calculatorClass =
"net.sourceforge.metrics.tests.MockCalculator"
id="testcalculator1"
level="method"
name="Test Calculator">
... lots irrelevant stuff omitted here
</calculator>
</extension>

In addition to the extension point, the base plugin also provides an interface ICalculator that it expects all contributions to implement:

public interface ICalculator {

void setName(String name);

void calculate(IMetricsElement source);
}

Since our MockCalculator class has to implement this interface, and our plugin cannot contain the ICalculator interface class itself to avoid the class loader issue, we have to get it from the base plugin. To do this we make our plugin depend on the base, by importing it:

<requires>
<import plugin="net.sourceforge.metrics.core"/>
</requires>

When you add this to the plugin.xml of your contributing plug-in, all the classes exported by the base plug-in will now be on its build path, including the required interface.

In the base plug-in, you will have code that discovers and processes the extensions to your extension point that will look something like the following. Note that because the base plug-in has no access to the classloader of the contributing plugin (because it does not and should not depend on it) it cannot instantiate the contributions (like the MockCalculator above) directly. It has to use the createExecutableExtension from the eclipse framework to do so:

public static void readExtensions() {
IExtensionPoint p = Platform.getExtensionRegistry()
.getExtensionPoint(XPOINT);
IExtension[] extensions = p.getExtensions();
for (int x = 0; x < extensions.length; x++) {
IConfigurationElement[] elements =
extensions[x].getConfigurationElements();
for (int i = 0; i < elements.length; i++) {
IConfigurationElement next = elements[i];
if (TAG_CALCULATOR.equals(next.getName())) {
readCalculator(next);
} else if ... other stuff
}
}
}

private static void readCalculator(IConfigurationElement next) {
try {
CalculatorDescriptor c = new CalculatorDescriptor(next);
...
} catch (CoreException x) {
...
}
}

...
class CalculatorDescriptor {

CalculatorDescriptor(IConfigurationElement element)
throws IllegalArgumentException {
super(element);
this.level = element.getAttribute(ATT_LEVEL);
if (level == null) {
throw new IllegalArgumentException(
"calculator must have level");
}
try {
calculator = (ICalculator)element
.createExecutableExtension("calculatorClass");
calculator.setName(name);
} catch (CoreException e) {
Log.logError("CalculatorDescriptor::", e);
throw new IllegalArgumentException(
"Error creating calculator");
} catch (ClassCastException x) {
Log.logError("CalculatorDescriptor::", x);
throw new IllegalArgumentException(
"calculator must implement ICalculator");
}
}

Make sure you protect the base plugin from badly behaving extensions like calculators not implementing the required interface. This will result in a CoreException or ClassCastException

Known Uses
See the above example, and also practically every eclipse extension point that has a class attribute, for example:

  • (AbstractPreferenceInitializer) element.createExecutableExtension(ATTRIBUTE_CLASS);
  • return (IQueryParticipant) fConfigurationElement.createExecutableExtension(CLASS);
  • result[0]= (ViewerFilter)fElement.createExecutableExtension(
    CLASS_ATTRIBUTE);

Thursday, March 17, 2005

Eclipse plug-ins are Existions?

Is that even a word? Ivar Jacobson seems to think so, and that's good enough for me. I just received my latest purchase from Amazon, titled "Aspect-oriented Software Development with Use Cases" by Ivar Jacobson and Pan-Wei Ng, and to my surprise, on page 13 is a diagram of the Eclipse architecture! In short, an Existion has extension points, into which extensions contribute. What's even more surprising is that this is from a 1986 paper Language Support for Changeable Large Real-Time Systems (OOPSLA '86, pp.377-384), which in turn explores ideas of a 1981 patent application!!! (Which was denied because it looked too much like an already patented - get this - patching technique :-). Wow...

Wednesday, March 09, 2005

AOP@Work: AOP and metadata: A perfect match, Part 1

Still have to read it, but AOP@Work: AOP and metadata: A perfect match, Part 1 looks to be a very interesting article!

Wednesday, March 02, 2005

Calculus of Procrastination

The seeds for a Calculus of Procrastination can be found here: ideasasylum ::Jamie's Weblog: The Distraction Operator

I love it! Very cool :-)