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:
In addition to the extension point, the base plugin also provides an interface ICalculator that it expects all contributions to implement:
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:
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:
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:
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);

