groovy.transform.options.ImmutablePropertyHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy Show documentation
Show all versions of groovy Show documentation
Groovy: A powerful, dynamic language for the JVM
/*
* 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 groovy.transform.options;
import groovy.lang.ReadOnlyPropertyException;
import org.apache.groovy.ast.tools.ImmutablePropertyUtils;
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.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.transform.AbstractASTTransformation;
import org.codehaus.groovy.transform.ImmutableASTTransformation;
import org.codehaus.groovy.transform.MapConstructorASTTransformation;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.cloneArrayOrCloneableExpr;
import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.cloneDateExpr;
import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.derivesFromDate;
import static org.apache.groovy.ast.tools.ImmutablePropertyUtils.implementsCloneable;
import static org.codehaus.groovy.ast.ClassHelper.make;
import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
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.castX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.classList2args;
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.equalsNullX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.findArg;
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.isInstanceOfX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
import static org.codehaus.groovy.ast.tools.GeneralUtils.list2args;
import static org.codehaus.groovy.ast.tools.GeneralUtils.notX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.safeExpression;
import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ternaryX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
public class ImmutablePropertyHandler extends PropertyHandler {
private static final ClassNode CLONEABLE_TYPE = make(Cloneable.class);
private static final ClassNode COLLECTION_TYPE = makeWithoutCaching(Collection.class, false);
private static final ClassNode DGM_TYPE = make(DefaultGroovyMethods.class);
private static final ClassNode SELF_TYPE = make(ImmutableASTTransformation.class);
private static final ClassNode MAP_TYPE = makeWithoutCaching(Map.class, false);
private static final ClassNode SORTEDSET_CLASSNODE = make(SortedSet.class);
private static final ClassNode SORTEDMAP_CLASSNODE = make(SortedMap.class);
private static final ClassNode SET_CLASSNODE = make(Set.class);
private static final ClassNode MAP_CLASSNODE = make(Map.class);
private static final ClassNode READONLYEXCEPTION_TYPE = make(ReadOnlyPropertyException.class);
@Override
public Statement createPropGetter(PropertyNode pNode) {
FieldNode fNode = pNode.getField();
BlockStatement body = new BlockStatement();
final ClassNode fieldType = fNode.getType();
final Statement statement;
if (fieldType.isArray() || implementsCloneable(fieldType)) {
statement = createGetterBodyArrayOrCloneable(fNode);
} else if (derivesFromDate(fieldType)) {
statement = createGetterBodyDate(fNode);
} else {
statement = createGetterBodyDefault(fNode);
}
body.addStatement(statement);
return body;
}
@Override
public Statement createPropSetter(PropertyNode pNode) {
return null;
}
@Override
public boolean validateAttributes(AbstractASTTransformation xform, AnnotationNode anno) {
boolean success = isValidAttribute(xform, anno, "useSuper");
return success;
}
@Override
public boolean validateProperties(AbstractASTTransformation xform, BlockStatement body, ClassNode cNode, List props) {
if (xform instanceof MapConstructorASTTransformation) {
body.addStatement(ifS(equalsNullX(varX("args")), assignS(varX("args"), new MapExpression())));
body.addStatement(stmt(callX(SELF_TYPE, "checkPropNames", args("this", "args"))));
}
return super.validateProperties(xform, body, cNode, props);
}
@Override
public Statement createPropInit(AbstractASTTransformation xform, AnnotationNode anno, ClassNode cNode, PropertyNode pNode, Parameter namedArgsMap) {
FieldNode fNode = pNode.getField();
if (fNode.isFinal() && fNode.isStatic()) return null;
if (fNode.isFinal() && fNode.getInitialExpression() != null) {
return checkFinalArgNotOverridden(cNode, fNode);
}
return createConstructorStatement(xform, cNode, pNode, namedArgsMap);
}
private static Statement createGetterBodyDefault(FieldNode fNode) {
final Expression fieldExpr = varX(fNode);
return stmt(fieldExpr);
}
private Statement createGetterBodyArrayOrCloneable(FieldNode fNode) {
final Expression fieldExpr = varX(fNode);
final Expression expression = cloneArrayOrCloneableExpr(fieldExpr, fNode.getType());
return safeExpression(fieldExpr, expression);
}
private Statement createGetterBodyDate(FieldNode fNode) {
final Expression fieldExpr = varX(fNode);
final Expression expression = cloneDateExpr(fieldExpr);
return safeExpression(fieldExpr, expression);
}
protected Expression cloneCollectionExpr(Expression fieldExpr, ClassNode type) {
return castX(type, createIfInstanceOfAsImmutableS(fieldExpr, SORTEDSET_CLASSNODE,
createIfInstanceOfAsImmutableS(fieldExpr, SORTEDMAP_CLASSNODE,
createIfInstanceOfAsImmutableS(fieldExpr, SET_CLASSNODE,
createIfInstanceOfAsImmutableS(fieldExpr, MAP_CLASSNODE,
createIfInstanceOfAsImmutableS(fieldExpr, ClassHelper.LIST_TYPE,
createAsImmutableX(fieldExpr, COLLECTION_TYPE))
)
)
)
));
}
private Expression createIfInstanceOfAsImmutableS(Expression expr, ClassNode type, Expression elseStatement) {
return ternaryX(isInstanceOfX(expr, type), createAsImmutableX(expr, type), elseStatement);
}
protected Expression createAsImmutableX(final Expression expr, final ClassNode type) {
return callX(DGM_TYPE, "asImmutable", castX(type, expr));
}
@Deprecated
protected Statement createConstructorStatement(AbstractASTTransformation xform, ClassNode cNode, PropertyNode pNode, boolean namedArgs) {
final List knownImmutableClasses = ImmutablePropertyUtils.getKnownImmutableClasses(xform, cNode);
final List knownImmutables = ImmutablePropertyUtils.getKnownImmutables(xform, cNode);
FieldNode fNode = pNode.getField();
final ClassNode fType = fNode.getType();
Statement statement;
if (ImmutablePropertyUtils.isKnownImmutableType(fType, knownImmutableClasses) || isKnownImmutable(pNode.getName(), knownImmutables)) {
statement = createConstructorStatementDefault(fNode, namedArgs);
} else if (fType.isArray() || implementsCloneable(fType)) {
statement = createConstructorStatementArrayOrCloneable(fNode, namedArgs);
} else if (derivesFromDate(fType)) {
statement = createConstructorStatementDate(fNode, namedArgs);
} else if (isOrImplements(fType, COLLECTION_TYPE) || fType.isDerivedFrom(COLLECTION_TYPE) || isOrImplements(fType, MAP_TYPE) || fType.isDerivedFrom(MAP_TYPE)) {
statement = createConstructorStatementCollection(fNode, namedArgs);
} else if (fType.isResolved()) {
xform.addError(ImmutablePropertyUtils.createErrorMessage(cNode.getName(), fNode.getName(), fType.getName(), "compiling"), fNode);
statement = EmptyStatement.INSTANCE;
} else {
statement = createConstructorStatementGuarded(cNode, fNode, namedArgs, knownImmutables, knownImmutableClasses);
}
return statement;
}
protected Statement createConstructorStatement(AbstractASTTransformation xform, ClassNode cNode, PropertyNode pNode, Parameter namedArgsMap) {
final List knownImmutableClasses = ImmutablePropertyUtils.getKnownImmutableClasses(xform, cNode);
final List knownImmutables = ImmutablePropertyUtils.getKnownImmutables(xform, cNode);
FieldNode fNode = pNode.getField();
final ClassNode fType = fNode.getType();
Statement statement;
if (ImmutablePropertyUtils.isKnownImmutableType(fType, knownImmutableClasses) || isKnownImmutable(pNode.getName(), knownImmutables)) {
statement = createConstructorStatementDefault(fNode, namedArgsMap);
} else if (fType.isArray() || implementsCloneable(fType)) {
statement = createConstructorStatementArrayOrCloneable(fNode, namedArgsMap);
} else if (derivesFromDate(fType)) {
statement = createConstructorStatementDate(fNode, namedArgsMap);
} else if (isOrImplements(fType, COLLECTION_TYPE) || fType.isDerivedFrom(COLLECTION_TYPE) || isOrImplements(fType, MAP_TYPE) || fType.isDerivedFrom(MAP_TYPE)) {
statement = createConstructorStatementCollection(fNode, namedArgsMap);
} else if (fType.isResolved()) {
xform.addError(ImmutablePropertyUtils.createErrorMessage(cNode.getName(), fNode.getName(), fType.getName(), "compiling"), fNode);
statement = EmptyStatement.INSTANCE;
} else {
statement = createConstructorStatementGuarded(fNode, namedArgsMap, knownImmutables, knownImmutableClasses);
}
return statement;
}
private static Statement createConstructorStatementDefault(FieldNode fNode, boolean namedArgs) {
final ClassNode fType = fNode.getType();
final Expression fieldExpr = propX(varX("this"), fNode.getName());
Expression initExpr = fNode.getInitialValueExpression();
Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression)initExpr).isNullExpression())) {
if (ClassHelper.isPrimitiveType(fType)) {
assignInit = EmptyStatement.INSTANCE;
} else {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
}
} else {
assignInit = assignS(fieldExpr, initExpr);
}
fNode.setInitialValueExpression(null);
Expression param = getParam(fNode, namedArgs);
Statement assignStmt = assignS(fieldExpr, castX(fType, param));
return assignWithDefault(namedArgs, assignInit, param, assignStmt);
}
private static Statement createConstructorStatementDefault(FieldNode fNode, Parameter namedArgsMap) {
final ClassNode fType = fNode.getType();
final Expression fieldExpr = propX(varX("this"), fNode.getName());
Expression param = getParam(fNode, namedArgsMap != null);
Statement assignStmt = assignS(fieldExpr, castX(fType, param));
Expression initExpr = fNode.getInitialValueExpression();
Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression)initExpr).isNullExpression())) {
if (ClassHelper.isPrimitiveType(fType)) {
assignInit = EmptyStatement.INSTANCE;
} else {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
}
} else {
assignInit = assignS(fieldExpr, initExpr);
}
return assignFieldWithDefault(namedArgsMap, fNode, assignStmt, assignInit);
}
private static Statement assignFieldWithDefault(Parameter map, FieldNode fNode, Statement assignStmt, Statement assignInit) {
if (map == null) {
return assignStmt;
}
ArgumentListExpression nameArg = args(constX(fNode.getName()));
MethodCallExpression var = callX(varX(map), "get", nameArg);
var.setImplicitThis(false);
MethodCallExpression containsKey = callX(varX(map), "containsKey", nameArg);
containsKey.setImplicitThis(false);
fNode.getDeclaringClass().getField(fNode.getName()).setInitialValueExpression(null); // to avoid default initialization
return ifElseS(containsKey, assignStmt, assignInit);
}
private static Statement assignWithDefault(boolean namedArgs, Statement assignInit, Expression param, Statement assignStmt) {
if (!namedArgs) {
return assignStmt;
}
return ifElseS(equalsNullX(param), assignInit, assignStmt);
}
private static Statement createConstructorStatementGuarded(ClassNode cNode, FieldNode fNode, boolean namedArgs, List knownImmutables, List knownImmutableClasses) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
Expression initExpr = fNode.getInitialValueExpression();
final Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, checkUnresolved(fNode, initExpr, knownImmutables, knownImmutableClasses));
}
Expression param = getParam(fNode, namedArgs);
Statement assignStmt = assignS(fieldExpr, checkUnresolved(fNode, param, knownImmutables, knownImmutableClasses));
return assignWithDefault(namedArgs, assignInit, param, assignStmt);
}
private static Statement createConstructorStatementGuarded(FieldNode fNode, Parameter namedArgsMap, List knownImmutables, List knownImmutableClasses) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
Expression param = getParam(fNode, namedArgsMap != null);
Statement assignStmt = assignS(fieldExpr, checkUnresolved(fNode, param, knownImmutables, knownImmutableClasses));
assignStmt = ifElseS(equalsNullX(param), assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION), assignStmt);
Expression initExpr = fNode.getInitialValueExpression();
final Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, checkUnresolved(fNode, initExpr, knownImmutables, knownImmutableClasses));
}
return assignFieldWithDefault(namedArgsMap, fNode, assignStmt, assignInit);
}
private static Expression checkUnresolved(FieldNode fNode, Expression value, List knownImmutables, List knownImmutableClasses) {
Expression args = args(callThisX("getClass"), constX(fNode.getName()), value, list2args(knownImmutables), classList2args(knownImmutableClasses));
return callX(SELF_TYPE, "checkImmutable", args);
}
private Statement createConstructorStatementCollection(FieldNode fNode, boolean namedArgs) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
ClassNode fieldType = fieldExpr.getType();
Expression initExpr = fNode.getInitialValueExpression();
final Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, fieldType));
}
Expression param = getParam(fNode, namedArgs);
Statement assignStmt = ifElseS(
isInstanceOfX(param, CLONEABLE_TYPE),
assignS(fieldExpr, cloneCollectionExpr(cloneArrayOrCloneableExpr(param, fieldType), fieldType)),
assignS(fieldExpr, cloneCollectionExpr(param, fieldType)));
return assignWithDefault(namedArgs, assignInit, param, assignStmt);
}
private Statement createConstructorStatementCollection(FieldNode fNode, Parameter namedArgsMap) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
ClassNode fieldType = fieldExpr.getType();
Expression param = getParam(fNode, namedArgsMap != null);
Statement assignStmt = ifElseS(
isInstanceOfX(param, CLONEABLE_TYPE),
assignS(fieldExpr, cloneCollectionExpr(cloneArrayOrCloneableExpr(param, fieldType), fieldType)),
assignS(fieldExpr, cloneCollectionExpr(param, fieldType)));
assignStmt = ifElseS(equalsNullX(param), assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION), assignStmt);
Expression initExpr = fNode.getInitialValueExpression();
final Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, cloneCollectionExpr(initExpr, fieldType));
}
return assignFieldWithDefault(namedArgsMap, fNode, assignStmt, assignInit);
}
private static Statement createConstructorStatementArrayOrCloneable(FieldNode fNode, boolean namedArgs) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
final Expression initExpr = fNode.getInitialValueExpression();
final ClassNode fieldType = fNode.getType();
final Expression param = getParam(fNode, namedArgs);
final Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, cloneArrayOrCloneableExpr(initExpr, fieldType));
}
Statement assignStmt = assignS(fieldExpr, cloneArrayOrCloneableExpr(param, fieldType));
return assignWithDefault(namedArgs, assignInit, param, assignStmt);
}
private static Statement createConstructorStatementArrayOrCloneable(FieldNode fNode, Parameter namedArgsMap) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
final ClassNode fieldType = fNode.getType();
final Expression param = getParam(fNode, namedArgsMap != null);
Statement assignStmt = assignS(fieldExpr, cloneArrayOrCloneableExpr(param, fieldType));
assignStmt = ifElseS(equalsNullX(param), assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION), assignStmt);
final Statement assignInit;
final Expression initExpr = fNode.getInitialValueExpression();
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, cloneArrayOrCloneableExpr(initExpr, fieldType));
}
return assignFieldWithDefault(namedArgsMap, fNode, assignStmt, assignInit);
}
private static Expression getParam(FieldNode fNode, boolean namedArgs) {
return namedArgs ? findArg(fNode.getName()) : varX(fNode.getName(), fNode.getType());
}
private static Statement createConstructorStatementDate(FieldNode fNode, boolean namedArgs) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
Expression initExpr = fNode.getInitialValueExpression();
final Statement assignInit;
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, cloneDateExpr(initExpr));
}
final Expression param = getParam(fNode, namedArgs);
Statement assignStmt = assignS(fieldExpr, cloneDateExpr(param));
return assignWithDefault(namedArgs, assignInit, param, assignStmt);
}
private static Statement createConstructorStatementDate(FieldNode fNode, Parameter namedArgsMap) {
final Expression fieldExpr = propX(varX("this"), fNode.getName());
final Expression param = getParam(fNode, namedArgsMap != null);
Statement assignStmt = assignS(fieldExpr, cloneDateExpr(param));
assignStmt = ifElseS(equalsNullX(param), assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION), assignStmt);
final Statement assignInit;
Expression initExpr = fNode.getInitialValueExpression();
if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression) initExpr).isNullExpression())) {
assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION);
} else {
assignInit = assignS(fieldExpr, cloneDateExpr(initExpr));
}
return assignFieldWithDefault(namedArgsMap, fNode, assignStmt, assignInit);
}
private static boolean isKnownImmutable(String fieldName, List knownImmutables) {
return knownImmutables.contains(fieldName);
}
protected Statement checkFinalArgNotOverridden(ClassNode cNode, FieldNode fNode) {
final String name = fNode.getName();
Expression value = findArg(name);
return ifS(
notX(equalsNullX(value)),
throwS(ctorX(READONLYEXCEPTION_TYPE,
args(constX(name), constX(cNode.getName()))
)));
}
}