用勺子修改java方法body
Modify java method body with spoon
我正在尝试重构旧的 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, code, 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 语句时,这会产生语法错误。自动导入打开,当有多个 class 同名时不添加导入(例如,Map 出现在少数库的 classpath 中)——这与文档一致。重构代码时可以避免这种情况吗?原始 java 文件具有正确的导入。
我尝试的另一种方法是直接操纵 body 作为一个整体。
@Override
public void process(CtMethod method) {
String code = method.getBody()
.getOriginalSourceFragment()
.getSourceCode();
Node beanNode = getBeanNode(method);
code = replaceViewCalls(beanNode, code, successView);
code = replaceViewCalls(beanNode, code, formView);
CtCodeSnippetStatement codeStatement = getFactory().createCodeSnippetStatement(code);
method.setBody(codeStatement);
}
这仍然存在与第一个相同的自动导入问题。除此之外,它还添加了多余的花括号,例如
void method() { x=y;}
会变成
void method() { {x=y;} }
那当然会很漂亮。
另外 javagetOriginalSourceFragment() 的文档也有以下警告
Warning: this is a advanced method which cannot be considered as part
of the stable API
我想做的另一件事是为 getSuccessView() 的每种用法创建模式,例如
viewName = getSuccessView();
return getSuccessView();
returnModelAndView(getSuccessView(), 地图);等等,但是为此我将不得不编写一大堆处理器/模板。
因为它是简单的替换,最简单的就是做下面的事情
//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
});
由于我可以在 spoon 的帮助下避免文本操作,例如更改修饰符、注释、方法名称、注释等,我希望应该有更好的方法来修改方法 body。
您应该将处理器输入视为抽象语法树而不是字符串:
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 元素。然后将找到的元素替换为新的字符串文字元素。
我正在尝试重构旧的 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, code, 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 语句时,这会产生语法错误。自动导入打开,当有多个 class 同名时不添加导入(例如,Map 出现在少数库的 classpath 中)——这与文档一致。重构代码时可以避免这种情况吗?原始 java 文件具有正确的导入。
我尝试的另一种方法是直接操纵 body 作为一个整体。
@Override
public void process(CtMethod method) {
String code = method.getBody()
.getOriginalSourceFragment()
.getSourceCode();
Node beanNode = getBeanNode(method);
code = replaceViewCalls(beanNode, code, successView);
code = replaceViewCalls(beanNode, code, formView);
CtCodeSnippetStatement codeStatement = getFactory().createCodeSnippetStatement(code);
method.setBody(codeStatement);
}
这仍然存在与第一个相同的自动导入问题。除此之外,它还添加了多余的花括号,例如
void method() { x=y;}
会变成
void method() { {x=y;} }
那当然会很漂亮。
另外 javagetOriginalSourceFragment() 的文档也有以下警告
Warning: this is a advanced method which cannot be considered as part of the stable API
我想做的另一件事是为 getSuccessView() 的每种用法创建模式,例如 viewName = getSuccessView(); return getSuccessView(); returnModelAndView(getSuccessView(), 地图);等等,但是为此我将不得不编写一大堆处理器/模板。
因为它是简单的替换,最简单的就是做下面的事情
//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
});
由于我可以在 spoon 的帮助下避免文本操作,例如更改修饰符、注释、方法名称、注释等,我希望应该有更好的方法来修改方法 body。
您应该将处理器输入视为抽象语法树而不是字符串:
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 元素。然后将找到的元素替换为新的字符串文字元素。