JavaParser入门:以编程方式分析Java代码
我最喜歡的事情之一是解析代碼并對(duì)其執(zhí)行自動(dòng)操作。 因此,我開(kāi)始為JavaParser做出貢獻(xiàn),并創(chuàng)建了兩個(gè)相關(guān)項(xiàng)目: java-symbol-solver和Effectivejava 。
作為JavaParser的貢獻(xiàn)者,我反復(fù)閱讀了一些非常類似的問(wèn)題,這些問(wèn)題涉及從Java源代碼提取信息。 因此,我認(rèn)為我可以幫助提供一些簡(jiǎn)單的示例,以幫助您開(kāi)始解析Java代碼。
- Github上提供了所有源代碼: analyzer-java-code-examples
通用代碼
使用JavaParser theere時(shí),我們總是希望進(jìn)行很多操作。 通常,我們希望對(duì)整個(gè)項(xiàng)目進(jìn)行操作,因此在給定目錄的情況下,我們將探索所有Java文件。 此類應(yīng)有助于完成此任務(wù):
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);}}}}對(duì)于每個(gè)Java文件,我們首先要為每個(gè)Java文件構(gòu)建一個(gè)抽象語(yǔ)法樹(shù)(AST),然后對(duì)其進(jìn)行導(dǎo)航。 這樣做有兩種主要策略:
可以編寫訪問(wèn)者擴(kuò)展JavaParser中包含的類,而這是一個(gè)簡(jiǎn)單的節(jié)點(diǎn)迭代器:
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);}}} }現(xiàn)在,讓我們看看如何使用此代碼解決Stack Overflow上的一些問(wèn)題。
如何從Java類中提取普通字符串中所有類的名稱?
- 在堆棧溢出時(shí)詢問(wèn)
尋找ClassOrInterfaceDeclaration節(jié)點(diǎn)可以解決此解決方案。 給定我們想要一種特定類型的節(jié)點(diǎn),我們可以使用訪客。 請(qǐng)注意,VoidVisitorAdapter允許傳遞任意參數(shù)。 在這種情況下,我們不需要這樣做,因此我們指定對(duì)象類型,而在訪問(wèn)方法中將其忽略即可。
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);} }我們?cè)贘Unit的源代碼上運(yùn)行示例,并獲得以下輸出:
/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代碼解析器可以返回組成語(yǔ)句的行號(hào)?
- 在堆棧溢出時(shí)詢問(wèn)
在這種情況下,我需要查找各種語(yǔ)句。 現(xiàn)在,有幾個(gè)類擴(kuò)展了Statement基類,因此我可以使用一個(gè)訪問(wèn)者,但我需要在幾種訪問(wèn)方法中編寫相同的代碼,一個(gè)用于Statement的每個(gè)子類。 另外,我只想獲取頂層語(yǔ)句,而不要獲取其中的語(yǔ)句。 例如,一個(gè)for語(yǔ)句可以包含其他幾個(gè)語(yǔ)句。 使用我們的自定義NodeIterator,我們可以輕松實(shí)現(xiàn)此邏輯。
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的源代碼上運(yùn)行程序所獲得的輸出的一部分。
/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))); }您可能會(huì)注意到報(bào)告的語(yǔ)句跨5個(gè),而不是報(bào)告的6個(gè)(12..17是6行)。 這是因?yàn)槲覀冋诖蛴≡撜Z(yǔ)句的純凈版本,刪除了白線,注釋并設(shè)置了代碼格式。
從Java代碼中提取方法調(diào)用
- 在堆棧溢出時(shí)詢問(wèn)
對(duì)于提取方法調(diào)用,我們可以再次使用Visitor,因此這非常簡(jiǎn)單,并且與我們看到的第一個(gè)示例非常相似。
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);} }如您所見(jiàn),該解決方案與列出類的解決方案非常相似。
/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)下一步
您可以使用此處介紹的方法回答很多問(wèn)題:瀏覽AST,找到您感興趣的節(jié)點(diǎn),并獲取所需的任何信息。 但是,我們還需要考慮其他幾件事:首先,如何轉(zhuǎn)換代碼。 盡管提取信息非常有用,但是重構(gòu)更加有用。 然后,對(duì)于更高級(jí)的問(wèn)題,我們需要使用java-symbol-solver解析符號(hào)。 例如:
- 查看AST,我們可以找到一個(gè)類的名稱,但不能找到它間接實(shí)現(xiàn)的接口列表
- 在查看方法調(diào)用時(shí),我們無(wú)法輕易找到該方法的聲明。 它在哪個(gè)類或接口中聲明? 我們要調(diào)用哪些不同的重載變體?
我們將在將來(lái)對(duì)此進(jìn)行研究。 希望這些例子可以幫助您入門!
翻譯自: https://www.javacodegeeks.com/2016/02/getting-started-javaparser-analyzing-java-code-programmatically.html
總結(jié)
以上是生活随笔為你收集整理的JavaParser入门:以编程方式分析Java代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: win10电脑怎么使用ssr(win10
- 下一篇: .bam.bai的意义_业务活动监视器(