javaparser_JavaParser入门:以编程方式分析Java代码-程序员宅基地

技术标签: jvm  python  java  编程语言  大数据  

javaparser

我最喜欢的事情之一是解析代码并对其执行自动操作。 因此,我开始为JavaParser做出贡献,并创建了两个相关项目: java-symbol-solverEffectivejavajava_jp-1024x648

作为JavaParser的贡献者,我反复阅读了一些有关从Java源代码提取信息的非常相似的问题。 因此,我认为我可以帮助提供一些简单的示例,以帮助您开始解析Java代码。

通用代码

使用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),然后对其进行导航。 这样做有两种主要策略:

  1. 使用访客:要在特定类型的AST节点上进行操作时,这是正确的策略
  2. 使用递归迭代器:这允许处理所有类型的节点

可以编写访问者扩展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>() {
                    @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基类,因此我可以使用一个访问者,但我需要在几种访问方法中编写相同的代码,每个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() {
                    @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,因此这非常简单,并且与我们看到的第一个示例非常相似。

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

下一步

您可以使用此处介绍的方法回答很多问题:浏览AST,找到您感兴趣的节点,并获取所需的信息。 但是,我们还需要考虑其他几件事:首先,如何转换代码。 虽然提取信息很棒,但是重构更加有用。 然后,对于更高级的问题,我们需要使用java-symbol-solver解析符号。 例如:

  • 查看AST,我们可以找到一个类的名称,但不能找到它间接实现的接口列表
  • 在查看方法调用时,我们无法轻易找到该方法的声明。 它在哪个类或接口中声明? 我们要调用哪些不同的重载变体?

我们将在将来对此进行研究。 希望这些例子可以帮助您入门!

翻译自: https://www.javacodegeeks.com/2016/02/getting-started-javaparser-analyzing-java-code-programmatically.html

javaparser

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/dnc8371/article/details/107262304

智能推荐

GPT4、文心一言4、ChatGLM、Claude2.1四大语言模型生成效果对比_文心一言和chatglm-程序员宅基地

文章浏览阅读1.8k次,点赞24次,收藏27次。随着AIGC大风起,也掀起了语言模型大争之世,各种语言模型如雨后春笋,让大家眼花缭乱。周周都有新的选手入场,月月都有新的模型问世。不过其中最受人瞩目的当属GPT4、文心一言4、ChatGLM、Claude2.1四大模型。隐隐有傲视群雄之姿,今天我们也对这语言模型的四大天王的生成做一下粗浅的对比,也为大家在选择接入自家产品的时候有些许参考。_文心一言和chatglm

java/php/node.js/python基于微信小程序的音乐网站的设计与实现【2024年毕设】-程序员宅基地

文章浏览阅读236次。本系统带文档lw万字以上文末可领取本课题的JAVA源码参考。

基数排序算法-程序员宅基地

文章浏览阅读480次,点赞12次,收藏9次。基数排序

Uni-app 详情页 播放视频功能_uni.createvideocontext-程序员宅基地

文章浏览阅读4.8k次,点赞2次,收藏20次。逻辑:1.课程详情页加载后,用token判断用户是否登录,登录状态则调用//查询课程是够有播放的权限的接口(只传入courseId),后端会返回hasAuth为true/false2.点击某一章节时,登录状态则调用//查询课程是够有播放的权限的接口(这次要传入courseId & chapterId),后端会返回hasAuth为true/false和videoId,如果这俩都有,则跳页面到视频播放页3.跳转到视频播放页,请求课程详情接口、视频播放接口(传入courseId & c_uni.createvideocontext

Nginx上传文件大小、超时限制_nignx webdav 文件大小-程序员宅基地

文章浏览阅读1.3w次。修改Nginx上传文件大小限制  我们使用ngnix做web server的时候,nginx对上传文件的大小有限制,默认是1M。  当超过大小的时候会报413(too large)错误。这个时候我们要修改nginx的参数client_max_body_size 20M; location / { ... client_max_body_size 100m; }..._nignx webdav 文件大小

InSAR形变监测方法与研究进展(朱建军,中南大学)_insar 冰川厚度-程序员宅基地

文章浏览阅读134次。(1)首先,InSAR监测变形原理和卫星数据来源(2)其次,InSAR形变监测方法分类(3)最后,讨论InSAR研究难点。_insar 冰川厚度

随便推点

gitlab搭建遇到的问题记录_[execute] fail: redis: runsv not running-程序员宅基地

文章浏览阅读5.5k次。1 镜像下载地址https://packages.gitlab.com/gitlab/gitlab-ce我目前使用的版本是:11.10.2版本13以后,把原始仓库路径默认都给变成了hash加密了,很难辨识出原始仓库,所以不选择13以上的版本。2 每次服务器断电后不能启动gitlab2.1 断电后启动gitlab报错fail: alertmanager: runsv not runningfail: gitaly: runsv not runningfail: gitlab-_[execute] fail: redis: runsv not running

C#常见错误—未将对象引用设置到对象的实例-程序员宅基地

文章浏览阅读3.3k次,点赞2次,收藏8次。C#常见错误—“未将对象引用设置到对象的实例”的产生原因和解决方案。_未将对象引用设置到对象的实例

IAR for ARM 安装与注册(超详细)-程序员宅基地

文章浏览阅读1.3w次,点赞14次,收藏57次。IAR for ARM 安装与注册(超详细)一.使用软件 IAR for arm 8.3.21 以及注册软件链接:https://pan.baidu.com/s/1kihV0zkOdFx1OUZSJtbT9A提取码:0key二.软件安装1.打开安装包,选择安装2.点击“Next>”3.选择接受,再点击“Next>”,不接受不给你安装。霸道4.开发软件,建议安装在C盘,默认即可。安装在其他盘也可以注意不要有中文路径5.这里是烧录器的驱动安装,我们后面用到什么在安装什么6_iar for arm

黑马程序员_Java基础_IO流(四)_21_创建一个像文件date中写基本数据类型数据的流对象的语言为-程序员宅基地

文章浏览阅读540次。------- android培训、java培训、期待与您交流!---------- 导读:对象的序列化,管道流,RandomAccessFile,操作基本数据类型的流对象, ByteArrayStream,字符流的字符编码,编码解码,字符编码-联通,练习 1、IO流(对象的序列化)IO包中的其他类:操作对象,ObjectInputStream与ObjectOutputStre_创建一个像文件date中写基本数据类型数据的流对象的语言为

vue-cli中使用sass或者less_cli默认是less还是sass-程序员宅基地

文章浏览阅读2.1k次。什么是sass、less?他们有什么区别?请移步官网!这里仅仅记录一下如何使用开门见山。sass 创建好vue脚手架后,打开命令行安装sass:脚手架虽然有配置sass,配置在build/utils.js,但对应的loader未安装 ,请执行以下命令:npm install --save-dev node-sassnpm install --save-dev sass-loader在这里需..._cli默认是less还是sass

【IMX6ULL驱动开发学习】12.Linux驱动之设备树_正点原子linux开发板imx6ull 设备树-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏5次。(1)platform_driver 结构体添加 of_match_table 属性,添加 of_device_id 结构体,匹配设备树。(2)在 probe 函数中提取设备树中的引脚,提取设备树节点中的属性(of_property_read_u32等函数)后缀为 dtb 的是二进制的设备树文件,我们需要修改它,那么真正要操作的是其对应的 dts 文件,即。其他地方都不用修改,下次想添加外设时,直接修改设备树,然后修改 of_device_id 结构体即可。即可替换成新的设备树了,如何查看是否替换成功呢。_正点原子linux开发板imx6ull 设备树

推荐文章

热门文章

相关标签