aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok/javac/handlers/HandleMemoize.java
blob: 32199369abade241aa869288acc1728baae5b062 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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);
    }
}