Home Tutorials Training Consulting Products Books Company Donate Contact us









NOW Hiring

Quick links

Share

This tutorial describes the usage of the the Abstract Syntax Tree (AST) and the Java model in Eclipse. This AST allows to access, change and read the elements of a Java program.

1. The Java model and the Java Abstract Syntax Tree

The Eclipse Java Development Tools (JDT) project provides the tools to develop Java applications. This project also provides APIs to access and manipulate Java source code.

JDT provides access to the Java source code via two different means. The Java Model and the Abstract Syntax Tree (AST), which works on a Document Object Model similar to the XML DOM.

1.1. Java Model

Each Java project is internally represented in Eclipse as a Java model. The Eclipse Java model is a light-weight and fault tolerant representation of the Java project.

It does not contain as many information as the AST but is fast to create. For example the Outline view is using the Java model for its representation. This way the information in the Outline view can quickly get updated.

The Java model is defined in the org.eclipse.jdt.core plug-in.

The Java model is represented as a tree structure which can be described via the following table.

Table 1. Java Model
Project Element Java Model element Description

Java project

IJavaProject

The Java project which contains all other objects.

src folder / bin folder / or external library

IPackageFragmentRoot

Hold source or binary files, can be a folder or a library (zip / jar file )

Each package

IPackageFragment

Each package is below the IPackageFragmentRoot, sub-packages are not leaves of the package, they are listed directly under IPackageFragmentRoot

Java Source File

ICompilationUnit

The Source file is always below the package node

Types / Fields / Methods

IType / IField / IMethod

Types, fields and methods

1.2. Abstract Syntax Tree (AST)

The AST is a detailed tree representation of the Java source code. The AST defines an API to modify, create, read and delete source code.

The main package for the AST is the org.eclipse.jdt.core.dom package and is located in the org.eclipse.jdt.core plug-in.

Each Java source element is represented as a subclass of the ASTNode class. Each specific AST node provides specific information about the object it represents. For example you have MethodDeclaration (for methods), VariableDeclarationFragment (for variable declarations) and SimpleName (for any string which is not a Java keyword).

The AST is typically created based on a ICompilationUnit from the Java Model.

2. Prerequisites

This article assume that you are familiar with Eclipse plug-in development. See Eclipse plug-in development tutorial for an introduction.

3. Example: Accessing your Java projects with the JDT Java model

The following example create a command which will read the project of the workspace, get all package and Java source files for these projects and read all methods for them by using the JDT Java Model API.

Create a plug-in project called com.vogella.jdt.infos. Choose "Plug-in with an Eclipse 4 handler" as a template.

Add the following dependencies to your plug-in:

  • org.eclipse.core.resources

  • org.eclipse.jdt

  • org.eclipse.jdt.core

  • org.eclipse.core.runtime

  • org.eclipse.jdt.ui

  • org.eclipse.jface.text

Change the handler to the following.

package de.vogella.jdt.infos.handlers;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.Document;

public class SampleHandler  {

        public Object execute(ExecutionEvent event) throws ExecutionException {
                // Get the root of the workspace
                IWorkspace workspace = ResourcesPlugin.getWorkspace();
                IWorkspaceRoot root = workspace.getRoot();
                // Get all projects in the workspace
                IProject[] projects = root.getProjects();
                // Loop over all projects
                for (IProject project : projects) {
                        try {
                                printProjectInfo(project);
                        } catch (CoreException e) {
                                e.printStackTrace();
                        }
                }
                return null;
        }

        private void printProjectInfo(IProject project) throws CoreException,
                        JavaModelException {
                System.out.println("Working in project " + project.getName());
                // check if we have a Java project
                if (project.isNatureEnabled("org.eclipse.jdt.core.javanature")) {
                        IJavaProject javaProject = JavaCore.create(project);
                        printPackageInfos(javaProject);
                }
        }

        private void printPackageInfos(IJavaProject javaProject)
                        throws JavaModelException {
                IPackageFragment[] packages = javaProject.getPackageFragments();
                for (IPackageFragment mypackage : packages) {
                        // Package fragments include all packages in the
                        // classpath
                        // We will only look at the package from the source
                        // folder
                        // K_BINARY would include also included JARS, e.g.
                        // rt.jar
                        if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
                                System.out.println("Package " + mypackage.getElementName());
                                printICompilationUnitInfo(mypackage);

                        }

                }
        }

        private void printICompilationUnitInfo(IPackageFragment mypackage)
                        throws JavaModelException {
                for (ICompilationUnit unit : mypackage.getCompilationUnits()) {
                        printCompilationUnitDetails(unit);

                }
        }

        private void printIMethods(ICompilationUnit unit) throws JavaModelException {
                IType[] allTypes = unit.getAllTypes();
                for (IType type : allTypes) {
                        printIMethodDetails(type);
                }
        }

        private void printCompilationUnitDetails(ICompilationUnit unit)
                        throws JavaModelException {
                System.out.println("Source file " + unit.getElementName());
                Document doc = new Document(unit.getSource());
                System.out.println("Has number of lines: " + doc.getNumberOfLines());
                printIMethods(unit);
        }

        private void printIMethodDetails(IType type) throws JavaModelException {
                IMethod[] methods = type.getMethods();
                for (IMethod method : methods) {

                        System.out.println("Method name " + method.getElementName());
                        System.out.println("Signature " + method.getSignature());
                        System.out.println("Return Type " + method.getReturnType());

                }
        }
}

Start your plug-in. Create a few projects in your new workspace. Create a few packages for them and a few Java files. Press the menu entry which points to your sample command.

You should see the projects, package and source files listed in the Console view of the calling workbench.

4. Modifying projects

4.1. Creating new Java elements via the Java model

The following will create a command which will create a new package to existing (and open) Java projects which have the same name as the Java project. For example if you have a project de.vogella.test in your workspace without any package this command will create the package de.vogella.test in this project.

Create a new plug-in project de.vogella.jdt.newelements.

Add a new command "de.vogella.jdt.newelements.AddPackage" and put it into the menu.

Create the following handler for this command.

package de.vogella.jdt.newelements.handler;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;

public class AddPackage extends AbstractHandler {

        @Override
        public Object execute(ExecutionEvent event) throws ExecutionException {
                IWorkspace workspace = ResourcesPlugin.getWorkspace();
                IWorkspaceRoot root = workspace.getRoot();
                // Get all projects in the workspace
                IProject[] projects = root.getProjects();
                // Loop over all projects
                for (IProject project : projects) {
                        try {
                                // only work on open projects with the Java nature
                                if (project.isOpen()
                                                & project.isNatureEnabled(JavaCore.NATURE_ID)) {
                                        createPackage(project);
                                }
                        } catch (CoreException e) {
                                e.printStackTrace();
                }
                return null;
        }

        private void createPackage(IProject project) throws JavaModelException {
                IJavaProject javaProject = JavaCore.create(project);
                IFolder folder = project.getFolder("src");
                // folder.create(true, true, null);
                IPackageFragmentRoot srcFolder = javaProject
                                .getPackageFragmentRoot(folder);
                IPackageFragment fragment = srcFolder.createPackageFragment(
                                project.getName(), true, null);
        }
}
This will add the package with the Java project name to all open projects in the workspace. Make you sure you really want this. You could also add the action to the context menu of the package explorer and apply it only for the selected project. You can learn how to do this in Extending the Package Explorer.

An example can be found on the source page in project de.vogella.jdt.packageexplorer. Have a look at the command handler AddPackage.java.

4.2. Change the classpath

You can also modify the classpath of your project. The following example handler is contained in project de.vogella.jdt.addclasspath. It will add JUnit4 to the classpath of all projects in the workspace.

package de.vogella.jdt.addclasspath.handlers;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.e4.core.di.annotations.Execute;

public class GlobalModifyClasspathHandler {

        private static final String JDT_NATURE = "org.eclipse.jdt.core.javanature";

        @Execute
        public void execute() {
                IWorkspace workspace = ResourcesPlugin.getWorkspace();
                IWorkspaceRoot root = workspace.getRoot();
                // Get all projects in the workspace
                IProject[] projects = root.getProjects();
                // Loop over all projects
                for (IProject project : projects) {
                        try {
                                // only work on open projects with the Java nature
                                if (project.isOpen() && project.isNatureEnabled(JDT_NATURE)) {
                                        changeClassPath(project);
                                }
                        } catch (CoreException e) {
                                e.printStackTrace();
                        }
                }
        }

        private void changeClasspath(IProject project) throws JavaModelException {
                IJavaProject javaProject = JavaCore.create(project);
                IClasspathEntry[] entries = javaProject.getRawClasspath();
                IClasspathEntry[] newEntries = new IClasspathEntry[entries.length + 1];

                System.arraycopy(entries, 0, newEntries, 0, entries.length);

                // add a new entry using the path to the container
                Path junitPath = new Path(
                                "org.eclipse.jdt.junit.JUNIT_CONTAINER/4");
                IClasspathEntry junitEntry = JavaCore
                                .newContainerEntry(junitPath);
                newEntries[entries.length] = JavaCore
                                .newContainerEntry(junitEntry.getPath());
                javaProject.setRawClasspath(newEntries, null);
        }


}

The next example shows a handler, which also adds Mockito to the classpath of a selected IJavaElement 's java project.

package de.vogella.jdt.addclasspath.handlers;

import javax.inject.Named;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.e4.core.di.annotations.CanExecute;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.ui.services.IServiceConstants;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;

public class ModifyClasspathHandler {

        @Execute
        public void execute(@Named(IServiceConstants.ACTIVE_SELECTION) IStructuredSelection selection)
                        throws JavaModelException {
                Object firstElement = selection.getFirstElement();
                if (firstElement instanceof IJavaElement) {

                        // Get the selected IJavaProject
                        final IJavaElement javaElement = (IJavaElement) firstElement;
                        Job job = Job.create("Setting the classpath", monitor -> {
                                IJavaProject javaProject = javaElement.getJavaProject();

                                // Test the best and get the IClasspathEntry for
                                // mockito-core
                                Path path = new Path(
                                                "/home/simon/.m2/repository/org/mockito/mockito-core/1.8.4/mockito-core-1.8.4.jar");
                                IClasspathEntry libraryEntry = JavaCore.newLibraryEntry(path, null, null);

                                try {
                                        // add the classpath to mockito-core for the java
                                        // project
                                        javaProject.setRawClasspath(new IClasspathEntry[] { libraryEntry }, monitor);
                                } catch (JavaModelException e) {
                                        Bundle bundle = FrameworkUtil.getBundle(getClass());
                                        return new Status(Status.ERROR, bundle.getSymbolicName(),
                                                        "Could not set classpath to Java project: " + javaProject.getElementName(), e);
                                }
                                return Status.OK_STATUS;
                        });

                        job.schedule();
                }
        }

        @CanExecute
        public boolean canExecute(@Named(IServiceConstants.ACTIVE_SELECTION) IStructuredSelection selection) {
                return selection.getFirstElement() instanceof IJavaElement;
        }

}

5. Using the AST

The following will create an AST for each source file in your workspace and print out the name and the return type of each method using the AST.

Create a new plug-in project de.vogella.jdt.astsimple using the "Hello world" template.

Maintain the following plug-in dependencies.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Astsimple Plug-in
Bundle-SymbolicName: de.vogella.jdt.astsimple;singleton:=true
Bundle-Version: 1.0.0
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Require-Bundle: org.eclipse.core.runtime;bundle-version="3.4.0",
 org.eclipse.ui;bundle-version="3.4.2",
 org.eclipse.jdt.core;bundle-version="3.4.4",
 org.eclipse.core.resources;bundle-version="3.4.2",
 org.eclipse.jdt.ui;bundle-version="3.4.2"

To get information about the AST you can use the Visitor Pattern. This allows you to add a visitor to the AST for a specific element. In this visitor can you capture information about the object and return it after processing the AST. Create for this the following class.

package de.vogella.jdt.astsimple.handler;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.MethodDeclaration;

public class MethodVisitor extends ASTVisitor {
        List<MethodDeclaration> methods = new ArrayList<MethodDeclaration>();

        @Override
        public boolean visit(MethodDeclaration node) {
                methods.add(node);
                return super.visit(node);
        }

        public List<MethodDeclaration> getMethods() {
                return methods;
        }
}

Add a new command de.vogella.jdt.astsimple.GetInfo and put it into the menu. Create the following handler for this command.

package de.vogella.jdt.astsimple.handler;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;

public class GetInfo extends AbstractHandler {

        private static final String JDT_NATURE = "org.eclipse.jdt.core.javanature";

        @Override
        public Object execute(ExecutionEvent event) throws ExecutionException {
                IWorkspace workspace = ResourcesPlugin.getWorkspace();
                IWorkspaceRoot root = workspace.getRoot();
                // Get all projects in the workspace
                IProject[] projects = root.getProjects();
                // Loop over all projects
                for (IProject project : projects) {
                        try {
                                if (project.isNatureEnabled(JDT_NATURE)) {
                                        analyseMethods(project);
                                }
                        } catch (CoreException e) {
                                e.printStackTrace();
                        }
                }
                return null;
        }

        private void analyseMethods(IProject project) throws JavaModelException {
                IPackageFragment[] packages = JavaCore.create(project)
                                .getPackageFragments();
                // parse(JavaCore.create(project));
                for (IPackageFragment mypackage : packages) {
                        if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
                                createAST(mypackage);
                        }

                }
        }

        private void createAST(IPackageFragment mypackage)
                        throws JavaModelException {
                for (ICompilationUnit unit : mypackage.getCompilationUnits()) {
                        // now create the AST for the ICompilationUnits
                        CompilationUnit parse = parse(unit);
                        MethodVisitor visitor = new MethodVisitor();
                        parse.accept(visitor);

                        for (MethodDeclaration method : visitor.getMethods()) {
                                System.out.print("Method name: " + method.getName()
                                                + " Return type: " + method.getReturnType2());
                        }

                }
        }

        /**
         * Reads a ICompilationUnit and creates the AST DOM for manipulating the
         * Java source file
         *
         * @param unit
         * @return
         */

        private static CompilationUnit parse(ICompilationUnit unit) {
                ASTParser parser = ASTParser.newParser(AST.JLS3);
                parser.setKind(ASTParser.K_COMPILATION_UNIT);
                parser.setSource(unit);
                parser.setResolveBindings(true);
                return (CompilationUnit) parser.createAST(null); // parse
        }
}

Add the command to your menu. Run your new plugin, create a new Java project and select the command. It should print out the information about your methods in the IDE from which you started your new plugin.

6. JDT AST View

The JDT project provides an Eclipse plugin called AST View. AST View can visualize the AST of a Java source file.

6.1. Installing AST View

You can install the plug-in from their update page: http://www.eclipse.org/jdt/ui/update-site

If you prefer to install manually you can find the latest binaries on the project page: https://www.eclipse.org/jdt/ui/astview/

6.2. Using AST View

You can start the plugin with the keybind Alt+Shift+Q, A or via the menu entry at Window ▸ Show View ▸ Other…​, Java ▸ AST View.

Now open the Java file that you want to analyze. Press Show AST of active editor to load the AST. By double clicking on a specific entry you jump to the correlating line in the source code.

7. Using extension points to define JDT templates

You can also define your templates within an Eclipse plug-in.

This makes it easier to distribute your templates to other developers.

Providing a template can be done via an extension to the org.eclipse.ui.editors.templates extension point.

The templates are activated for a certain scope. For example, you have templates for statements within a method or templates for new methods.

Table 2. Context in the template
Statement Description

java-statements

Templates which are available within a method

java-members

Allows to define templates for members, e.g., new methods

To provide a code template based on this extension point, create a new plug-in called com.vogella.jdt.templates.

Add an extension for the org.eclipse.ui.editors.templates extension point and add an include as in the following screenshot.

jdttemplates10

In the reference file you can add your templates following a predefined schema. The file can look like the following, the structure should be easily to adjust for other use cases.

<?xml version="1.0" encoding="UTF-8"?>
<templates>
        <template name="localvariable" description="Local Variable"
                id="org.eclipse.jdt.ui.templates.localvariable" context="java-statements"
                enabled="true" autoinsert="false">
                ${variabletype} ${myvar:newName(var)}= new ${variabletype}(${cursor});
        </template>

        <template name="string" description="String (empty)"
                id="org.eclipse.jdt.ui.templates.emptystring" context="java-statements"
                enabled="true" autoinsert="false">
                String ${myvar:newName(var)}= "";
        </template>

        <template name="ifnotnull" description="If not null"
                id="org.eclipse.jdt.ui.templates.notnull" context="java-statements"
                enabled="true" autoinsert="false">
                if (${myvar:newName(variable)}!=null) {
                        ${cursor}
                }
        </template>

        <template name="arrayexample" description="Creates an Array with some example content"
                id="org.eclipse.jdt.ui.templates.arrayexample" context="java-statements"
                enabled="true" autoinsert="false">
                String[] ${myvar:newName(var)} =
                new String[]{"Windows 7", "MacOS", "Debian", "Ubuntu", "Redhat",
                "Android", "iPhone", "Suse", "Windows XP", "Windows Mobile", "OS/2", "WebOS"};
        </template>

        <template name="method-public"
                description="Creates public method"
                id="org.eclipse.jdt.ui.templates.methodpublic"
                context="java-members" enabled="true"
                autoinsert="false">public void ${name}(${}) {
                        ${cursor}
}
        </template>




        <template name="finallyCloseStream"
                description="Creates the finally block for closing a stream"
                id="org.eclipse.jdt.ui.templates.finallyclosestream"
                context="java-statements" enabled="true" autoinsert="false">
                finally {
                if (stream != null)
                try {
                stream.close();
                } catch (IOException e) {
                e.printStackTrace();
                }
                }
        </template>

</templates>

8. Developing custom JDT refactoring operations

9. Developing custom Eclipse quick fixes and quick assist for Java

9.1. Using quick fixes and quick assists

The Eclipse IDE provides quick fixes and quick assists to efficiently solve common problems in your Java code. For your Java code you can create custom quick fixes and quick assistance via additional custom plug-ins. Such plug-ins require the following plug-ins as dependency in their manifest.

  • org.eclipse.jdt.ui

  • org.eclipse.jdt.core

  • org.eclipse.core.runtime

  • org.eclipse.jface

  • org.eclipse.jface.text

9.2. Quick assist processors

Quick Assists Processors provide the opportunity to provide context sensitive refactorings and assistance in an editor, e.g., the Java editor.

In order to provide quick assistance the org.eclipse.jdt.ui.quickAssistProcessors extension point has to be used.

quickassist extensionpoint

The referenced class in the extension point has to be an instance of IQuickAssistProcessor, which specifies the boolean hasAssists(IInvocationContext context) and IJavaCompletionProposal[] getAssists(IInvocationContext context, IProblemLocation[] locations) methods.

The hasAssists method specifies whether the quick assist is appropriate.

The getAssists method returns an array of IJavaCompletionProposal objects, which can apply refactorings and changes to the code.

9.3. Quick fix processors

The Eclipse Java Development Tools (JDT) allow the creation of custom quick fixes via the org.eclipse.jdt.ui.quickFixProcessors extension point from the org.eclipse.jdt.ui plug-in.

The class which is referred by the extension points must implement the IQuickFixProcessor interface. This defines the hasCorrections() and getCorrections() methods.

The IProblem interface defines the possible problems.

9.4. Finding predefined problem ids

The IProblem interface contains a huge amount of potential problem ids, where a quick fix could be used to resolve a certain problem.

Due to the huge amount of problem ids it sometimes is pretty hard to figure out, which problem id is in charge for a particular problem.

Therefore an empty implementation of an IQuickFixProcessor can be used to find the correct id.

package com.vogella.jdt.quickfix;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.IQuickFixProcessor;

public class ProblemIdQuickFixProcessor implements IQuickFixProcessor {

        @Override
        public boolean hasCorrections(ICompilationUnit unit, int problemId) {

                System.out.println(problemId);

                return false;
        }

        @Override
        public IJavaCompletionProposal[] getCorrections(IInvocationContext context, IProblemLocation[] locations)
                        throws CoreException {

                return null;
        }

}

With the IQuickFixProcessor from above the Eclipse IDE can be started from the Eclipse IDE itself and a certain problem can be reconstructed, e.g., in a Java editor.

Once a problem appears and the hasCorrections() is invoked the actual problem id will be printed to the system output.

10. Exercise - Eclipse JDT Quick Assist

In this exercise a custom JDT quick assist is created. It is available on the class definition and opens the Generate Getters and Setters…​ dialog from the Source menu.

10.1. Creating a Plug-in Project and add dependencies

Create an empty Plug-in project called com.vogella.jdt.quickfix and add the following dependencies:

  • org.eclipse.jdt.ui

  • org.eclipse.jdt.core

  • org.eclipse.core.runtime

  • org.eclipse.jface

  • org.eclipse.jface.text

10.2. Using the org.eclipse.jdt.ui.quickAssistProcessors extension point

Press the Add button in the Extensions tab of the plugin.xml editor and choose org.eclipse.jdt.ui.quickAssistProcessors.

add quickassist extensionpoint

Pressing the Finish button will result in the extension point being added and already containing an entry.

quickassist extensionpoint

By clicking on the class: link a class creation wizard will appear.

quickassist class wizard

Just stick to the defaults and press the Finish button.

10.3. Find out how to call the generate getter and setter action

By using the menu spy ALT+SHIFT+F2 the action behind the Source ▸ Generate Getters and Setter…​ menu can be found out.

10.4. Provide IJavaCompletionProposals with an IQuickAssistProcessor

The following IQuickAssistProcessor can be used to show an IJavaCompletionProposal, which can be used to open the Generate Getters and Setters dialog.

package com.vogella.jdt.quickfix;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.internal.ui.text.correction.AssistContext;
import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal;
import org.eclipse.jdt.ui.actions.AddGetterSetterAction;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.IQuickAssistProcessor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.viewers.StyledString;

@SuppressWarnings("restriction")
public class QuickAssistProcessor1 implements IQuickAssistProcessor {

        @Override
        public boolean hasAssists(IInvocationContext context) throws CoreException {
                ASTNode coveredNode = context.getCoveredNode();

                return coveredNode.getNodeType() == ASTNode.TYPE_DECLARATION;
        }

        @Override
        public IJavaCompletionProposal[] getAssists(IInvocationContext context, IProblemLocation[] locations)
                        throws CoreException {
                return new IJavaCompletionProposal[] { new AbstractJavaCompletionProposal() {
                        public org.eclipse.jface.viewers.StyledString getStyledDisplayString() {
                                ICompilationUnit compilationUnit = context.getCompilationUnit();
                                return new StyledString(
                                                "Generate Getter and setter for " + compilationUnit.findPrimaryType().getElementName());
                        }

                        protected int getPatternMatchRule(String pattern, String string) {
                                // override the match rule since we do not work with a pattern, but just want to open the "Generate Getters and Setters..." dialog
                                return -1;
                        };

                        public void apply(org.eclipse.jface.text.ITextViewer viewer, char trigger, int stateMask, int offset) {

                                if(context instanceof AssistContext) {
                                        AssistContext assistContext = (AssistContext) context;
                                        AddGetterSetterAction addGetterSetterAction = new AddGetterSetterAction((CompilationUnitEditor)assistContext.getEditor());

                                        addGetterSetterAction.run();
                                }

                        }


                } };
        }
}

10.5. Validate

When starting an Eclipse IDE the Generate Getter and Setter for {IType} quick assist should show up, when pressing CTRL+1 in a Java editor.

generate getters and setters assist

11. Exercise - Eclipse JDT quick fix

In this exercise a JDT quick fix is created. The previously created com.vogella.jdt.quickfix project is reused for this.

11.1. Using the org.eclipse.jdt.ui.quickFixProcessors extension point

Press the Add button in the Extensions tab of the plugin.xml editor and choose org.eclipse.jdt.ui.quickFixProcessors.

add quickfix extensionpoint

Pressing the Finish button will result in the extension point being added and already containing an entry.

added quickfix extensionpoint

By clicking on the class*: link a class creation wizard will appear.

quickfix class wizard

Just stick to the defaults and press the Finish button.

11.2. Provide IJavaCompletionProposals with an IQuickFixProcessor

The IQuickFixProcessor is pretty similar to an IQuickAssistProcessor, but just reacts on certain problems in an editor or in the problems view.

In Finding predefined problem ids a way how to find the right problem id for a certain problem is described.

Your task will be to grab a certain problem id and implement a Quick fix for that particular problem.

12. Exercise - Eclipse JDT clean up action

This exercise shows you how to provide an Eclipse clean up action. It is based on the Eclipse clean up guide. You can reuse the com.vogella.jdt.quickfix project or create a new plug-in project with the following dependencies:

  • org.eclipse.jdt.ui

  • org.eclipse.jdt.core

  • org.eclipse.core.runtime

  • org.eclipse.text

  • org.eclipse.swt

  • org.eclipse.ltk.core.refactoring

12.1. Provide a ICleanUpFix with ICleanUp

Create a new extensions for the org.eclipse.jdt.ui.cleanUps extension point. Do a right click on the extensions and choose Add ▸ cleanUp. After that click on class*: to create the corresponding Java class and call it CopyrightUpdaterCleanUp.

Click on

After that we want to implement the methods required by the interface ICleanUp.

With the CleanUpRequirements the action tells the framework the requirements needed for it’s execution. The action can demand an AST or the changed regions since last save to be provided by the CleanUpContext. The requirements can depend on the options passed through setOptions(CleanUpOptions).

@Override
public CleanUpRequirements getRequirements() {
    boolean changedRegionsRequired = false;
    Map<String, String> compilerOptions = null;
    boolean isUpdateCopyrights = options.isEnabled(CLEANUP_COPYRIGHTS_FLAG);
    return new CleanUpRequirements(isUpdateCopyrights, isUpdateCopyrights, changedRegionsRequired, compilerOptions);
}

With getStepDescriptions we provide a human readable description of our action. It gets displayed in the overview of the selected clean up actions.

Clean up step descriptions
@Override
public String[] getStepDescriptions() {
    if (options.isEnabled(CLEANUP_COPYRIGHTS_FLAG)) {
        return new String[] {"Update Copyrights"};//$NON-NLS-1$
    }
    return null;
}

The options that get set over the menu get passed in via the setOptions method.

@Override
public void setOptions(CleanUpOptions options) {
    Assert.isLegal(options != null);
    Assert.isTrue(options == null);
    this.options = options;
}

The action only starts to create a fix if the preconditions are met. The pre condition check fails if the RefactoringStatus contains a RefactoringStatusEntry with a fatal severity.

@Override
public RefactoringStatus checkPreConditions(IJavaProject project, ICompilationUnit[] compilationUnits, IProgressMonitor monitor) throws CoreException {
    if (options.isEnabled(CLEANUP_COPYRIGHTS_FLAG)) {
        status = new RefactoringStatus();
    }
    return new RefactoringStatus();
}

The checkPostConditions method gets called after the fix was applied. If the RefactoringStatus contains a RefactoringStatusEntry with a severity of RefactoringStatus.WARNING or higher the action will fail.

@Override
public RefactoringStatus checkPostConditions(IProgressMonitor monitor) throws CoreException {
    try {
        if (status == null || status.isOK()) {
            return new RefactoringStatus();
        } else {
            return status;
        }
    } finally {
        status = null;
    }
}

The createFix method must return an ICleanUpFix that does provide the actual fix. The method is supposed to return null if there is nothing found to be fixed.

@Override
public ICleanUpFix createFix(CleanUpContext context) throws CoreException {
    CompilationUnit compilationUnit = context.getAST();
    if (compilationUnit == null) {
        return null;
    }
    return CopyrightsFix.createCleanUp(compilationUnit, options.isEnabled(CLEANUP_COPYRIGHTS_FLAG));
}

The whole class then looks like this:

public class CopyrightsFix implements ICleanUp {

    public static final String CLEANUP_COPYRIGHTS_FLAG = "cleanup.update_copyrights";
    private CleanUpOptions options;
    private RefactoringStatus status;

    public CopyrightsFix() {
    }

    @Override
    public CleanUpRequirements getRequirements() {
        boolean changedRegionsRequired = false;
        Map<String, String> compilerOptions = null;
        boolean isUpdateCopyrights = options.isEnabled(CLEANUP_COPYRIGHTS_FLAG);
        return new CleanUpRequirements(isUpdateCopyrights, isUpdateCopyrights, changedRegionsRequired, compilerOptions);
    }

    @Override
    public String[] getStepDescriptions() {
        if (options.isEnabled(CLEANUP_COPYRIGHTS_FLAG)) {
            return new String[] {"Update Copyrights"};//$NON-NLS-1$
        }
        return null;
    }

    @Override
    public void setOptions(CleanUpOptions options) {
        Assert.isLegal(options != null);
        Assert.isTrue(options == null);
        this.options = options;
    }

    @Override
    public RefactoringStatus checkPreConditions(IJavaProject project, ICompilationUnit[] compilationUnits, IProgressMonitor monitor) throws CoreException {
        if (options.isEnabled(CLEANUP_COPYRIGHTS_FLAG)) {
            status = new RefactoringStatus();
        }
        return new RefactoringStatus();
    }

    @Override
    public RefactoringStatus checkPostConditions(IProgressMonitor monitor) throws CoreException {
        try {
            if (status == null || status.isOK()) {
                return new RefactoringStatus();
            } else {
                return status;
            }
        } finally {
            status = null;
        }
    }

    @Override
    public ICleanUpFix createFix(CleanUpContext context) throws CoreException {
        CompilationUnit compilationUnit = context.getAST();
        if (compilationUnit == null) {
            return null;
        }
        return CopyrightsFix.createCleanUp(compilationUnit, options.isEnabled(CLEANUP_COPYRIGHTS_FLAG));
    }

}

12.2. Provide default options with an ICleanUpOptionsInitializer

If we want to provide default options for your cleanup we can do this by implementing the ICleanUpOptionsInitializer interface. Add a new child element to the org.eclipse.jdt.ui.cleanUps extension point via New ▸ cleanUpOptionsInitializer. Create the class outline by clicking on the class*: link. The setDefaultOptions method can provide preselected options.

public class CopyrightOnSaveOptionsInitializer implements ICleanUpOptionsInitializer {

    @Override
    public void setDefaultOptions(CleanUpOptions options) {
        options.setOption(CopyrightsFix.CLEANUP_COPYRIGHTS_FLAG, CleanUpOptions.TRUE);
    }

}

12.3. Add the fix to the UI with an ICleanUpConfigurationUI

Finally we want to add our new clean up action to the Eclipse UI. This is done by implementing the ICleanUpConfigurationUI interface. Add a new child element to the org.eclipse.jdt.ui.cleanUps extension point via New ▸ cleanUpConfigurationUI. For cleanUpKind choose cleanUp. An implementation adding a new tab to the clean up configuration menu can look like this:

public class CopyrightTabPage implements ICleanUpConfigurationUI {

    private static final int CURRENT_YEAR = Calendar.getInstance().get(Calendar.YEAR);

    private CleanUpOptions options;

    public CopyrightTabPage() {
        super();
    }

    @Override
    public void setOptions(CleanUpOptions options) {
        this.options = options;
    }

    @Override
    public Composite createContents(Composite parent) {
        Composite result = new Composite(parent, SWT.NONE);
        result.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        GridLayout layout = new GridLayout(1, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        result.setLayout(layout);

        Group group= new Group(result, SWT.NONE);
        group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        group.setLayout(new GridLayout(1, false));
        group.setText("Copyright Update");

        final Button updateCheckbox= new Button(group, SWT.CHECK);
        updateCheckbox.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
        updateCheckbox.setText("Update the Copyrights");
        updateCheckbox.setSelection(options.isEnabled(CopyrightsFix.CLEANUP_COPYRIGHTS_FLAG));
        updateCheckbox.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
            options.setOption(CopyrightsFix.CLEANUP_COPYRIGHTS_FLAG,
                    updateCheckbox.getSelection() ? CleanUpOptions.TRUE : CleanUpOptions.FALSE);
        }));

        return result;
    }

    @Override
    public int getCleanUpCount() {
        return 1;
    }

    @Override
    public int getSelectedCleanUpCount() {
        return options.isEnabled(CopyrightsFix.CLEANUP_COPYRIGHTS_FLAG) ? 1 : 0;
    }

    @Override
    public String getPreview() {
        StringBuffer buf = new StringBuffer();

        buf.append("/*******************************************************************************\n"); //$NON-NLS-1$
        if (options.isEnabled(CopyrightsFix.CLEANUP_COPYRIGHTS_FLAG)) {
            buf.append(" * Copyright (c) 2005, ").append(CURRENT_YEAR).append(" IBM Corporation and others.\n"); //$NON-NLS-1$ //$NON-NLS-2$
        } else {
            buf.append(" * Copyright (c) 2005 IBM Corporation and others.\n"); //$NON-NLS-1$
        }
        buf.append(" * All rights reserved. This  program and the accompanying materials\n"); //$NON-NLS-1$
        buf.append(" * are made available under the terms of the Eclipse Public License v1.0\n"); //$NON-NLS-1$
        buf.append(" * which accompanies this distribution, and is available at\n"); //$NON-NLS-1$
        buf.append(" * http://www.eclipse.org/legal/epl-v10.html\n"); //$NON-NLS-1$
        buf.append(" *\n"); //$NON-NLS-1$
        buf.append(" * Contributors:\n"); //$NON-NLS-1$
        buf.append(" *     IBM Corporation - initial API and implementation\n"); //$NON-NLS-1$
        buf.append(" *******************************************************************************/\n"); //$NON-NLS-1$

        return buf.toString();
    }

}

12.4. Implement the ICleanUpFix

The only step missing now is the implementation of the ICleanUpFix interface. The ICleanUpFix implementation performs the desired change.

You can find a complete example of a Copyright clean up / save action including the ICleanUpFix implementation here: https://github.com/vogellacompany/saneclipse/tree/master/org.eclipse.jdt.copyrightsaveaction

13. Exercise - Eclipse JDT save action

To add a save action instead of a clean up action follow the Exercise - Eclipse JDT clean up action section above but choose saveAction as cleanUpKind in the cleanUpConfigurationUI extension point. You can add a fix as save and clean up action by creating two cleanUpConfigurationUI extension points.

14. Contributions to JDT

The contribution process is described in the following pages: https://wiki.eclipse.org/JDT_UI/How_to_Contribute

15. About this website

16.4. vogella GmbH training and consulting support

TRAINING SERVICE & SUPPORT

The vogella company provides comprehensive training and education services from experts in the areas of Eclipse RCP, Android, Git, Java, Gradle and Spring. We offer both public and inhouse training. Whichever course you decide to take, you are guaranteed to experience what many before you refer to as “The best IT class I have ever attended”.

The vogella company offers expert consulting services, development support and coaching. Our customers range from Fortune 100 corporations to individual developers.

Copyright © 2012-2016 vogella GmbH. Free use of the software examples is granted under the terms of the EPL License. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.

See Licence.