目录

Javaparse包的使用和讲解

Javaparse包的使用和讲解

JavaParser 是一个开源的 Java 源代码解析器和操作库,它能够解析 Java 源代码并构建对应的抽象语法树(AST)。JavaParser 提供了一组 API,使得开发者可以轻松地分析、修改和生成 Java 源代码。据此,你可以通过这些API来实现Java动态编译,原生的java库有javax.tools,但可惜不能支持对于AST的修改

著名的java效率包lombok的底层实现原理就是基于对java的AST的修改。

如何下载javapraser

(1) Maven下载,下文附上依赖坐标

<dependency>
    <groupId>com.github.javaparser</groupId>
    <artifactId>javaparser-core</artifactId>
    <version>最新版本号</version>
</dependency>

(2)Gradle下载

implementation 'com.github.javaparser:javaparser-core:最新版本号'

(3)源地址下载:github

##在最新的3.25.10版的API中,使用了大量的函数式编程。

JavaParser类是源代码的解析器,可以通过使用其实例的parse()方法解析源代码或字符串得到一个解析结果ParseResult

public class Test{
public static void main(String args[]){
 ParserConfiguration parserConfiguration = new ParserConfiguration();
        JavaParser javaParser = new JavaParser(parserConfiguration);
String code = "public class HelloWorld {\n" +
                      "    public static void main(String[] args) {\n" +
                      "        System.out.println(\"Hello, JavaParser!\");\n" +
                      "    }\n" +
                      "}";

        // 解析代码
        ParseResult<CompilationUnit> compilationUnit = javaParser.parse(code);
        
}

Javaparser有两种构造方法

无参构造 JavaParser()

有参构造 Java Parser(ParserConfiguration)

ParserConfiguration用于配置一些选项

ParserConfiguration 类提供了一系列方法用于设置解析器的配置选项。以下是所有可用的设置方法:

  1. setLanguageLevel(LanguageLevel level)
    设置解析器使用的语言级别。
  2. setAttributeComments(boolean attributeComments)
    设置是否解析属性的注释。
  3. setLexicalPreservationEnabled(boolean lexicalPreservationEnabled)
    设置是否启用词法保留。
  4. setTabSize(int tabSize)
    设置解析器使用的制表符大小。
  5. setColumnStart(int columnStart)
    设置解析器的起始列。
  6. setColumnEnd(int columnEnd)
    设置解析器的结束列。
  7. setTabulationSize(int tabulationSize)
    设置解析器的制表符大小。
  8. setPreprocessUnicodeEscapes(boolean preprocessUnicodeEscapes)
    设置是否预处理 Unicode 转义字符。
  9. setAutoCloseEnabled(boolean autoCloseEnabled)
    设置是否启用自动关闭功能。
  10. setAutomaticImports(boolean automaticImports)
    设置是否自动导入。
  11. setLexicalPreservationEnabled(boolean lexicalPreservationEnabled)
    设置是否启用词法保留。

ParserConfiguration 类中提供了一些获取配置选项的方法,你可以使用这些方法来获取当前配置的信息。以下是所有可用的获取方法:

  1. getLanguageLevel()
    获取解析器使用的语言级别。
  2. isAttributeComments()
    获取是否解析属性的注释。
  3. isLexicalPreservationEnabled()
    获取是否启用词法保留。
  4. getTabSize()
    获取解析器使用的制表符大小。
  5. getColumnStart()
    获取解析器的起始列。
  6. getColumnEnd()
    获取解析器的结束列。
  7. getTabulationSize()
    获取解析器的制表符大小。
  8. isPreprocessUnicodeEscapes()
    获取是否预处理 Unicode 转义字符。
  9. isAutoCloseEnabled()
    获取是否启用自动关闭功能。
  10. isAutomaticImports()
    获取是否自动导入。
  11. isLexicalPreservationEnabled()
    获取是否启用词法保留。

你可以使用这些方法来获取当前配置的信息,并根据需要进行进一步处理。

在此有几个概念的辨析:

语言级别

指的是解析器使用的编程语言的版本或规范。在 JavaParser 中,语言级别可以设置为不同的 Java 版本,例如 Java 8、Java 11 等。这决定了解析器在解析代码时会考虑哪些语言特性和语法规则。

词法保留(Lexical Preservation)

指的是在解析代码时保留代码的原始格式和排版。启用词法保留功能后,解析器将尽可能地保留代码中的空格、缩进、换行符等词法结构,以保持代码的视觉布局和格式不变。这对于需要保留代码原始格式的应用场景非常有用,例如代码转换、代码生成、代码格式化等。

制表符大小(Tab Size)

是指在解析器中使用的制表符的宽度。制表符是一种特殊字符,通常用于在代码中表示缩进。制表符大小表示一个制表符字符在文本中所占的空格数目。例如,如果制表符大小为 4,则一个制表符字符在文本中将会被替换为四个空格字符。这有助于在不同的编辑器和环境中保持代码的一致缩进样式。

标题自动关闭(Auto-closing)和自动导入(Auto-import)是在编程环境中常见的功能。

  1. 自动关闭 :指的是编辑器或IDE(集成开发环境)在输入代码时自动补全相应的闭合符号,例如括号、引号、标签等。这样可以减少编码过程中的疏忽或错误,提高编码效率和代码质量。例如,当你输入左括号时,编辑器会自动添加右括号,并将光标放置在括号中间,方便你输入括号内的内容。
  2. 自动导入 :指的是编辑器或IDE根据你的代码内容自动引入所需的外部模块、库或类。当你在代码中使用了未导入的模块或类时,编辑器会自动检测并提供导入建议,你可以通过快捷键或点击建议来自动导入所需的内容。这样可以减少手动导入的工作量,并确保代码的正确性和一致性。

parse()方法有多个参数不同的重载,支持文件流,字符串的解析 ,同时也有对单个语句进行解析的方法parse

在 JavaParser 中,解析 Java 代码有多种方法,取决于您想要解析的代码的类型和结构。以下是一些常用的解析方法:

  1. 解析整个 Java 文件(Compilation Unit)
ParseResult<CompilationUnit> result = StaticJavaParser.parse(file);
  1. 解析代码块(Block)
ParseResult<BlockStmt> result = StaticJavaParser.parseBlock(code);
  1. 解析表达式(Expression)
ParseResult<Expression> result = StaticJavaParser.parseExpression(expression);
  1. 解析语句(Statement)
ParseResult<Statement> result = StaticJavaParser.parseStatement(statement);
  1. 解析类型(Type)
ParseResult<Type> result = StaticJavaParser.parseType(type);
  1. 解析注解(Annotation)
ParseResult<AnnotationExpr> result = StaticJavaParser.parseAnnotation(annotation);
  1. 解析名称(Name)
ParseResult<Name> result = StaticJavaParser.parseName(name);
  1. 解析 import 语句
ParseResult<ImportDeclaration> result = StaticJavaParser.parseImport(importDeclaration);
  1. 解析注释(Comment)
ParseResult<Comment> result = StaticJavaParser.parseComment(comment);

以上代码中的 filecodeexpressionstatementtypeannotationnameimportDeclarationcomment 分别是您要解析的 Java 文件、代码片段、表达式、语句、类型、注解、名称、import 语句、注释。

您可以根据需要选择适合您情况的解析方法。

除此之外 你还可以通过staticJavaParser这个类的静态方法直接调用这些函数

结果类 ParesResult 类

ParseResult<T> 是 JavaParser 中的一个泛型类,用于表示解析操作的结果。它通常包含两个主要部分:

  1. 解析成功与否的状态信息 :通过 isSuccessful() 方法可以获取解析是否成功的布尔值。如果解析成功,则可以通过 getResult() 方法获取解析得到的对象;如果解析失败,则可以通过 getProblems() 方法获取解析过程中遇到的问题列表。
  2. 解析得到的对象 :泛型类型 T 表示解析得到的对象的类型。根据解析操作的不同,可以得到不同类型的对象,例如 CompilationUnitBlockStmtExpression 等。

通常,您可以按照以下方式使用 ParseResult<T>

ParseResult<T> result = // 执行解析操作,获取解析结果

if (result.isSuccessful()) {
    // 解析成功
    T parsedObject = result.getResult();
    // 对解析得到的对象进行操作
} else {
    // 解析失败
    List<Problem> problems = result.getProblems();
    // 处理解析过程中遇到的问题
}

在这个例子中,根据 isSuccessful() 方法的返回值来判断解析操作是否成功,如果成功,则通过 getResult() 方法获取解析得到的对象进行后续操作,如果失败,则通过 getProblems() 方法获取解析过程中遇到的问题列表进行处理。

节点类Node

在 JavaParser 中,节点类用于表示 Java 源代码中的各种结构,如表达式、语句、类型、注解等。这些节点类都位于 com.github.javaparser.ast 包中。以下是 JavaParser 中常见的节点类:

  1. CompilationUnit(编译单元) :表示整个 Java 源文件的抽象语法树。它是 AST 的根节点。
  2. PackageDeclaration(包声明) :表示 Java 源文件中的包声明。
  3. ImportDeclaration(导入声明) :表示 Java 源文件中的导入声明。
  4. ClassOrInterfaceDeclaration(类或接口声明) :表示类或接口的声明。
  5. FieldDeclaration(字段声明) :表示类或接口中的字段声明。
  6. MethodDeclaration(方法声明) :表示类或接口中的方法声明。
  7. ConstructorDeclaration(构造函数声明) :表示类的构造函数声明。
  8. EnumDeclaration(枚举声明) :表示枚举类型的声明。
  9. EnumConstantDeclaration(枚举常量声明) :表示枚举类型中的常量声明。
  10. AnnotationDeclaration(注解声明) :表示注解类型的声明。
  11. AnnotationMemberDeclaration(注解成员声明) :表示注解类型中的成员声明。
  12. Parameter(参数) :表示方法或构造函数的参数。
  13. VariableDeclarator(变量声明符) :表示变量的声明符号。
  14. Expression(表达式) :表示 Java 中的各种表达式,如赋值表达式、方法调用、算术表达式等。
  15. Statement(语句) :表示 Java 中的各种语句,如 if 语句、for 循环、while 循环等。
  16. Type(类型) :表示 Java 中的各种类型,如基本类型、类类型、数组类型等。
  17. AnnotationExpr(注解表达式) :表示 Java 中的注解。
  18. Name(名称) :表示 Java 中的标识符名称。
  19. Comment(注释) :表示 Java 源代码中的注释。

这些节点类构成了 JavaParser 中的 AST(抽象语法树),可以通过解析 Java 源代码得到相应的节点对象,并通过这些节点对象进行语法分析、代码生成等操作。

他们都继承了Node这个类,除此之外,你可以通过操纵NodeList这个容器类来实现遍历的操作

这些节点类在 JavaParser 中都有一些常用的方法,用于获取节点的属性、进行节点的遍历和修改等操作。以下是这些节点类常用的方法:

  1. 获取节点的名称、类型等属性

    • getName()
      获取节点的名称。
    • getType()
      获取节点的类型。
    • 其他类似的获取属性的方法,根据节点类型的不同而有所不同。
  2. 获取节点的子节点

    • getChildNodes()
      获取节点的所有子节点。
    • 根据节点的类型,可能会有特定的方法来获取特定类型的子节点。
  3. 遍历节点的属性

    • 根据节点的类型和属性的不同,通常会有一些遍历属性的方法,例如获取方法的参数列表、获取字段的类型等。
  4. 修改节点的属性

    • 根据节点的类型和属性的不同,通常会有一些设置属性的方法,例如设置方法的参数列表、设置字段的类型等。
  5. 判断节点类型

    • isMethodDeclaration()
      判断节点是否是方法声明。
    • isFieldDeclaration()
      判断节点是否是字段声明。
    • isExpressionStmt()
      判断节点是否是表达式语句。
    • 其他类似的判断节点类型的方法,根据节点类型的不同而有所不同。
  6. 节点的位置信息

    • getBegin()
      getEnd()
      获取节点在源代码中的起始位置和结束位置。
    • getRange()
      获取节点在源代码中的范围。
  7. 节点的文本表示

    • toString()
      获取节点的文本表示。
  8. 节点的克隆和复制

    • clone()
      克隆节点,生成一个与原节点相同的新节点。
    • cloneForImport() 等特定的克隆方法,用于在导入节点时进行克隆。
  9. 其他操作

    • 根据节点类型的不同,可能会有其他特定的方法用于特定操作,如获取方法的异常列表、获取注解的成员列表等。

这些方法可以帮助您对节点进行各种操作,包括遍历、修改、分析等。具体使用哪些方法取决于您的需求和对节点的操作目的。

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;

public class Main {
    public static void main(String[] args) throws Exception {
        // 解析Java源代码
        CompilationUnit cu = StaticJavaParser.parse("public class MyClass {\n" +
                "    private int myField;\n" +
                "\n" +
                "    public void myMethod(int parameter) {\n" +
                "        System.out.println(\"Hello, world!\");\n" +
                "    }\n" +
                "}");

        // 获取类声明
        ClassOrInterfaceDeclaration classDeclaration = cu.getClassByName("MyClass").orElseThrow();
        String className = classDeclaration.getNameAsString();
        System.out.println("Class Name: " + className);

        // 获取字段声明
        FieldDeclaration fieldDeclaration = classDeclaration.getFieldByName("myField").orElseThrow();
        String fieldName = fieldDeclaration.getVariable(0).getNameAsString();
        System.out.println("Field Name: " + fieldName);

        // 获取方法声明
        MethodDeclaration methodDeclaration = classDeclaration.getMethodsByName("myMethod").get(0);
        String methodName = methodDeclaration.getNameAsString();
        System.out.println("Method Name: " + methodName);

        // 获取方法参数
        Parameter parameter = methodDeclaration.getParameter(0);
        String parameterName = parameter.getNameAsString();
        System.out.println("Parameter Name: " + parameterName);
    }
}

Type和TypeSolver类

TypeSolver 是 JavaParser 中用于解析和解决类型的重要类。它在 JavaParser 的类型处理过程中起着关键作用,特别是对于类类型的解析和解决。

以下是关于 TypeSolver 类的主要信息:

  • 作用TypeSolver 主要用于帮助 JavaParser 解析代码中的类引用,找到类的定义并建立类之间的关系,以便在语法树中正确表示类的继承、实现等关系。
  • 类型解析 :通过配置适当的 TypeSolver ,你可以确保 JavaParser 在解析代码时能够正确地处理类型信息,从而更准确地分析和理解代码。
  • 类引用解析TypeSolver 可以解析代码中对类的引用,例如在方法参数、变量声明等处使用的类名,通过解析这些引用,JavaParser 可以找到对应的类定义。
  • 类关系建立 :除了解析类引用外, TypeSolver 还能够建立类之间的关系,包括类的继承关系、实现接口关系等。这些关系对于理解代码的结构和行为非常重要。

总的来说, TypeSolver 在 JavaParser 中扮演着重要的角色,通过解析和解决类型信息,帮助 JavaParser 更准确地理解和分析代码的结构和含义。

实例:

编译一个源文件,增加一个public void your(String str){System.out.println(str);}方法和删除源代码原本就有的一个字段,最后输出更改后的代码

 import java.util.function.Consumer;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.body.Parameter;

public class Main {
    public static void main(String[] args) {
        // 解析现有的源代码
        String code = "public class MyClass { " +
        				"public String myfield;"+
                      "    public void existingMethod() { " +
                      "        System.out.println(\"Existing method\"); " +
                      "    } " +
                      "}";
        
        CompilationUnit cu = StaticJavaParser.parse(code);
        
        // 创建一个新的方法声明
        final MethodDeclaration newMethod = new MethodDeclaration();
        
        // 设置新方法的修饰符为 public
        newMethod.addModifier(Modifier.Keyword.PUBLIC,Modifier.Keyword.STATIC);
        
        // 设置新方法的名称
        newMethod.setName("newMethod");
        
        // 添加参数
        Parameter parameter = new Parameter();
        parameter.setType("String");
        parameter.setName("arg");
        newMethod.addParameter(parameter);
      
   
        
        // 将新方法添加到类中
        cu.getClassByName("MyClass").ifPresent(new Consumer<ClassOrInterfaceDeclaration>() {
            public void accept(ClassOrInterfaceDeclaration classDeclaration) {
                classDeclaration.addMember(newMethod);
            }
        });
        
        // 移除字段
        // 获取类声明
        ClassOrInterfaceDeclaration classDeclaration = cu.getClassByName("MyClass").orElseThrow();
        
        // 通过字段名称获取 FieldDeclaration
        FieldDeclaration fieldDeclaration = classDeclaration.getFieldByName("myfield").orElseThrow();
        
        fieldDeclaration.remove();
        // 输出修改后的代码
        System.out.println(cu.toString());
    }
}