/* * 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 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.Parameter; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.EmptyExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.stmt.SynchronizedStatement; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.runtime.MetaClassHelper; import java.lang.ref.SoftReference; import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching; import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS; import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX; import static org.codehaus.groovy.ast.tools.GeneralUtils.block; 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.classX; import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; import static org.codehaus.groovy.ast.tools.GeneralUtils.declS; import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS; import static org.codehaus.groovy.ast.tools.GeneralUtils.notNullX; import static org.codehaus.groovy.ast.tools.GeneralUtils.param; import static org.codehaus.groovy.ast.tools.GeneralUtils.params; import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; 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; /** * Handles generation of code for the @Lazy annotation */ @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class LazyASTTransformation extends AbstractASTTransformation { private static final ClassNode SOFT_REF = makeWithoutCaching(SoftReference.class, false); private static final Expression NULL_EXPR = ConstantExpression.NULL; public void visit(ASTNode[] nodes, SourceUnit source) { init(nodes, source); AnnotatedNode parent = (AnnotatedNode) nodes[1]; AnnotationNode node = (AnnotationNode) nodes[0]; if (parent instanceof FieldNode) { final FieldNode fieldNode = (FieldNode) parent; visitField(this, node, fieldNode); } } static void visitField(ErrorCollecting xform, AnnotationNode node, FieldNode fieldNode) { final Expression soft = node.getMember("soft"); final Expression init = getInitExpr(xform, fieldNode); String backingFieldName = "$" + fieldNode.getName(); fieldNode.rename(backingFieldName); fieldNode.setModifiers(ACC_PRIVATE | (fieldNode.getModifiers() & (~(ACC_PUBLIC | ACC_PROTECTED)))); PropertyNode pNode = fieldNode.getDeclaringClass().getProperty(backingFieldName); if (pNode != null) { fieldNode.getDeclaringClass().getProperties().remove(pNode); } if (soft instanceof ConstantExpression && ((ConstantExpression) soft).getValue().equals(true)) { createSoft(fieldNode, init); } else { create(fieldNode, init); // @Lazy not meaningful with primitive so convert to wrapper if needed if (ClassHelper.isPrimitiveType(fieldNode.getType())) { fieldNode.setType(ClassHelper.getWrapper(fieldNode.getType())); } } } private static void create(FieldNode fieldNode, final Expression initExpr) { final BlockStatement body = new BlockStatement(); if (fieldNode.isStatic()) { addHolderClassIdiomBody(body, fieldNode, initExpr); } else if (fieldNode.isVolatile()) { addDoubleCheckedLockingBody(body, fieldNode, initExpr); } else { addNonThreadSafeBody(body, fieldNode, initExpr); } addMethod(fieldNode, body, fieldNode.getType()); } private static void addHolderClassIdiomBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) { final ClassNode declaringClass = fieldNode.getDeclaringClass(); final ClassNode fieldType = fieldNode.getType(); final int visibility = ACC_PRIVATE | ACC_STATIC; final String fullName = declaringClass.getName() + "$" + fieldType.getNameWithoutPackage() + "Holder_" + fieldNode.getName().substring(1); final InnerClassNode holderClass = new InnerClassNode(declaringClass, fullName, visibility, ClassHelper.OBJECT_TYPE); final String innerFieldName = "INSTANCE"; // we have two options: // (1) embed initExpr within holder class but redirect field access/method calls to declaring class members // (2) keep initExpr within a declaring class method that is only called by the holder class // currently we have gone with (2) for simplicity with only a slight memory footprint increase in the declaring class final String initializeMethodName = (fullName + "_initExpr").replace('.', '_'); declaringClass.addMethod(initializeMethodName, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, fieldType, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, returnS(initExpr)); holderClass.addField(innerFieldName, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, fieldType, callX(declaringClass, initializeMethodName)); final Expression innerField = propX(classX(holderClass), innerFieldName); declaringClass.getModule().addClass(holderClass); body.addStatement(returnS(innerField)); } private static void addDoubleCheckedLockingBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) { final Expression fieldExpr = varX(fieldNode); final VariableExpression localVar = varX(fieldNode.getName() + "_local"); body.addStatement(declS(localVar, fieldExpr)); body.addStatement(ifElseS( notNullX(localVar), returnS(localVar), new SynchronizedStatement( syncTarget(fieldNode), ifElseS( notNullX(fieldExpr), returnS(fieldExpr), returnS(assignX(fieldExpr, initExpr)) ) ) )); } private static void addNonThreadSafeBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) { final Expression fieldExpr = varX(fieldNode); body.addStatement(ifElseS(notNullX(fieldExpr), stmt(fieldExpr), assignS(fieldExpr, initExpr))); } private static void addMethod(FieldNode fieldNode, BlockStatement body, ClassNode type) { int visibility = ACC_PUBLIC; if (fieldNode.isStatic()) visibility |= ACC_STATIC; String propName = MetaClassHelper.capitalize(fieldNode.getName().substring(1)); fieldNode.getDeclaringClass().addMethod("get" + propName, visibility, type, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body); if (ClassHelper.boolean_TYPE.equals(type)) { fieldNode.getDeclaringClass().addMethod("is" + propName, visibility, type, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, stmt(callThisX("get" + propName))); } } private static void createSoft(FieldNode fieldNode, Expression initExpr) { final ClassNode type = fieldNode.getType(); fieldNode.setType(SOFT_REF); createSoftGetter(fieldNode, initExpr, type); createSoftSetter(fieldNode, type); } private static void createSoftGetter(FieldNode fieldNode, Expression initExpr, ClassNode type) { final BlockStatement body = new BlockStatement(); final Expression fieldExpr = varX(fieldNode); final Expression resExpr = varX("res", type); final MethodCallExpression callExpression = callX(fieldExpr, "get"); callExpression.setSafe(true); body.addStatement(declS(resExpr, callExpression)); final Statement mainIf = ifElseS(notNullX(resExpr), stmt(resExpr), block( assignS(resExpr, initExpr), assignS(fieldExpr, ctorX(SOFT_REF, resExpr)), stmt(resExpr))); if (fieldNode.isVolatile()) { body.addStatement(ifElseS( notNullX(resExpr), stmt(resExpr), new SynchronizedStatement(syncTarget(fieldNode), block( assignS(resExpr, callExpression), mainIf) ) )); } else { body.addStatement(mainIf); } addMethod(fieldNode, body, type); } private static void createSoftSetter(FieldNode fieldNode, ClassNode type) { final BlockStatement body = new BlockStatement(); final Expression fieldExpr = varX(fieldNode); final String name = "set" + MetaClassHelper.capitalize(fieldNode.getName().substring(1)); final Parameter parameter = param(type, "value"); final Expression paramExpr = varX(parameter); body.addStatement(ifElseS( notNullX(paramExpr), assignS(fieldExpr, ctorX(SOFT_REF, paramExpr)), assignS(fieldExpr, NULL_EXPR) )); int visibility = ACC_PUBLIC; if (fieldNode.isStatic()) visibility |= ACC_STATIC; fieldNode.getDeclaringClass().addMethod(name, visibility, ClassHelper.VOID_TYPE, params(parameter), ClassNode.EMPTY_ARRAY, body); } private static Expression syncTarget(FieldNode fieldNode) { return fieldNode.isStatic() ? classX(fieldNode.getDeclaringClass()) : varX("this"); } private static Expression getInitExpr(ErrorCollecting xform, FieldNode fieldNode) { Expression initExpr = fieldNode.getInitialValueExpression(); fieldNode.setInitialValueExpression(null); if (initExpr == null || initExpr instanceof EmptyExpression) { if (fieldNode.getType().isAbstract()) { xform.addError("You cannot lazily initialize '" + fieldNode.getName() + "' from the abstract class '" + fieldNode.getType().getName() + "'", fieldNode); } initExpr = ctorX(fieldNode.getType()); } return initExpr; } }