> 文章列表 > python解析Java文件三方库javalang用法简介

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

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")@propertydef 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].nameprint(declare)
print("######################################")

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

解析一个Java文件

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

import javalang.parsefrom permission_checker import file_readerdata = file_reader.read_file_and_format("test_res/Test.java")
full_content = ''
for d in data:# 需要剔除 // 开头的日志if not d.strip().startswith("//"):full_content += dtree = 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].nameprint(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 = Falsefor p in m.parameters:hasParameters = Truedeclare += p.type.name + " " + p.name + ","# 去除最后一个逗号if hasParameters:declare = declare[:-1]declare += ")"print(declare)

踩到的坑

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