Plugin Ready Java Application
Preface
Everything started with the need of adding plugin support to some Java/SWT application.
Plugin support is far from being easy to implement, unless you use a plugin framework
to help. We first looked at the Eclipse Equinox
plugin framework (implementing the OSGi
framework specification). Later on, I found another project named
Java Plugin Framework (JPF).
The standard way of using JPF is: start the plugin framework, by launching the main
method
of the framework. The framework loads the plugins and searches for the application plugin. Using
the application plugin, it launches your application. Equinox implements the same scheme.
We didn't want to convert the original application to a plugin. We wanted to keep on starting the
application in a more standard way. The application would, at some point, start the plugin framework
and load the plugins. Doing so with JPF appeared to be quite easy.
This article presents a simple application and how to add plugin support, in
two phases: the first is very simple, the second is more elegant.
The application, named Bookmark, displays a tree view with bookmarks, on the left-side,
and a browser view on the right-side (see picture). The application loads plugins that
add new sections with bookmarks to the default bookmarks. The goal here is
not to build a useful application, but to show how to add plugin support. We won't even
perform any validation or merge sections.
The application uses the Eclipse/SWT for the GUI because integrating a Web browser is
really easy.
The application without plugin support
Let's first create the main class, named Main
. Add the main
method that
creates an instance of the Main
class and calls its start
method.
public static void main(String[] args) {
new Main().start();
}
The start
method creates first the shell (the main window in SWT), using the
createShell
method. The whole code is presented later.
Then, it adds the default
bookmarks to the tree view (addDefaultBookmarks
), sets the listeners to respond
to user's clicks (setListeners
), opens the shell (shell.open()
)
and runs the event loop (the event loop is explicit in SWT).
private void start() {
createShell();
addDefaultBookmarks();
setListeners();
shell.open();
Display display = shell.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
In SWT the tree nodes are TreeItem
objects. A TreeItem
object has a text
and can have any object as data. We are going to set the target URL as data (see next snippet of code
showing how we set the URL for the Google entry).
TreeItem item = new TreeItem(root, SWT.NULL);
item.setText("Google");
item.setData("http://www.google.com");
The TreeItem
constructor requires the parent tree or another TreeItem
, here
the root
object is the Bookmarks node (a TreeItem
object).
Click here to show
the whole code.
hide
package org.alef1.bookmark;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
public class Main {
public static void main(String[] args) {
new Main().start();
}
private void start() {
createShell();
addDefaultBookmarks();
setListeners();
shell.open();
Display display = shell.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private void addDefaultBookmarks() {
root = new TreeItem(tree, SWT.NULL);
root.setText("Bookmarks");
TreeItem item = new TreeItem(root, SWT.NULL);
item.setText("Google");
item.setData("http://www.google.com");
TreeItem dev = new TreeItem(root, SWT.NULL);
dev.setText("Java");
item = new TreeItem(dev, SWT.NULL);
item.setText("java.net");
item.setData("http://www.java.net");
item = new TreeItem(dev, SWT.NULL);
item.setText("Java World");
item.setData("http://www.javaworld.com/");
}
private void setListeners() {
tree.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
}
public void widgetDefaultSelected(SelectionEvent e) {
TreeItem item = (TreeItem) e.item;
if (item.getItemCount() == 0) {
CTabItem tab = new CTabItem(tabFolder, SWT.CLOSE);
tab.setText(item.getText());
Browser b = new Browser(tabFolder, SWT.BORDER);
b.setUrl((String) item.getData());
tab.setControl(b);
tabFolder.setSelection(tabFolder.getItemCount() - 1);
}
}
});
}
private Shell shell = null;
private SashForm sashForm = null;
private Tree tree = null;
private CTabFolder tabFolder = null;
private TreeItem root;
private PluginManager pluginManager;
/**
* This method initializes shell
*
*/
private void createShell() {
shell = new Shell();
shell.setLayout(new FillLayout());
shell.setText("Plugin Aware Application");
createSashForm();
shell.setSize(new Point(435, 196));
}
/**
* This method initializes sashForm
*
*/
private void createSashForm() {
sashForm = new SashForm(shell, SWT.V_SCROLL);
sashForm.setOrientation(SWT.HORIZONTAL);
tree = new Tree(sashForm, SWT.BORDER);
tree.setItemCount(0);
createTabFolder();
sashForm.setWeights(new int[] {30, 70});
}
/**
* This method initializes tabFolder
*
*/
private void createTabFolder() {
tabFolder = new CTabFolder(sashForm, SWT.NONE);
tabFolder.setSimple(false);
Display display = shell.getDisplay();
tabFolder.setSelectionForeground(display.getSystemColor(SWT.COLOR_WHITE));
tabFolder.setSelectionBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
display.getSystemColor(SWT.COLOR_BLUE),
display.getSystemColor(SWT.COLOR_WHITE),
display.getSystemColor(SWT.COLOR_WHITE)},
new int[] {25, 50, 100});
}
}
Adding Plugin Support
The bookmark application could benefit of plugins to extend the bookmarks presented to the user.
Sure, the real purpose is adding plugin support here.
We are going to be very basic and simple to keep this presentation easier. Let's add an
interface, named BookmarkSection
, to get additional bookmarks from implementing classes.
public interface BookmarkSection {
String category();
int count();
String getName(int index);
String getUrl(int index);
}
The idea is that any plugin found in the plugins
directory should implement
the above interface. The application can retrieve a section name and add every entry, below
the given section (the picture, shown previously, has two such sections named Java and
Ruby).
The start
method now loads the plugins and then use them to add new (dynamic)
bookmarks. Hereafter is the new version of the method, new code is highlighted.
private void start() {
loadPlugins();
createShell();
addDefaultBookmarks();
addDynamicBookmarks();
setListeners();
shell.open();
Display display = shell.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
Now we need to implement the new methods.
Loading the plugins
Let's look at the implementation of the loadPlugins
method.
First, we must create a plugin manager object.
pluginManager = ObjectFactory.newInstance().createManager();
Next, get all the plugins found in the plugins directory (any file name
ending with .zip
extension, for instance).
File pluginsDir = new File("plugins");
File[] plugins = pluginsDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".zip");
}
});
Last step is the actual loading, create an array of PluginLocation
objects for every plugin file. Loading the plugins is easy, just call the plugin
manager's publishPlugins
method. The exception management policy here
is very basic, stop the application if we cannot load the plugins.
try {
PluginLocation[] locations = new PluginLocation[plugins.length];
for (int i = 0; i < plugins.length; i++) {
locations[i] = StandardPluginLocation.create(plugins[i]);
}
pluginManager.publishPlugins(locations);
} catch (Exception e) {
throw new RuntimeException(e);
}
Using the plugins
The addDynamicBookmarks
method uses the plugins to extend the bookmark
list.
We must start iterating over all the plugins that were loaded, we use the plugin
manager's registry to get an iterator of the plugin descriptors.
Iterator it = pluginManager.getRegistry().getPluginDescriptors().iterator();
We get an instance of every plugin with the help of the PluginManager
,
using its plugin ID provided by the PluginDescriptor
object. As we
expect any plugin to implement our BookmarkSection
interface, we
just cast the plugin instance.
PluginDescriptor p = (PluginDescriptor) it.next();
BookmarkSection section = (BookmarkSection) pluginManager.getPlugin(p.getId());
The plugin manager uses plugin specific class loaders so that the plugin bundle
is used in isolation.
Finally, we use the BookmarkSection
objects to retrieve additional
bookmarks.
TreeItem entry = new TreeItem(root, SWT.NULL);
entry.setText(section.category());
for (int i = 0; i < section.count(); i++) {
TreeItem item = new TreeItem(entry, SWT.NULL);
item.setText(section.getName(i));
item.setData(section.getUrl(i));
}
Click here to show
the whole code with the first version of plugin support.
hide
package org.alef1.bookmark;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Iterator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.java.plugin.ObjectFactory;
import org.java.plugin.PluginManager;
import org.java.plugin.PluginManager.PluginLocation;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.standard.StandardPluginLocation;
public class Main {
public static void main(String[] args) {
new Main().start();
}
private void start() {
loadPlugins();
createShell();
addDefaultBookmarks();
addDynamicBookmarks();
setListeners();
shell.open();
Display display = shell.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private void loadPlugins() {
pluginManager = ObjectFactory.newInstance().createManager();
File pluginsDir = new File("plugins");
File[] plugins = pluginsDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".zip");
}
});
try {
PluginLocation[] locations = new PluginLocation[plugins.length];
for (int i = 0; i < plugins.length; i++) {
locations[i] = StandardPluginLocation.create(plugins[i]);
}
pluginManager.publishPlugins(locations);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void addDefaultBookmarks() {
root = new TreeItem(tree, SWT.NULL);
root.setText("Bookmarks");
TreeItem item = new TreeItem(root, SWT.NULL);
item.setText("Google");
item.setData("http://www.google.com");
TreeItem dev = new TreeItem(root, SWT.NULL);
dev.setText("Java");
item = new TreeItem(dev, SWT.NULL);
item.setText("java.net");
item.setData("http://www.java.net");
item = new TreeItem(dev, SWT.NULL);
item.setText("Java World");
item.setData("http://www.javaworld.com/");
}
private void addDynamicBookmarks() {
try {
Iterator it = pluginManager.getRegistry().getPluginDescriptors().iterator();
while (it.hasNext()) {
PluginDescriptor p = (PluginDescriptor) it.next();
BookmarkSection section = (BookmarkSection) pluginManager.getPlugin(p.getId());
TreeItem entry = new TreeItem(root, SWT.NULL);
entry.setText(section.category());
for (int i = 0; i < section.count(); i++) {
TreeItem item = new TreeItem(entry, SWT.NULL);
item.setText(section.getName(i));
item.setData(section.getUrl(i));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void setListeners() {
tree.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
}
public void widgetDefaultSelected(SelectionEvent e) {
TreeItem item = (TreeItem) e.item;
if (item.getItemCount() == 0) {
CTabItem tab = new CTabItem(tabFolder, SWT.CLOSE);
tab.setText(item.getText());
Browser b = new Browser(tabFolder, SWT.BORDER);
b.setUrl((String) item.getData());
tab.setControl(b);
tabFolder.setSelection(tabFolder.getItemCount() - 1);
}
}
});
}
private Shell shell = null;
private SashForm sashForm = null;
private Tree tree = null;
private CTabFolder tabFolder = null;
private TreeItem root;
private PluginManager pluginManager;
/**
* This method initializes shell
*
*/
private void createShell() {
shell = new Shell();
shell.setLayout(new FillLayout());
shell.setText("Plugin Aware Application");
createSashForm();
shell.setSize(new Point(435, 196));
}
/**
* This method initializes sashForm
*
*/
private void createSashForm() {
sashForm = new SashForm(shell, SWT.V_SCROLL);
sashForm.setOrientation(SWT.HORIZONTAL);
tree = new Tree(sashForm, SWT.BORDER);
tree.setItemCount(0);
createTabFolder();
sashForm.setWeights(new int[] {30, 70});
}
/**
* This method initializes tabFolder
*
*/
private void createTabFolder() {
tabFolder = new CTabFolder(sashForm, SWT.NONE);
tabFolder.setSimple(false);
Display display = shell.getDisplay();
tabFolder.setSelectionForeground(display.getSystemColor(SWT.COLOR_WHITE));
tabFolder.setSelectionBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
display.getSystemColor(SWT.COLOR_BLUE),
display.getSystemColor(SWT.COLOR_WHITE),
display.getSystemColor(SWT.COLOR_WHITE)},
new int[] {25, 50, 100});
}
}
Creating a plugin
Let's write a plugin to add some URLs, that creates a Ruby section. We
must create a class that implements the BookmarkSection
. We name
the class RubyURLs
.
package org.alef1.bookmark;
public class RubyURLs implements BookmarkSection {
public String category() {
return "Ruby";
}
public int count() {
return names.length;
}
public String getName(int index) {
return names[index];
}
public String getUrl(int index) {
return urls[index];
}
final static String[] names = {
"Ruby Home",
"Try Ruby!",
"Rake",
"Rails",
};
final static String[] urls = {
"http://www.ruby-lang.org/",
"http://tryruby.hobix.com/",
"http://rubyforge.org/projects/rake/",
"http://www.rubyonrails.org/",
};
}
But as we want it to be a plugin, we need to extend the org.java.plugin.Plugin
class and implement the doStart
and doStop
methods. The
implementations are empty as we don't need to do anything. See following code
for the new version of the RubyURLs
class.
public class RubyURLs extends Plugin implements BookmarkSection {
public String category() {
return "Ruby";
}
public int count() {
return names.length;
}
public String getName(int index) {
return names[index];
}
public String getUrl(int index) {
return urls[index];
}
final static String[] names = {
"Ruby Home",
"Try Ruby!",
"Rake",
"Rails",
};
final static String[] urls = {
"http://www.ruby-lang.org/",
"http://tryruby.hobix.com/",
"http://rubyforge.org/projects/rake/",
"http://www.rubyonrails.org/",
};
protected void doStart() throws Exception {
}
protected void doStop() throws Exception {
}
}
To bundle the plugin, we must create a ZIP file. It must contain a plugin manifest file.
<?xml version="1.0" ?>
<!DOCTYPE plugin PUBLIC "-//JPF//Java Plug-in Manifest 1.0" "http://jpf.sourceforge.net/plugin_1_0.dtd">
<plugin id="org.alef1.bookmarks.ruby" version="1.0.0"
class="org.alef1.bookmarks.RubyURLs">
<runtime>
<library id="ruby" path="/" type="code"/>
</runtime>
</plugin>
The plugin manifest contains the plugin id, org.alef1.bookmarks.ruby
,
the plugin version and the plugin class, org.alef1.bookmarks.RubyURLs
.
We also declare the plugin code as starting at the zip root.
The plugin zip file, we name org.alef1.bookmarks.ruby-1.0.0.zip
,
contains two files: /org/alef1/bookmarks/RubyURLs.class
and
/plugin.xml
.
Once the zip file is ready, copy it in the plugins directory of the application.
Before copying the plugin to the plugins directory, the application shows:
After copying the org.alef1.bookmarks. ruby-1.0.0.zip
plugin
the application show a new entry:
Improve plugin support
If an application supports different kind of plugins (like extending
application menu, adding new import/export formats, adding more GUI views), a better
approach is to use the extension points mecanism provided by the Java Plugin
Framework.
With the extension point mechanism a plugin can declare one or more extension
points, other plugins then can extend the point declared. In our case, we would
declare (publish) a Section extenstion point and our previous plugin is
going to create an Section extension.
A Core Plugin
We are first going to create a plugin that declares the extension point. We
name this plugin org.alef1.bookmarks.core
. Here is the
plugin manifest part that declares the extension point.
<extension-point id="Section">
<parameter-def id="class"/>
<parameter-def id="name"/>
</extension-point>
The extension point id is Secion and requires two parameters: the
implementing class and a name, for a given extension. A plugin that wants to
extend the Secion extension point must provide a class and a name.
The core plugin class is an empty plugin.
public class PluginCore extends Plugin {
protected void doStart() throws Exception {
}
protected void doStop() throws Exception {
}
}
We can create a zip file to bundle the core plugin.
Click here to show
the whole manifest file.
hide
<?xml version="1.0" ?>
<!DOCTYPE plugin PUBLIC "-//JPF//Java Plug-in Manifest 1.0" "http://jpf.sourceforge.net/plugin_1_0.dtd">
<plugin id="org.alef1.bookmarks.core" version="1.0.0"
class="org.alef1.bookmarks.PluginCore">
<runtime>
<library id="core" path="/" type="code">
<export prefix="*" />
</library>
</runtime>
<extension-point id="Section">
<parameter-def id="class"/>
<parameter-def id="name"/>
</extension-point>
</plugin>
Using the plugin (revisited)
Loading the plugin does not change, the same loadPlugins
method
applies. The difference arises in the way we use the plugin, when adding the
dynamic bookmarks.
In the addDynamicBookmarks
, we start retrieving the Secion
extension point declared in the core plugin.
PluginDescriptor core = pluginManager.getRegistry().getPluginDescriptor("org.alef1.bookmarks.core");
ExtensionPoint point = pluginManager.getRegistry().getExtensionPoint(core.getId(), "Section");
Then, we iterate over all extensions to get their plugin desciptors.
for (Iterator it = point.getConnectedExtensions().iterator(); it.hasNext();) {
Extension ext = (Extension) it.next();
PluginDescriptor descr = ext.getDeclaringPluginDescriptor();
pluginManager.activatePlugin(descr.getId());
For each plugin, that extends the Section point, we retrieve the
class
parameter to get the implementing class.
ClassLoader classLoader = pluginManager.getPluginClassLoader(descr);
Class pluginCls = classLoader.loadClass(ext.getParameter("class").valueAsString());
The remaining code is unchanged, from previous implementation, we create an
instance and cast it to our BookmarkSection
interface. Using the
BookmarkSection
we add new bookmarks.
Look at the complete method
or see the whole code.
hide
private void addDynamicBookmarks() {
try {
PluginDescriptor core = pluginManager.getRegistry().getPluginDescriptor("org.alef1.bookmarks.core");
ExtensionPoint point = pluginManager.getRegistry().getExtensionPoint(core.getId(), "Section");
for (Iterator it = point.getConnectedExtensions().iterator(); it.hasNext();) {
Extension ext = (Extension) it.next();
PluginDescriptor descr = ext.getDeclaringPluginDescriptor();
pluginManager.activatePlugin(descr.getId());
ClassLoader classLoader = pluginManager.getPluginClassLoader(descr);
Class pluginCls = classLoader.loadClass(ext.getParameter("class").valueAsString());
BookmarkSection section = (BookmarkSection) pluginCls.newInstance();
TreeItem entry = new TreeItem(root, SWT.NULL);
entry.setText(section.category());
for (int i = 0; i < section.count(); i++) {
TreeItem item = new TreeItem(entry, SWT.NULL);
item.setText(section.getName(i));
item.setData(section.getUrl(i));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
hide
package org.alef1.bookmark;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Iterator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.java.plugin.ObjectFactory;
import org.java.plugin.PluginManager;
import org.java.plugin.PluginManager.PluginLocation;
import org.java.plugin.registry.Extension;
import org.java.plugin.registry.ExtensionPoint;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.standard.StandardPluginLocation;
public class Main {
public static final File pluginsDir = new File("plugins");
public static void main(String[] args) {
new Main().start();
}
private void start() {
loadPlugins();
createShell();
addDefaultBookmarks();
addDynamicBookmarks();
setListeners();
shell.open();
Display display = shell.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private void loadPlugins() {
try {
pluginManager = ObjectFactory.newInstance().createManager();
File[] plugins = pluginsDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".zip");
}
});
PluginLocation[] locations = new PluginLocation[plugins.length];
for (int i = 0; i < plugins.length; i++) {
locations[i] = StandardPluginLocation.create(plugins[i]);
}
pluginManager.publishPlugins(locations);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void addDefaultBookmarks() {
root = new TreeItem(tree, SWT.NULL);
root.setText("Bookmarks");
TreeItem item = new TreeItem(root, SWT.NULL);
item.setText("Google");
item.setData("http://www.google.com");
TreeItem dev = new TreeItem(root, SWT.NULL);
dev.setText("Java");
item = new TreeItem(dev, SWT.NULL);
item.setText("java.net");
item.setData("http://www.java.net");
item = new TreeItem(dev, SWT.NULL);
item.setText("Java World");
item.setData("http://www.javaworld.com/");
}
private void addDynamicBookmarks() {
try {
PluginDescriptor core = pluginManager.getRegistry().getPluginDescriptor("org.alef1.bookmarks.core");
ExtensionPoint point = pluginManager.getRegistry().getExtensionPoint(core.getId(), "Section");
for (Iterator it = point.getConnectedExtensions().iterator(); it.hasNext();) {
Extension ext = (Extension) it.next();
PluginDescriptor descr = ext.getDeclaringPluginDescriptor();
pluginManager.activatePlugin(descr.getId());
ClassLoader classLoader = pluginManager.getPluginClassLoader(descr);
Class pluginCls = classLoader.loadClass(ext.getParameter("class").valueAsString());
BookmarkSection section = (BookmarkSection) pluginCls.newInstance();
TreeItem entry = new TreeItem(root, SWT.NULL);
entry.setText(section.category());
for (int i = 0; i < section.count(); i++) {
TreeItem item = new TreeItem(entry, SWT.NULL);
item.setText(section.getName(i));
item.setData(section.getUrl(i));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void setListeners() {
tree.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
}
public void widgetDefaultSelected(SelectionEvent e) {
TreeItem item = (TreeItem) e.item;
if (item.getItemCount() == 0) {
CTabItem tab = new CTabItem(tabFolder, SWT.CLOSE);
tab.setText(item.getText());
Browser b = new Browser(tabFolder, SWT.BORDER);
b.setUrl((String) item.getData());
tab.setControl(b);
tabFolder.setSelection(tabFolder.getItemCount() - 1);
}
}
});
}
private Shell shell = null;
private SashForm sashForm = null;
private Tree tree = null;
private CTabFolder tabFolder = null;
private TreeItem root;
private PluginManager pluginManager;
/**
* This method initializes shell
*
*/
private void createShell() {
shell = new Shell();
shell.setLayout(new FillLayout());
shell.setText("Plugin Aware Application");
createSashForm();
shell.setSize(new Point(435, 196));
}
/**
* This method initializes sashForm
*
*/
private void createSashForm() {
sashForm = new SashForm(shell, SWT.V_SCROLL);
sashForm.setOrientation(SWT.HORIZONTAL);
tree = new Tree(sashForm, SWT.BORDER);
tree.setItemCount(0);
createTabFolder();
sashForm.setWeights(new int[] {30, 70});
}
/**
* This method initializes tabFolder
*
*/
private void createTabFolder() {
tabFolder = new CTabFolder(sashForm, SWT.NONE);
tabFolder.setSimple(false);
Display display = shell.getDisplay();
tabFolder.setSelectionForeground(display.getSystemColor(SWT.COLOR_WHITE));
tabFolder.setSelectionBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
display.getSystemColor(SWT.COLOR_BLUE),
display.getSystemColor(SWT.COLOR_WHITE),
display.getSystemColor(SWT.COLOR_WHITE)},
new int[] {25, 50, 100});
}
}
The plugin again
Our plugin only needs to declare that it extends the Section point,
our previous class is declared as the one that extends the Section point.
<extension plugin-id="org.alef1.bookmarks.core" point-id="Section" id="rubySection">
<parameter id="class" value="org.alef1.bookmarks.RubyURLs"/>
<parameter id="name" value="Ruby"/>
</extension>
But we also need to declare that the plugin requires the core plugin.
<requires>
<import plugin-id="org.alef1.bookmarks.core"/>
</requires>
Check the whole manifest here.
hide
<?xml version="1.0" ?>
<!DOCTYPE plugin PUBLIC "-//JPF//Java Plug-in Manifest 1.0" "http://jpf.sourceforge.net/plugin_1_0.dtd">
<plugin id="org.alef1.bookmarks.ruby" version="1.0.0">
<requires>
<import plugin-id="org.alef1.bookmarks.core"/>
</requires>
<runtime>
<library id="ruby" path="/" type="code">
<export prefix="*" />
</library>
</runtime>
<extension plugin-id="org.alef1.bookmarks.core" point-id="Section" id="rubySection">
<parameter id="class" value="org.alef1.bookmarks.RubyURLs"/>
<parameter id="name" value="Ruby"/>
</extension>
</plugin>
Our previous RubyURLs
class does not need to extend the framework's
Plugin
class anymore.
package org.alef1.bookmarks;
import org.alef1.bookmark.BookmarkSection;
public class RubyURLs implements BookmarkSection {
public String category() {
return "Ruby";
}
public int count() {
return names.length;
}
public String getName(int index) {
return names[index];
}
public String getUrl(int index) {
return urls[index];
}
final static String[] names = {
"Ruby Home",
"Try Ruby!",
"Rake",
"Rails",
};
final static String[] urls = {
"http://www.ruby-lang.org/",
"http://tryruby.hobix.com/",
"http://rubyforge.org/projects/rake/",
"http://www.rubyonrails.org/",
};
}
Download
You can download the first version here and the second version
here.
Links
The Java Plug-in Framework (JPF) Project
OSGi Alliance - The Dynamic Module System for Java
Eclipse Equinox plugin framework.
Colored syntax was generated with jEdit