微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

用勺子修改java方法体

如何解决用勺子修改java方法体

我正在尝试重构旧的 SimpleFormController。我想用实际的成功视图和表单视图字符串替换 getSuccessView() 和 gerFormView() 调用

我浏览了https://spoon.gforge.inria.fr/first_transformation.html,它展示了如何生成添加语句,但我不明白如何修改

我尝试了几件事。

用 getSuccessView() 和 getFormView() 调用替换语句

public class SimpleFormControllerReplaceViewCall extends AbstractProcessor<CtMethod> {
    MetaData Meta;
    String successView= "successView";
    String formView = "formView";
    
    public SimpleFormControllerReplaceViewCall(MetaData Meta)  {
        this.Meta = Meta;
    }
    
    @Override
    public boolean isToBeProcessed(CtMethod candidate) {
        if(candidate.getBody() == null) { //Ignore abstract methods
            return false;
        }
        
        String sourceCode;
        try {
            sourceCode = candidate.getBody()
            .getoriginalSourceFragment()
            .getSourceCode();
        } catch (Exception e) {
            return false;
        }
        
        return sourceCode.contains(getViewFunctionName(successView)) 
                ||  sourceCode.contains(getViewFunctionName(formView));
    }
    
    @Override
    public void process(CtMethod method) {
        Node beanNode = getBeanNode(method);
        
        CtBlock<Object> body = getFactory().createBlock();

        method.getBody().getStatements()
            .stream()
            .map(s -> {
                Optional<String> sourceCode = getStatementSourceCode(s);
                if(!sourceCode.isPresent()) {
                    return s.clone(); // Clone required to handle runtime error for trying attach a node to two parents
                } else {
                    System.out.println("Modifying: " + method.getSignature());
                    String code = sourceCode.get();
                    code = replaceViewCalls(beanNode,code,successView);
                    code = replaceViewCalls(beanNode,formView);
                    return getFactory().createCodeSnippetStatement(code);
                }
            }).forEach(body::addStatement);
        
        
        
        method.setBody(body);
    }
    
    private Optional<String> getStatementSourceCode(CtStatement s) {
        String sourceCode = null;
        
        try {
            sourceCode = s.getoriginalSourceFragment()
            .getSourceCode();
        } catch (Exception e) {}
        
        System.out.println(sourceCode);
        if (sourceCode != null && 
                (sourceCode.contains(getViewFunctionName(successView)) 
                        || sourceCode.contains(getViewFunctionName(formView)))) {
            sourceCode = sourceCode.trim();
            if(sourceCode.endsWith(";"))
                sourceCode = sourceCode.substring(0,sourceCode.length()-1);
            return Optional.of(sourceCode);
        } else {
            return Optional.empty();
        }
    }
    
    public String replaceViewCalls(Node beanNode,String code,String viewType) {
        String getViewFunctionName = getViewFunctionName(viewType);
        if (!code.contains(getViewFunctionName)) {
            return code;
        }
        String view = AppUtil.getSpringBeanPropertyValue(beanNode,viewType);
        return code.replaceAll(getViewFunctionName + "\\(\\)",String.format("\"%s\"",view));
    }
    
    public Node getBeanNode(CtMethod method) {
        String qualifiedname = method.getParent(CtClass.class).getQualifiedname();
        return Meta.getFullyQualifiednametoNodeMap().get(qualifiedname);
    }
    
    private String getViewFunctionName(String viewType) {
        return "get" + viewType.substring(0,1).toupperCase() + viewType.substring(1);
    }
}

然而,这会在块的末尾添加不需要的 if() {... };当 if {} else {} 块包含 return 语句时,这会产生语法错误。当有多个同名类(例如,Map 存在于少数库的类路径中)时,自动导入已打开,并且不会添加导入 - 这与文档一致。重构代码时可以避免这种情况吗?原始 java 文件具有正确的导入。

我尝试的另一种方法是直接操纵整个身体。

@Override
public void process(CtMethod method) {
    String code = method.getBody()
            .getoriginalSourceFragment()
            .getSourceCode();
    
    Node beanNode = getBeanNode(method);
    
    code = replaceViewCalls(beanNode,successView);
    code = replaceViewCalls(beanNode,formView);
    
    CtCodeSnippetStatement codeStatement = getFactory().createCodeSnippetStatement(code);
    method.setBody(codeStatement);
}

这仍然具有与第一个相同的自动导入问题。除此之外,它还添加了多余的花括号,例如

void method() { x=y;} 

将成为

void method() { {x=y;} }

那当然会很漂亮。

getoriginalSourceFragment() 的 javadocs 也有以下警告

警告:这是一种高级方法,不能被视为一部分 稳定的API

我想做的另一件事是为 getSuccessView() 的每种使用类型创建模式,例如 视图名称 = getSuccessView(); 返回 getSuccessView(); 返回 ModelAndView(getSuccessView(),map);等等,但是为此我将不得不编写一大堆处理器/模板。

因为它是简单的替换,最简单的就是做下面的事情

//Walk over all files and execute
Files.lines(Paths.get("/path/to/java/file"))
        .map(l -> l.replaceAll("getSuccessView\\(\\)","actualViewNameWithEscapedQuotes"))
        .map(l -> l.replaceAll("getFormView\\(\\)","actualViewNameWithEscapedQuotes"))
        .forEach(l -> {
            //write to file
        });

由于我可以在勺子的帮助下避免文本操作,例如更改修饰符、注释、方法名称、注释等,我希望应该有更好的方法修改方法主体。

解决方法

您应该将处理器输入视为抽象语法树而不是字符串:

public class SimpleFormControllerReplaceViewCall extends AbstractProcessor<CtMethod<?>> {

    @Override
    public boolean isToBeProcessed(CtMethod candidate) {
        if(candidate.isAbstract()) { //Ignore abstract methods
            return false;
        }
        return !candidate.filterChildren((CtInvocation i)->
                i.getExecutable().getSimpleName().equals("getSuccessView")
                        || i.getExecutable().getSimpleName().equals("getFormView")).list().isEmpty();
    }

    @Override
    public void process(CtMethod<?> ctMethod) {
        Launcher launcher = new Launcher();
        CodeFactory factory = launcher.createFactory().Code();
        List<CtInvocation> invocations = ctMethod.filterChildren((CtInvocation i)->
                i.getExecutable().getSimpleName().equals("getSuccessView")
        || i.getExecutable().getSimpleName().equals("getFormView")).list();
        for(CtInvocation i : invocations) {
            if(i.getExecutable().getSimpleName().equals("getSuccessView")) {
                i.replace(factory.createLiteral("successView"));
            } else {
                i.replace(factory.createLiteral("formView"));
            }
        }
    }
}

此处遍历 CtMethod AST 以搜索具有指定属性的 CtInvocation 元素。然后用新的字符串文字元素替换找到的元素。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。