上篇介绍的形形色色的语法元素大概让人眼花缭乱了,而且每种元素都对应一个Java类。知道是一回事,使用就是另一回事了,这么多个类,要给每个类写对应的处理代码,不胜其烦。ASTVisitor虽然能自动遍历语法树,但是并不能帮你处理每一种结点。
好在JDT提供了更加抽象的属性描述符(property descriptor),寥寥几个类就能掌控所有Java语法。用术语来说,上篇的那些类属于异构AST,本篇讲的是同构AST。
对任何AST结点都可调用方法structuralPropertiesForType()
,你会得到List<StructuralPropertyDescriptor>
,其中每一项都代表这个结点所属的类的一个结构性字段(就是跟AST有关的字段)。
StructuralPropertyDescriptor 是一个抽象类,有三个子类:SimplePropertyDescriptor, ChildPropertyDescriptor, ChildListPropertyDescriptor。这些东西是元数据,用来描述各种语法元素的固有结构,使用它们有种在用Java反射的感觉。
SimplePropertyDescriptor 表示这个字段存放的不是AST结点,而是个值,可能是int, String,Operator之类的,SimplePropertyDescriptor.valueType 能告诉我们这个值是什么类型。ChildPropertyDescriptor 表示这个字段存放的是一个AST结点,比如我们解析了一个class,得到typeDeclaration结点,然后调用typeDeclaration.structuralPropertiesForType(),得到的list中有一项就是typeName的描述符,嗯,就是AbstractTypeDeclaration类的typeName字段,字段类型为SimpleName。
ChildListPropertyDescriptor 表示这个字段存放的是一组AST结点! 比如AbstractTypeDeclaration拥有一组bodyDeclarations,而CompilationUnit则拥有一组imports。bodyDeclarations和imports都是List!
有了描述符能做什么呢? 可以自由访问一棵语法树了。
我们来想象一个流程:你有一个java文件,你把它交给JDT的parser,解析出一个CompilationUnit cu,也就是一棵语法树的根结点。调用cu.structuralPropertiesForType(),得到描述符的list,循环遍历list,对每个描述符prop,用instanceof判断具体类型(总共就3个类型),分别做"不同处理"。
不同处理:instanceof操作发现某个描述符是ChildListPropertyDescriptor, 于是你把描述符强转(cast)成该类型,调用prop.getId()
得到"imports",哦,是imports字段啊,调用prop.getElementType()
得到ImportDeclaration.class,确认了这一发现。然后你调用cu.getStructuralProperty(prop)得到一个object,你知道它实际是List<ImportDeclaration>
,因此你将它强转为这个List类型,遍历它,对每个ImportDeclaration,调用getName().getFullyQualifiedName()
,就得到了每个import的名称。(当然,对ImportDeclaration也可以假装不知道其类型,也用元数据来操控之)
由此你就完成了一个分析流程。因为不用关心具体的结点类型,所以你可以方便地进行一些宏观、抽象的分析。
最后提供一段我用Scala写的代码供参考(50行就能把任意Java代码结构转换成JSON输出, 使用了lift json库):
https://github.com/sorra/Lank...
利用强大的属性描述符,写出通用的JSON转换代码,避免了给每个结点类写对应的JSON转换代码(几十种结点类,要死啊)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。