Java >> Java Tutorial >  >> Java

Lösen Sie Methodenaufrufe in Java-Code mit dem JavaSymbolSolver auf

Warum ich den Java-Symbol-Solver erstellt habe?

Vor ein paar Jahren habe ich angefangen, JavaParser zu benutzen und dann habe ich angefangen, etwas beizutragen. Nach einer Weile wurde mir klar, dass viele Operationen, die wir mit Java-Code durchführen möchten, nicht einfach mit dem von einem Parser erzeugten abstrakten Syntaxbaum durchgeführt werden können. Wir müssen auch Typen, Symbole und Methodenaufrufe auflösen. Aus diesem Grund habe ich den JavaSymbolSolver erstellt. Es wird jetzt verwendet, um statische Analysewerkzeuge von Coati herzustellen.

Eine Sache, die fehlt, ist die Dokumentation:Leute öffnen Fragen zu JavaParser und fragen, wie man eine bestimmte Frage beantwortet, und die Antwort lautet oft „dafür müssen Sie JavaSymbolSolver verwenden“. Ausgehend von diesen Themen werde ich einige Beispiele zeigen.

Inspiriert von dieser Ausgabe werde ich zeigen, wie man eine Liste aller Aufrufe einer bestimmten Methode erstellt.

Wie können wir Methodenaufrufe in Java mit dem Java-Symbol-Solver auflösen?

Dies kann in zwei Schritten erfolgen:

  1. Sie verwenden JavaParser für den Quellcode, um Ihre ASTs zu erstellen
  2. Sie rufen JavaSymbolSolver auf den Knoten der ASTs auf, die Methodenaufrufe darstellen, und erhalten die Antwort

Wir schreiben ein kurzes Beispiel. Am Ende erhalten wir eine Anwendung, die bei gegebener Quelldatei Folgendes erzeugt:

 
 * 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()

bei Ausführung auf dieser Quelldatei:

/*
 * 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;
    }
}

Einrichten des Projekts

Wir werden Kotlin und Gradle verwenden. Unsere Build-Datei sieht so aus:

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()

Aufbau des AST

Das Erstellen eines AST ist ganz einfach, Sie rufen einfach diese Methode auf:

JavaParser.parse(file)

Ich habe auch einige Methoden verwendet, um im AST zu navigieren und bestimmte Knoten zu erhalten. Insbesondere werde ich dies verwenden, um nur die Methodenaufrufe entgegenzunehmen. Bei Interesse sehen sie so aus:

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
}

Geben Sie den Typlöser an

Was zum Teufel ist ein Type Solver? Es ist das Objekt, das weiß, wo es nach Klassen suchen muss. Bei der Verarbeitung von Quellcode haben Sie normalerweise Verweise auf Code, der noch nicht kompiliert ist, aber nur in anderen Quelldateien vorhanden ist. Sie können auch in JARs enthaltene Klassen oder Klassen aus den Java-Standardbibliotheken verwenden. Sie müssen Ihrem TypeSolver nur sagen, wo er nach Klassen suchen soll, und er wird es herausfinden.

In unserem Beispiel parsen wir den Quellcode aus dem JavaParser-Projekt (wie Meta?!). Dieses Projekt hat Quellcode in zwei verschiedenen Verzeichnissen, für den richtigen Quellcode und den von JavaCC generierten Code (Sie können ignorieren, was JavaCC ist, es ist für Sie nicht relevant). Natürlich verwenden wir auch Klassen aus den Java-Standardbibliotheken. So sieht unser TypeSolver aus:

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
}

Unsere Bewerbung

Hier rufen wir JavaParserFacade auf, eine der von JavaSymbolSolver bereitgestellten Klassen. Wir nehmen zur Zeit einfach einen Methodenaufruf und übergeben ihn an die Methode solve der JavaParserFacade. Wir erhalten eine MethodUsage (die im Grunde eine Methodendeklaration + der Wert der Parametertypen für diesen bestimmten Aufruf ist). Daraus erhalten wir die MethodDeclaration und drucken die qualifizierte Signatur, d. h. den qualifizierten Namen der Klasse, gefolgt von der Signatur der Methode. So erhalten wir die endgültige Ausgabe:

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()
        }
    }
}

Schlussfolgerungen

Es gibt so viel Klempnerarbeiten zu erledigen, aber im Grunde erledigt JavaSymbolSolver die ganze schwere Arbeit hinter den Kulissen. Sobald Sie einen Knoten des AST haben, können Sie ihn auf die Klasse JavaParserFacade werfen und er wird Ihnen alle Informationen zurückgeben, die Sie möglicherweise benötigen:Er findet entsprechende Typen, Felder, Methoden usw.

Das Problem ist… wir brauchen mehr Dokumentation und Feedback von Benutzern. Ich hoffe, dass einige von Ihnen JavaSymbolSolver verwenden und uns sagen, wie wir es verbessern können.

Außerdem wurde der JavaSymbolSolver letzte Woche unter die JavaParser-Organisation verschoben. Das bedeutet, dass wir in Zukunft enger mit dem JavaParser-Projekt zusammenarbeiten werden.

Der Code ist auf GitHub verfügbar:java-symbol-solver-examples

Java-Tag