怎样分析 Java 代码以进行修改?
JDT 提供了几个工具来帮助您分析代码本文有意选择了最简单的 IScanner 接口进行演示它的作用域也最有限这个接口属于 JDT 工具箱可以通过 JDT 的 ToolFactory 类访问它其 createScanner 方法返回一个扫描程序该扫描程序会简化对一串 Java 代码作标记的工作它不处理任何特别困难的操作只是对所返回的标记进行简单的解析和分类例如它指出下一个标记是 public 关键字其后的标记是一个标识符再后面的标记是左圆括号等等随后只有当您希望分析一小段代码(您明确理解想要在这段代码中得到什么)时这个扫描程序才是合适的您决不会使用扫描程序分析整个 Java 源代码因为您会转而使用一些对编译器迷而言十分熟悉的工具JDT 的抽象语法树(Abstract Syntax TreeAST)框架
与简单的扫描程序不同AST 理解语言元素(它们不再只是标记)之间的关系它可以识别象局部变量实例变量表达式以及 if 语句等六十多种不同的语言元素它将帮助您进行涉及范围广泛的重构或难以满足对标记进行一对一分类的模糊程度特别高的重构要更清晰地了解何时使用扫描程序与何时使用 AST 之间的差别请考虑清单 中的代码
清单 模糊的变量引用
public class Foo {
int foo = ;
public int foo(int foo) {
return foo + thisfoo;
}
public int getFoo() {
return foo;
}
}
如果作为重构的一部分您希望查找对实例变量 foo 的引用那么就会明白一个单纯的解析会使区分本地引用和实例变量引用成为一个难题AST 创建了完整的分析树其中表示了 Java 源代码的每个元素并对这些元素进行了区分在这个特例中不同的类会考虑foo引用的上下文将foo引用表示成 AST 的节点(如 FieldDeclarationSimpleName 和 ThisExpression)因此您会很轻松地识别它们
正如前面提到的本文将只讨论我们所选择的简单例子对于比较复杂的修改和分析示例请参阅参考资料一节现在让我们回到我们前面跳过的用省略号表示的代码这个代码将使用 IScanner 的实例以确定并替换源代码中确定成员可视性的关键字我们将处理的可视性修饰符是 publicprivateprotected 和 final通过采用蛮力方法我们可以简化这个解决方案即采用两个步骤就可以完成首先删除方法特征符中所有的可视性修饰符(或至少扫描查找它们如果找到就删除)然后插入所希望的修饰符特别地
如果在方法特征符中找到 publicprivate 或 protected就删除它们
插入所请求的可视性修饰符(对于包可视性的情况不作任何操作因为这是缺省操作即没有任何修饰符)
final 修饰符很简单因为所希望的行为就是插入和除去这个修饰符所以如果它存在我们除去它否则就插入它清单 中的代码只显示了一个例子它无条件地将成员的可视性从 pubilc 改成 private在与本文相关的解决方案中您将看到每个操作的公共代码都被移到了抽象超类中它基本上与下面的代码相同只不过稍作了整理以避免冗余
清单 扫描是否有 pubilc 关键字
ICompilationUnit cu = membergetCompilationUnit();
if (cuisWorkingCopy()) {
IBuffer buffer = cugetBuffer();
IScanner scanner =
ToolFactorycreateScanner(false false false false);
scannersetSource(buffergetCharacters());
ISourceRange sr = membergetSourceRange();
scannerresetTo(
srgetOffset()
srgetOffset() + srgetLength() );
int token = scannergetNextToken();
while (token != ITerminalSymbolsTokenNameEOF
&& token != ITerminalSymbolsTokenNameLPAREN)
token = scannergetNextToken();
if (token == ITerminalSymbolsTokenNamePUBLIC) {
bufferreplace(
scannergetCurrentTokenStartPosition()
scannergetCurrentTokenEndPosition()
scannergetCurrentTokenStartPosition() +
private);
break;
}
}
cureconcile();
}
注ITerminalSymbols 定义了扫描程序可以返回的标记名称它们对应于 Java 语法的标准标记您可以进一步查询扫描程序以询问当前标记在缓沖区中开始和结束的具体位置它出现在哪一行上当然还有标记本身(特别是象 ITerminalSymbolsTokenNameStringLiteral 和 ITerminalSymbolsTokenNameIdentifier 这样的例子它们不是保留的关键字)
上述代码片段中向 scannersetSource 方法提供了编译单元的完整源代码即 Java 源文件中的所有内容正如前面提到的扫描程序并不非常适合于大型分析所以我们必须将它限制用于只有以目标方法的第一个字符开始一直到调用 setSourceRange 方法作为结束的那部分源代码IMember 接口继承了 ISourceReferenceISourceReference 是一个允许您查询包含编译单元内的源代码字符串和源代码位置的接口这使我们不必确定目标方法在 Java 源代码内开始和结束的位置原本可以用 AST 实现这一点而 ISourceReference 接口使 AST 成了多余的工具由于 Java 方法特征符易于解析所以 IScanner 接口的解析能力和它很匹配我们必须做的就是查找 public 关键字它出现在方法声明的前一个字符之后参数声明的左圆括号之前用 private 关键字替换它当然在该解决方案中这个接口将处理所有的可能情况不管该方法最初是 publicprivateprotected 还是 package(缺省)
下一步是什么?
本文设定的目标是向您提供一个对 Eclipse 的 Java 开发环境颇具价值的扩展这样的扩展增强了这个开发环境的生产率坦率地说出于简洁性考虑我多次跳过了一些细节该解决方案本身就作了一些简化假设象只允许在编辑器中对已打开的 Java 源代码进行修改您可能希望在更完整的实现中取消这个限制
虽然如此但我还是希望您能感受到什么是可能的并确信这样做不是特别困难本文中我们讨论的是 The Java Developers Guide to Eclipse 一书某一高级章节的部分内容该书中有十一个比较浅显的章节讨论了插件开发的基础象本文一样大多数章节都包含了一个已文档化的工作解决方案它可以强化您所学到的知识大多数内容是以本文中您已看到的相同风格编写的(不过可能没有以这么快的节奏进行讨论!)
重要您可能需要向工作空间添加必要的插件这样解决方案才能编译和运行选择 Window > Preferences > Plugin Development > Target Platform然后选择 Not in Workspace这将确保解决方案所依赖的基础插件在导入和重新编译过程中可用
一旦导入完成您可能需要切换至 Plugin Development 透视图在 comibmlabsolnjdtexcerpt 项目中选择 pluginxml然后选择 Update Classpath这将修改由于 Eclipse 安装路径和解决方案的安装路径不同所引起的编译错误