插件可以使用 JDT API 来创建类或接口、将方法添加到现有类型中或者改变方法的类型。
改变 Java 对象最简单的方法就是使用 Java 元素 API。可以使用更常见的技术来处理 Java 元素的初始源代码。
通过程序来生成编译单元最简单的方法是使用 IPackageFragment.createCompilationUnit。指定编译单元的名称和内容。于是在包中创建了编译单元,并返回新的 ICompilationUnit。
通常,可以通过在对应包目录的相应文件夹中创建扩展名为“.java”的文件资源来按类别创建编译单元。使用类属资源 API 对于 Java 工具是一种旁门左道,因此,在通知类属资源更改侦听器以及 JDT 侦听器将 Java 模型更新为新编译单元之前,Java 模型不会更新。
大部分对 Java 源代码的简单修改可以使用 Java 元素 API 来完成。
例如,可以通过编译单元查询类型。一旦您具有 IType,就可以使用诸如 createField、createInitializer、createMethod 或 createType 的协议来将源代码成员添加至类型。在这些方法中提供了源代码以及关于成员的位置的信息。
ISourceManipulation 接口定义 Java 元素的常见源代码处理。这包括用于重命名、移动、复制或删除类型的成员的方法。
可以通过处理编译单元来修改代码(于是修改了底层 IFile)或人们可以修改编译单元的内存副本(称为工作副本)。
工作副本是使用 getWorkingCopy 方法从编译单元中获取的。(注意,要创建工作副本,编译单元不需要存在于 Java 模型中。)当不再需要这种工作副本时,创建这种工作副本的人员应负责使用 discardWorkingCopy 方法来废弃它。
工作副本修改内存缓冲区。getWorkingCopy() 方法创建缺省缓冲区,但是客户机可以使用 getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor) 方法提供它们自己的缓冲区实现。客户机可以直接处理此缓冲区的文本。如果客户机直接处理文本,则它们必须经常使用 reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor) 方法来使工作副本与缓冲区同步。
最后,可以使用 commitWorkingCopy 方法将工作副本保存至磁盘(用来替换原始的编译单元)。
例如,下列代码段使用定制工作副本拥有者在编译单元上创建工作副本。该代码段将修改缓冲区、协调更改、将更改落实到磁盘,最后废弃工作副本。
// Get original compilation unit ICompilationUnit originalUnit = ...; // Get working copy owner WorkingCopyOwner owner = ...; // Create working copy ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Modify buffer and reconcile IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Commit changes workingCopy.commitWorkingCopy(false, null); // Destroy working copy workingCopy.discardWorkingCopy();
工作副本还可以供若干使用工作副本拥有者的客户机共享。稍后可以使用 findWorkingCopy 方法来检索工作副本。因此,对于原始编译单元和工作副本拥有者,共享工作副本很重要。
下列内容说明了客户机 1 如何创建共享的工作副本、客户机 2 如何检索此工作副本、客户机 1 如何废弃此工作副本以及尝试检索共享的工作副本的客户机 2 如何注意到该工作副本不再存在:
// Client 1 & 2: Get original compilation unit ICompilationUnit originalUnit = ...; // Client 1 & 2: Get working copy owner WorkingCopyOwner owner = ...; // Client 1: Create shared working copy ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2: Retrieve shared working copy ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // This is the same working copy assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Discard shared working copy workingCopyForClient1.discardWorkingCopy(); // Client 2: Attempt to retrieve shared working copy and find out it's null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
可以通过对 AST 使用工厂方法来从头创建 CompilationUnit。这些方法名以 new... 开头。以下是创建 HelloWorld 类的一个示例。
第一个代码段是生成的输出:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
下列代码段是生成输出的相应代码。
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);
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; }IScanner 用来将输入源代码分成多个标记。每个标记都具有特定值,这些特定值是在 ITerminalSymbols 接口中定义的。迭代和检索正确的标记是非常简单的。如果想要在 SuperMethodInvocation 中查找 super 关键字的位置,则还建议您使用扫描程序。
某些源代码修改不是通过 Java 元素 API 来提供的。编辑源代码(例如,更改现有元素的源代码)的较常见方法是使用编译单元的初始源代码和 DOM/AST 的重写 API 来完成的。
要执行 DOM/AST 重写,存在两组 API:描述性重写和修改重写。
描述性 API 不修改 AST,而是使用
ASTRewrite API
来生成修改描述。AST 重写程序收集对节点修改的描述并将这些描述转换为可以适用于初始源代码的文本编辑。
// creation of a Document
ICompilationUnit cu = ... ; // content is "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// creation of DOM/AST from a ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// creation of ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// description of the change
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// computation of the text edits
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// computation of the new source code
edits.apply(document);
String newSource = document.get();
// update of the compilation unit
cu.getBuffer().setContents(newSource);
修改 API 允许直接修改 AST:
// creation of a Document ICompilationUnit cu = ... ; // content is "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // creation of DOM/AST from a ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // start record of the modifications astRoot.recordModifications(); // modify the AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // computation of the text edits TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // computation of the new source code edits.apply(document); String newSource = document.get(); // update of the compilation unit cu.getBuffer().setContents(newSource);
如果事后插件需要知道对 Java 元素所作的更改,则可以向 JavaCore 注册 Java IElementChangedListener。
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
还可以更具体,并使用 addElementChangedListener(IElementChangedListener, int) 来指定您感兴趣的事件的类型。
例如,如果您只想在协调操作期间侦听事件:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore 支持二种类型的事件:
Java 元素更改侦听器在概念上类似于资源更改侦听器(在跟踪资源更改中进行了描述)。下列代码段实现 Java 元素更改指示器,它将元素变化打印到系统控制台中。
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); } } }
IJavaElementDelta 包括已更改的元素和描述所发生的更改类型的标志。在大多数时候,delta 树的根位于“Java 模型”级别。然后,客户机必须使用 getAffectedChildren 来浏览此 delta 以了解已经更改了哪些项目。
以下示例方法遍历 delta,并打印已添加、已除去和已更改的元素:
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]); } }
有多种操作可以触发 Java 元素更改通知。以下是一些示例:
与 IResourceDelta 相似,可以使用 IWorkspaceRunnable 来对 Java 元素 delta 进行批处理。立即就会合并和报告在 IWorkspaceRunnable 中运行的若干“Java 模型”操作产生的 delta。
JavaCore 提供了 run 方法来对 Java 元素更改进行批处理。
例如,以下代码段将触发两个 Java 元素更改事件:
// 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);
而以下代码段将触发 1 个 Java 元素更改事件:
// Get package IPackageFragment pkg = ...; // Create 2 compilation units JavaCore.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);