/* * 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)); } } }