Eclipse has received much fanfare and accolades because of its powerful Java development environment (JDE). That — coupled with the team environment and other base capabilities — makes Eclipse a compelling integrated development environment (IDE), which is great news for Java developers. Moreover, Eclipse is an open source project. But what makes Eclipse truly exciting is the extension possibilities it offers.
A number of commercially available Eclipse-based products show the practical implications of this way of delivering integrated products. IBM® WebSphere® Application Developer and Rational® XDE, for example, demonstrate the impact that Eclipse has already had. These products and others based on Eclipse diminish the user's learning curve because of their similar UI. Sure, this is valuable to large software houses, but what's in it for the little guy?
That's where the extensibility story of Eclipse gets interesting. Not just integration for those who have large development organizations but also for anyone willing to invest some time in learning a few Eclipse frameworks. "Oh no," you may be thinking. "Not more frameworks. I don't have the time to learn more frameworks." Don't worry — it will be quick and fairly easy. And before that little voice in your head has time to say it, no, this article will not be a trivial Hello World extension of Eclipse. Rest assured, you'll see practical value and a clear demonstration of how you can enhance your productive use of Eclipse's JDE. You may even be a little surprised to see that it takes only a few dozen lines of code to do some fairly amazing things.
This article will show you what is possible and where to start, and give you a firm appreciation for what's involved in getting there. Though extending Eclipse is an advanced topic, you can start with only passing knowledge of how to use Eclipse's JDE (and be sure to check out the suggested reading in Resources for further study).
Your own easy refactoring of member visibility
Initially when writing code, I don't worry too much about categorizing method visibility as default (package), private, public, or protected. As I create methods, I make them all public. Only once I've finalized the organization of packages and finished refactoring methods — whether that be by extracting new methods from existing code, pulling up or pushing down methods in the hierarchy, or moving them to another class entirely — do I then review method visibility. I figure that until I know the final class shapes and have a little practical usage of the code, I don't want to declare what my clients might need. In other words, before sharing a new framework, one must decide what is implementation detail and what is necessary so others can extend it.
It would be handy if you could merely select methods in the Outline view, Hierarchy view, or wherever you see methods — and with a click of a menu choice, set one or more methods to the desired visibility. Admittedly, I am accustomed to this capability from my VisualAge for Smalltalk days. Figure 1 shows an extension to Eclipse's JDE in the context of the Java editor's Outline view.
Figure 1. Extension of a method's context menu
This is subtle, from a user's perspective because of the natural way this was introduced into the UI. There is no inkling that these new menu choices weren't part of Eclipse's original Java Development Tools (JDT). In fact, that's why the menu cascade is prefixed with "soln," so you can tell it's an extension. What's more, the developer doesn't have to remember that these choices are only available in a particular view or editor because they will be shown anywhere a method is shown.
"Hey, wait a minute — you promised no Hello World!" True, but we do need to cover a little about Eclipse's underpinnings before getting to the really interesting things. So if you have never written your own extension to Eclipse, please join me in a quick tour of the Eclipse architecture and plug-in development environment. Otherwise, skip to the next section.
In essence, Eclipse is a collection of loosely bound, yet interconnected, pieces of code. How these pieces of code are discovered and how they discover and extend each other captures the fundamental principles of the Eclipse architecture.
Figure 2. Eclipse Platform architecture
These functional units are called plug-ins. The Eclipse Platform Runtime, shown in Figure 2, is responsible for finding the declarations of these plug-ins, called a plug-in manifest, in a file named plugin.xml, each located in its own subdirectory below a common directory of Eclipse's installation directory named plugins (specifically, <inst_dir>\eclipse\plugins). From these files, it builds at startup a global in-memory registry, called the plug-in registry, from which a given plug-in can determine at run time what other plug-ins wish to extend it. A plug-in that wishes to allow others to extend it will declare an extension point. This is a sort of "power strip" for a plug-in that others can take advantage of by declaring an extension to it.
Returning to our example, the mission then is to decide where to plug into Eclipse by finding the appropriate extension point for our needs. Fortunately, once you have used Eclipse for a while, you know a surprising amount about what is available, perhaps without realizing it. This is because what you see in the Eclipse UI and what is modeled by the classes that make up the Eclipse plug-ins often correspond nearly 1:1 to each other. Figure 3 makes this point clearer.
Figure 3. Views and their models
Here we see an ordinary progression of UIs starting from the lowest common
denominator on the right, the file system contents shown by a dir command
in a Command Prompt window, continuing to a highly specialized view — that of the JDT's Package Explorer
on the left. From a UI perspective, all these views are visualizing a representation
of the same model, namely some files. As Eclipse users, we naturally expect the two Eclipse views to
present us different ways of looking at the same thing simultaneously: The Navigator shows a specialized
view of a portion of the operating system files (Eclipse's workspace), while the Package Explorer
shows us some of the same files organized and presented in a way that is more natural and efficient
for a Java programmer.
Seeing how the Eclipse UI reflects its underlying model and how its models
build upon each other gives us an important clue about how we can find the best place
to plug in our extension. The Eclipse interface names shown below the
views, IFile and ICompilationUnit, are
just two examples of the interfaces we can expect from the model that makes up Eclipse. Since they so
often correspond to what is shown in the UI, you already have an intuitive appreciation
for what's available programmatically.
That is the first part of our tour. The second part is a look at our developing solution. Rather than present the solution and explain it piece by piece, wouldn't it be more interesting to discover some of it? Let's start with some questions related to the problem at hand: extending the JDT with our own method visibility refactoring capability.
Asking the right question is more important than knowing the answer
Our quest begins with some general questions:
- How and where will the extension be shown in the UI?
- How do we extend the UI in general?
- How does an extension to the UI know about basic events like selection?
Once we've got a good handle on the basic Eclipse landscape, we'll turn to some JDT-specific questions:
- How do we extend the UI of specific elements of the JDT, like members shown in the Outline view? Do we extend the view(s) or their underlying model?
- What is the relationship between elements shown in the Package Explorer and the same elements shown in other views like the Outline view? Does our extension need to be aware of any differences between them?
- How do you change the JDT model programmatically?
- How do you analyze Java source code to apply modifications?
And of course, the final big question:
How and where will the extension be shown in the UI?
This is mostly a gentle reminder, since we've already decided on the answer. We want to show context menu choices for one or more selected methods that allow us to change their visibility with a single action. We prefer that they be available wherever the methods can be displayed, such as the Hierarchy view and Package Explorer. This leads us to our next question.
How do we extend the UI in general?
Learning by example is more fun, and this is where the Plug-in Project wizard can give us a hand by providing some sample code that we can then modify to our needs. We'll answer just a few of its questions and it will automatically launch the specialized perspective for plug-in development, known as the Plug-in Development Environment (PDE), ready for testing. This wizard includes a number of examples that will get us started. In fact, our old friend is there — Hello World. Just for old time's sake, let's generate it, look at the result to verify that the environment is set up correctly, and modify it to help us answer the current question and lead us to the next question: How does an extension to the UI know about basic events like selection? That will be important, since we want to apply our newly introduced menu choices to the currently selected method(s).
Note that these instructions assume that you're starting from a fresh Eclipse installation.
If you have modified the environment or changed preferences, they may not work precisely as described below.
You might consider starting Eclipse with a fresh workspace by opening a Command Prompt window,
changing to the <inst_dir>\eclipse directory, and starting
Eclipse with the -data parameter, as shown below.
Listing 1. Starting a fresh instance of Eclipse
cd c:\eclipse2.1\eclipse
eclipse.exe -data workspaceDevWorks
|
Begin by creating a plug-in project using the New Plug-in Project wizard.
Select File > New > Project. In the New Project dialog, select Plug-in Development and
Plug-in Project in the list of wizards, then select Next.
Name the project com.ibm.lab.helloworld.
The wizard will create a plug-in ID based on this name, so it must be unique in the system
(by convention, the project name and the plug-in ID are the same).
The proposed default workspace location shown under "Project contents" is fine. Select Next.
Accept the default plug-in
project structure on the following page by selecting Next. The plug-in code generator page
proposes a number of samples that the wizard can help you further parameterize.
Select the "Hello, World" option
and Next. The next page, shown in Figure 4,
proposes a plug-in name and plug-in class name. These are based on the last word of the plug-in
project, com.ibm.lab.helloworld
. This example doesn't need
any of the plug-in class convenience methods, so deselect the three code generation options,
as shown in Figure 4, and select Next (not Finish; you've got one more page to go).
Figure 4. Simple plug-in content
The next page, shown in Figure 5, is where you can specify parameters that are unique to the Hello World example, such as the message that will be displayed.
Figure 5. Sample action set
To simplify the resulting code, change the target package name for the action
from com.ibm.lab.helloworld.actions
to com.ibm.lab.helloworld, the same name as the project.
While you might choose to have separate packages for grouping related classes in a
real-world plug-in,
in this case, there will be only two classes, so there's no need. Plus, that adheres to the convention
that the main package is named the same as the project. Now select Finish.
You should see an information message saying "Plug-ins required to compile Java classes in this plug-in are currently disabled. The wizard will enable them to avoid compile errors." Select OK to continue. If this is a fresh workspace, you will see another information message saying "This kind of project is associated with the Plug-in Development Perspective. Do you want to switch to this perspective now?" Select Yes to switch.
To verify that everything is set up correctly, let's test your new plug-in. Select Run > Run As > Run-Time Workbench. This will launch a second instance of Eclipse that will include your plug-in. This new instance will create a new workspace directory named runtime-workspace, so don't worry — whatever testing you do with that instance will not affect your development setup. You should see something like Figure 6 with a new menu pull-down labeled Sample Menu having a single choice: Sample Action. Selecting it will show the information message below. If you didn't start from a fresh workspace, you can select Window > Reset Perspective to see the newly contributed pull-down menu. It isn't shown when starting from an existing workspace since the Workbench remembers what action sets were active the last time Eclipse was running (you can also add and remove action sets from the Window > Customize Perspective pull-down menu choice).
Figure 6. Hello, Eclipse world
Let's take a quick glance at the plug-in manifest file, plugin.xml. Double-click it to open it in the Plug-in Manifest editor. This editor presents several wizard-like pages and a raw source page. Turn to it by selecting the Source tab. You'll see something what's shown in Listing 2. We're interested in the parts in bold.
Listing 2. Generated Hello World plugin.xml
<extension
point="org.eclipse.ui.actionSets">>
<actionSet
label="Sample Action Set"
visible="true"
id="com.ibm.lab.helloworld.actionSet">
<menu
label="Sample &Menu"
id="sampleMenu">
<separator
name="sampleGroup">
</separator>
</menu>
<action
label="&Sample Action"
icon="icons/sample.gif"
class="com.ibm.lab.helloworld.SampleAction"
tooltip="Hello, Eclipse world"
menubarPath="sampleMenu/sampleGroup"
toolbarPath="sampleGroup"
id="com.ibm.lab.helloworld.SampleAction">
</action>
</actionSet>
</extension>
|
It isn't necessary to study this too closely. The purpose of Part II of our tour is only to
familiarize you with some of the basic mechanisms whereby we can introduce our extensions
to the JDT. Here you see a sample of one such technique to add menus and menu choices to the Workbench
as an action set. It begins with an extension,
declared with the <extension point="org.eclipse.ui.actionSets"> tag.
The Workbench UI plug-in defines this extension
point, org.eclipse.ui.actionSets,
and several others like it where other plug-ins can contribute to the various UI elements.
We still haven't answered how we can add menu choices to the context menu of Java methods.
A simple example can give us some hints.
Begin by opening the class that displays the Hello World
message, SampleAction, and note its run method.
It isn't particularly interesting. However, we also see another
method, selectionChanged. Aha! The answer to our next question awaits.
How does an extension to the UI know about basic events like selection?
Contributed actions, like our contributed menu pull-down choice, are notified when the
Workbench selection changes. That's confirmed in the Javadoc comments before the method.
Let's modify this method to tell us a bit more about the selection.
First, if you haven't already closed the run-time instance of the Workbench, do so now.
Then add the code in Listing 3 to the selectionChanged method.
Listing 3. selectionChanged method, first modification
public void selectionChanged(IAction action, ISelection selection) {
System.out.println("==========> selectionChanged");
System.out.println(selection);
}
|
With this debug code, we'll see what is selected and learn a little more about what makes Eclipse work. Save the method and relaunch the runtime Workbench.
NOTE: Eclipse has a deferred load strategy to avoid loading plug-ins until
the user does something that requires their code. So you must first select the Sample Action menu
choice to load your plug-in before your selectionChanged method will be called.
Now select different things like text in an editor, files in the Navigator, and, of course, members in the Outline view (recall that you'll have to create a Java project and an example Java class to do this, since the run-time instance uses a different workspace). Listing 4 shows some example output you will see in the console of the development instance of Eclipse.
Listing 4. selectionChanged output, first modification
==========> selectionChanged
[package com.ibm.lab.soln.jdt.excerpt [in [Working copy] ChangeIMemberFlagAction.java
[in com.ibm.lab.soln.jdt.excerpt [in src [in com.ibm.lab.soln.jdt.excerpt]]]]]
==========> selectionChanged
<empty selection>
==========> selectionChanged
org.eclipse.jface.text.TextSelection@9fca283
==========> selectionChanged
<empty selection>
==========> selectionChanged
[package com.ibm.lab.soln.jdt.excerpt [in [Working copy] ChangeIMemberFlagAction.java
[in com.ibm.lab.soln.jdt.excerpt [in src [in com.ibm.lab.soln.jdt.excerpt]]]]]
==========> selectionChanged
[IMember[] members [in ChangeIMemberFlagAction [in [Working copy] ChangeIMemberFlagAction.java
[in com.ibm.lab.soln.jdt.excerpt [in src [in com.ibm.lab.soln.jdt.excerpt]]]]]]
==========> selectionChanged
<empty selection>
==========> selectionChanged
[ChangeIMemberFlagAction.java [in com.ibm.lab.soln.jdt.excerpt
[in src [in com.ibm.lab.soln.jdt.excerpt]]]
package com.ibm.lab.soln.jdt.excerpt
import org.eclipse.jdt.core.Flags
import org.eclipse.jdt.core.IBuffer
...lines omitted...
void selectionChanged(IAction, ISelection)]
==========> selectionChanged
[boolean isChecked(IAction, IMember) [in ToggleIMemberFinalAction
[in ToggleIMemberFinalAction.java [in com.ibm.lab.soln.jdt.excerpt
[in src [in com.ibm.lab.soln.jdt.excerpt]]]]]]
|
Well, that isn't as enlightening as we'd hoped. Clearly, the selection isn't something as primitive
as an instance of String, but it isn't evident what classes
are involved, either,
because these classes have clearly overridden their default toString
method. We're not yet at the point where we can appreciate what information they are showing
without a little more investigation.
Returning to the selectionChanged method, browse the hierarchy of the
interface of the selection parameter, ISelection.
Its hierarchy reveals that there are not many general-purpose subtype interfaces,
just IStructuredSelection (for lists)
and ITextSelection. We'll make
the selectionChanged method a bit smarter by outputting the class that's selected.
Modify the selectionChanged method, as shown below.
Listing 5. selectionChanged method, second modification
public void selectionChanged(IAction action, ISelection selection) {
System.out.println("==========> selectionChanged");
if (selection != null) {
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
if (ss.isEmpty())
System.out.println("<empty selection>");
else
System.out.println("First selected element is " + ss.getFirstElement().getClass());
} else if (selection instanceof ITextSelection) {
ITextSelection ts = (ITextSelection) selection;
System.out.println("Selected text is <" + ts.getText() + ">");
}
} else {
System.out.println("<empty selection>");
}
}
|
Remember to close the run-time instance and relaunch. Now when you select various elements of the UI, is it far more revealing, as shown below.
Listing 6. selectionChanged output, second modification
selected some methods in the Outline view
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged
<selection is empty>
activated the Java editor
==========> selectionChanged
Selected text is <isChecked>
==========> selectionChanged
<selection is empty>
selected same methods and classes, package in the Package Explorer
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.SourceType
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.PackageFragment
activated the Navigator view, selected some files, folders, and projects
==========> selectionChanged
First selected element is class org.eclipse.core.internal.resources.File
==========> selectionChanged
<selection is empty>
==========> selectionChanged
First selected element is class org.eclipse.core.internal.resources.File
==========> selectionChanged
First selected element is class org.eclipse.core.internal.resources.Project
==========> selectionChanged
First selected element is class org.eclipse.core.internal.resources.Folder
==========> selectionChanged
<selection is empty>
reactivated the Package Explorer,
selected some classes and methods in JARs of reference libraries
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.JarPackageFragment
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.ClassFile
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.BinaryMethod
|
Specifically, we confirm that what we see in the UI corresponds 1:1 with model classes of the JDT. Why we're seeing what appears to be models as selections, and not lower-level primitives like strings and images, is thanks to another Eclipse framework: JFace. This framework maps between primitives like strings that the widgets close to the operating system expect and the higher-level model objects with which your code prefers to work. This article will only peripherally touch on this topic, since our stated goal is extending the JDT. The Resources section suggests other references for JFace that will broaden your understanding. This article will only cover what's necessary to understand the basics of our JDT extension.
Returning to the output, a particular selection result draws our attention: those corresponding to the selection of Java members in the UI. They are repeated in Listing 7.
Listing 7. selectionChanged output, revisited
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.SourceMethod
==========> selectionChanged
First selected element is class org.eclipse.jdt.internal.core.BinaryMethod
|
The internal in the middle of the package name for these classes is a little disquieting.
However, as you'll often find, Eclipse will have a public interface that corresponds to
the (internal) implementation class, as is the case here. A quick class lookup reveals
that these classes all implement a common set of interfaces that look promising,
namely, ISourceReference, IJavaElement,
and especially IMember. Finally! Now we have what we had hoped to extend,
leading us to the answer to our next question.
Our simple Hello World example showed that adding a menu choice requires just a few lines of XML
in the plug-in manifest file
(<extension point="org.eclipse.ui.actionSet">)
and a class that handles the actual action
(com.ibm.lab.helloworld.SampleAction).
Adding actions to the views' pull-down menu, the common editors' toolbar, and pop-up menus is nearly
as straightforward. Contributed pop-up menus come in two flavors: those associated with a view alone and not selected objects (that is, the "default" pop-up menu
that views often display when you right-click on their "whitespace"), and the
more common variety, those that are associated with choices applying to the selected object(s).
In our case, we want to target only specific selected objects,
so we'll contribute what's called an action object contribution to their pop-up menu
by defining an extension in the plug-in manifest
(some of the identifiers below will be shortened to format better; they are denoted by
'…'), as shown below.
Listing 8. Modifier actions
<extension point="org.eclipse.ui.popupMenus">
<objectContribution
objectClass="org.eclipse.jdt.core.IMember"
id="...imember">
<menu
label="Soln: Modifiers"
path="group.reorganize"
id="...imember.modifiers">
<separator name="group1"/>
<separator name="group2"/>
</menu>
<action
label="Private"
menubarPath="...imember.modifiers/group1"
class="...jdt.excerpt.MakeIMemberPrivateAction"
id="...imember.makeprivate">
</action>
<action
label="Protected"
menubarPath="...imember.modifiers/group1"
class="...jdt.excerpt.MakeIMemberProtectedAction"
id="...imember.makeprotected">
</action>
...all menu choices not shown...
</objectContribution>
</extension>
|
The extension point is named org.eclipse.ui.popupMenus,
and, as the name suggests, it defines contributions to pop-up menus appearing in the Workbench.
This particular example will contribute only to specifically selected objects, those
implementing the IMember interface (recall that as defined in the Java
language specification, members include both methods and fields).
Our investigation has paid off. We have the answer to our current question,
and we're almost ready to move to the next question.
Before doing so, note at this point that the pattern we found for our
simple Hello World action example will repeat itself for other menu action contributions.
That is, the class named in the class attribute will be notified of
selection changes (by its selectionChanged method) and will
be notified when the user selects the menu choice (by its run method).
The UI portion of our tour is almost over. The harder part,
effecting our desired change, lies ahead. There is only an observation or two to make before
continuing, as stated in our next question.
You may have noticed that when you selected a method in the Outline view and the Hierarchy view, the class of the selected object was not always the same. For example, if you expanded the contents of a library (JAR file) in the Package Explorer, then selected a class or method, it was not the same class as the same selection in the Java editor's Outline view. What's up with that?
Here we are observing the difference between those parts of the JDT's Java model that
are "editable" vs. those that are always read-only.
Both parts of the Java model will implement a common interface, like IMember,
but have different implementation classes that understand the underlying restrictions.
As another example, there is an implementation class representing a Java
compilation unit derived from a .class file in a JAR file shown in the
Package Explorer and another class representing a compilation unit derived directly
from a .java file.
The latter implementation will allow modifications where the former cannot, yet a shared portion of
their API is represented by the interface ICompilationUnit.
You have no doubt observed beforehand when editing Java source code that the Outline view updates as you're typing a method signature (if you haven't noticed, try it). This is an example of how the JDT stages its uncommitted changes in a separate area vs. those changes that have already been saved, compiled, and integrated into the Java model. Some views, like the Java editor's Outline view, are aware of uncommitted and committed changes, while others like the Navigator view are only concerned with committed changes saved to the file system.
Subsequently, our contributed action that will modify Java members has to be,
at least to some extent, aware of the context in which it is invoked.
That is, it will have to recognize that some selected members are modifiable
(those in the Java editor's Outline view) while others are not
(members from .class file stored in a JAR file and shown in the Package Explorer).
Keeping this in mind, let's continue on to our next question.
How do you change the JDT model programmatically?
If you explored a bit during the prior tour, you may have noticed
that IMember, IJavaElement,
and what appears to be the majority of
the interfaces implemented by the selected Java-related items our action saw
have no setXXX methods. So how do you modify them?
You'll find it is surprisingly easy, yet perhaps not intuitively obvious. The JDT's Java model is in most practical respects read-only. With the integrated cooperation of the Java compiler, changes to the underlying Java source of a given element are synchronized with the rest of the Java model. In effect, all you have to do is update the Java source, and the rest of the necessary model changes are propagated to whomever is dependent on them. For example, the JDT's indices are automatically updated whenever Java source/Java model changes occur, so searches continue to work quickly, dependent classes are recompiled (as dictated by the Java build path specified in the project's properties), etc.
That's a big relief! This point is why the Java model is key to plug-in integration:
It provides a common shared in-memory model of the entire Java environment,
its scope beginning from a project and continuing to all its referenced libraries,
all without you having to worry about
manipulating .java files, .class files,
and .jar files in the file system.
You can focus on the high-level model and let the JDT deal with many of those messy details.
Not yet convinced it is that easy?
Listing 9 contains the diminutive snippet of code that is at the heart of this solution,
extracted from the contribute action's run method and
simplified slightly for readability.
Listing 9. selectionChanged method, diminutive solution
public void selectionChanged(IAction action, ISelection selection) {
IMember member = (IMember)
((IStructuredSelection) selection).getFirstElement();
ICompilationUnit cu = member.getCompilationUnit();
if (cu.isWorkingCopy()) {
IBuffer buffer = cu.getBuffer();
buffer.replace(...);
cu.reconcile();
}
}
|
Seems a bit anticlimactic, doesn't it?
Your contributed action is given the selected member, you ask it for its parent container
(the model of the Java .class or .java file,
collectively referred to as a compilation unit in JDT parlance)
because that's who manages the underlying source,
verify that it is part of the uncommitted Java model
(in other words, it is currently open in an editor),
then modify the source code returned as a buffer.
The IBuffer interface is similar to StringBuffer,
the principal difference being that changing the buffer
associated with a compilation unit updates the corresponding elements of the Java model.
The final call to reconcile tells the JDT to notify other interested parties like the
Package Explorer view that your model updates are ready for public consumption.
You no doubt noticed the ellipsis in the code above. That's where you have to analyze the source code itself to apply your modifications. Again, the JDT comes to your aid, as we'll see in the next question.
How do you analyze Java code to apply modifications?
The JDT offers several tools to help you analyze code.
This article intentionally chose the easiest to demonstrate and the one with the most
limited scope: the IScanner interface. This interface is part of the JDT toolbox and is
accessible from the JDT's ToolFactory class.
Its createScanner method returns a scanner that simplifies
the tokenizing of a string of Java code. It isn't handling anything
particularly difficult, just straightforward parsing and categorization of the returned tokens.
For example, it indicates the next token is
the public keyword, the token after that is an identifier,
the token after that is an open parenthesis, etc.
Subsequently, this scanner is only appropriate when you want to analyze
a small piece of code where you have a precise understanding of what to expect.
You would never use a scanner to analyze an entire Java source. For that, you would turn to something quite familiar to compiler aficionados:
the JDT's Abstract Syntax Tree (AST) framework.
Unlike the simple scanner, an AST understands the relationships between language elements
(they are no longer simply "tokens"). It can recognize something as a local variable,
instance variable, expression, if statement —
more than 60 language elements.
It will help you with refactoring that covers a wide scope
or has particularly sticky ambiguities that defy a 1:1 categorization of tokens.
To see more clearly, this distinction of when you would use a scanner vs. an AST,
consider the code in Listing 10.
Listing 10. Ambiguous variable references
public class Foo {
int foo = 1;
public int foo(int foo) {
return foo + this.foo;
}
public int getFoo() {
return foo;
}
}
|
If you wanted to find references to the instance
variable foo as part of your refactoring,
you can see how a naïve parse would make it challenging to distinguish between
local references and instance variable references.
An AST creates a full analysis tree where each element of the Java source is
represented and distinguished. In this particular case,
the foo references would be represented as nodes of the AST by different classes
that take their context into consideration,
like FieldDeclaration, SimpleName,
and ThisExpression,
making it easy for you to recognize which is which.
As mentioned, this article will only cover the simple case we've chosen.
For examples of more complex modifications and analyses,
see Resources.
Now let's return to the ellipsis code we skipped earlier.
This code will use an instance of IScanner to identify
and replace the keyword(s) in the source that determine a member's visibility.
The visibility modifiers that we'll handle
are public, private,
protected, and final.
We can simplify the solution by taking a brute force approach, that is, doing it in two steps.
First remove all visibility modifiers in the method signature
(or at least scan for them and remove them if found), then insert the desired modifier. Specifically:
- Remove
public,private, orprotectedif found in the method signature - Insert the requested visibility modifiers (in the case of package visibility, do nothing because it is the default; that is, there are no modifiers)
The final modifier is easy.
Since the desired behavior is to toggle it on and off,
we only have to remove it if it is present; otherwise, insert it.
The code in Listing 11 will show just one case, unconditionally changing a member's visibility
from public to private. In the solution associated with this article,
you'll see that the common code for each of the actions has been moved to an
abstract superclass. It is basically the same as the code below, just tidied up to eliminate redundancy.
Listing 11. Scanning for public keyword
ICompilationUnit cu = member.getCompilationUnit();
if (cu.isWorkingCopy()) {
IBuffer buffer = cu.getBuffer();
IScanner scanner =
ToolFactory.createScanner(false, false, false, false);
scanner.setSource(buffer.getCharacters());
ISourceRange sr = member.getSourceRange();
scanner.resetTo(
sr.getOffset(),
sr.getOffset() + sr.getLength() - 1);
int token = scanner.getNextToken();
while (token != ITerminalSymbols.TokenNameEOF
&& token != ITerminalSymbols.TokenNameLPAREN)
token = scanner.getNextToken();
if (token == ITerminalSymbols.TokenNamePUBLIC) {
buffer.replace(
scanner.getCurrentTokenStartPosition(),
scanner.getCurrentTokenEndPosition(),
scanner.getCurrentTokenStartPosition() + 1,
"private");
break;
}
}
cu.reconcile();
}
|
NOTE:
ITerminalSymbols defines the token names that
a scanner can return, corresponding to the standard tokens of the Java grammar.
You can further query the scanner to ask where specifically in the buffer the
current token begins and ends, what line number it appears on,
and, of course, the token itself (especially cases
like ITerminalSymbols.TokenNameStringLiteral
and ITerminalSymbols.TokenNameIdentifier that aren't reserved keywords).
In the code snippet above, the scanner.setSource method
is given the entire source code for the compilation unit, that is,
everything in the Java source file. As was mentioned earlier,
the scanner isn't well suited to large analyses,
so we must restrict it to only the source portion starting at the first character
of the target method until its end by calling the setSourceRange method.
The IMember interface is an extension
of ISourceReference,
an interface that allows you to query the source string and source location
within the containing compilation unit. This saves us the trouble of
having to figure out where our target method begins and ends within the Java source.
We could have done just that with an AST,
but the ISourceReference interface renders that unnecessary.
Since Java method signatures are easy to parse,
the parsing capability of the IScanner interface is a good match.
All we have to do is look for a public keyword that appears
between the first character of the method declaration and before the opening
parenthesis of the parameter declaration, replacing it with
the private keyword. Of course, in the solution it will handle
all of the possible cases, whether the method was
originally public, private, protected, or package (default).
The stated goal of this article was to give you a nontrivial extension to Eclipse's JDE that enhanced its productivity. In all honesty, more than once, I skipped over some details for reasons of brevity. The solution itself makes some simplifying assumptions, like only allowing modifications to Java source already open in the editor. You would probably want to relax that restriction in a fuller implementation.
Nonetheless, I hope that you got a good taste of what's possible and you're convinced that it isn't all too difficult. What we've covered in this article is a portion of one of the advanced chapters in our book, The Java Developer's Guide to Eclipse. Eleven less-advanced chapters cover the fundamentals of plug-in development. Like this article, most chapters include a documented working solution that reinforces what you've learned, much in the same style as what you've seen in this article (though perhaps not covered at such a breakneck pace!).
Learning more about the solution and downloading it
Get more details about what was covered in this article
in the solution excerpt (see Resources).
The solution excerpt also describes several other useful extensions to the JDT
that are included on the CD-ROM accompanying
The Java Developer's Guide to Eclipse.
To install the solution excerpt, download it,
unzip the project contained in it to your workspace
(for example, c:\eclipse2.1\eclipse\workspace),
then import the project into your current Eclipse
workspace by selecting File > Import > Existing Project into Workspace.
NOTE: You may need to add required plug-ins to your workspace so the solution will compile and run. Select Window > Preferences > Plug-in Development > Target Platform and select Not in Workspace. This will ensure that the base plug-ins upon which the solution relies will be available during the import and recompilation process.
Once it is imported,
you will probably need to switch to the Plug-in Development perspective, select
the plugin.xml in
the com.ibm.lab.soln.jdt.excerpt project,
and choose Update Classpath. This will
correct compilation errors caused by differences between your Eclipse installation
paths and the solution's.
| Name | Size | Download method |
|---|---|---|
| os-ecjdt/solution_excerpt.zip | HTTP |
Information about download methods
Learn
-
Visit Eclipse.org for more information about Eclipse.
-
This article's solution is based in part on the companion solution on Chapter 26 of
The Java Developer's Guide to
Eclipse
, by Sherry Shavor, Jim D'Anjou, Dan Kehn, Scott Fairbrother, John Kellerman, and Pat McCarthy.
-
Check out the "Recommended Eclipse reading list."
-
Browse all the Eclipse content on developerWorks.
-
New to Eclipse? Read the developerWorks article "Get started with Eclipse Platform" to learn its origin and architecture, and how to extend Eclipse with plug-ins.
-
Expand your Eclipse skills by checking out IBM developerWorks' Eclipse project resources.
-
To listen to interesting interviews and discussions for software developers, check out check out developerWorks podcasts.
-
For an introduction to the Eclipse platform, see "Getting started with the Eclipse Platform."
-
Stay current with developerWorks' Technical events and webcasts.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Check out the latest Eclipse technology downloads at IBM alphaWorks.
-
Download Eclipse Platform and other projects from the Eclipse Foundation.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
-
The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
Dan Kehn is a Senior Software Engineer at IBM in Research Triangle Park, North Carolina. His interest in object-oriented programming goes back to 1985, long before it enjoyed the acceptance it has today. He has a broad range of software experience, having worked on development tools like VisualAge for Smalltalk, operating system performance and memory analysis, and user interface design. Dan worked as a consultant for object-oriented development projects throughout the U.S. as well as for four years in Europe. His recent interests include object-oriented analysis/design, application development tools, and Web programming with the WebSphere Application Server. In May 2001 he joined the Eclipse Jumpstart team, which helps ISVs create commercial offerings based on the Eclipse Platform. He and the rest of the Jumpstart team authored The Java Developer's Guide to Eclipse from which the solution presented in this article was excerpted.
Comments (Undergoing maintenance)





