From 83c63686d7ef6c745ed9d853e33c293f7d6bb8d5 Mon Sep 17 00:00:00 2001 From: nea Date: Thu, 6 Apr 2023 00:34:41 +0200 Subject: Initial draft for @lombok.experimental.Memoize This is an initial draft for how an @Memoize annotation could look like. It currently lacks features such as: - Thread safety - Cache invalidation - Soft/Weak References for a lower memory profile - Avoiding using a Map entirely for parameterless methods Ideally at least some of these properties should be configurable using flags and/or annotation properties (some users might prefer a non thread safe cache for better performance). Example Usage: ```java class Test { int invocationCount = 0; public static void main(String[] args) { Test test = new Test(); System.out.println(test.x(1, 2)); System.out.println(test.x(1, 2)); System.out.println(test.x(1, 3)); System.out.println("Invocation Count: " + test.invocationCount); } @lombok.experimental.Memoize public int x(int y, int z) { invocationCount++; return y + z; } } ``` --- src/core/lombok/experimental/Memoize.java | 11 ++ src/core/lombok/javac/handlers/HandleMemoize.java | 119 ++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/core/lombok/experimental/Memoize.java create mode 100644 src/core/lombok/javac/handlers/HandleMemoize.java diff --git a/src/core/lombok/experimental/Memoize.java b/src/core/lombok/experimental/Memoize.java new file mode 100644 index 00000000..d7f6c2e5 --- /dev/null +++ b/src/core/lombok/experimental/Memoize.java @@ -0,0 +1,11 @@ +package lombok.experimental; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface Memoize { +} diff --git a/src/core/lombok/javac/handlers/HandleMemoize.java b/src/core/lombok/javac/handlers/HandleMemoize.java new file mode 100644 index 00000000..32199369 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleMemoize.java @@ -0,0 +1,119 @@ +package lombok.javac.handlers; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; +import lombok.core.AST; +import lombok.core.AnnotationValues; +import lombok.experimental.Memoize; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.spi.Provides; + +import static lombok.javac.handlers.JavacHandlerUtil.*; + +@Provides +public class HandleMemoize extends JavacAnnotationHandler { + + @Override + public void handle(AnnotationValues annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) { + // TODO: handle experimental flag usage + + deleteAnnotationIfNeccessary(annotationNode, Memoize.class); + + JavacNode annotatedNode = annotationNode.up(); + if (annotatedNode.getKind() != AST.Kind.METHOD) { + annotationNode.addError("@Memoize is only valid on a method."); + return; + } + JCTree.JCMethodDecl md = (JCTree.JCMethodDecl) annotatedNode.get(); + + JavacNode containerNode = annotatedNode.up(); + if (containerNode.getKind() != AST.Kind.TYPE) { + annotationNode.addError("@Memoize is only valid on methods in a class."); + return; + } + JCTree.JCClassDecl cd = (JCTree.JCClassDecl) containerNode.get(); + + if ((md.mods.flags & Flags.ABSTRACT) != 0) { + annotationNode.addError("@Memoize is not valid on an abstract method."); + return; + } + + if (md.name.charAt(0) == '<') { + annotationNode.addError("@Memoize is not valid on a constructor."); + } + + boolean isStatic = (md.mods.flags & Flags.STATIC) != 0; + + JavacTreeMaker maker = containerNode.getTreeMaker(); + Name cacheName = annotationNode.toName("$lombok$memoizeCache$" + cd.name.toString() + "$" + md.name.toString()); + JCTree.JCVariableDecl cacheVariable = maker.VarDef( + maker.Modifiers(Flags.PRIVATE | Flags.TRANSIENT | (isStatic ? Flags.STATIC : 0)), + cacheName, + chainDots(annotatedNode, "java", "util", "Map"), + maker.NewClass(null, List.nil(), chainDots(annotatedNode, "java", "util", "HashMap"), List.nil(), null) + ); + injectFieldAndMarkGenerated(containerNode, cacheVariable); + recursiveSetGeneratedBy(cacheVariable, annotationNode); + + + Name newName = annotatedNode.toName(md.name.toString() + "$uncached"); + Name keyName = annotatedNode.toName("key"); + Name resultName = annotatedNode.toName("result"); + + ListBuffer paramNames = new ListBuffer(); + for (JCTree.JCVariableDecl param : md.params) { + paramNames.add(maker.Ident(param.name)); + } + + ListBuffer tyParamNames = new ListBuffer(); + for (JCTree.JCTypeParameter param : md.typarams) { + tyParamNames.add(maker.Ident(param.name)); + } + + + List newStatements = List.of( + maker.VarDef(maker.Modifiers(0), keyName, genJavaLangTypeRef(annotatedNode, ast.pos, "Object"), maker.Apply( + List.nil(), chainDots(annotatedNode, "java", "util", "Arrays", "asList"), List.of( + maker.NewArray( + genJavaLangTypeRef(annotatedNode, ast.pos, "Object"), + List.nil(), + paramNames.toList() + ) + ) + )), + maker.If( + maker.Apply( + List.nil(), chainDots(annotatedNode, cacheName.toString(), "containsKey"), List.of(maker.Ident(keyName)) + ), + maker.Return(maker.TypeCast( + md.restype, + maker.Apply( + List.nil(), chainDots(annotatedNode, cacheName.toString(), "get"), List.of(maker.Ident(keyName)) + ) + )), + maker.Block(0, List.of( + maker.VarDef(maker.Modifiers(0), resultName, md.restype, + maker.Apply(tyParamNames.toList(), maker.Select(isStatic ? maker.Ident(cd.name) : maker.Ident(annotatedNode.toName("this")), newName), paramNames.toList()) + ), + maker.Exec(maker.Apply(List.nil(), chainDots(annotatedNode, cacheName.toString(), "put"), List.of(maker.Ident(keyName), maker.Ident(resultName)))), + maker.Return(maker.Ident(resultName)) + )) + ) + ); + + ListBuffer newAnnotations = new ListBuffer(); + for (JCTree.JCAnnotation jcAnnotation : md.mods.annotations) { + if (jcAnnotation != annotationNode.get()) + newAnnotations.add(jcAnnotation); + } + md.mods.annotations = newAnnotations.toList(); + JCTree.JCMethodDecl newMethod = maker.MethodDef(maker.Modifiers((md.mods.flags & ~Flags.PUBLIC & ~Flags.PROTECTED) | Flags.PRIVATE, List.nil()), newName, md.restype, md.typarams, md.params, md.thrown, md.body, null); + injectMethod(containerNode, newMethod); + md.body = maker.Block(0, newStatements); + } +} -- cgit