JavaParser使用报告

什么是JavaParser

JavaParser库可以在Java环境中以Java对象的表示形式(即抽象语法树AST)与Java源码交互。它提供一种名为“访问者支持”的机制来导航树。并且。他能操作源码的底层结构,将其写入文件,为开发人员提供了构建他们自己代码生成软件的工具。

库可以解析的语法上正确的源代码不一定能成功编译。
例如引用未定义的变量,语法上正确但存在语义错误

主要类模块

JavaParser和StaticJavaParser
  1. JavaParser Class提供了一个用于从代码生成AST的完整API
    一般使用整个类文件,其中.parse方法被重载用于接收path、file、inputstream和string,并且返回一个CompilationUnit对象。
  2. StaticJavaParser提供了一个快速和简单的API,可以从代码中生成AST。

下面的例子解析一个字符串,需要知道返回类型以免解析错误

Statement statement = StaticJavaParser.parseStatement("int a = 0;");
CompilationUnit

CompilationUnit表示AST的根结点,从一个完整且语法正确的类文件中解析出来的Java表示。
从根节点出发,可以访问树中所有结点属性。

Visitors

Visitors能够轻松查找AST的特定部分的所有结点。

可以通过拓展不同Visitor类并重载visit方法即可对所有结点进行操作,其中第一个参数类型是我们想要的结点类型,第二个参数和Visitor的类型参数有关。在实现中调用super方法访问当前结点的子结点。
之后只需在main方法中实例化拓展类并调用visit即可。

下例使用前面定义的文件路径作为输入流,通过调用静态JavaParser类上的静态parse方法来实例化CompilationUnit

VoidVisitorAdapter

VoidVisitorAdapter类不能改变底层AST,visit方法返回类型为void。

public class VoidVisitorStarter {
  private static final String FILE_PATH = "src/main/java/org/javaparser/samples/ReversePolishNotation.java";
  public static void main(String[] args) throws Exception {
    CompilationUnit cu = StaticJavaParser.parse(Files.newInputStream(Paths.get(FILE_PATH)));
  }
}
  1. 输出所有方法声明的方法名,对结点无需操作
private static class MethodNamePrinter extends VoidVisitorAdapter<Void> {
  @Override
  public void visit(MethodDeclaration md, Void arg) {
    super.visit(md, arg);
    System.out.println("Method Name Printed: " + md.getName());
  }
}
// main
VoidVisitor<Void> methodNameVisitor = new MethodNamePrinter();
methodNameVisitor.visit(cu, null);
  1. visitor收集所有方法名称,调用者可以对collector进行操作
private static class MethodNameCollector extends VoidVisitorAdapter<List<String>> {
  @Override
  public void visit(MethodDeclaration md, List<String> collector) {
    super.visit(md, collector);
    collector.add(md.getNameAsString());
  }
}
// main
List<String> methodNames = new ArrayList<>();
VoidVisitor<List<String>> methodNameCollector = new MethodNameCollector();
methodNameCollector.visit(cu, methodNames);
methodNames.forEach(n -> System.out.println("Method Name Collected: " + n));
ModifierVisitor

ModifierVisitor能够修改底层AST,并且visit方法的返回类型同第一个方法参数,意味着这个visitor将用一个字段声明代替另一个字段声明。

private static class IntegerLiteralModifier extends ModifierVisitor<Void> {
 @Override
 public FieldDeclaration visit(FieldDeclaration fd, Void arg) {
   super.visit(fd, arg);
   system.out.println(fd.toString());
   return fd;
 }
}
// main
ModifierVisitor<Void> FieldDeclarationVisitor = new IntegerLiteralModifier();
FieldDeclarationVisitor.visit(cu, null);
System.out.println(cu.toString());
JavaParser Symbol Solver

JavaParser Symbol Solver检查所有符号并获得它的声明信息,包括类型。

TypeSolver描述
JarTypeSolver在已知的JAR文件中搜索类
JavaParserTypeSolver在已知路径的源文件中搜索类
ReflectionTypeSolver找到没有JAR但属于JAVA的一部分的类定义,例如java.lang.Object
MemoryTypeSolver返回我们在其中记录的类,主要用于测试
CombinedTypeSolver组合多个TypeSolver
解析特定名字中的类型

下例展示了ReflectionTypeSolver解析java.lang.Object,可以从返回的a变量中访问所有的祖先、继承方法和字段、注解以及类型。其他TypeSolver同理。

TypeSolver typeSolver = new ReflectionTypeSolver();
ResolvedReferenceTypeDeclaration a = typeSolver.solveType("java.lang.Object");
解析上下文中的类型

JavaSymbolSolver能使用上下文找到某个声明对应的实际类型,例如来自当前包或类型还是来自导入的包。

// combinedSolver组合了reflectionTypeSolver和javaParserTypeSolver
JavaSymbolSolver symbolSolver = new JavaSymbolSolver(combinedSolver);
StaticJavaParser.getParserConfiguration().setSymbolResolver(symbolSolver);

CompilationUnit cu = StaticJavaParser.parse(new File(FILE_PATH));
FieldDeclaration fieldDeclaration = Navigator.demandNodeOfGivenClass(cu, FieldDeclaration.class);
// 得到第一个字段声明变量,将其类型转换为JavaSymbolSolver类型,传递给JavaParser类型和上下文
System.out.println("Field type: " + fieldDeclaration.getVariables().get(0)
    .getType().resolve().asReferenceType().getQualifiedName());
解析方法调用

Java支持方法重载,但有时无法区分调用的重载方法,例如输出数字和字符串。JavaSymbolSolver能够找出特定上下文中的方法的作用域和参数信息等。

TypeSolver typeSolver = new ReflectionTypeSolver();

JavaSymbolSolver symbolSolver = new JavaSymbolSolver(typeSolver);
StaticJavaParser.getParserConfiguration().setSymbolResolver(symbolSolver);

CompilationUnit cu = StaticJavaParser.parse(new File(FILE_PATH));
cu.findAll(MethodCallExpr.class).forEach(mce ->
    System.out.println(mce.resolve().getQualifiedSignature()));

注释

代码中的注释可以通过CompilationUnit对象方法获得其列表,从而得到他们的文本内容、类型等,例如块注释、行注释、JavaDoc风格注释。

List<Comment> comments = cu.getAllContainedComments();

每个结点都有comment字段与其注释相关联,每个注释也维护一个commentedNode引用,从而保持双向关联。但是每个注释都不能对自身注释,也没有孩子,因此是独立的。

  1. 块注释或者JavaDoc风格注释查找后续结点(同一行或者下一行);若包含该注释的结点无后继结点,则其属于orphan注释,否则归属于包含该注释的结点;。
  2. 行注释归先查找与其同一行的结点,再查找后续结点作为归属结点,否则属于orphan注释。
  3. orphan注释没有明确的归属结点,通常归属于期望节点的父结点
注释选项配置
  1. 解析时忽略注释
ParserConfiguration parserConfiguration = new ParserConfiguration().setAttributeComments(false);
StaticJavaParser.setConfiguration(parserConfiguration);
  1. 默认情况下,如果注释后存在空行,则忽略将注释分配给后续节点,否则将其作为orphan注释分配给父结点;下面代码考虑第二种情况。
ParserConfiguration parserConfiguration = new ParserConfiguration().setDoNotAssignCommentsPrecedingEmptyLines(true);

打印

pretty printing

pretty printing主要以易于阅读和美观的方式显示代码,提高代码可读性,适用于生成代码或者转换无需手动检查的代码的情况

ClassOrInterfaceDeclaration myClass = new ClassOrInterfaceDeclaration();
myClass.setComment(new LineComment("A very cool class!"));
myClass.setName("MyClass");
myClass.addField("String", "foo");
myClass.addAnnotation("MySecretAnnotation");

PrettyPrinterConfiguration conf = new PrettyPrinterConfiguration();
conf.setIndentSize(2);
conf.setIndentType(Indentation.IndentType.SPACES);
conf.setPrintComments(false);
Function<PrinterConfiguration, VoidVisitor<Void>> prettyPrinterFactory = (configuration) -> new DefaultPrettyPrinterVisitor(conf) {
	@Override
	public void visit(MarkerAnnotationExpr n, Void arg) {
		// ignore
	}
	@Override
	public void visit(SingleMemberAnnotationExpr n, Void arg) {
		// ignore
	}
	@Override
	public void visit(NormalAnnotationExpr n, Void arg) {
		// ignore
	}
};
Printer prettyPrinter = new DefaultPrettyPrinter(prettyPrinterFactory, conf);
System.out.println(prettyPrinter.print(myClass));
// 输出的类忽略注解和注释
lexical-preserving printing

lexical-preserving printing旨在保持代码的原始格式和语法结构,尤其是对于修改后的代码而言,包括缩进、换行和其他细节。
当编程构建AST或解析代码后编程添加结点时,代码默认pretty printing。
适用于在大型代码库中执行转换的情况,此时所有未被转换修改的代码将完全保持不变
当使用lexical-preserving printing时,setup方法将解析保存初始文本,向AST中添加一个observer。之后每次在AST中修改时observor将调整文本。本质上就是为每个结点创建一个NodeText,它要么是tokens要么是占位符,将observor附加到所有结点,每次改变代码时都能收到通知并计算更改后的结点,更新到NodeText。
ConcreteSyntaxModel解释了如何将AST结点去解析化并转换成文本。

String code = "// Hey, this is a comment\n\n\n// Another one\n\nclass A { }";
CompilationUnit cu = StaticJavaParser.parse(code);
LexicalPreservingPrinter.setup(cu);
ClassOrInterfaceDeclaration myClass = cu.getClassByName("A").get();
myClass.setName("MyNewClassName");
myClass.addModifier(Modifier.Keyword.PUBLIC);
cu.setPackageDeclaration("org.javaparser.samples");
System.out.println(LexicalPreservingPrinter.print(cu));

优缺点

优点

  1. 简单易用:javaParser提供了简洁的API,使得解析Java代码变得非常容易。
  2. 功能强大:JavaParser可以将Java代码解析成抽象语法树(AST),并提供了许多功能来分析和操作AST,如查找、修改和生成代码等。
  3. 支持最新的Java版本:JavaParser可以解析和处理最新版本的Java代码,包括Java 16。

缺点
性能较低。对于大量Java代码来说,JavaParser需要解析和构建整个抽象语法树。

用途

  1. 识别违规代码,保证代码安全。
  2. 用于代码测试中确保代码覆盖率
  3. 在实现软件质量分析工具中加速软件的实现
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值