NOW Hiring

Quick links

Share

This tutorial describes how the Eclipse JDT (Java Development Tools) and the Eclipse AST (Abstract Syntax Tree) can be used to access, change and read the elements of a Java program.

1. Eclipse JDT

1.1. Overview

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.

It allows to access the existing projects in the workspace, create new projects and modify and read existing projects. It also allows to launch Java programs.

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

1.2. 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 directed 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.3. Abstract Syntax Tree (AST)

The AST is a detailed tree representation of the Java source code. The AST defines 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 file 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. Accessing your Java projects with the JDT Java model

The following will 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 de.vogella.jdt.infos. Choose the "Hello World, command" 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 extends AbstractHandler {

        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 = new Job("Setting the classpath") {

                                @Override
                                protected IStatus run(IProgressMonitor 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. Contributions to JDT

The contribution process to JDT is described in the following page JDT UI/How to Contribute

7. About this website

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