当前位置: 首页 > article >正文

python解析Java文件三方库javalang用法简介

书接上文,在我一顿操作猛如虎的土鳖扫描后(python提取android工程代码中的一些数据),发现仅仅只是扫描出关键字的话,有些封装后的调用或者将关键字声明称常量后的调用都没法识别出来。这种关键字扫描怎么说呢?有点鸡肋,误报太多,无法聚焦重点。所以我们需要一个识别关键字是否为变量或者封装在那个函数中的能力。这个相当于解析整个Java文件,按照基础的Java文件结构进行归类查询。这种功能其实就是IDE的实现跳转功能,想着这么成熟的技术一定有库的,又去搜索了一波,总算发现了这个宝藏库——javalang。

javalang是一个用于处理Java源代码的纯Python库。javalang提供了针对Java 8的词法分析器和解析器。该实现基于http://docs.oracle.com/javase/specs/jls/se8/html/上提供的Java语言规范。

javalang官方首页上如是说。

安装方法

pip install javalang

使用方法简介

  1. 将待解析文件读入内存后拼接成一起字符串传入方法tree = javalang.parse.parse(full_content)中。如此我们便将这个Java文件解析完成了。
  2. 遍历这个解析结果,筛选我们想要的东西。比如包名、依赖列表、类名、变量以及方法等。调用方法如下:
# 包名
print("package:")
print(tree.package.name)
print("######################################")

# 依赖
print("imports:")
for i in tree.imports:
    print(str(i.path))
print("######################################")

# 方法名
print("方法名:" + tree.types[0].methods[1].name)
print("方法修饰符:" + str(tree.types[0].methods[1].modifiers))
print("第一入参名:" + str(tree.types[0].methods[1].parameters[0].name))
print("第一入参类型:" + str(tree.types[0].methods[1].parameters[0].type.name))
print("返回值类型:" + str(tree.types[0].methods[1].return_type.name))
print("######################################")

3.javalang这个库解析的十分到位,将其中声明对象的类名以及子类名等都有分门别类的分割,甚至还把每行命令代码都进行了解析。穷尽显然是没必要的,想用什么自己在他的tree.py文件中可以找到声明方式,然后就可以一步步的打出来了。相信聪明的各位读者,也懒得看我在这儿千篇一律的举例子。以上面漏掉的成员类型为例,抛砖引玉。

从上面的例子中我们能知道tree下一级中types是类列表,取第一项打印一下可以得到。

ClassDeclaration(annotations=[], body=[FieldDeclaration(annotations=[], declarators=[VariableDeclarator(dimensions=[], initializer=Literal(postfix_operators=[], prefix_operators=[], qualifier=None, selectors=[], value=“android.permission.RECEIVE_SMS”), name=RECEIVE_SMS)], documentation=None, modifiers={‘static’, ‘public’, ‘final’}, type=ReferenceType(arguments=None, dimensions=[], name=String, sub_type=None)), MethodDeclaration(annotations=[], body=[ReturnStatement(expression=Literal(postfix_operators=[], prefix_operators=[], qualifier=None, selectors=[], value=“hello world”), label=None)], documentation=None, modifiers={‘public’}, name=testReturn, parameters=[FormalParameter(annotations=[], modifiers=set(), name=testInt, type=BasicType(dimensions=[], name=int), varargs=False), FormalParameter(annotations=[], modifiers=set(), name=testString, type=ReferenceType(arguments=None, dimensions=[], name=String, sub_type=None), varargs=False)], return_type=ReferenceType(arguments=None, dimensions=[], name=String, sub_type=None), throws=None, type_parameters=None)], documentation=/** * desc : 权限请求实体类 */, extends=None, implements=None, modifiers={‘public’, ‘final’}, name=Permission, type_parameters=None)

分析其中,发现变量在FieldDeclaration中,找到tree.py文件中这个的声明可以看到:

class TypeDeclaration(Declaration, Documented):
    attrs = ("name", "body")

    @property
    def fields(self):
        return [decl for decl in self.body if isinstance(decl, FieldDeclaration)]

于是可以知道通过tree.types[i].fields可以获得变量集合。根据此可以写出如下的遍历逻辑:

# 成员变量
print("成员变量:")
for f in tree.types[0].fields:
    declare = ''
    # 声明变量修饰符
    for m in f.modifiers:
        declare += m + " "
    # 声明变量类型
    declare += str(f.type.name) + " "
    # 声明变量名
    declare += f.declarators[0].name
    print(declare)
print("######################################")

其他的Java解析方法都可以根据上述流程逐步整理出来,然后想怎么用就看你的心情咯。

解析一个Java文件

基于上面的介绍,我将一个Java文件解析后重新打印了出来,具体逻辑如下。其余还有关于Interface,Enum等解析等后面用到再来解析吧。其中file_reader.read_file_and_format详见python提取android工程代码中的一些数据。

import javalang.parse

from permission_checker import file_reader

data = file_reader.read_file_and_format("test_res/Test.java")
full_content = ''
for d in data:
    # 需要剔除 // 开头的日志
    if not d.strip().startswith("//"):
        full_content += d

tree = javalang.parse.parse(full_content)

# 包名
print("package:")
print(tree.package.name)
print("######################################")

# 依赖
print("imports:")
for i in tree.imports:
    print(str(i.path))
print("######################################")

# types以class为分割一个class一个type
# 成员变量
print("成员变量:")
for f in tree.types[0].fields:
    declare = ''
    # 声明变量修饰符
    for m in f.modifiers:
        declare += m + " "
    # 声明变量类型
    declare += str(f.type.name) + " "
    # 声明变量名
    declare += f.declarators[0].name
    print(declare)
print("######################################")

# 还原拼接方法名
for m in tree.types[0].methods:
    declare = ''
    # 拼接变量修饰符
    for mod in m.modifiers:
        declare += mod + " "
    # 拼接返回类型,当void时,没有return_type需要注意
    if m.return_type:
        declare += str(m.return_type.name)
    else:
        declare += "void"
    declare += " "
    # 拼接方法名
    declare += str(m.name)
    # 拼接入参
    declare += "("
    hasParameters = False
    for p in m.parameters:
        hasParameters = True
        declare += p.type.name + " " + p.name + ","
    # 去除最后一个逗号
    if hasParameters:
        declare = declare[:-1]
    declare += ")"
    print(declare)

踩到的坑

在将Java文件导入到内存中时需要将\\开头的注释过滤掉,这种注释会将后面拼接的代码一并注释掉,造成文件结构异常,导致解析失败抛出异常javalang.parser.JavaSyntaxError。但值得注意的是Javadoc类型以及\**\不会异常。


http://www.kler.cn/a/1336.html

相关文章:

  • Spring Boot3 配合ProxySQL实现对 MySQL 主从同步的读写分离和负载均衡
  • 微信小程序实现登录注册
  • uniapp 微信小程序内嵌h5实时通信
  • Huawei Cloud EulerOS上安装sshpass
  • C#中的常用集合
  • Ubuntu网络连接问题(笔记本更换wifi后,虚拟机连不上网络)
  • MySQL索引特性
  • 【完整代码】用HTML/CSS制作一个美观的个人简介网页
  • 若依整合Easy-Es实现文章列表分页查询
  • 02-PostgreSQL 存储过程的进阶介绍(含游标、错误处理、自定义函数、事务)
  • 真实的软件测试日常工作是咋样的?
  • Delphi 一个函数实现腾讯云最新版(API3.0)短信发送
  • C++基础教程
  • PMSM矢量控制笔记(1.1)——电机的机械结构与运行原理
  • News乐鑫科技亮相德国嵌入式展 Embedded World 2023!
  • Java每日一练(20230319)
  • Mini-Xml 经典实例Demo
  • 博客系统实现自动化测试
  • PMP考前冲刺3.18 | 2023新征程,一举拿证
  • 【深度强化学习】(5) DDPG 模型解析,附Pytorch完整代码
  • 102.【Redis】
  • 用sql计算两个经纬度坐标距离(米数互转)
  • day10-字符串作业1
  • C语言的灵魂---指针(基础)
  • MultipartEntityBuilder实现多附件上传
  • day10-字符串作业2