2年前 (2022-12-21)  Java系列 |   抢沙发  7263 
文章评分 10 次,平均分 4.6

是否可以解析代码并对其执行自动操作?

JavaParser库(https://github.com/javaparser/javaparser)提供Java代码的抽象语法树。AST结构允许您以简单的编程方式使用Java代码。

当使用JavaParser时,我们通常希望每次都执行一系列操作。通常,我们希望对整个项目进行操作,因此,给定一个目录,我们将探索所有Java文件。该类应有助于完成以下操作:

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),然后对其进行导航。有两种主要策略可以做到这一点:

  • 使用visitor:当您想对特定类型的AST节点进行操作时,这是正确的策略
  • 使用递归迭代器:这允许处理所有类型的节点

Visitors 可以编写JavaParser中包含的扩展类,而这是一个简单的节点迭代器:

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

现在让我们看看如何使用此代码来解决堆栈溢出中的一些问题。

如何从java类中提取普通字符串中所有类的名称?

这个解决方案可以通过查找ClassOrInterfaceDeclaration节点来解决。如果我们需要特定类型的节点,我们可以使用访问者。注意,VoidVisitorAdapter允许传递任意参数。在这种情况下,我们不需要这样做,所以我们指定了Object类型,我们只是在访问方法中忽略它。

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>() {
                    @Override
                    public 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基类,所以我可以使用visitor,但我需要在多个访问方法中编写相同的代码,每个访问方法用于Statement的子类。此外,我只想获取顶级语句,而不是其中的语句。例如,For语句可以包含几个其他语句。使用我们的自定义NodeIterator,我们可以轻松实现这个逻辑。

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() {
                    @Override
                    public 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,因此这非常简单,与我们看到的第一个示例非常相似。

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>() {
                    @Override
                    public 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)

更多示例可参考官网Blog:http://javaparser.org/blog.html

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2772.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册