javaparser_JavaParser入门:以编程方式分析Java代码
javaparser
我最喜歡的事情之一是解析代碼并對其執行自動操作。 因此,我開始為JavaParser做出貢獻,并創建了兩個相關項目: java-symbol-solver和Effectivejava 。
作為JavaParser的貢獻者,我反復閱讀了一些有關從Java源代碼提取信息的非常相似的問題。 因此,我認為我可以幫助提供一些簡單的示例,以幫助您開始解析Java代碼。
- Github上提供了所有源代碼: analyzer-java-code-examples
通用代碼
使用JavaParser theere時,我們總是希望進行很多操作。 通常,我們希望對整個項目進行操作,因此在給定目錄的情況下,我們將瀏覽所有Java文件。 此類應幫助完成此任務:
package me.tomassetti.support;import java.io.File;public class DirExplorer {public interface FileHandler {void handle(int level, String path, File file);}public interface Filter {boolean interested(int level, String path, File file);}private FileHandler fileHandler;private Filter filter;public DirExplorer(Filter filter, FileHandler fileHandler) {this.filter = filter;this.fileHandler = fileHandler;}public void explore(File root) {explore(0, "", root);}private void explore(int level, String path, File file) {if (file.isDirectory()) {for (File child : file.listFiles()) {explore(level + 1, path + "/" + child.getName(), child);}} else {if (filter.interested(level, path, file)) {fileHandler.handle(level, path, file);}}}}對于每個Java文件,我們首先要為每個Java文件構建一個抽象語法樹(AST),然后對其進行導航。 這樣做有兩種主要策略:
可以編寫訪問者擴展JavaParser中包含的類,但這是一個簡單的節點迭代器:
package me.tomassetti.support;import com.github.javaparser.ast.Node;public class NodeIterator {public interface NodeHandler {boolean handle(Node node);}private NodeHandler nodeHandler;public NodeIterator(NodeHandler nodeHandler) {this.nodeHandler = nodeHandler;}public void explore(Node node) {if (nodeHandler.handle(node)) {for (Node child : node.getChildrenNodes()) {explore(child);}}} }現在,讓我們看看如何使用此代碼來解決Stack Overflow上的一些問題。
如何從Java類中提取普通字符串中所有類的名稱?
- 在堆棧溢出時詢問
尋找ClassOrInterfaceDeclaration節點可以解決此解決方案。 給定我們想要一種特定類型的節點,我們可以使用訪客。 請注意,VoidVisitorAdapter允許傳遞任意參數。 在這種情況下,我們不需要這樣做,因此我們指定對象類型,而在訪問方法中將其忽略即可。
package me.tomassetti.examples;import com.github.javaparser.JavaParser; import com.github.javaparser.ParseException; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.google.common.base.Strings; import me.tomassetti.support.DirExplorer;import java.io.File; import java.io.IOException;public class ListClassesExample {public static void listClasses(File projectDir) {new DirExplorer((level, path, file) -> path.endsWith(".java"), (level, path, file) -> {System.out.println(path);System.out.println(Strings.repeat("=", path.length()));try {new VoidVisitorAdapter<Object>() {@Overridepublic void visit(ClassOrInterfaceDeclaration n, Object arg) {super.visit(n, arg);System.out.println(" * " + n.getName());}}.visit(JavaParser.parse(file), null);System.out.println(); // empty line} catch (ParseException | IOException e) {new RuntimeException(e);}}).explore(projectDir);}public static void main(String[] args) {File projectDir = new File("source_to_parse/junit-master");listClasses(projectDir);} }我們在JUnit的源代碼上運行示例,并得到以下輸出:
/src/test/java/org/junit/internal/MethodSorterTest.java =======================================================* DummySortWithoutAnnotation* Super* Sub* DummySortWithDefault* DummySortJvm* DummySortWithNameAsc* MethodSorterTest/src/test/java/org/junit/internal/matchers/StacktracePrintingMatcherTest.java =============================================================================* StacktracePrintingMatcherTest/src/test/java/org/junit/internal/matchers/ThrowableCauseMatcherTest.java =========================================================================* ThrowableCauseMatcherTest... ... many other lines follow是否有Java代碼解析器可以返回組成語句的行號?
- 在堆棧溢出時詢問
在這種情況下,我需要查找各種語句。 現在,有幾個類擴展了Statement基類,因此我可以使用一個訪問者,但我需要在幾種訪問方法中編寫相同的代碼,每個Statement方法的子類一個。 另外,我只想獲取頂層語句,而不要獲取其中的語句。 例如,一個for語句可以包含其他幾個語句。 使用我們的自定義NodeIterator,我們可以輕松實現此邏輯。
package me.tomassetti.examples;import com.github.javaparser.JavaParser; import com.github.javaparser.ParseException; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.stmt.Statement; import com.google.common.base.Strings; import me.tomassetti.support.DirExplorer; import me.tomassetti.support.NodeIterator;import java.io.File; import java.io.IOException;public class StatementsLinesExample {public static void statementsByLine(File projectDir) {new DirExplorer((level, path, file) -> path.endsWith(".java"), (level, path, file) -> {System.out.println(path);System.out.println(Strings.repeat("=", path.length()));try {new NodeIterator(new NodeIterator.NodeHandler() {@Overridepublic boolean handle(Node node) {if (node instanceof Statement) {System.out.println(" [Lines " + node.getBeginLine() + " - " + node.getEndLine() + " ] " + node);return false;} else {return true;}}}).explore(JavaParser.parse(file));System.out.println(); // empty line} catch (ParseException | IOException e) {new RuntimeException(e);}}).explore(projectDir);}public static void main(String[] args) {File projectDir = new File("source_to_parse/junit-master");statementsByLine(projectDir);} }這是在JUnit的源代碼上運行程序所獲得的輸出的一部分。
/src/test/java/org/junit/internal/matchers/ThrowableCauseMatcherTest.java =========================================================================[Lines 12 - 17 ] {NullPointerException expectedCause = new NullPointerException("expected");Exception actual = new Exception(expectedCause);assertThat(actual, hasCause(is(expectedCause))); }您可能會注意到所報告的語句跨5個,而不是所報告的6個(12..17是6行)。 這是因為我們正在打印該語句的純凈版本,刪除白線,注釋并設置代碼格式。
從Java代碼中提取方法調用
- 在堆棧溢出時詢問
對于提取方法調用,我們可以再次使用Visitor,因此這非常簡單,并且與我們看到的第一個示例非常相似。
package me.tomassetti.examples;import com.github.javaparser.JavaParser; import com.github.javaparser.ParseException; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.google.common.base.Strings; import me.tomassetti.support.DirExplorer;import java.io.File; import java.io.IOException;public class MethodCallsExample {public static void listMethodCalls(File projectDir) {new DirExplorer((level, path, file) -> path.endsWith(".java"), (level, path, file) -> {System.out.println(path);System.out.println(Strings.repeat("=", path.length()));try {new VoidVisitorAdapter<Object>() {@Overridepublic void visit(MethodCallExpr n, Object arg) {super.visit(n, arg);System.out.println(" [L " + n.getBeginLine() + "] " + n);}}.visit(JavaParser.parse(file), null);System.out.println(); // empty line} catch (ParseException | IOException e) {new RuntimeException(e);}}).explore(projectDir);}public static void main(String[] args) {File projectDir = new File("source_to_parse/junit-master");listMethodCalls(projectDir);} }如您所見,該解決方案與用于列出類的解決方案非常相似。
/src/test/java/org/junit/internal/MethodSorterTest.java =======================================================[L 58] MethodSorter.getDeclaredMethods(clazz)[L 64] m.isSynthetic()[L 65] m.toString()[L 65] clazz.getName()[L 65] m.toString().replace(clazz.getName() + '.', "")[L 65] names.add(m.toString().replace(clazz.getName() + '.', ""))[L 74] Arrays.asList(EPSILON, BETA, ALPHA, DELTA, GAMMA_VOID, GAMMA_BOOLEAN)[L 75] getDeclaredMethodNames(DummySortWithoutAnnotation.class)[L 76] assertEquals(expected, actual)[L 81] Arrays.asList(SUPER_METHOD)[L 82] getDeclaredMethodNames(Super.class)[L 83] assertEquals(expected, actual)[L 88] Arrays.asList(SUB_METHOD)[L 89] getDeclaredMethodNames(Sub.class)[L 90] assertEquals(expected, actual)[L 118] Arrays.asList(EPSILON, BETA, ALPHA, DELTA, GAMMA_VOID, GAMMA_BOOLEAN)[L 119] getDeclaredMethodNames(DummySortWithDefault.class)[L 120] assertEquals(expected, actual)[L 148] DummySortJvm.class.getDeclaredMethods()[L 149] MethodSorter.getDeclaredMethods(DummySortJvm.class)[L 150] assertArrayEquals(fromJvmWithSynthetics, sorted)[L 178] Arrays.asList(ALPHA, BETA, DELTA, EPSILON, GAMMA_VOID, GAMMA_BOOLEAN)[L 179] getDeclaredMethodNames(DummySortWithNameAsc.class)[L 180] assertEquals(expected, actual)下一步
您可以使用此處介紹的方法回答很多問題:瀏覽AST,找到您感興趣的節點,并獲取所需的信息。 但是,我們還需要考慮其他幾件事:首先,如何轉換代碼。 雖然提取信息很棒,但是重構更加有用。 然后,對于更高級的問題,我們需要使用java-symbol-solver解析符號。 例如:
- 查看AST,我們可以找到一個類的名稱,但不能找到它間接實現的接口列表
- 在查看方法調用時,我們無法輕易找到該方法的聲明。 它在哪個類或接口中聲明? 我們要調用哪些不同的重載變體?
我們將在將來對此進行研究。 希望這些例子可以幫助您入門!
翻譯自: https://www.javacodegeeks.com/2016/02/getting-started-javaparser-analyzing-java-code-programmatically.html
javaparser
總結
以上是生活随笔為你收集整理的javaparser_JavaParser入门:以编程方式分析Java代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对象容器设计模式_容器对象模式。 一种新
- 下一篇: 面试如何防止被骗方案(面试如何防止ddo