diff options
author | nea <nea@nea.moe> | 2023-04-06 00:34:41 +0200 |
---|---|---|
committer | nea <nea@nea.moe> | 2023-04-06 01:04:06 +0200 |
commit | 83c63686d7ef6c745ed9d853e33c293f7d6bb8d5 (patch) | |
tree | 30d84f0d61b532d92b12de18335602076973e091 | |
parent | b80ff39d82cadf5f61e65e022d6e828496b091ef (diff) | |
download | lombok-83c63686d7ef6c745ed9d853e33c293f7d6bb8d5.tar.gz lombok-83c63686d7ef6c745ed9d853e33c293f7d6bb8d5.tar.bz2 lombok-83c63686d7ef6c745ed9d853e33c293f7d6bb8d5.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.java | 11 | ||||
-rw-r--r-- | src/core/lombok/javac/handlers/HandleMemoize.java | 119 |
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); + } +} |