欢迎大家关注我的公众号,会不定期更新一些开发与测试的一些技术文章。
本篇完。持续更新完善中......关注有惊喜,共同学习
获取Java源代码中的方法调用关系
可以分两步完成:
- 您在源代码上使用JavaParser来构建AST
- 您在表示方法调用的AST的节点上调用JavaSymbolSolver并获得答案
一个简短的例子。最后,我们将得到一个应用程序,给定源文件将产生以下结果:
L55 setId(id) -> com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId) * L59 setId(new VariableDeclaratorId(variableName)) -> com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId) * L71 setId(id) -> com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId) * L72 setInit(init) -> com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression) * L76 setId(new VariableDeclaratorId(variableName)) -> com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId) * L77 setInit(init) -> com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression) * L82 setId(id) -> com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId) * L83 setInit(init) -> com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression) * L88 v.visit(this, arg) -> com.github.javaparser.ast.visitor.GenericVisitor.visit(com.github.javaparser.ast.body.VariableDeclarator, A) * L93 v.visit(this, arg) -> com.github.javaparser.ast.visitor.VoidVisitor.visit(com.github.javaparser.ast.body.VariableDeclarator, A) * L106 setAsParentNodeOf(this.id) -> com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node) * L112 setAsParentNodeOf(this.init) -> com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node) * L121 setAsParentNodeOf(this.init) -> com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node) * L128 getParentNodeOfType(NodeWithElementType.class) -> com.github.javaparser.ast.Node.getParentNodeOfType(java.lang.Class<T>) * L130 wrapInArrayTypes(elementType.getElementType(), elementType.getArrayBracketPairsAfterElementType(), getId().getArrayBracketPairsAfterId()) -> com.github.javaparser.ast.type.ArrayType.wrapInArrayTypes(com.github.javaparser.ast.type.Type, java.util.List<com.github.javaparser.ast.ArrayBracketPair>...) * L130 elementType.getElementType() -> com.github.javaparser.ast.nodeTypes.NodeWithElementType.getElementType() * L131 elementType.getArrayBracketPairsAfterElementType() -> com.github.javaparser.ast.nodeTypes.NodeWithElementType.getArrayBracketPairsAfterElementType() * L132 getId().getArrayBracketPairsAfterId() -> com.github.javaparser.ast.body.VariableDeclaratorId.getArrayBracketPairsAfterId() * L132 getId() -> com.github.javaparser.ast.body.VariableDeclarator.getId() * L137 ArrayType.unwrapArrayTypes(type) -> com.github.javaparser.ast.type.ArrayType.unwrapArrayTypes(com.github.javaparser.ast.type.Type) * L138 getParentNodeOfType(NodeWithElementType.class) -> com.github.javaparser.ast.Node.getParentNodeOfType(java.lang.Class<T>) * L142 nodeWithElementType.setElementType(unwrapped.a) -> com.github.javaparser.ast.nodeTypes.NodeWithElementType.setElementType(com.github.javaparser.ast.type.Type<?>) * L143 nodeWithElementType.setArrayBracketPairsAfterElementType(null) -> com.github.javaparser.ast.nodeTypes.NodeWithElementType.setArrayBracketPairsAfterElementType(java.util.List<com.github.javaparser.ast.ArrayBracketPair>) * L144 getId().setArrayBracketPairsAfterId(unwrapped.b) -> com.github.javaparser.ast.body.VariableDeclaratorId.setArrayBracketPairsAfterId(java.util.List<com.github.javaparser.ast.ArrayBracketPair>) * L144 getId() -> com.github.javaparser.ast.body.VariableDeclarator.getId()
在此源文件上执行时:
/* * Copyright (C) 2007-2010 Júlio Vilmar Gesser. * Copyright (C) 2011, 2013-2016 The JavaParser Team. * * This file is part of JavaParser. * * JavaParser can be used either under the terms of * a) the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * b) the terms of the Apache License * * You should have received a copy of both licenses in LICENCE.LGPL and * LICENCE.APACHE. Please refer to those files for details. * * JavaParser is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ package com.github.javaparser.ast.body; import com.github.javaparser.Range; import com.github.javaparser.ast.ArrayBracketPair; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.nodeTypes.NodeWithElementType; import com.github.javaparser.ast.nodeTypes.NodeWithType; import com.github.javaparser.ast.type.ArrayType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.GenericVisitor; import com.github.javaparser.ast.visitor.VoidVisitor; import com.github.javaparser.utils.Pair; import java.util.List; import static com.github.javaparser.ast.type.ArrayType.wrapInArrayTypes; /** * @author Julio Vilmar Gesser */ public final class VariableDeclarator extends Node implements NodeWithType<VariableDeclarator> { private VariableDeclaratorId id; private Expression init; public VariableDeclarator() { } public VariableDeclarator(VariableDeclaratorId id) { setId(id); } public VariableDeclarator(String variableName) { setId(new VariableDeclaratorId(variableName)); } /** * Defines the declaration of a variable. * * @param id The identifier for this variable. IE. The variables name. * @param init What this variable should be initialized to. * An {@link com.github.javaparser.ast.expr.AssignExpr} is unnecessary as the <code>=</code> operator is * already added. */ public VariableDeclarator(VariableDeclaratorId id, Expression init) { setId(id); setInit(init); } public VariableDeclarator(String variableName, Expression init) { setId(new VariableDeclaratorId(variableName)); setInit(init); } public VariableDeclarator(Range range, VariableDeclaratorId id, Expression init) { super(range); setId(id); setInit(init); } @Override public <R, A> R accept(GenericVisitor<R, A> v, A arg) { return v.visit(this, arg); } @Override public <A> void accept(VoidVisitor<A> v, A arg) { v.visit(this, arg); } public VariableDeclaratorId getId() { return id; } public Expression getInit() { return init; } public VariableDeclarator setId(VariableDeclaratorId id) { this.id = id; setAsParentNodeOf(this.id); return this; } public VariableDeclarator setInit(Expression init) { this.init = init; setAsParentNodeOf(this.init); return this; } /** * Will create a {@link NameExpr} with the init param */ public VariableDeclarator setInit(String init) { this.init = new NameExpr(init); setAsParentNodeOf(this.init); return this; } @Override public Type getType() { NodeWithElementType<?> elementType = getParentNodeOfType(NodeWithElementType.class); return wrapInArrayTypes(elementType.getElementType(), elementType.getArrayBracketPairsAfterElementType(), getId().getArrayBracketPairsAfterId()); } @Override public VariableDeclarator setType(Type type) { Pair<Type, List<ArrayBracketPair>> unwrapped = ArrayType.unwrapArrayTypes(type); NodeWithElementType<?> nodeWithElementType = getParentNodeOfType(NodeWithElementType.class); if (nodeWithElementType == null) { throw new IllegalStateException("Cannot set type without a parent"); } nodeWithElementType.setElementType(unwrapped.a); nodeWithElementType.setArrayBracketPairsAfterElementType(null); getId().setArrayBracketPairsAfterId(unwrapped.b); return this; } }
设置项目
我们将使用Kotlin和Gradle。我们的构建文件如下所示:
buildscript { ext.kotlin_version = '1.0.4' repositories { mavenCentral() maven { name 'JFrog OSS snapshot repo' url 'https://oss.jfrog.org/oss-snapshot-local/' } jcenter() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin: 'kotlin' apply plugin: 'application' apply plugin: 'java' apply plugin: 'idea' apply plugin: 'antlr' repositories { mavenLocal() mavenCentral() jcenter() } dependencies { compile "me.tomassetti:java-symbol-solver-core:0.3.1" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testCompile "junit:junit:latest.release" } idea { module { excludeDirs += file('src/main/resources') } }
建立AST
建立AST非常容易,您只需调用此方法:
JavaParser.parse(file)
我还使用了几种方法来导航AST并获取特定的节点。特别是,我将使用它仅接受方法调用。如果您有兴趣,他们看起来像这样:
class SpecificNodeIterator<T>(private val type: Class<T>, private val nodeHandler: SpecificNodeIterator.NodeHandler<T>) { interface NodeHandler<T> { fun handle(node: T): Boolean } fun explore(node: Node) { if (type.isInstance(node)) { if (!nodeHandler.handle(type.cast(node))) { return } } for (child in node.childrenNodes) { explore(child) } } } // this is a method extension: we had this method to the existing class "Node" fun <T> Node.descendantsOfType(type: Class<T>) : List<T> { val descendants = LinkedList<T>() SpecificNodeIterator(type, object : SpecificNodeIterator.NodeHandler<T> { override fun handle(node: T): Boolean { descendants.add(node) return true } }).explore(this) return descendants }
指定类型求解器
什么是类型求解器?它是知道在哪里查找类的对象。在处理源代码时,通常会引用尚未编译的代码,但这些代码仅存在于其他源文件中。您也可以使用JAR中包含的类或Java标准库中的类。您只需要告诉TypeSolver在哪里寻找类,它就会弄清楚。
在我们的示例中,我们将解析JavaParser项目的源代码(如何转换成meta ?!)。该项目的源代码位于两个不同的目录中,以获取正确的源代码和JavaCC生成的代码(您可以忽略JavaCC是什么,它与您无关)。我们当然也使用来自Java标准库的类。这是我们的TypeSolver的样子:
fun typeSolver() : TypeSolver { val combinedTypeSolver = CombinedTypeSolver() combinedTypeSolver.add(JreTypeSolver()) combinedTypeSolver.add(JavaParserTypeSolver(File("src/main/resources/javaparser-core"))) combinedTypeSolver.add(JavaParserTypeSolver(File("src/main/resources/javaparser-generated-sources"))) return combinedTypeSolver }
我们的应用
在这里我们调用JavaParserFacade,它是JavaSymbolSolver提供的类之一。当时我们只接受一个方法调用,然后将其传递给JavaParserFacade的方法求解。我们得到一个MethodUsage(基本上是一个方法声明+该特定调用的参数类型的值)。从中获得MethodDeclaration,然后打印合格的签名,即类的合格名称,后跟方法的签名。这是我们获得最终输出的方式:
var solved = 0 var unsolved = 0 var errors = 0 fun processJavaFile(file: File, javaParserFacade: JavaParserFacade) { println(file) JavaParser.parse(file).descendantsOfType(MethodCallExpr::class.java).forEach { print(" * L${it.begin.line} $it ") try { val methodRef = javaParserFacade.solve(it) if (methodRef.isSolved) { solved++ val methodDecl = methodRef.correspondingDeclaration println(" -> ${methodDecl.qualifiedSignature}") } else { unsolved++ println(" ???") } } catch (e: Exception) { println(" ERR ${e.message}") errors++ } catch (t: Throwable) { t.printStackTrace() } } }
结论
有很多事情要做,但是基本上JavaSymbolSolver会在后台完成所有繁重的工作。一旦有了AST的节点,就可以将其扔到JavaParserFacade类上,它将为您提供可能需要的所有信息:它将找到相应的类型,字段,方法等。
该代码可在GitHub上找到: java-symbol-solver-examples
欢迎大家关注我的公众号,会不定期更新一些开发与测试的一些技术文章。
本篇完。持续更新完善中......关注有惊喜,共同学习
来源:Resolve method calls in Java code using the JavaSymbolSolver - Strumenta