blob: 0a4fe8431c219af84b69cf61106e56e8ef951d83 [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.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.Expression;
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 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.assignX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifElseS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.notNullX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
import static org.codehaus.groovy.ast.tools.GenericsUtils.newClass;
/**
* Handles generation of code for the @Singleton annotation
*
* @author Alex Tkachman
* @author Paul King
*/
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class SingletonASTTransformation extends AbstractASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit source) {
init(nodes, source);
AnnotatedNode parent = (AnnotatedNode) nodes[1];
AnnotationNode node = (AnnotationNode) nodes[0];
if (parent instanceof ClassNode) {
ClassNode classNode = (ClassNode) parent;
String propertyName = getMemberStringValue(node, "property", "instance");
boolean isLazy = memberHasValue(node, "lazy", true);
boolean isStrict = !memberHasValue(node, "strict", false);
createField(classNode, propertyName, isLazy, isStrict);
}
}
private void createField(ClassNode classNode, String propertyName, boolean isLazy, boolean isStrict) {
int modifiers = isLazy ? ACC_PRIVATE | ACC_STATIC | ACC_VOLATILE : ACC_PUBLIC | ACC_FINAL | ACC_STATIC;
Expression initialValue = isLazy ? null : ctorX(classNode);
final FieldNode fieldNode = classNode.addField(propertyName, modifiers, newClass(classNode), initialValue);
createConstructor(classNode, fieldNode, propertyName, isStrict);
final BlockStatement body = new BlockStatement();
body.addStatement(isLazy ? lazyBody(classNode, fieldNode) : nonLazyBody(fieldNode));
classNode.addMethod(getGetterName(propertyName), ACC_STATIC | ACC_PUBLIC, newClass(classNode), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, body);
}
private static Statement nonLazyBody(FieldNode fieldNode) {
return returnS(varX(fieldNode));
}
private static Statement lazyBody(ClassNode classNode, FieldNode fieldNode) {
final Expression instanceExpression = varX(fieldNode);
return ifElseS(
notNullX(instanceExpression),
returnS(instanceExpression),
new SynchronizedStatement(
classX(classNode),
ifElseS(
notNullX(instanceExpression),
returnS(instanceExpression),
returnS(assignX(instanceExpression, ctorX(classNode)))
)
)
);
}
private static String getGetterName(String propertyName) {
return "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
}
private void createConstructor(ClassNode classNode, FieldNode field, String propertyName, boolean isStrict) {
final List<ConstructorNode> cNodes = classNode.getDeclaredConstructors();
ConstructorNode foundNoArg = null;
for (ConstructorNode cNode : cNodes) {
final Parameter[] parameters = cNode.getParameters();
if (parameters == null || parameters.length == 0) {
foundNoArg = cNode;
break;
}
}
if (isStrict && !cNodes.isEmpty()) {
for (ConstructorNode cNode : cNodes) {
addError("@Singleton didn't expect to find one or more additional constructors: remove constructor(s) or set strict=false", cNode);
}
}
if (foundNoArg == null) {
final BlockStatement body = new BlockStatement();
body.addStatement(ifS(
notNullX(varX(field)),
throwS(
ctorX(make(RuntimeException.class),
args(constX("Can't instantiate singleton " + classNode.getName() + ". Use " + classNode.getName() + "." + propertyName))))
));
classNode.addConstructor(new ConstructorNode(ACC_PRIVATE, body));
}
}
}