JDT之抽象语法树

问题描述

公司业务迭代通过开关进行风险规避,在开关打开时走新业务逻辑,关闭时走老业务逻辑。这导致有大量的开关代码遗留,每次人工清理开关容易遗漏出错。为了减少出错概率,将清理开关动作进行自动化

解决方案

使用JDT工具解析java源码代码进行针对开关的语义自动替换清理

JDT使用

引入依赖

<dependency>
  <groupId>org.eclipse.jdt</groupId>
  <artifactId>org.eclipse.jdt.core</artifactId>
  <version>3.16.0</version>
</dependency>

生成抽象语法树,重写AST

public static void switchClean(String javaFilePath) throws Exception {
    // 1. 读取源文件代码
    String src;
    try {
        src = FileUtils.readFileToString(new File(javaFilePath), "utf8");
    } catch (Exception var5) {
        log.error("读取源文件失败", var5);
        return;
    }
    if (StringUtils.isBlank(src)) {
        log.error("源文件为空");
        return;
    }
    // 2. 根据源代码创建dom
    Document document= new Document(src);

    // 3. 根据规范创建ASTParser(抽象语法树解析器)
    ASTParser astParser = ASTParser.newParser(AST.JLS11);
    // 4. 创建编译参数配置
    Map<String, String> compilerOptions = JavaCore.getOptions();
    // 5. 指定编译参数,1.8 jdk
    JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, compilerOptions);
    astParser.setCompilerOptions(compilerOptions);
    astParser.setResolveBindings(true);
    astParser.setBindingsRecovery(true);
    astParser.setStatementsRecovery(true);
    astParser.setSource(src.toCharArray());
    // 6. 创建编辑单元(抽象语法树根节点)
    CompilationUnit astRoot = (CompilationUnit) astParser.createAST(null);

    // 7. 创建AST重写实例
    ASTRewrite astRewrite = ASTRewrite.create(astRoot.getAST());

    // 8. 获取旧类名称节点
    SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
    // 9. 创建新类名称节点
    SimpleName newName = astRoot.getAST().newSimpleName("Y");
    // 10. 使用新名称节点替换老名称节点
    astRewrite.replace(oldName, newName, null);

    // 11. 创建开关清理访问实例,继承ASTVisitor实例,重写相关的方法实现
    SwitchesCleaner switchesFinder = new SwitchesCleaner(astRewrite);
    // 12. 将ASTVisitor与AST绑定
    astRoot.accept(switchesFinder);

    // 13. 编辑单元文本编辑对象,根据AST对象重新源dom
    TextEdit edits = astRewrite.rewriteAST(document, compilerOptions);

    // 14. 源dom应用编辑使重写动作生效
    edits.apply(document);

    // 15. 输出修改后代码
    System.out.println(document.get());
}

访问AST并重写实现案例

/**
 * @author 会灰翔的灰机
 * @date 2019/10/18
 */
public class SwitchesCleaner extends ASTVisitor {

    // 开关工具使用的代码关键字
    private static final String SWITCH_UTILS = "SwitchUtils";
    // 重写实例
    private ASTRewrite astRewrite;

    SwitchesCleaner(ASTRewrite astRewrite) {
        this.astRewrite = astRewrite;
    }

    // 访问所有If语句节点,开关均使用if(true) {} else {}语句处理业务)
    @Override
    public boolean visit(IfStatement node) {
        if (node.getExpression() != null) {
            Statement statement = null;
            boolean needReplace = false;
            // 处理开关为true的场景,例如:if (SwitchUtils.currentCityOpen(switchesNewBywayDegreeCal, order.getCityId()))
            if (isSwitchUtilsExpression(node.getExpression())) {
                statement = node.getThenStatement();
                needReplace = true;
            }
            // 处理开关为false的场景,例如:if(!SwitchUtils.currentCityOpen(openBywaydegreeLog, order.getCityId()))
            if (isSwitchUtilsPrefixExpression(node.getExpression())) {
                statement = node.getElseStatement();
                needReplace = true;
            }
            // 替换节点(即删除旧业务代码,保留新业务代码)
            if (needReplace) {
                astRewrite.replace(node, statement, null);
            }
        }
        return super.visit(node);
    }

    // 访问所有含中缀表达式,例如:!=,>等等中缀
    @Override
    public boolean visit(InfixExpression node) {
        // 处理开关与其他条件共同判断场景,例如:if (platformId != null && SwitchUtils.currentCityOpen(null, order.getCityId()) && orderType != null)
        if (isSwitchUtilsExpression(node.getLeftOperand())) {
            astRewrite.replace(node.getLeftOperand(), null, null);
        }
        if (isSwitchUtilsExpression(node.getRightOperand())) {
            astRewrite.replace(node.getRightOperand(), null, null);
        }
        return super.visit(node);
    }

    // 访问所有字段声明节点
    @Override
    public boolean visit(FieldDeclaration node) {
        astRewrite.replace(node, null, null);
        return super.visit(node);
    }

    private boolean isSwitchUtilsExpression(Expression expression){
        boolean isSwitchExpression = false;
        // 方法调用类型的表达式,并且包含关键字
        if (expression instanceof MethodInvocation && expression.toString().contains(SWITCH_UTILS)) {
            isSwitchExpression = true;
        }
        return isSwitchExpression;
    }

    private boolean isSwitchUtilsPrefixExpression(Expression expression){
        boolean isSwitchExpression = false;
        // 含前缀的表达式,例如:if(!SwitchUtils...
        if (expression instanceof PrefixExpression && expression.toString().contains(SWITCH_UTILS)) {
            isSwitchExpression = true;
        }
        return isSwitchExpression;
    }
}

总结

JDT是eclipse针对java语言开源的java语法的AST工具,idea也有引用,工具开源且功能强大而又稳定。是我我们这样的简单的语义分析与修改需求的福音啊。使用方法简单,官方文档齐全。总之感谢开源,感谢所有的开源贡献者-,拥抱开源吧23333

你可能感兴趣的:(java,算法)