Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Extend Eclipse's Java Development Tools

What is possible, where to start, and how to proceed

Dan Kehn, Senior Programmer, IBM
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.

Summary:  Extend Eclipse's Java development tools through the refactoring capability of Eclipse's Java™ development environment. It is one of the most useful features Eclipse Java platform provides. This article will introduce you to the steps for creating your own refactoring as a natural extension of Eclipse.

Date:  22 Jul 2003
Level:  Introductory
Also available in:   Japanese

Activity:  29347 views
Comments:  

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
Extension of 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.


A brief tour of Hello World

"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
Eclipse Platform architecture

Extension vs. extension point

Be aware that the XML tags for these two are quite similar. An extension point declares the availability of a plug-in's functionality to other plug-ins and is denoted by the <extension-point> tag. An extension uses a previously defined extension point and is denoted by the <extension> tag having the point attribute naming the extension point it wishes to use.

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
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:

Once we've got a good handle on the basic Eclipse landscape, we'll turn to some JDT-specific questions:

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
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
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
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.

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?

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.

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?

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:

  1. Remove public, private, or protected if found in the method signature
  2. 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).


Where do you go from here?

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.



Download

NameSizeDownload method
os-ecjdt/solution_excerpt.zip HTTP

Information about download methods


Resources

Learn

Get products and technologies

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.

About the author

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.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=10841
ArticleTitle=Extend Eclipse's Java Development Tools
publish-date=07222003
author1-email=
author1-email-cc=