Manipulating Java code

Your plug-in can use the JDT API to create classes or interfaces, add methods to existing types, or alter the methods for types.

The simplest way to alter Java objects is to use the Java element API. More general techniques can be used to work with the raw source code for a Java element.

Code modification using Java elements

Generating a compilation unit

The easiest way to programmatically generate a compilation unit is to use IPackageFragment.createCompilationUnit. You specify the name and contents of the compilation unit. The compilation unit is created inside the package and the new ICompilationUnit is returned.

A compilation unit can be created generically by creating a file resource whose extension is ".java" in the appropriate folder that corresponds to the package directory. Using the generic resource API is a back door to the Java tooling, so the Java model is not updated until the generic resource change listeners are notified and the JDT listeners update the Java model with the new compilation unit.

Modifying a compilation unit

Most simple modifications of Java source can be done using the Java element API.

For example, you can query a type from a compilation unit. Once you have the IType, you can use protocols such as createField, createInitializer, createMethod, or createType to add source code members to the type. The source code and information about the location of the member is supplied in these methods.

The ISourceManipulation interface defines common source manipulations for Java elements. This includes methods for renaming, moving, copying, or deleting a type's member.

Working copies

Code can be modified by manipulating the compilation unit (and thus the underlying IFile is modified) or one can modify an in-memory copy of the compilation unit called a working copy.

A working copy is obtained from a compilation unit using the getWorkingCopy method. Whoever creates such a working copy is responsible for destroying it when not needed any longer using the destroy method.

Working copies modify an in-memory buffer. The getWorkingCopy() method creates a default buffer, but clients can provide their own buffer implementation using the getWorkingCopy(IProgressMonitor, IBufferFactory, IProblemRequestor) method. Clients can manipulate the text of this buffer directly. If they do so, they must synchronize the working copy with the buffer from time to time using either the reconcile() method or the reconcile(boolean,IProgressMonitor) method.

Finally a working copy can be saved to disk (replacing the original compilation unit) using the commit method.

For example the following code snippet creates a working copy on a compilation unit using a custom buffer factory. The snippet modifies the buffer, reconciles the changes, commits the changes to disk and finally destroys the working copy.

    // Get original compilation unit
    ICompilationUnit originalUnit = ...;
    
    // Get buffer factory
    IBufferFactory factory = ...;
    
    // Create working copy
    IWorkingCopy workingCopy = originalUnit.getWorkingCopy(null, factory, null);
    
    // Modify buffer and reconcile
    IBuffer buffer = ((IOpenable)workingCopy).getBuffer();
    buffer.append("class X {}");
    workingCopy.reconcile();
    
    // Commit changes
    workingCopy.commit(false, null);
    
    // Destroy working copy
    workingCopy.destroy();

Working copies can also be shared by several clients. A shared working copy is created using the getSharedWorkingCopy method and it can be later retrieved using the findSharedWorkingCopy method. A shared working copy is thus keyed on the original compilation unit and on a buffer factory.

The following shows how client 1 creates a shared working copy, client 2 retrieves this working copy, client 1 destroys the working copy, and client 2 trying to retrieve the shared working copy notices it does not exist any longer:

    // Client 1 & 2: Get original compilation unit
    ICompilationUnit originalUnit = ...;
    
    // Client 1 & 2: Get buffer factory
    IBufferFactory factory = ...;
    
    // Client 1: Create shared working copy
    IWorkingCopy workingCopyForClient1 = originalUnit.getSharedWorkingCopy(null, factory, null);
    
    // Client 2: Retrieve shared working copy
    IWorkingCopy workingCopyForClient2 = originalUnit.findSharedWorkingCopy(factory);
     
    // This is the same working copy
    assert workingCopyForClient1 == workingCopyForClient2;
    
    // Client 1: Destroy shared working copy
    workingCopyForClient1.destroy();
    
    // Client 2: Attempt to retrieve shared working copy and find out it's null
    workingCopyForClient2 = originalUnit.findSharedWorkingCopy(factory);
    assert workingCopyForClient2 == null;

Code modification using the DOM/AST API

There are two ways to create a CompilationUnit. The first one is to use an existing compilation unit. The second is to start from scratch using the factory methods on AST (Abstract Syntax Tree).

Creating an AST from an existing compilation unit

This is achieved with the parse methods on AST: All these methods will properly set the positions for each node in the resulting tree. The resolution of bindings has to be requested before the creation of the tree. Resolving the bindings is a costly operation and should be done only when necessary. As soon as the tree has been modified, all positions and bindings are lost.

From scratch

It is possible to create a CompilationUnit from scratch using the factory methods on AST. These method names start with new.... The following is an example that creates a HelloWorld class.

The first snippet is the generated output:

	package example;
	import java.util.*;
	public class HelloWorld {
		public static void main(String[] args) {
			System.out.println("Hello" + " world");
		}
	}

The following snippet is the corresponding code that generates the output.

		AST ast = new AST();
		CompilationUnit unit = ast.newCompilationUnit();
		PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
		packageDeclaration.setName(ast.newSimpleName("example"));
		unit.setPackage(packageDeclaration);
		ImportDeclaration importDeclaration = ast.newImportDeclaration();
		QualifiedName name = 
			ast.newQualifiedName(
				ast.newSimpleName("java"),
				ast.newSimpleName("util"));
		importDeclaration.setName(name);
		importDeclaration.setOnDemand(true);
		unit.imports().add(importDeclaration);
		TypeDeclaration type = ast.newTypeDeclaration();
		type.setInterface(false);
		type.setModifiers(Modifier.PUBLIC);
		type.setName(ast.newSimpleName("HelloWorld"));
		MethodDeclaration methodDeclaration = ast.newMethodDeclaration();
		methodDeclaration.setConstructor(false);
		methodDeclaration.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
		methodDeclaration.setName(ast.newSimpleName("main"));
		methodDeclaration.setReturnType(ast.newPrimitiveType(PrimitiveType.VOID));
		SingleVariableDeclaration variableDeclaration = ast.newSingleVariableDeclaration();
		variableDeclaration.setModifiers(Modifier.NONE);
		variableDeclaration.setType(ast.newArrayType(ast.newSimpleType(ast.newSimpleName("String"))));
		variableDeclaration.setName(ast.newSimpleName("args"));
		methodDeclaration.parameters().add(variableDeclaration);
		org.eclipse.jdt.core.dom.Block block = ast.newBlock();
		MethodInvocation methodInvocation = ast.newMethodInvocation();
		name = 
			ast.newQualifiedName(
				ast.newSimpleName("System"),
				ast.newSimpleName("out"));
		methodInvocation.setExpression(name);
		methodInvocation.setName(ast.newSimpleName("println")); 
		InfixExpression infixExpression = ast.newInfixExpression();
		infixExpression.setOperator(InfixExpression.Operator.PLUS);
		StringLiteral literal = ast.newStringLiteral();
		literal.setLiteralValue("Hello");
		infixExpression.setLeftOperand(literal);
		literal = ast.newStringLiteral();
		literal.setLiteralValue(" world");
		infixExpression.setRightOperand(literal);
		methodInvocation.arguments().add(infixExpression);
		ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation);
		block.statements().add(expressionStatement);
		methodDeclaration.setBody(block);
		type.bodyDeclarations().add(methodDeclaration);
		unit.types().add(type);

Retrieving extra positions

The DOM/AST node contains only a pair of positions (the starting position and the length of the node). This is not always sufficient. In order to retrieve intermediate positions, the IScanner API should be used. For example, we have an InstanceofExpression for which we want to know the positions of the instanceof operator. We could write the following method to achieve this:
	private int[] getOperatorPosition(Expression expression, char[] source) {
		if (expression instanceof InstanceofExpression) {
			IScanner scanner = ToolFactory.createScanner(false, false, false, false);
			scanner.setSource(source);
			int start = expression.getStartPosition();
			int end = start + expression.getLength();
			scanner.resetTo(start, end);
			int token;
			try {
				while ((token = scanner.getNextToken()) != ITerminalSymbols.TokenNameEOF) {
					switch(token) {
						case ITerminalSymbols.TokenNameinstanceof:
							return new int[] {scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()};
					}
				}
			} catch (InvalidInputException e) {
			}
		}
		return null;
	}
The IScanner is used to divide the input source into tokens. Each token has a specific value that is defined in the ITerminalSymbols interface. It is fairly simple to iterate and retrieve the right token. We also recommend that you use the scanner if you want to find the position of the super keyword in a SuperMethodInvocation.

Generic source code modification

Some source code modifications are not provided via the Java element API. A more general way to edit source code (such as changing the source code for existing elements) is accomplished using the compilation unit's raw source code and the Java DOM.

These techniques include the following:

   // get the source for a compilation unit
   String contents = myCompilationUnit.getBuffer().getContents();

   // Create an editable JDOM
   myJDOM = new DOMFactory();
   myDOMCompilationUnit = myJDOM.createCompilationUnit(contents, "MyClass");

   // Navigate and edit the compilation unit structure using 
   // JDOM node protocol. 
   ...
   // Once modififications have been made to all of the nodes 
   // get the source back from the compilation unit DOM node.
   String newContents = myDOMCompilationUnit.getContents();

   // Set this code back into the compilation unit element
   myCompilationUnit.getBuffer().setContents(newContents);

   // Save the buffer to the file.
   myCompilationUnit.save();

This technique can result in problem markers being associated with incorrect line numbers, since the Java elements were not updated directly.

The Java element model does not go any finer than methods and fields. The abstract syntax tree used by the compiler is not available as API, so the techniques used by the JDT to parse source into programmatic structures are not currently available as API.

Responding to changes in Java elements

If your plug-in needs to know about changes to Java elements after the fact, you can register a Java IElementChangedListener with JavaCore.

   JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());

You can be more specific and specify the type of events you're interested in using addElementChangedListener(IElementChangedListener, int).

For example, if you're only interested in listening for events before the builders run:

   JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.PRE_AUTO_BUILD);

There are three kinds of events that are supported by JavaCore:

Java element change listeners are similar conceptually to resource change listeners (described in tracking resource changes). The following snippet implements a Java element change reporter that prints the element deltas to the system console.

   public class MyJavaElementChangeReporter implements IElementChangedListener {
      public void elementChanged(ElementChangedEvent event) {
         IJavaElementDelta delta= event.getDelta();
         if (delta != null) {
            System.out.println("delta received: ");
            System.out.print(delta);
         }
      }
   }

The IJavaElementDelta includes the element that was changed and flags describing the kind of change that occurred. Most of the time the delta tree is rooted at the Java Model level. Clients must then navigate this delta using getAffectedChildren to find out what projects have changed.

The following example method traverses a delta and prints the elements that have been added, removed and changed:

    void traverseAndPrint(IJavaElementDelta delta) {
        switch (delta.getKind()) {
            case IJavaElementDelta.ADDED:
                System.out.println(delta.getElement() + " was added");
                break;
            case IJavaElementDelta.REMOVED:
                System.out.println(delta.getElement() + " was removed");
                break;
            case IJavaElementDelta.CHANGED:
                System.out.println(delta.getElement() + " was changed");
                if ((delta.getFlags() & IJavaElementDelta.F_CHILDREN) != 0) {
                    System.out.println("The change was in its children");
                }
                if ((delta.getFlags() & IJavaElementDelta.F_CONTENT) != 0) {
                    System.out.println("The change was in its content");
                }
                /* Others flags can also be checked */
                break;
        }
        IJavaElementDelta[] children = delta.getAffectedChildren();
        for (int i = 0; i < children.length; i++) {
            traverseAndPrint(children[i]);
        }
    }

Several kinds of operations can trigger a Java element change notification. Here are some examples:

As for IResourceDelta the Java element deltas can be batched using an IWorkspaceRunnable. The deltas resulting from several Java Model operations that are run inside a IWorkspaceRunnable are merged and reported at once.

For example the following will trigger 2 Java element change events:

    // Get package
    IPackageFragment pkg = ...;
    
    // Create 2 compilation units
    ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null);
    ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);

Whereas the following will trigger 1 Java element change event:

    // Get package
    IPackageFragment pkg = ...;
    
    // Create 2 compilation units
    IWorkspace workspace = ResourcesPlugin.getWorkspace();
    workspace.run(
        new IWorkspaceRunnable() {
 	        public void run(IProgressMonitor monitor) throws CoreException {
 	            ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null);
 	            ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
 	        }
        },
        null);

 Copyright IBM Corporation and others 2000, 2002. All Rights Reserved.