Writing BlueJ Extensions

The BlueJ extensions
mechanism is a way of adding new functionality to BlueJ as and when
it is needed, avoiding user interface clutter and user confusion.

The BlueJ Extensions API
provides access for extensions to the BlueJ application via a proxy
object, and to the classes and objects which BlueJ is manipulating
via a number of wrapper classes.

An extension can add a menu item to the BlueJ Tools menu, and
to Class and Object menus, add a preference panel to the Tools/Preferences/Extensions
panel, and interact with the BlueJ editor to retrieve and modify
the text in source files. The BlueJ proxy object generates
events when the user performs significant actions within BlueJ,
and provides access to its projects, packages, classes and objects
via a set of wrapper classes which behave in a similar way to the
java.lang.reflect classes.

A Simple Extension

The following example implements an extension which logs the name
of every BlueJ project opened by the user to System.out
and demonstrates the use of the other extension features. Once it
is installed you should also see:

an entry for it in the "Installed Extensions" panel of the
BlueJ help menu

(fairly useless) menu entries to BlueJ's Tools menu, and to
the menus displayed for classes and objects, including one which
adds a comment to the source code for an existing class.

an entry in the Tools/Preferences/Extensions panel which prompts
for (and stores) your favourite colour.

Compile it, including lib/bluejext.jar from your
BlueJ installation in the compiler classpath. How you do this will depend on the tool you are using to develop your extension.

Create a Jar file containing the classes of your extension,
and add a Main-Class attribute to the Manifest of
the Jar file. Note that this does not mean that the extension
needs to have a main() method. The simplest way to
do this is to create a file (called, say, manifest.txt)
containing the single line:Main-Class: SimpleExtension
This information can be included in the Jar file when it is created
using the m option to the jar command,
e.g.:jar cmf manifest.txt SimpleExtension.jar *.class

Install the extension by copying the Jar file either into the
lib/extensions directory of your BlueJ installation,
or into an extensions directory in your BlueJ user
configuration directory (<USER_HOME>/.bluej/extensions
or <USER_HOME>\bluej\extensions), or into an
extensions directory in the single BlueJ project
you wish the extension to be active for.

Run it. The extension should start when you next run BlueJ,
or when you open the BlueJ project into which you installed the
extension.

Note to Mac users: To navigate to the lib
directory of your BlueJ installation, right-click (or control-click)
on the BlueJ application icon, select "Show Package Contents" and
then "Contents/Resources/Java". Here you should find the bluejext.jar file you need to include in your compiler classpath, and the extensions folder into which you should place your extension's Jar file.

The main class of
the Simple Extension

An object of this type will be constructed by the BlueJ extension
manager, so this class must have a no-args constructor (which this
one does by default). The class registers itself as a listener for
BlueJ package events.

import bluej.extensions.*;
import bluej.extensions.event.*;
import java.net.URL;
/*
* This is the starting point of a BlueJ Extension
*/
public class SimpleExtension extends Extension implements PackageListener
{
/*
* When this method is called, the extension may start its work.
*/
public void startup(BlueJ bluej)
{
// Listen for BlueJ events at the "package" level
bluej.addPackageListener(this);
}
/*
* A package has been opened. Print the name of the project it is part of.
* System.out is redirected to the BlueJ debug log file.
* The location of this file is given in the Help/About BlueJ dialog box.
*/
public void packageOpened(PackageEvent ev)
{
try
{
System.out.println("Project " + ev.getPackage().getProject().getName()
+ " opened.");
}
catch (ExtensionException e)
{
System.out.println("Project closed by BlueJ");
}
}
/*
* A package is closing.
*/
public void packageClosing(PackageEvent ev)
{
}
/*
* This method must decide if this Extension is compatible with the
* current release of the BlueJ Extensions API
*/
public boolean isCompatible()
{
return true;
}
/*
* Returns the version number of this extension
*/
public String getVersion ()
{
return ("2004.09");
}
/*
* Returns the user-visible name of this extension
*/
public String getName ()
{
return ("Simple Extension");
}
public void terminate()
{
System.out.println ("Simple extension terminates");
}
public String getDescription ()
{
return ("A simple extension");
}
/*
* Returns a URL where you can find info on this extension.
* The real problem is making sure that the link will still be alive
* in three years...
*/
public URL getURL ()
{
try
{
return new URL("http://www.bluej.org/doc/writingextensions.html");
}
catch ( Exception e )
{
// The link is either dead or otherwise unreachable
System.out.println ("Simple extension: getURL: Exception="+e.getMessage());
return null;
}
}
}

Adding menus to BlueJ's
menus

Extensions which wish to add a menu item to BlueJ's menus should
register an instance of MenuGenerator with the BlueJ proxy object.
A MenuGenerator provides a set of functions which can be called
back by BlueJ to request the actual menu items which will be displayed,
and to indicate that a particular menu item is about to be displayed,
so that an extension can (e.g.) enable or disable appropriate items.
Note that the JMenuItem which is returned by the extension can itself
be a JMenu, allowing extensions to build more complex menu structures,
but that the "notify" methods below will only be called
for the item which has actually been added, and not any subsidiary
items. Below is a simple example which creates menus for Tools,
Classes and Objects. To activate the menus you instantiate an object
of the MenuGenerator class and then register it with the BlueJ proxy
object, e.g.:

may be called more than once during a BlueJ session, they should
return a new set of MenuItems for each invocation. This is a restriction
required by the Swing implementation, which does not allow sharing
of MenuItems between menus. You can, of course, share MenuActions
between all of the appropriate MenuItems.

may not be called between the registration of a new MenuGenerator
and the display of a menu. That is to say old menu items may still
be active for previously registered menus, despite the registration
of a new MenuGenerator.

Extensions which wish to add preference items to BlueJ's Tools/Preferences/Extensions
panel should register an instance of PreferenceGenerator with the
BlueJ proxy object. The PreferenceGenerator allows the creation
of a Panel to contain preference data, and the loading and saving
of that data. Below is a simple example to create a preference panel
with a single text item to record a user's favourite colour. To
activate the preference panel you instantiate an object of the Preferences
class and then register it with the BlueJ proxy object (see above).

A proxy for a particular class's editor can be obtained from the
getEditor() method of BClass.

This proxy can be used to retrieve the text currently associated
with the class, to determine the user's current cursor position
and text selection, and to modify the text being edited. A simple
example of how to use these facilities is given below.

Extension authors who need more sophisticated interaction between
their own, modified, version of the BlueJ editor and their extension
can use the set/getProperty() mechanism in the Editor
class.

Note that there is no way to determine whether the BlueJ user has
an existing open editor for a class. Partly this is because there
is no way (in general) to tell whether an editor is being actively
used: it may be iconised, or obscured by other windows on the users
screen, or otherwise not the focus of their attention. It is also
because BlueJ does not guarantee to release the resources of editors
which have been closed by the user: an editor may be present for
a particular class, even if there is no window representing it.

Changes which affect the editor GUI (e.g. selecting or modifying
text) should be performed from a Swing thread.

As a simple example, the following method adds a comment before
the last source line of the given class: