怎样分析 Java 代码以进行修改?
JDT 提供了几个工具来帮助您分析代码。本文有意选择了最简单的 IScanner 接口进行演示,它的作用域也最有限。这个接口属于 JDT 工具箱,可以通过 JDT 的 ToolFactory 类访问它。其 createScanner 方法返回一个扫描程序,该扫描程序会简化对一串
Java 代码作标记的工作。它不处理任何特别困难的操作,只是对所返回的标记进行简单的解析和分类。例如,它指出下一个标记是 public 关键字,其后的标记是一个标识符,再后面的标记是左圆括号,等等。随后,只有当您希望分析一小段代码(您明确理解想要在这段代码中得到什么)时,这个扫描 程序才是合适的。您决不会使用扫描程序分析整个
Java 源代码;因为您会转而使用一些对编译器迷而言十分熟悉的工具:JDT 的抽象语法树(Abstract Syntax Tree,AST)框架。
与简单的扫描程序不同,AST 理解语言元素(它们不再只是“标记”)之间的关系。它可以识别象局部变量、实例变量、表达式以及 if 语句等六十多种不同的语言元素。它将帮助您进行涉及范围广泛的重构,或难以满足对标记进行一对一分类的模糊程度特别高的重构。要更清晰地了解何时使用扫描 程序与何时使用 AST 之间的差别,请考虑清单 1 中的代码。
清单 1. 模糊的变量引用
public
class
Foo {
int
foo
=
1
;
public
int
foo(
int
foo) {
return
foo
+
this
.foo;
}
public
int
getFoo() {
return
foo;
}
}
如果作为重构的一部分,您希望查找对实例变量 foo 的引用,那么就会明白一个单纯的解析会使区分本地引用和实例变量引用成为一个难题。AST 创建了完整的分析树,其中表示了
Java 源代码的每个元素并对这些元素进行了区分。在这个特例中,不同的类会考虑“foo”引用的上下文,将“foo”引用表示成 AST 的节点(如 FieldDeclaration、SimpleName 和 ThisExpression),因此您会很轻松地识别它们。
正如前面提到的,本文将只讨论我们所选择的简单例子。
创建 IScanner
IType objectClass
=
....;
ICompilationUnit cu
=
objectClass.getCompilationUnit();
IBuffer buffer
=
cu.getBuffer();
IScanner scanner
=
ToolFactory.createScanner(
false
,
false
,
false
,
false
);
scanner.setSource(
this
.buffer.getCharacters());
//
不扫描 package 和 import 部分
ISourceRange sr
=
objectClass.getSourceRange();
scanner.resetTo(sr.getOffset(), sr.getOffset()
+
sr.getLength()
-
1
);
基本方法:
IScanner.getNextToken()
IScanner.getCurrentTokenSource()
IScanner.getCurrentTokenStartPosition()
IScanner.getCurrentTokenEndPosition()
IScanner.resetTo()
将扫描结果配合 IBuffer 就可以修改源代码了
// Replace Code
buffer.replace(
scanner.getCurrentTokenStartPosition(),
scanner.getCurrentTokenEndPosition() - scanner.getCurrentTokenStartPosition() + 1,
text
);
// Insert Code
buffer.replace(
scanner.getCurrentTokenStartPosition(),
0,
text
);
// Delete Code
buffer.replace(
scanner.getCurrentTokenStartPosition(),
scanner.getCurrentTokenEndPosition() - scanner.getCurrentTokenStartPosition() + 1,
""
);