17.3. Code Behind an Extension PointAfter the extension point has been defined, you must write the code behind it that builds Favorites item types and Favorites objects based on the information declared in extensions of the extension point. Following the Eclipse theme of lazy initialization, you want to keep the memory footprint down, so each Favorites item type and plug-in containing it must be loaded only if necessary. To achieve this, refactor portions of FavoriteItemType (see Section 17.2.3, Extension point elements and attributes, on page 601) into a new FavoriteItemFactory and then reorganize FavoriteItemType to build types from extension information. This is followed by recasting the Favorites item type constants as extensions to the new Favorites extension point. 17.3.1. Parsing extension informationThe first modification to the FavoriteItemType involves building instances of this class from the extension information rather than hard-coding the information in the class as constants. Rename the TYPES array to cachedTypes to more accurately represent the purpose of this static field. Modify the getTypes() method to build a new instance of FavoriteItemType for each extension found.
private static final String TAG_ITEMTYPE = "itemType";
private static FavoriteItemType[] cachedTypes;
public static FavoriteItemType[] getTypes() {
if (cachedTypes != null)
return cachedTypes;
IExtension[] extensions = Platform.getExtensionRegistry()
.getExtensionPoint(FavoritesPlugin.ID, "favorites")
.getExtensions();
List found = new ArrayList(20);
found.add(UNKNOWN);
for (int i = 0; i < extensions.length; i++) {
IConfigurationElement[] configElements =
extensions[i].getConfigurationElements();
for (int j = 0; j < configElements.length; j++) {
FavoriteItemType proxy =
parseType(configElements[j], found.size());
if (proxy != null)
found.add(proxy);
}
}
cachedTypes =
(FavoriteItemType[]) found.toArray(
new FavoriteItemType[found.size()]);
return cachedTypes;
}
private static FavoriteItemType parseType(
IConfigurationElement configElement, int ordinal
) {
if (!configElement.getName().equals(TAG_ITEMTYPE))
return null;
try {
return new FavoriteItemType(configElement, ordinal);
}
catch (Exception e) {
String name = configElement.getAttribute(ATT_NAME);
if (name == null)
name = "[missing name attribute]";
String msg =
"Failed to load itemType named "
+ name
+ " in "
+ configElement.getDeclaringExtension().getNamespace();
// Eclipse 3.2 - replace getNamespace()
// with getContributor().getName()
FavoritesLog.logError(msg, e);
return null;
}
}
Tip
As always, proper exception handling is necessary,
17.3.2. Constructing proxiesNext, you modify the FavoriteItemType constructor to extract the basic information from the extension without loading the plug-in that declared the extension. This instance stands in as a proxy for the factory contained in the declaring plug-in. If a required attribute is missing, then an IllegalArgumentException is thrown, to be caught in the exception handler of the parseType() method described earlier.
private static final String ATT_ID = "id";
private static final String ATT_NAME = "name";
private static final String ATT_CLASS = "class";
private static final String ATT_TARGETCLASS = "targetClass";
private static final String ATT_ICON = "icon";
private final IConfigurationElement configElement;
private final int ordinal;
private final String id;
private final String name;
private final String targetClassName;
private FavoriteItemFactory factory;
private ImageDescriptor imageDescriptor;
public FavoriteItemType(
IConfigurationElement configElem, int ordinal
) {
this.configElement = configElem;
this.ordinal = ordinal;
id = getAttribute(configElem, ATT_ID, null);
name = getAttribute(configElem, ATT_NAME, id);
targetClassName =
getAttribute(configElem, ATT_TARGETCLASS, null);
// Make sure that class is defined,
// but don't load it.
getAttribute(configElem, ATT_CLASS, null);
}
private static String getAttribute(
IConfigurationElement configElem,
String name,
String defaultValue
) {
String value = configElem.getAttribute(name);
if (value != null)
return value;
if (defaultValue != null)
return defaultValue;
throw new IllegalArgumentException(
"Missing " + name + " attribute");
}
Tip
How do you determine what information to load from an extension immediately versus what should be deferred via lazy initialization to an accessor method?
Potentially, every extension could be invalid and you could end up with no valid instances of FavoriteItemType returned by getTypes() . To alleviate this problem, hard-code a single FavoriteItemType named UNKNOWN and add this as the first object in the collection returned by getTypes() .
public static final FavoriteItemType UNKNOWN =
new FavoriteItemType()
{
public IFavoriteItem newFavorite(Object obj) {
return null;
}
public IFavoriteItem loadFavorite(String info) {
return null;
}
};
private FavoriteItemType() {
this.id = "Unknown";
this.ordinal = 0;
this.name = "Unknown";
this.configElement = null;
this.targetClassName = " ";
}
Now,
private static final ImageCache imageCache = new ImageCache();
public String getId() {
return id;
}
public String getName() {
return name;
}
public Image getImage() {
return imageCache.getImage(getImageDescriptor());
}
public ImageDescriptor getImageDescriptor() {
if (imageDescriptor != null)
return imageDescriptor;
String iconName = configElement.getAttribute(ATT_ICON);
if (iconName == null)
return null;
IExtension extension =
configElement.getDeclaringExtension();
String extendingPluginId = extension.getNamespace();
// Eclipse 3.2 - replace getNamespace()
// with getContributor().getName()
imageDescriptor =
AbstractUIPlugin.imageDescriptorFromPlugin(
extendingPluginId,
iconName);
return imageDescriptor;
}
17.3.3. Creating executable extensionsThe loadFavorite(String) and newFavorite(Object) methods are redirected to the factory object as specified in the extension. Since instantiating the factory object involves loading the plug-in that contains it, this operation is deferred until needed. The targetClassName is used by the newFavorite(Object) method to determine whether the associated factory can handle the specified object and thus whether the associated factory needs to be loaded. The code that instantiates the factory object is wrapped in an exception handler so that detailed information can be logged concerning the failure that occurred and which plug-in and extension are involved.
public IFavoriteItem newFavorite(Object obj) {
if (!isTarget(obj)) {
return null;
}
FavoriteItemFactory factory = getFactory();
if (factory == null) {
return null;
}
return factory.newFavorite(this, obj);
}
private boolean isTarget(Object obj) {
if (obj == null) {
return false;
}
Class clazz = obj.getClass();
if (clazz.getName().equals(targetClassName)) {
return true;
}
Class[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (interfaces[i].getName().equals(targetClassName)) {
return true;
}
}
return false;
}
public IFavoriteItem loadFavorite(String info) {
FavoriteItemFactory factory = getFactory();
if (factory == null) {
return null;
}
return factory.loadFavorite(this, info);
}
private FavoriteItemFactory getFactory() {
if (factory != null) {
return factory;
}
try {
factory = (FavoriteItemFactory) configElement
.createExecutableExtension(ATT_CLASS);
} catch (Exception e) {
FavoritesLog.logError(
"Failed to instantiate factory: "
+ configElement.getAttribute(ATT_CLASS)
+ " in type: "
+ id
+ " in plugin: "
+ configElement.getDeclaringExtension().getNamespace(),e);
// Eclipse 3.2 - replace getNamespace()
// with getContributor().getName()
}
return factory;
}
Tip Whenever instantiating an object specified in an extension, always use the IConfigurationElement.createExecutable(String) method. This method automatically handles references from extensions in one plug-in's manifest to code located in another plug-in's runtime library as well as various forms of post-instantiation initialization specified in the extension (see Section 20.5, Types Specified in an Extension Point, on page 723). If you use Class.forName(String) , then you will only be able to instantiate objects already known to your plug-in because Class.forName(String) uses your plug-in's class loader and thus will only instantiate objects in your plug-in's classpath (see Section 20.9, Plug-in ClassLoaders, on page 742 for more on class loaders). The new factory type is an abstract base class that must be extended by other plug-ins providing new types of Favorites objects. See the " Tip" in Section 17.2.3, Extension point elements and attributes, on page 601 for a discussion of interface versus abstract base class. The factory type includes a concrete dispose method so that subclasses can perform cleanup if necessary, but are not required to implement this method if cleanup is not needed.
package com.qualityeclipse.favorites.model;
public abstract class FavoriteItemFactory
{
public abstract IFavoriteItem newFavorite(
FavoriteItemType type, Object obj);
public abstract IFavoriteItem loadFavorite(
FavoriteItemType type, String info);
public void dispose() {
// Nothing to do... subclasses may override.
}
}
17.3.4. CleanupWhen the plug-in shuts down, you must dispose of all cached images and give each of the factory objects an opportunity to clean up. Add the methods, disposeTypes() and dispose() , to the FavoriteItemType . Modify the FavoritesPlugin stop() method to call this new disposeTypes() method.
public static void disposeTypes() {
if (cachedTypes == null) return;
for (int i = 0; i < cachedTypes.length; i++)
cachedTypes[i].dispose();
imageCache.dispose();
cachedTypes = null;
}
public void dispose() {
if (factory == null) return;
factory.dispose();
factory = null;
}
|