blob: 871fe7081e26adeb9f79bdd2755e594156a2f75a [file] [log] [blame]
/*
* 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.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.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);
fieldNode.rename("$" + fieldNode.getName());
fieldNode.setModifiers(ACC_PRIVATE | (fieldNode.getModifiers() & (~(ACC_PUBLIC | ACC_PROTECTED))));
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;
final String name = "get" + MetaClassHelper.capitalize(fieldNode.getName().substring(1));
fieldNode.getDeclaringClass().addMethod(name, visibility, type, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body);
}
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;
}
}