aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornea <nea@nea.moe>2023-04-06 00:34:41 +0200
committernea <nea@nea.moe>2023-04-06 01:04:06 +0200
commit83c63686d7ef6c745ed9d853e33c293f7d6bb8d5 (patch)
tree30d84f0d61b532d92b12de18335602076973e091
parentb80ff39d82cadf5f61e65e022d6e828496b091ef (diff)
downloadlombok-feat/memoize.tar.gz
lombok-feat/memoize.tar.bz2
lombok-feat/memoize.zip
Initial draft for @lombok.experimental.Memoizefeat/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; } } ```
-rw-r--r--src/core/lombok/experimental/Memoize.java11
-rw-r--r--src/core/lombok/javac/handlers/HandleMemoize.java119
2 files changed, 130 insertions, 0 deletions
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<Memoize> {
+
+ @Override
+ public void handle(AnnotationValues<Memoize> 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.<JCTree.JCExpression>nil(), chainDots(annotatedNode, "java", "util", "HashMap"), List.<JCTree.JCExpression>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<JCTree.JCExpression> paramNames = new ListBuffer<JCTree.JCExpression>();
+ for (JCTree.JCVariableDecl param : md.params) {
+ paramNames.add(maker.Ident(param.name));
+ }
+
+ ListBuffer<JCTree.JCExpression> tyParamNames = new ListBuffer<JCTree.JCExpression>();
+ for (JCTree.JCTypeParameter param : md.typarams) {
+ tyParamNames.add(maker.Ident(param.name));
+ }
+
+
+ List<JCTree.JCStatement> newStatements = List.<JCTree.JCStatement>of(
+ maker.VarDef(maker.Modifiers(0), keyName, genJavaLangTypeRef(annotatedNode, ast.pos, "Object"), maker.Apply(
+ List.<JCTree.JCExpression>nil(), chainDots(annotatedNode, "java", "util", "Arrays", "asList"), List.<JCTree.JCExpression>of(
+ maker.NewArray(
+ genJavaLangTypeRef(annotatedNode, ast.pos, "Object"),
+ List.<JCTree.JCExpression>nil(),
+ paramNames.toList()
+ )
+ )
+ )),
+ maker.If(
+ maker.Apply(
+ List.<JCTree.JCExpression>nil(), chainDots(annotatedNode, cacheName.toString(), "containsKey"), List.<JCTree.JCExpression>of(maker.Ident(keyName))
+ ),
+ maker.Return(maker.TypeCast(
+ md.restype,
+ maker.Apply(
+ List.<JCTree.JCExpression>nil(), chainDots(annotatedNode, cacheName.toString(), "get"), List.<JCTree.JCExpression>of(maker.Ident(keyName))
+ )
+ )),
+ maker.Block(0, List.<JCTree.JCStatement>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.<JCTree.JCExpression>nil(), chainDots(annotatedNode, cacheName.toString(), "put"), List.<JCTree.JCExpression>of(maker.Ident(keyName), maker.Ident(resultName)))),
+ maker.Return(maker.Ident(resultName))
+ ))
+ )
+ );
+
+ ListBuffer<JCTree.JCAnnotation> newAnnotations = new ListBuffer<JCTree.JCAnnotation>();
+ 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.<JCTree.JCAnnotation>nil()), newName, md.restype, md.typarams, md.params, md.thrown, md.body, null);
+ injectMethod(containerNode, newMethod);
+ md.body = maker.Block(0, newStatements);
+ }
+}