/*
* Copyright 2008-2010 the original author or authors.
*
* Licensed 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.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.syntax.Token;
import org.objectweb.asm.Opcodes;
import java.lang.ref.SoftReference;
import java.util.Arrays;
/**
* Handles generation of code for the @Lazy annotation
*
* @author Alex Tkachman
* @author Paul King
*/
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class LazyASTTransformation implements ASTTransformation, Opcodes {
private static final ClassNode SOFT_REF = ClassHelper.make(SoftReference.class);
private static final Expression NULL_EXPR = ConstantExpression.NULL;
private static final ClassNode OBJECT_TYPE = new ClassNode(Object.class);
private static final Token ASSIGN = Token.newSymbol("=", -1, -1);
private static final Token COMPARE_NOT_EQUAL = Token.newSymbol("!=", -1, -1);
public void visit(ASTNode[] nodes, SourceUnit source) {
if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
throw new RuntimeException("Internal error: expecting [AnnotationNode, AnnotatedNode] but got: " + Arrays.asList(nodes));
}
AnnotatedNode parent = (AnnotatedNode) nodes[1];
AnnotationNode node = (AnnotationNode) nodes[0];
if (parent instanceof FieldNode) {
final FieldNode fieldNode = (FieldNode) parent;
final Expression member = node.getMember("soft");
final Expression init = getInitExpr(fieldNode);
fieldNode.rename("$" + fieldNode.getName());
fieldNode.setModifiers(ACC_PRIVATE | (fieldNode.getModifiers() & (~(ACC_PUBLIC | ACC_PROTECTED))));
if (member instanceof ConstantExpression && ((ConstantExpression) member).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 void create(FieldNode fieldNode, final Expression initExpr) {
final BlockStatement body = new BlockStatement();
if (fieldNode.isStatic()) {
addHolderClassIdiomBody(body, fieldNode, initExpr);
} else if (isVolatile(fieldNode)) {
addNonThreadSafeBody(body, fieldNode, initExpr);
} else {
addDoubleCheckedLockingBody(body, fieldNode, initExpr);
}
addMethod(fieldNode, body, fieldNode.getType());
}
private 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, OBJECT_TYPE);
final String innerFieldName = "INSTANCE";
holderClass.addField(innerFieldName, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, fieldType, initExpr);
final Expression innerField = new PropertyExpression(new ClassExpression(holderClass), innerFieldName);
declaringClass.getModule().addClass(holderClass);
body.addStatement(new ReturnStatement(innerField));
}
private void addDoubleCheckedLockingBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
final Expression fieldExpr = new VariableExpression(fieldNode);
final VariableExpression localVar = new VariableExpression(fieldNode.getName() + "_local");
body.addStatement(new ExpressionStatement(new DeclarationExpression(localVar, ASSIGN, fieldExpr)));
body.addStatement(new IfStatement(
new BooleanExpression(new BinaryExpression(localVar, COMPARE_NOT_EQUAL, NULL_EXPR)),
new ReturnStatement(localVar),
new SynchronizedStatement(
synchTarget(fieldNode),
new IfStatement(
new BooleanExpression(new BinaryExpression(fieldExpr, COMPARE_NOT_EQUAL, NULL_EXPR)),
new ReturnStatement(fieldExpr),
new ReturnStatement(new BinaryExpression(fieldExpr, ASSIGN, initExpr))
)
)
));
}
private void addNonThreadSafeBody(BlockStatement body, FieldNode fieldNode, Expression initExpr) {
final Expression fieldExpr = new VariableExpression(fieldNode);
body.addStatement(new IfStatement(
new BooleanExpression(new BinaryExpression(fieldExpr, COMPARE_NOT_EQUAL, NULL_EXPR)),
new ExpressionStatement(fieldExpr),
new ExpressionStatement(new BinaryExpression(fieldExpr, ASSIGN, initExpr))
));
}
private 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 void createSoft(FieldNode fieldNode, Expression initExpr) {
final ClassNode type = fieldNode.getType();
fieldNode.setType(SOFT_REF);
createSoftGetter(fieldNode, initExpr, type);
createSoftSetter(fieldNode, type);
}
private void createSoftGetter(FieldNode fieldNode, Expression initExpr, ClassNode type) {
final BlockStatement body = new BlockStatement();
final Expression fieldExpr = new VariableExpression(fieldNode);
final Expression resExpr = new VariableExpression("res", type);
final MethodCallExpression callExpression = new MethodCallExpression(fieldExpr, "get", new ArgumentListExpression());
callExpression.setSafe(true);
body.addStatement(new ExpressionStatement(new DeclarationExpression(resExpr, ASSIGN, callExpression)));
final BlockStatement elseBlock = new BlockStatement();
elseBlock.addStatement(new ExpressionStatement(new BinaryExpression(resExpr, ASSIGN, initExpr)));
elseBlock.addStatement(new ExpressionStatement(new BinaryExpression(fieldExpr, ASSIGN, new ConstructorCallExpression(SOFT_REF, resExpr))));
elseBlock.addStatement(new ExpressionStatement(resExpr));
final Statement mainIf = new IfStatement(
new BooleanExpression(new BinaryExpression(resExpr, COMPARE_NOT_EQUAL, NULL_EXPR)),
new ExpressionStatement(resExpr),
elseBlock
);
if (isVolatile(fieldNode)) {
body.addStatement(mainIf);
} else {
body.addStatement(new IfStatement(
new BooleanExpression(new BinaryExpression(resExpr, COMPARE_NOT_EQUAL, NULL_EXPR)),
new ExpressionStatement(resExpr),
new SynchronizedStatement(synchTarget(fieldNode), mainIf)
));
}
addMethod(fieldNode, body, type);
}
private void createSoftSetter(FieldNode fieldNode, ClassNode type) {
final BlockStatement body = new BlockStatement();
final Expression fieldExpr = new VariableExpression(fieldNode);
final String name = "set" + MetaClassHelper.capitalize(fieldNode.getName().substring(1));
final Parameter parameter = new Parameter(type, "value");
final Expression paramExpr = new VariableExpression(parameter);
body.addStatement(new IfStatement(
new BooleanExpression(new BinaryExpression(paramExpr, COMPARE_NOT_EQUAL, NULL_EXPR)),
new ExpressionStatement(new BinaryExpression(fieldExpr, ASSIGN, new ConstructorCallExpression(SOFT_REF, paramExpr))),
new ExpressionStatement(new BinaryExpression(fieldExpr, ASSIGN, NULL_EXPR))
));
int visibility = ACC_PUBLIC;
if (fieldNode.isStatic()) visibility |= ACC_STATIC;
fieldNode.getDeclaringClass().addMethod(name, visibility, ClassHelper.VOID_TYPE, new Parameter[]{parameter}, ClassNode.EMPTY_ARRAY, body);
}
private Expression synchTarget(FieldNode fieldNode) {
return fieldNode.isStatic() ? new ClassExpression(fieldNode.getDeclaringClass()) : VariableExpression.THIS_EXPRESSION;
}
private boolean isVolatile(FieldNode fieldNode) {
return (fieldNode.getModifiers() & ACC_VOLATILE) == 0;
}
private Expression getInitExpr(FieldNode fieldNode) {
Expression initExpr = fieldNode.getInitialValueExpression();
fieldNode.setInitialValueExpression(null);
if (initExpr == null)
initExpr = new ConstructorCallExpression(fieldNode.getType(), new ArgumentListExpression());
return initExpr;
}
}