From 9171b52b92df11d6163af8280de29f63ee5254fc Mon Sep 17 00:00:00 2001 From: glelouet Date: Wed, 11 Oct 2023 18:24:45 +0200 Subject: [PATCH] modified annotation to have shorter name for prefix, suffix, added option to camelcase the variable name and the method name. Created util to extract the var name and met name from the requested name and the annotation. Changed the tests to have the var match the getter, and implemented the var name and assignment replacement in the node. --- src/core/lombok/Onstruct.java | 28 +++- .../lombok/core/handlers/OnstructUtils.java | 24 ++++ .../eclipse/handlers/HandleOnstruct.java | 12 +- .../lombok/javac/handlers/HandleOnstruct.java | 132 ++++++++++++++++++ .../resource/before/OnstructBook.java | 20 ++- 5 files changed, 203 insertions(+), 13 deletions(-) create mode 100644 src/core/lombok/core/handlers/OnstructUtils.java create mode 100644 src/core/lombok/javac/handlers/HandleOnstruct.java diff --git a/src/core/lombok/Onstruct.java b/src/core/lombok/Onstruct.java index 34cb2d4df0..2c6f175818 100644 --- a/src/core/lombok/Onstruct.java +++ b/src/core/lombok/Onstruct.java @@ -42,8 +42,7 @@ * Before: * *
- * @Onstruct(prefix = "b_")
- * Object author, name, editiondate, purchasable = mybook;
+ * @Onstruct(pre = "b_") Object author, name, editiondate, purchasable = mybook;
  * 
* * After: @@ -60,8 +59,29 @@ @Retention(RetentionPolicy.SOURCE) public @interface Onstruct { - String prefix() default ""; + /** + * prefix to start the created var name with. Default is empty + */ + String pre() default ""; - String suffix() default ""; + /** + * suffix to append to created var name. Default is empty + */ + String suf() default ""; + + /** + * if true, should camel case the variable name. Only applied when prefix is + * non blank. Default is false. + */ + boolean cml() default false; + + /** prefix to start the getter call by. Default is "get" */ + String methodPre() default "get"; + + /** + * if true, should camel case the method call. Only applied when method + * prefix is non blank. Default is true. + */ + boolean methodCml() default true; } diff --git a/src/core/lombok/core/handlers/OnstructUtils.java b/src/core/lombok/core/handlers/OnstructUtils.java new file mode 100644 index 0000000000..fa4254dfbc --- /dev/null +++ b/src/core/lombok/core/handlers/OnstructUtils.java @@ -0,0 +1,24 @@ +package lombok.core.handlers; + +import lombok.Onstruct; + +public class OnstructUtils { + + public static String varName(String requestedName, Onstruct instance) { + String prefix = instance.pre(); + String suffix = instance.suf(); + boolean cml = instance.cml() && prefix != null && !prefix.isEmpty(); + return (prefix != null ? prefix : "") + (cml ? cml(requestedName) : requestedName) + (suffix != null ? suffix : ""); + } + + public static String methodName(String requestedName, Onstruct instance) { + String methodPrefix = instance.methodPre(); + if (methodPrefix == null || methodPrefix.isEmpty()) return requestedName; + return methodPrefix + (instance.methodCml() ? cml(requestedName) : requestedName); + } + + public static String cml(String name) { + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + +} diff --git a/src/core/lombok/eclipse/handlers/HandleOnstruct.java b/src/core/lombok/eclipse/handlers/HandleOnstruct.java index c2fb638a2a..059c6d34f8 100644 --- a/src/core/lombok/eclipse/handlers/HandleOnstruct.java +++ b/src/core/lombok/eclipse/handlers/HandleOnstruct.java @@ -1,21 +1,29 @@ package lombok.eclipse.handlers; +import java.io.PrintStream; + import org.eclipse.jdt.internal.compiler.ast.Annotation; import lombok.Onstruct; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; +import lombok.eclipse.DeferUntilPostDiet; +import lombok.eclipse.EclipseASTVisitor; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.spi.Provides; +@Provides +@DeferUntilPostDiet @HandlerPriority(65536) // same as HandleValue // TODO public class HandleOnstruct extends EclipseAnnotationHandler { public static final HandleOnstruct INSTANCE = new HandleOnstruct(); @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { - // TODO Auto-generated method stub - + PrintStream stream = System.out; + stream.println("got annotation on " + ast); + annotationNode.up().traverse(new EclipseASTVisitor.Printer(true, stream, true)); } diff --git a/src/core/lombok/javac/handlers/HandleOnstruct.java b/src/core/lombok/javac/handlers/HandleOnstruct.java new file mode 100644 index 0000000000..121e6520a6 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleOnstruct.java @@ -0,0 +1,132 @@ +package lombok.javac.handlers; + +import static lombok.javac.handlers.JavacHandlerUtil.deleteAnnotationIfNeccessary; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; + +import lombok.Onstruct; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.LombokNode; +import lombok.core.handlers.OnstructUtils; +import lombok.eclipse.DeferUntilPostDiet; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.spi.Provides; + +@Provides +@DeferUntilPostDiet +@HandlerPriority(65536) // same as HandleValue // TODO +public class HandleOnstruct extends JavacAnnotationHandler { + + + /** + * find the siblings with same kind and annotation. Copy of + * {@link LombokNode#upFromAnnotationToFields()} with same kind and no check + * on the parent. + * + */ + public static Collection upFromAnnotationToSameKind(JavacNode node) { + if (node.getKind() != Kind.ANNOTATION) return Collections.emptyList(); + JavacNode declaration = node.up(); + if (declaration == null) return Collections.emptyList(); + + List fields = new ArrayList(); + + for (JavacNode potentialField : declaration.up().down()) { + if (potentialField.getKind() != declaration.getKind()) continue; + for (JavacNode child : potentialField.down()) { + if (child.getKind() != Kind.ANNOTATION) continue; + if (child.get() == node.get()) fields.add(potentialField); + } + } + + return fields; + } + + /** + * retrieve the children statements from a list of node + */ + protected static List findChildrenStatements(Collection fields) { + List ret = new ArrayList(); + for (JavacNode f : fields) { + for (JavacNode potentialStatement : f.down()) { + if (potentialStatement.getKind() == Kind.STATEMENT) { + ret.add(potentialStatement.get()); + } + } + } + return ret; + } + + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + Collection annotatedVariables = upFromAnnotationToSameKind(annotationNode); + JavacNode parentNode = annotationNode.up(); + Onstruct annotationInstance = annotation.getInstance(); + deleteAnnotationIfNeccessary(annotationNode, Onstruct.class); + + List statements = findChildrenStatements(annotatedVariables); + // sanity checks on statements. Among the variables declaration, there + // must be one statement. + if (statements.isEmpty()) { + annotationNode.addError("no assignment. Requires one identifier assignment."); + return; + } + if (statements.size() > 1) { + annotationNode.addError("Too many assignments:" + statements + " Requires exactly one identifier assignment."); + return; + } + + JCTree tree = statements.get(0); + JCTree.JCIdent ident; + String varName = null; + // sanity checks on the assignment. It must be an identifier. + if (tree instanceof JCTree.JCIdent) { + ident = (JCTree.JCIdent) tree; + varName = (ident.name.toString()); + } else { + annotationNode.addError("invalid assignment" + tree + " : must be an identifier"); + return; + } + if (varName == null) { + annotationNode.addError("assignement is null . Must be an identifier"); + return; + } + + for (JavacNode f : annotatedVariables) { + handleVarDeclaration(f, annotationInstance, parentNode, ident); + } + // parentNode.rebuild(); + } + + private void handleVarDeclaration(JavacNode varNode, Onstruct annotationInstance, JavacNode parentNode, JCIdent ident) { + String varName = OnstructUtils.varName(varNode.getName(), annotationInstance); + String methName = OnstructUtils.methodName(varNode.getName(), annotationInstance); + JCTree elem = varNode.get(); + JavacTreeMaker maker = varNode.getTreeMaker(); + // System.out.println("create : var " + varName + " = " + ident.name + "." + methName + "();"); + if (elem instanceof JCVariableDecl) { + JCVariableDecl variable = (JCVariableDecl) elem; + variable.type = (JavacHandlerUtil.chainDotsString(varNode, "java.lang.String")).type; + JCExpression methCall = maker.Select(ident, varNode.toName(methName)); + variable.init = maker.Apply(com.sun.tools.javac.util.List.nil(), + methCall, + com.sun.tools.javac.util.List.nil()); + variable.name = varNode.toName(varName); + // System.out.println("replaced with " + variable); + } else + System.err.println(varNode.get()); + } + +} diff --git a/test/transform/resource/before/OnstructBook.java b/test/transform/resource/before/OnstructBook.java index 6d4501a10d..69a4d801fb 100644 --- a/test/transform/resource/before/OnstructBook.java +++ b/test/transform/resource/before/OnstructBook.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Onstruct; +import lombok.core.PrintAST; import lombok.experimental.Accessors; public class OnstructBook { @@ -21,21 +22,26 @@ public static class Book { void test() { Book mybook = new Book("author0", "bookname0", new Date(), true); - @Onstruct() - Object author, name, editiondate, purchasable = mybook; + @Onstruct + Object author, editionDate = mybook; + @Onstruct(methodPre = "") + Object name = mybook; assert Objects.equals(mybook.getAuthor(), author); assert Objects.equals(mybook.name(), name); - assert Objects.equals(mybook.getEditionDate(), editiondate); + assert Objects.equals(mybook.getEditionDate(), editionDate); + @Onstruct(methodPre = "is") + Object purchasable = mybook; assert Objects.equals(mybook.isPurchasable(), purchasable); } void testPrefix() { Book mybook = new Book("author0", "bookname0", new Date(), true); - @Onstruct(prefix = "b_") - Object author, name, editiondate, purchasable = mybook; + @Onstruct(pre = "b_") + Object author, editionDate = mybook; assert Objects.equals(mybook.getAuthor(), b_author); - assert Objects.equals(mybook.name(), b_name); - assert Objects.equals(mybook.getEditionDate(), b_editiondate); + assert Objects.equals(mybook.getEditionDate(), b_editionDate); + @Onstruct(pre="b_", methodPre = "is") + Object purchasable = mybook; assert Objects.equals(mybook.isPurchasable(), b_purchasable); }