org.grails.datastore.gorm.jpa.GormToJpaTransform.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.datastore.gorm.jpa.GormToJpaTransform.java

Source

/* Copyright (C) 2011 SpringSource
 *
 * 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.grails.datastore.gorm.jpa;

import grails.gorm.JpaEntity;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.FieldNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.NamedArgumentListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SimpleMessage;
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
import org.codehaus.groovy.grails.compiler.injection.DefaultGrailsDomainClassInjector;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import org.grails.datastore.mapping.model.MappingFactory;
import org.springframework.util.ClassUtils;

/**
 * A AST transformation that turns a GORM entity into a JPA entity.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class GormToJpaTransform implements ASTTransformation {

    private static Log LOG = LogFactory.getLog(GormToJpaTransform.class);
    private static final AnnotationNode ANNOTATION_VERSION = new AnnotationNode(new ClassNode(Version.class));
    private static final AnnotationNode ANNOTATION_ID = new AnnotationNode(new ClassNode(Id.class));
    private static final AnnotationNode ANNOTATION_ENTITY = new AnnotationNode(new ClassNode(Entity.class));
    private static final AnnotationNode ANNOTATION_BASIC = new AnnotationNode(new ClassNode(Basic.class));

    private static final PropertyExpression EXPR_CASCADE_ALL = new PropertyExpression(
            new ClassExpression(new ClassNode(CascadeType.class)), "ALL");
    private static final PropertyExpression EXPR_CASCADE_PERSIST = new PropertyExpression(
            new ClassExpression(new ClassNode(CascadeType.class)), "PERSIST");

    private static final ClassNode MY_TYPE = new ClassNode(JpaEntity.class);
    private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();

    @SuppressWarnings("serial")
    private static final Map<String, AnnotationNode> gormEventMethodToJpaAnnotation = new HashMap<String, AnnotationNode>() {
        {
            put("beforeInsert", new AnnotationNode(new ClassNode(PrePersist.class)));
            put("afterInsert", new AnnotationNode(new ClassNode(PostPersist.class)));
            put("beforeUpdate", new AnnotationNode(new ClassNode(PreUpdate.class)));
            put("afterUpdate", new AnnotationNode(new ClassNode(PostUpdate.class)));
            put("beforeDelete", new AnnotationNode(new ClassNode(PreRemove.class)));
            put("afterDelete", new AnnotationNode(new ClassNode(PostRemove.class)));
            put("onSave", new AnnotationNode(new ClassNode(PrePersist.class)));
            put("afterLoad", new AnnotationNode(new ClassNode(PostLoad.class)));
        }
    };
    public static final String ERRORS = "errors";

    public void visit(ASTNode[] astNodes, SourceUnit source) {
        if (!(astNodes[0] instanceof AnnotationNode) || !(astNodes[1] instanceof AnnotatedNode)) {
            throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class");
        }

        AnnotatedNode parent = (AnnotatedNode) astNodes[1];
        AnnotationNode node = (AnnotationNode) astNodes[0];
        if (!MY_TYPE.equals(node.getClassNode()) || !(parent instanceof ClassNode)) {
            return;
        }

        ClassNode cNode = (ClassNode) parent;
        String cName = cNode.getName();
        if (cNode.isInterface()) {
            throw new RuntimeException(
                    "Error processing interface '" + cName + "'. " + MY_TYPE_NAME + " not allowed for interfaces.");
        }

        try {
            transformEntity(source, cNode);
        } catch (Exception e) {
            String message = "Error occured transfoming GORM entity to JPA entity: " + e.getMessage();
            LOG.error(message, e);
            source.getErrorCollector().addFatalError(new SimpleMessage(message, source));
        }
    }

    public static void transformEntity(SourceUnit source, ClassNode classNode) {

        // add the JPA @Entity annotation
        classNode.addAnnotation(ANNOTATION_ENTITY);
        final AnnotationNode entityListenersAnnotation = new AnnotationNode(new ClassNode(EntityListeners.class));
        entityListenersAnnotation.addMember("value",
                new ClassExpression(new ClassNode(EntityInterceptorInvokingEntityListener.class)));
        classNode.addAnnotation(entityListenersAnnotation);

        PropertyNode mappingNode = classNode.getProperty(GrailsDomainClassProperty.MAPPING);
        Map<String, Map<String, ?>> propertyMappings = new HashMap<String, Map<String, ?>>();
        if (mappingNode != null && mappingNode.isStatic()) {
            populateConfigurationMapFromClosureExpression(classNode, mappingNode, propertyMappings);
        }

        // annotate the id property with @Id
        String idPropertyName = GrailsDomainClassProperty.IDENTITY;
        String generationType = GenerationType.AUTO.toString();

        final PropertyNode errorsProperty = classNode.getProperty("errors");
        if (errorsProperty == null) {
            if (ClassUtils.isPresent("org.codehaus.groovy.grails.compiler.injection.ASTValidationErrorsHelper",
                    Thread.currentThread().getContextClassLoader())) {
                addErrorsProperty(classNode);
            }
        }

        if (propertyMappings.containsKey(GrailsDomainClassProperty.IDENTITY)) {
            final Map<String, ?> idConfig = propertyMappings.get(GrailsDomainClassProperty.IDENTITY);
            if (idConfig.containsKey("name")) {
                idPropertyName = idConfig.get("name").toString();
            }
            if (idConfig.containsKey("generator")) {
                String generatorName = idConfig.get("generator").toString();
                if ("assigned".equals(generatorName)) {
                    generationType = null;
                } else if ("sequence".equals(generatorName)) {
                    generationType = GenerationType.SEQUENCE.toString();
                } else if ("identity".equals(generatorName)) {
                    generationType = GenerationType.IDENTITY.toString();
                }
            }
        }

        PropertyNode idProperty = classNode.getProperty(idPropertyName);
        if (idProperty == null) {
            new DefaultGrailsDomainClassInjector().performInjectionOnAnnotatedEntity(classNode);
            idProperty = classNode.getProperty(GrailsDomainClassProperty.IDENTITY);
        }

        if (!idPropertyName.equals(GrailsDomainClassProperty.IDENTITY)) {
            PropertyNode toDiscard = classNode.getProperty(GrailsDomainClassProperty.IDENTITY);
            if (toDiscard != null && toDiscard.getType().equals("java.lang.Long")) {
                classNode.getProperties().remove(toDiscard);
            }
        }

        if (idProperty != null) {
            final FieldNode idField = idProperty.getField();

            idField.addAnnotation(ANNOTATION_ID);
            if (generationType != null) {
                final AnnotationNode generatedValueAnnotation = new AnnotationNode(
                        new ClassNode(GeneratedValue.class));
                generatedValueAnnotation.addMember("strategy", new PropertyExpression(
                        new ClassExpression(new ClassNode(GenerationType.class)), generationType));
                idField.addAnnotation(generatedValueAnnotation);
            }
        }

        // annotate the version property with @Version
        PropertyNode versionProperty = classNode.getProperty(GrailsDomainClassProperty.VERSION);
        if (versionProperty != null) {
            if (propertyMappings.containsKey(GrailsDomainClassProperty.VERSION)) {
                final Map<String, ?> versionSettings = propertyMappings.get(GrailsDomainClassProperty.VERSION);
                final Object enabledObject = versionSettings.get("enabled");
                if (enabledObject instanceof Boolean) {
                    if (((Boolean) enabledObject).booleanValue()) {
                        versionProperty.addAnnotation(ANNOTATION_VERSION);
                    }
                }
            } else {
                versionProperty.addAnnotation(ANNOTATION_VERSION);
            }
        }

        final List<MethodNode> methods = classNode.getMethods();
        for (MethodNode methodNode : methods) {
            if (methodNode.isStatic() || !methodNode.isPublic() || methodNode.isAbstract()) {
                continue;
            }

            final AnnotationNode annotationNode = gormEventMethodToJpaAnnotation.get(methodNode.getName());
            if (annotationNode == null) {
                continue;
            }

            //methodNode.setReturnType(new ClassNode(void.class));
            methodNode.addAnnotation(annotationNode);
        }

        Map<String, ClassNode> hasManyMap = lookupStringToClassNodeMap(classNode,
                GrailsDomainClassProperty.HAS_MANY);
        Map<String, ClassNode> hasOneMap = lookupStringToClassNodeMap(classNode, GrailsDomainClassProperty.HAS_ONE);
        Map<String, ClassNode> belongsToMap = lookupStringToClassNodeMap(classNode,
                GrailsDomainClassProperty.BELONGS_TO);
        Map<String, String> mappedByMap = lookupStringToStringMap(classNode, GrailsDomainClassProperty.MAPPED_BY);

        final List<PropertyNode> properties = classNode.getProperties();
        for (PropertyNode propertyNode : properties) {
            if (!propertyNode.isPublic() || propertyNode.isStatic()) {
                continue;
            }

            if (propertyNode == idProperty || propertyNode == versionProperty) {
                continue;
            }

            final String typeName = propertyNode.getType().getName();

            if (typeName.equals("java.util.Date") || typeName.equals("java.util.Calendar")) {
                AnnotationNode temporalAnnotation = new AnnotationNode(new ClassNode(Temporal.class));
                temporalAnnotation.addMember("value",
                        new PropertyExpression(new ClassExpression(new ClassNode(TemporalType.class)), "DATE"));
                propertyNode.getField().addAnnotation(temporalAnnotation);
            } else if (MappingFactory.isSimpleType(typeName)) {
                propertyNode.getField().addAnnotation(ANNOTATION_BASIC);
            } else {
                final String propertyName = propertyNode.getName();
                if (!belongsToMap.containsKey(propertyName) && !hasOneMap.containsKey(propertyName)
                        && !hasManyMap.containsKey(propertyName)) {
                    handleToOne(classNode, belongsToMap, propertyName);
                }
            }
        }

        final PropertyNode transientsProp = classNode.getProperty(GrailsDomainClassProperty.TRANSIENT);
        List<String> propertyNameList = new ArrayList<String>();
        populateConstantList(propertyNameList, transientsProp);
        annotateAllProperties(classNode, propertyNameList, Transient.class);

        propertyNameList.clear();
        final PropertyNode embeddedProp = classNode.getProperty(GrailsDomainClassProperty.EMBEDDED);
        populateConstantList(propertyNameList, embeddedProp);
        annotateAllProperties(classNode, propertyNameList, Embedded.class);

        if (embeddedProp != null) {
            for (String propertyName : propertyNameList) {
                final PropertyNode property = classNode.getProperty(propertyName);
                if (property == null) {
                    continue;
                }

                ClassNode embeddedType = property.getField().getType();
                annotateIfNecessary(embeddedType, Embeddable.class);
            }
        }

        if (!belongsToMap.isEmpty()) {
            for (String propertyName : belongsToMap.keySet()) {
                handleToOne(classNode, belongsToMap, propertyName);
            }
        }

        if (!hasOneMap.isEmpty()) {
            for (String propertyName : hasOneMap.keySet()) {
                final AnnotationNode oneToOneAnnotation = new AnnotationNode(new ClassNode(OneToOne.class));
                oneToOneAnnotation.addMember("optional", ConstantExpression.FALSE);
                oneToOneAnnotation.addMember("cascade", EXPR_CASCADE_ALL);
                annotateProperty(classNode, propertyName, oneToOneAnnotation);
            }
        }

        if (!hasManyMap.isEmpty()) {
            for (String propertyName : hasManyMap.keySet()) {
                ClassNode associatedClass = hasManyMap.get(propertyName);
                final Map<String, ClassNode> inverseBelongsToMap = lookupStringToClassNodeMap(associatedClass,
                        GrailsDomainClassProperty.BELONGS_TO);
                final Map<String, ClassNode> inverseHasManyMap = lookupStringToClassNodeMap(associatedClass,
                        GrailsDomainClassProperty.HAS_MANY);

                final AnnotationNode oneToManyAnnotation = new AnnotationNode(new ClassNode(OneToMany.class));
                oneToManyAnnotation.addMember("targetEntity", new ClassExpression(associatedClass));

                if (mappedByMap.containsKey(propertyName)) {
                    oneToManyAnnotation.addMember("mappedBy",
                            new ConstantExpression(mappedByMap.get(propertyName)));
                    oneToManyAnnotation.addMember("cascade", EXPR_CASCADE_PERSIST);
                    annotateProperty(classNode, propertyName, oneToManyAnnotation);
                } else {
                    if (inverseHasManyMap.containsValue(classNode)) {
                        // many-to-many association
                        List<ClassNode> belongsToList = getBelongsToList(classNode);

                        final AnnotationNode manyToManyAnnotation = new AnnotationNode(
                                new ClassNode(ManyToMany.class));
                        manyToManyAnnotation.addMember("targetEntity", new ClassExpression(associatedClass));
                        if (belongsToList.contains(associatedClass)) {
                            for (String inversePropertyName : inverseHasManyMap.keySet()) {
                                if (classNode.equals(inverseHasManyMap.get(inversePropertyName))) {
                                    manyToManyAnnotation.addMember("mappedBy",
                                            new ConstantExpression(inversePropertyName));
                                }
                            }
                        } else {
                            manyToManyAnnotation.addMember("cascade", EXPR_CASCADE_ALL);
                        }
                        annotateProperty(classNode, propertyName, manyToManyAnnotation);
                    }
                    // Try work out the other side of the association
                    else if (inverseBelongsToMap.containsValue(classNode)) {
                        for (String inversePropertyName : inverseBelongsToMap.keySet()) {
                            if (classNode.equals(inverseBelongsToMap.get(inversePropertyName))) {
                                oneToManyAnnotation.addMember("mappedBy",
                                        new ConstantExpression(inversePropertyName));
                                oneToManyAnnotation.addMember("cascade", EXPR_CASCADE_ALL);
                            }
                        }
                        annotateProperty(classNode, propertyName, oneToManyAnnotation);
                    } else {
                        PropertyNode inverseClosestMatch = findClosestInverstTypeMatch(classNode, associatedClass);
                        if (inverseClosestMatch != null) {
                            oneToManyAnnotation.addMember("mappedBy",
                                    new ConstantExpression(inverseClosestMatch.getName()));
                        }
                        // unidrectional one-to-many
                        oneToManyAnnotation.addMember("cascade", EXPR_CASCADE_ALL);
                        annotateProperty(classNode, propertyName, oneToManyAnnotation);
                    }
                }
            }
        }
    }

    private static String addErrorsScript = null;

    private static void addErrorsProperty(ClassNode classNode) {
        // Horrible to have to do this, but only way to support both Grails 1.3.7 and Grails 2.0
        if (addErrorsScript == null) {
            URL resource = GormToJpaTransform.class.getResource("/org/grails/datastore/gorm/jpa/AddErrors.script");
            try {
                if (resource != null) {
                    addErrorsScript = DefaultGroovyMethods.getText(resource);
                }
            } catch (IOException e) {
                // ignore
            }
        }

        if (addErrorsScript != null) {
            Binding b = new Binding();
            b.setVariable("classNode", classNode);
            new GroovyShell(b).evaluate(addErrorsScript);
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static void populateConfigurationMapFromClosureExpression(ClassNode classNode, PropertyNode mappingNode,
            Map propertyMappings) {
        ClosureExpression ce = (ClosureExpression) mappingNode.getInitialExpression();
        final Statement code = ce.getCode();
        if (!(code instanceof BlockStatement)) {
            return;
        }
        final List<Statement> statements = ((BlockStatement) code).getStatements();
        for (Statement statement : statements) {
            if (!(statement instanceof ExpressionStatement)) {
                continue;
            }
            ExpressionStatement es = (ExpressionStatement) statement;
            final Expression expression = es.getExpression();
            if (!(expression instanceof MethodCallExpression)) {
                continue;
            }
            MethodCallExpression mce = (MethodCallExpression) expression;
            final String methodName = mce.getMethodAsString();
            Map propertyMapping = new HashMap();
            propertyMappings.put(methodName, propertyMapping);

            final Expression arguments = mce.getArguments();
            if (arguments instanceof ArgumentListExpression) {
                if (methodName.equals("table")) {
                    ArgumentListExpression ale = (ArgumentListExpression) arguments;
                    final List<Expression> expressions = ale.getExpressions();
                    if (!expressions.isEmpty()) {
                        final String tableName = expressions.get(0).getText();
                        final AnnotationNode tableAnnotation = new AnnotationNode(new ClassNode(Table.class));
                        tableAnnotation.addMember("name", new ConstantExpression(tableName));
                        classNode.addAnnotation(tableAnnotation);
                    }
                } else if (methodName.equals("version")) {
                    ArgumentListExpression ale = (ArgumentListExpression) arguments;
                    final List<Expression> expressions = ale.getExpressions();
                    if (!expressions.isEmpty()) {
                        final Expression expr = expressions.get(0);
                        if (expr instanceof BooleanExpression) {
                            propertyMapping.put("enabled", Boolean.valueOf(expr.getText()));
                        }
                    }
                }
            } else if (arguments instanceof TupleExpression) {
                final List<Expression> tupleExpressions = ((TupleExpression) arguments).getExpressions();
                for (Expression te : tupleExpressions) {
                    if (!(te instanceof NamedArgumentListExpression)) {
                        continue;
                    }

                    NamedArgumentListExpression nale = (NamedArgumentListExpression) te;
                    for (MapEntryExpression mee : nale.getMapEntryExpressions()) {
                        String settingName = mee.getKeyExpression().getText();

                        final Expression valueExpression = mee.getValueExpression();
                        if (!(valueExpression instanceof ConstantExpression)) {
                            continue;
                        }

                        if (valueExpression instanceof BooleanExpression) {
                            propertyMapping.put(settingName, Boolean.valueOf(valueExpression.getText()));
                        } else {
                            propertyMapping.put(settingName, valueExpression.getText());
                        }
                    }
                }
            }
        }
    }

    private static PropertyNode findClosestInverstTypeMatch(ClassNode classNode, ClassNode associatedClass) {
        for (PropertyNode inverseProperty : associatedClass.getProperties()) {
            if (inverseProperty.isPublic() && inverseProperty.getType().equals(classNode)) {
                return inverseProperty;
            }
        }
        return null;
    }

    static void handleToOne(ClassNode classNode, Map<String, ClassNode> belongsToMap, String propertyName) {
        ClassNode associatedClass = belongsToMap.get(propertyName);

        final Map<String, ClassNode> inverseHasManyMap = lookupStringToClassNodeMap(associatedClass,
                GrailsDomainClassProperty.HAS_MANY);
        final Map<String, ClassNode> inverseHasOneMap = lookupStringToClassNodeMap(associatedClass,
                GrailsDomainClassProperty.HAS_ONE);

        if (inverseHasManyMap.containsValue(classNode)) {
            for (String inversePropertyName : inverseHasManyMap.keySet()) {
                if (classNode.equals(inverseHasManyMap.get(inversePropertyName))) {
                    final AnnotationNode manyToOneAnnotation = new AnnotationNode(new ClassNode(ManyToOne.class));
                    manyToOneAnnotation.addMember("cascade", EXPR_CASCADE_PERSIST);
                    annotateProperty(classNode, propertyName, manyToOneAnnotation);
                }
            }
        } else if (inverseHasOneMap.containsValue(classNode)) {
            for (String inversePropertyName : inverseHasOneMap.keySet()) {
                if (classNode.equals(inverseHasOneMap.get(inversePropertyName))) {
                    final AnnotationNode oneToOneAnnotation = new AnnotationNode(new ClassNode(OneToOne.class));
                    oneToOneAnnotation.addMember("mappedBy", new ConstantExpression(inversePropertyName));
                    annotateProperty(classNode, propertyName, oneToOneAnnotation);
                }
            }
        } else {
            AnnotationNode annotationNode = new AnnotationNode(new ClassNode(ManyToOne.class));
            annotationNode.addMember("cascade", EXPR_CASCADE_ALL);
            annotateProperty(classNode, propertyName, annotationNode);
        }
    }

    private static List<ClassNode> getBelongsToList(ClassNode classNode) {
        PropertyNode propertyNode = classNode.getProperty(GrailsDomainClassProperty.BELONGS_TO);

        List<ClassNode> classNodes = new ArrayList<ClassNode>();
        if (propertyNode != null && propertyNode.isStatic()) {
            final Expression initialExpression = propertyNode.getInitialExpression();
            if (initialExpression instanceof ListExpression) {
                for (Expression expr : ((ListExpression) initialExpression).getExpressions()) {
                    if (expr instanceof ClassExpression) {
                        classNodes.add(expr.getType());
                    }
                }
            } else if (initialExpression instanceof ClassExpression) {
                classNodes.add(initialExpression.getType());
            }
        }
        return classNodes;
    }

    private static Map<String, String> lookupStringToStringMap(ClassNode classNode, String mapName) {

        final PropertyNode mapProperty = classNode.getProperty(mapName);
        if (mapProperty == null) {
            return Collections.emptyMap();
        }

        final Expression initialExpression = mapProperty.getInitialExpression();
        if (!(initialExpression instanceof MapExpression)) {
            return Collections.emptyMap();
        }

        Map<String, String> stringToClassNodeMap = new HashMap<String, String>();
        MapExpression mapExpr = (MapExpression) initialExpression;
        final List<MapEntryExpression> mapEntryExpressions = mapExpr.getMapEntryExpressions();
        for (MapEntryExpression mapEntryExpression : mapEntryExpressions) {
            final Expression keyExpression = mapEntryExpression.getKeyExpression();
            if (!(keyExpression instanceof ConstantExpression)) {
                continue;
            }
            ConstantExpression ce = (ConstantExpression) keyExpression;
            String propertyName = ce.getValue().toString();
            final Expression valueExpression = mapEntryExpression.getValueExpression();
            if (valueExpression instanceof ConstantExpression) {
                stringToClassNodeMap.put(propertyName,
                        ((ConstantExpression) valueExpression).getValue().toString());
            }
        }
        return stringToClassNodeMap;
    }

    private static Map<String, ClassNode> lookupStringToClassNodeMap(ClassNode classNode, String mapName) {

        if (classNode == null) {
            return Collections.emptyMap();
        }

        final PropertyNode mapProperty = classNode.getProperty(mapName);
        if (mapProperty == null) {
            return Collections.emptyMap();
        }

        final Expression initialExpression = mapProperty.getInitialExpression();
        if (!(initialExpression instanceof MapExpression)) {
            return Collections.emptyMap();
        }

        Map<String, ClassNode> stringToClassNodeMap = new HashMap<String, ClassNode>();
        MapExpression mapExpr = (MapExpression) initialExpression;
        final List<MapEntryExpression> mapEntryExpressions = mapExpr.getMapEntryExpressions();
        for (MapEntryExpression mapEntryExpression : mapEntryExpressions) {
            final Expression keyExpression = mapEntryExpression.getKeyExpression();
            if (!(keyExpression instanceof ConstantExpression)) {
                continue;
            }
            ConstantExpression ce = (ConstantExpression) keyExpression;
            String propertyName = ce.getValue().toString();
            final Expression valueExpression = mapEntryExpression.getValueExpression();
            if (valueExpression instanceof ClassExpression) {
                ClassExpression clsExpr = (ClassExpression) valueExpression;
                stringToClassNodeMap.put(propertyName, clsExpr.getType());
            }
        }
        return stringToClassNodeMap;
    }

    private static void annotateIfNecessary(ClassNode classNode, Class<?> annotationClass) {
        AnnotationNode ann = new AnnotationNode(new ClassNode(annotationClass));

        final List<AnnotationNode> annotations = classNode.getAnnotations();
        if (annotations != null) {
            for (AnnotationNode annotationNode : annotations) {
                if (annotationNode.equals(ann))
                    return;
            }
        }

        classNode.addAnnotation(ann);
    }

    protected static void annotateAllProperties(ClassNode classNode, List<String> propertyNames,
            final Class<?> annotation) {
        final AnnotationNode annotationNode = new AnnotationNode(new ClassNode(annotation));
        annotateAllProperties(classNode, propertyNames, annotationNode);
    }

    protected static void annotateAllProperties(ClassNode classNode, Collection<String> propertyNames,
            final AnnotationNode annotationNode) {
        for (String propertyName : propertyNames) {
            annotateProperty(classNode, propertyName, annotationNode);
        }
    }

    protected static void annotateProperty(ClassNode classNode, String propertyName, Class<?> annotation) {
        annotateProperty(classNode, propertyName, new AnnotationNode(new ClassNode(annotation)));
    }

    protected static void annotateProperty(ClassNode classNode, String propertyName,
            final AnnotationNode annotationNode) {
        final PropertyNode prop = classNode.getProperty(propertyName);
        if (prop == null) {
            return;
        }

        final FieldNode fieldNode = prop.getField();
        if (fieldNode == null) {
            return;
        }

        final List<AnnotationNode> annotations = fieldNode.getAnnotations(annotationNode.getClassNode());
        if (annotations == null || annotations.isEmpty()) {
            fieldNode.addAnnotation(annotationNode);
        }
    }

    protected static void populateConstantList(List<String> theList, final PropertyNode theProperty) {
        if (theProperty == null) {
            return;
        }

        final Expression initialExpression = theProperty.getInitialExpression();
        if (initialExpression instanceof ListExpression) {
            ListExpression listExpression = (ListExpression) initialExpression;
            final List<Expression> entries = listExpression.getExpressions();
            for (Expression expression : entries) {
                if (expression instanceof ConstantExpression) {
                    addConstantExpressionToList(theList, expression);
                }
            }
        } else if (initialExpression instanceof ConstantExpression) {
            addConstantExpressionToList(theList, initialExpression);
        }
    }

    protected static void addConstantExpressionToList(List<String> theList, Expression expression) {
        final Object val = ((ConstantExpression) expression).getValue();
        if (val != null) {
            theList.add(val.toString());
        }
    }
}