/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.codehaus.groovy.transform; import groovy.transform.Memoized; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.classgen.VariableScopeVisitor; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import java.util.ArrayList; import java.util.List; import static org.codehaus.groovy.ast.ClassHelper.make; import static org.codehaus.groovy.ast.tools.GeneralUtils.args; import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX; import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; import static org.codehaus.groovy.ast.tools.GeneralUtils.cloneParams; import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX; import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; import static org.codehaus.groovy.ast.tools.GenericsUtils.newClass; /** * Handles generation of code for the {@link Memoized} annotation. * * @author Andrey Bloschetsov */ @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class MemoizedASTTransformation extends AbstractASTTransformation { private static final String CLOSURE_CALL_METHOD_NAME = "call"; private static final Class<Memoized> MY_CLASS = Memoized.class; private static final ClassNode MY_TYPE = make(MY_CLASS); private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); private static final String PROTECTED_CACHE_SIZE_NAME = "protectedCacheSize"; private static final String MAX_CACHE_SIZE_NAME = "maxCacheSize"; private static final String CLOSURE_LABEL = "Closure"; private static final String METHOD_LABEL = "Priv"; public void visit(ASTNode[] nodes, final SourceUnit source) { init(nodes, source); AnnotationNode annotationNode = (AnnotationNode) nodes[0]; AnnotatedNode annotatedNode = (AnnotatedNode) nodes[1]; if (MY_TYPE.equals(annotationNode.getClassNode()) && annotatedNode instanceof MethodNode) { MethodNode methodNode = (MethodNode) annotatedNode; if (methodNode.isAbstract()) { addError("Annotation " + MY_TYPE_NAME + " cannot be used for abstract methods.", methodNode); return; } if (methodNode.isVoidMethod()) { addError("Annotation " + MY_TYPE_NAME + " cannot be used for void methods.", methodNode); return; } ClassNode ownerClassNode = methodNode.getDeclaringClass(); MethodNode delegatingMethod = buildDelegatingMethod(methodNode, ownerClassNode); ownerClassNode.addMethod(delegatingMethod); int modifiers = FieldNode.ACC_PRIVATE | FieldNode.ACC_FINAL; if (methodNode.isStatic()) { modifiers = modifiers | FieldNode.ACC_STATIC; } int protectedCacheSize = getMemberIntValue(annotationNode, PROTECTED_CACHE_SIZE_NAME); int maxCacheSize = getMemberIntValue(annotationNode, MAX_CACHE_SIZE_NAME); MethodCallExpression memoizeClosureCallExpression = buildMemoizeClosureCallExpression(delegatingMethod, protectedCacheSize, maxCacheSize); String memoizedClosureFieldName = buildUniqueName(ownerClassNode, CLOSURE_LABEL, methodNode); FieldNode memoizedClosureField = new FieldNode(memoizedClosureFieldName, modifiers, newClass(ClassHelper.CLOSURE_TYPE), null, memoizeClosureCallExpression); ownerClassNode.addField(memoizedClosureField); BlockStatement newCode = new BlockStatement(); MethodCallExpression closureCallExpression = callX( fieldX(memoizedClosureField), CLOSURE_CALL_METHOD_NAME, args(methodNode.getParameters())); closureCallExpression.setImplicitThis(false); newCode.addStatement(returnS(closureCallExpression)); methodNode.setCode(newCode); VariableScopeVisitor visitor = new VariableScopeVisitor(source); if (ownerClassNode instanceof InnerClassNode) { visitor.visitClass(((InnerClassNode) ownerClassNode).getOuterMostClass()); } else { visitor.visitClass(ownerClassNode); } } } private static MethodNode buildDelegatingMethod(final MethodNode annotatedMethod, final ClassNode ownerClassNode) { Statement code = annotatedMethod.getCode(); int access = ACC_PROTECTED; if (annotatedMethod.isStatic()) { access = ACC_PRIVATE | ACC_STATIC; } MethodNode method = new MethodNode( buildUniqueName(ownerClassNode, METHOD_LABEL, annotatedMethod), access, annotatedMethod.getReturnType(), cloneParams(annotatedMethod.getParameters()), annotatedMethod.getExceptions(), code ); List<AnnotationNode> sourceAnnotations = annotatedMethod.getAnnotations(); method.addAnnotations(new ArrayList<AnnotationNode>(sourceAnnotations)); return method; } private static final String MEMOIZE_METHOD_NAME = "memoize"; private static final String MEMOIZE_AT_MOST_METHOD_NAME = "memoizeAtMost"; private static final String MEMOIZE_AT_LEAST_METHOD_NAME = "memoizeAtLeast"; private static final String MEMOIZE_BETWEEN_METHOD_NAME = "memoizeBetween"; private static MethodCallExpression buildMemoizeClosureCallExpression(MethodNode privateMethod, int protectedCacheSize, int maxCacheSize) { Parameter[] srcParams = privateMethod.getParameters(); Parameter[] newParams = cloneParams(srcParams); List<Expression> argList = new ArrayList<Expression>(newParams.length); for (int i = 0; i < srcParams.length; i++) { argList.add(varX(newParams[i])); } ClosureExpression expression = new ClosureExpression( newParams, stmt(callThisX(privateMethod.getName(), args(argList))) ); MethodCallExpression mce; if (protectedCacheSize == 0 && maxCacheSize == 0) { mce = callX(expression, MEMOIZE_METHOD_NAME); } else if (protectedCacheSize == 0) { mce = callX(expression, MEMOIZE_AT_MOST_METHOD_NAME, args(constX(maxCacheSize))); } else if (maxCacheSize == 0) { mce = callX(expression, MEMOIZE_AT_LEAST_METHOD_NAME, args(constX(protectedCacheSize))); } else { mce = callX(expression, MEMOIZE_BETWEEN_METHOD_NAME, args(constX(protectedCacheSize), constX(maxCacheSize))); } mce.setImplicitThis(false); return mce; } private static String buildUniqueName(ClassNode owner, String ident, MethodNode methodNode) { StringBuilder nameBuilder = new StringBuilder("memoizedMethod" + ident + "$").append(methodNode.getName()); if (methodNode.getParameters() != null) { for (Parameter parameter : methodNode.getParameters()) { nameBuilder.append(buildTypeName(parameter.getType())); } } while (owner.getField(nameBuilder.toString()) != null) { nameBuilder.insert(0, "_"); } return nameBuilder.toString(); } private static String buildTypeName(ClassNode type) { if (type.isArray()) { return String.format("%sArray", buildTypeName(type.getComponentType())); } return type.getNameWithoutPackage(); } }