使用JavaSymbolSolver解决Java代码中的方法调用

本文深入探讨JavaSymbolSolver的创建背景及使用方法,演示了如何利用它解析Java代码中的方法调用,提供了一个Kotlin和Gradle示例项目,并讨论了类型求解器的概念及其在JavaParser项目中的应用。

为什么创建java-symbol-solver?

几年前,我开始使用JavaParser ,然后开始做出贡献。 过了一会儿,我意识到我们想对Java代码进行的许多操作不能仅通过使用解析器生成的抽象语法树来完成,我们还需要解析类型,符号和方法调用。 因此,我创建了JavaSymbolSolver 。 现在, Coati已将其用于生产静态分析工具。

缺少的一件事是文档:人们在JavaParser上打开问题,询问如何回答某个问题,而答案通常是“为此,您需要使用JavaSymbolSolver”。 从这些问题开始,我将展示一些示例。

受此问题的启发,我将展示如何生成对特定方法的所有调用的列表。

我们如何使用java-symbol-solver解决Java中的方法调用?

可以分两步完成:

  1. 您在源代码上使用JavaParser来构建AST
  2. 您在表示方法调用的AST的节点上调用JavaSymbolSolver并获得答案

我们将写一个简短的例子。 最后,我们将得到一个应用程序,给定源文件将产生以下结果:

* L55 setId(id)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L59 setId(new VariableDeclaratorId(variableName))   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L71 setId(id)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L72 setInit(init)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression)
 * L76 setId(new VariableDeclaratorId(variableName))   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L77 setInit(init)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression)
 * L82 setId(id)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L83 setInit(init)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression)
 * L88 v.visit(this, arg)   -&qt; com.github.javaparser.ast.visitor.GenericVisitor.visit(com.github.javaparser.ast.body.VariableDeclarator, A)
 * L93 v.visit(this, arg)   -&qt; com.github.javaparser.ast.visitor.VoidVisitor.visit(com.github.javaparser.ast.body.VariableDeclarator, A)
 * L106 setAsParentNodeOf(this.id)   -&qt; com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node)
 * L112 setAsParentNodeOf(this.init)   -&qt; com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node)
 * L121 setAsParentNodeOf(this.init)   -&qt; com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node)
 * L128 getParentNodeOfType(NodeWithElementType.class)   -&qt; com.github.javaparser.ast.Node.getParentNodeOfType(java.lang.Class<T&qt;)
 * L130 wrapInArrayTypes(elementType.getElementType(), elementType.getArrayBracketPairsAfterElementType(), getId().getArrayBracketPairsAfterId())   -&qt; com.github.javaparser.ast.type.ArrayType.wrapInArrayTypes(com.github.javaparser.ast.type.Type, java.util.List<com.github.javaparser.ast.ArrayBracketPair&qt;...)
 * L130 elementType.getElementType()   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.getElementType()
 * L131 elementType.getArrayBracketPairsAfterElementType()   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.getArrayBracketPairsAfterElementType()
 * L132 getId().getArrayBracketPairsAfterId()   -&qt; com.github.javaparser.ast.body.VariableDeclaratorId.getArrayBracketPairsAfterId()
 * L132 getId()   -&qt; com.github.javaparser.ast.body.VariableDeclarator.getId()
 * L137 ArrayType.unwrapArrayTypes(type)   -&qt; com.github.javaparser.ast.type.ArrayType.unwrapArrayTypes(com.github.javaparser.ast.type.Type)
 * L138 getParentNodeOfType(NodeWithElementType.class)   -&qt; com.github.javaparser.ast.Node.getParentNodeOfType(java.lang.Class<T&qt;)
 * L142 nodeWithElementType.setElementType(unwrapped.a)   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.setElementType(com.github.javaparser.ast.type.Type<?&qt;)
 * L143 nodeWithElementType.setArrayBracketPairsAfterElementType(null)   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.setArrayBracketPairsAfterElementType(java.util.List<com.github.javaparser.ast.ArrayBracketPair&qt;)
 * L144 getId().setArrayBracketPairsAfterId(unwrapped.b)   -&qt; com.github.javaparser.ast.body.VariableDeclaratorId.setArrayBracketPairsAfterId(java.util.List<com.github.javaparser.ast.ArrayBracketPair&qt;)
 * L144 getId()   -&qt; 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&qt; {
 
    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&qt;=</code&qt; 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&qt; R accept(GenericVisitor<R, A&qt; v, A arg) {
        return v.visit(this, arg);
    }
 
    @Override
    public <A&qt; void accept(VoidVisitor<A&qt; 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<?&qt; elementType = getParentNodeOfType(NodeWithElementType.class);
 
        return wrapInArrayTypes(elementType.getElementType(),
                elementType.getArrayBracketPairsAfterElementType(),
                getId().getArrayBracketPairsAfterId());
    }
 
    @Override
    public VariableDeclarator setType(Type type) {
        Pair<Type, List<ArrayBracketPair&qt;&qt; unwrapped = ArrayType.unwrapArrayTypes(type);
        NodeWithElementType<?&qt; 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')
    }
}
 * L55 setId(id)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L59 setId(new VariableDeclaratorId(variableName))   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L71 setId(id)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L72 setInit(init)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression)
 * L76 setId(new VariableDeclaratorId(variableName))   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L77 setInit(init)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression)
 * L82 setId(id)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setId(com.github.javaparser.ast.body.VariableDeclaratorId)
 * L83 setInit(init)   -&qt; com.github.javaparser.ast.body.VariableDeclarator.setInit(com.github.javaparser.ast.expr.Expression)
 * L88 v.visit(this, arg)   -&qt; com.github.javaparser.ast.visitor.GenericVisitor.visit(com.github.javaparser.ast.body.VariableDeclarator, A)
 * L93 v.visit(this, arg)   -&qt; com.github.javaparser.ast.visitor.VoidVisitor.visit(com.github.javaparser.ast.body.VariableDeclarator, A)
 * L106 setAsParentNodeOf(this.id)   -&qt; com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node)
 * L112 setAsParentNodeOf(this.init)   -&qt; com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node)
 * L121 setAsParentNodeOf(this.init)   -&qt; com.github.javaparser.ast.Node.setAsParentNodeOf(com.github.javaparser.ast.Node)
 * L128 getParentNodeOfType(NodeWithElementType.class)   -&qt; com.github.javaparser.ast.Node.getParentNodeOfType(java.lang.Class<T&qt;)
 * L130 wrapInArrayTypes(elementType.getElementType(), elementType.getArrayBracketPairsAfterElementType(), getId().getArrayBracketPairsAfterId())   -&qt; com.github.javaparser.ast.type.ArrayType.wrapInArrayTypes(com.github.javaparser.ast.type.Type, java.util.List<com.github.javaparser.ast.ArrayBracketPair&qt;...)
 * L130 elementType.getElementType()   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.getElementType()
 * L131 elementType.getArrayBracketPairsAfterElementType()   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.getArrayBracketPairsAfterElementType()
 * L132 getId().getArrayBracketPairsAfterId()   -&qt; com.github.javaparser.ast.body.VariableDeclaratorId.getArrayBracketPairsAfterId()
 * L132 getId()   -&qt; com.github.javaparser.ast.body.VariableDeclarator.getId()
 * L137 ArrayType.unwrapArrayTypes(type)   -&qt; com.github.javaparser.ast.type.ArrayType.unwrapArrayTypes(com.github.javaparser.ast.type.Type)
 * L138 getParentNodeOfType(NodeWithElementType.class)   -&qt; com.github.javaparser.ast.Node.getParentNodeOfType(java.lang.Class<T&qt;)
 * L142 nodeWithElementType.setElementType(unwrapped.a)   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.setElementType(com.github.javaparser.ast.type.Type<?&qt;)
 * L143 nodeWithElementType.setArrayBracketPairsAfterElementType(null)   -&qt; com.github.javaparser.ast.nodeTypes.NodeWithElementType.setArrayBracketPairsAfterElementType(java.util.List<com.github.javaparser.ast.ArrayBracketPair&qt;)
 * L144 getId().setArrayBracketPairsAfterId(unwrapped.b)   -&qt; com.github.javaparser.ast.body.VariableDeclaratorId.setArrayBracketPairsAfterId(java.util.List<com.github.javaparser.ast.ArrayBracketPair&qt;)
 * L144 getId()   -&qt; com.github.javaparser.ast.body.VariableDeclarator.getId()

建立AST非常容易,您只需调用此方法:

JavaParser.parse(file)

我还使用了几种方法来导航AST并获取特定的节点。 特别是,我将使用它仅接受方法调用。 如果您有兴趣,他们看起来像这样:

class SpecificNodeIterator<T&qt;(private val type: Class<T&qt;, private val nodeHandler: SpecificNodeIterator.NodeHandler<T&qt;) {
    interface NodeHandler<T&qt; {
        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&qt; Node.descendantsOfType(type: Class<T&qt;) : List<T&qt; {
    val descendants = LinkedList<T&qt;()
    SpecificNodeIterator(type, object : SpecificNodeIterator.NodeHandler<T&qt; {
        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的地方,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类上,它将为您提供可能需要的所有信息:它将找到相应的类型,字段,方法等。

问题是……我们需要更多文档和用户反馈。 我希望你们中的一些人将开始使用JavaSymbolSolver并告诉我们如何改进它。

同样,上周JavaSymbolSolver被移至JavaParser组织之下。 这意味着将来我们将与JavaParser项目更加紧密地合作。

该代码可在GitHub上找到: java-symbol-solver-examples

翻译自: https://www.javacodegeeks.com/2016/11/resolve-method-calls-java-code-using-javasymbolsolver.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值