/* * 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 groovy.transform.AutoClone; import groovy.transform.AutoCloneStyle; 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.Parameter; import org.codehaus.groovy.ast.VariableScope; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.EmptyStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.IfStatement; import org.codehaus.groovy.ast.stmt.ReturnStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.syntax.Token; import org.codehaus.groovy.syntax.Types; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.List; import static org.codehaus.groovy.transform.AbstractASTTransformUtil.assignStatement; import static org.codehaus.groovy.transform.AbstractASTTransformUtil.getInstanceNonPropertyFields; import static org.codehaus.groovy.transform.AbstractASTTransformUtil.getInstancePropertyFields; import static org.codehaus.groovy.transform.AbstractASTTransformUtil.isInstanceOf; /** * Handles generation of code for the @AutoClone annotation. * * @author Paul King */ @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) public class AutoCloneASTTransformation extends AbstractASTTransformation { static final Class MY_CLASS = AutoClone.class; static final ClassNode MY_TYPE = new ClassNode(MY_CLASS); static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); private static final ClassNode CLONEABLE_TYPE = ClassHelper.make(Cloneable.class); private static final ClassNode BAOS_TYPE = ClassHelper.make(ByteArrayOutputStream.class); private static final ClassNode BAIS_TYPE = ClassHelper.make(ByteArrayInputStream.class); private static final Token ASSIGN = Token.newSymbol(Types.ASSIGN, -1, -1); public void visit(ASTNode[] nodes, SourceUnit source) { init(nodes, source); AnnotatedNode parent = (AnnotatedNode) nodes[1]; AnnotationNode anno = (AnnotationNode) nodes[0]; if (!MY_TYPE.equals(anno.getClassNode())) return; if (parent instanceof ClassNode) { ClassNode cNode = (ClassNode) parent; checkNotInterface(cNode, MY_TYPE_NAME); cNode.addInterface(CLONEABLE_TYPE); boolean includeFields = memberHasValue(anno, "includeFields", true); AutoCloneStyle style = getStyle(anno, "style"); List<String> excludes = tokenize((String) getMemberValue(anno, "excludes")); List<FieldNode> list = getInstancePropertyFields(cNode); if (includeFields) { list.addAll(getInstanceNonPropertyFields(cNode)); } if (style == null) style = AutoCloneStyle.CLONE; switch (style) { case COPY_CONSTRUCTOR: createCloneCopyConstructor(cNode, list, excludes); break; case SERIALIZATION: createCloneSerialization(cNode, list, excludes); break; case CLONE: createClone(cNode, list, excludes); break; } } } private void createCloneSerialization(ClassNode cNode, List<FieldNode> list, List<String> excludes) { final BlockStatement body = new BlockStatement(); // def baos = new ByteArrayOutputStream() final Expression baos = new VariableExpression("baos"); body.addStatement(new ExpressionStatement(new DeclarationExpression(baos, ASSIGN, new ConstructorCallExpression(BAOS_TYPE, MethodCallExpression.NO_ARGUMENTS)))); // baos.withObjectOutputStream{ it.writeObject(this) } BlockStatement writeClosureCode = new BlockStatement(); final Expression it = new VariableExpression("it"); writeClosureCode.addStatement(new ExpressionStatement(new MethodCallExpression(it, "writeObject", VariableExpression.THIS_EXPRESSION))); ClosureExpression writeClosure = new ClosureExpression(new Parameter[]{}, writeClosureCode); writeClosure.setVariableScope(new VariableScope()); body.addStatement(new ExpressionStatement(new MethodCallExpression(baos, "withObjectOutputStream", new ArgumentListExpression(writeClosure)))); // def bais = new ByteArrayInputStream(baos.toByteArray()) final Expression bais = new VariableExpression("bais"); ConstructorCallExpression bytes = new ConstructorCallExpression(BAIS_TYPE, new TupleExpression(new MethodCallExpression(baos, "toByteArray", MethodCallExpression.NO_ARGUMENTS))); body.addStatement(new ExpressionStatement(new DeclarationExpression(bais, ASSIGN, bytes))); // return bais.withObjectInputStream(getClass().classLoader){ it.readObject() } BlockStatement readClosureCode = new BlockStatement(); readClosureCode.addStatement(new ExpressionStatement(new MethodCallExpression(it, "readObject", MethodCallExpression.NO_ARGUMENTS))); ClosureExpression readClosure = new ClosureExpression(new Parameter[]{}, readClosureCode); readClosure.setVariableScope(new VariableScope()); Expression klass = new MethodCallExpression(VariableExpression.THIS_EXPRESSION, "getClass", MethodCallExpression.NO_ARGUMENTS); Expression classLoader = new MethodCallExpression(klass, "getClassLoader", MethodCallExpression.NO_ARGUMENTS); Expression result = new MethodCallExpression(bais, "withObjectInputStream", new ArgumentListExpression(classLoader, readClosure)); body.addStatement(new ReturnStatement(result)); ClassNode[] exceptions = {ClassHelper.make(CloneNotSupportedException.class)}; cNode.addMethod("clone", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, new Parameter[0], exceptions, body); } private void createCloneCopyConstructor(ClassNode cNode, List<FieldNode> list, List<String> excludes) { BlockStatement initBody = new BlockStatement(); if (cNode.getDeclaredConstructors().size() == 0) { // add no-arg constructor initBody.addStatement(new EmptyStatement()); cNode.addConstructor(ACC_PUBLIC, new Parameter[0], ClassNode.EMPTY_ARRAY, initBody); initBody = new BlockStatement(); } Parameter initParam = new Parameter(cNode, "other"); final Expression other = new VariableExpression(initParam); boolean hasParent = cNode.getSuperClass() != ClassHelper.OBJECT_TYPE; if (hasParent) { initBody.addStatement(new ExpressionStatement(new ConstructorCallExpression(ClassNode.SUPER, other))); } for (FieldNode fieldNode : list) { String name = fieldNode.getName(); if (excludes.contains(name)) continue; PropertyExpression direct = new PropertyExpression(other, name); Expression cloned = new MethodCallExpression(direct, "clone", MethodCallExpression.NO_ARGUMENTS); Expression to = new PropertyExpression(VariableExpression.THIS_EXPRESSION, name); Statement assignCloned = assignStatement(to, cloned); Statement assignDirect = assignStatement(to, direct); initBody.addStatement(new IfStatement(isInstanceOf(direct, CLONEABLE_TYPE), assignCloned, assignDirect)); } ClassNode[] exceptions = {ClassHelper.make(CloneNotSupportedException.class)}; cNode.addConstructor(ACC_PROTECTED, new Parameter[]{initParam}, ClassNode.EMPTY_ARRAY, initBody); final BlockStatement cloneBody = new BlockStatement(); cloneBody.addStatement(new ExpressionStatement(new ConstructorCallExpression(cNode, new ArgumentListExpression(VariableExpression.THIS_EXPRESSION)))); cNode.addMethod("clone", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, new Parameter[0], exceptions, cloneBody); } private void createClone(ClassNode cNode, List<FieldNode> list, List<String> excludes) { final BlockStatement body = new BlockStatement(); final Expression result = new VariableExpression("_result"); final Expression clone = new MethodCallExpression(VariableExpression.SUPER_EXPRESSION, "clone", MethodCallExpression.NO_ARGUMENTS); body.addStatement(new ExpressionStatement(new DeclarationExpression(result, ASSIGN, clone))); for (FieldNode fieldNode : list) { if (excludes.contains(fieldNode.getName())) continue; Expression fieldExpr = new VariableExpression(fieldNode); Expression from = new MethodCallExpression(fieldExpr, "clone", MethodCallExpression.NO_ARGUMENTS); Expression to = new PropertyExpression(result, fieldNode.getName()); Statement doClone = assignStatement(to, from); Statement doNothing = new EmptyStatement(); body.addStatement(new IfStatement(isInstanceOf(fieldExpr, CLONEABLE_TYPE), doClone, doNothing)); } body.addStatement(new ReturnStatement(result)); ClassNode[] exceptions = {ClassHelper.make(CloneNotSupportedException.class)}; cNode.addMethod("clone", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, new Parameter[0], exceptions, body); } private AutoCloneStyle getStyle(AnnotationNode node, String name) { final Expression member = node.getMember(name); if (member != null && member instanceof PropertyExpression) { PropertyExpression prop = (PropertyExpression) member; Expression oe = prop.getObjectExpression(); if (oe instanceof ClassExpression) { ClassExpression ce = (ClassExpression) oe; if (ce.getType().getTypeClass() == AutoCloneStyle.class) { return AutoCloneStyle.valueOf(prop.getPropertyAsString()); } } } return null; } }