/* * 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.grails.compiler.injection; //import org.apache.commons.logging.Log; //import org.apache.commons.logging.LogFactory; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; 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.transform.ASTTransformation; import org.codehaus.groovy.transform.GroovyASTTransformation; import java.lang.reflect.Modifier; import java.net.URL; import java.util.*; /** * This is substantially the same code from Grails, except some references de-referenced * and the macro class added. * * Default implementation of domain class injector interface that adds the 'id' * and 'version' properties and other previously boilerplate code * * @author Graeme Rocher * * @since 0.2 * * Created: 20th June 2006 */ @GroovyASTTransformation(phase= CompilePhase.CANONICALIZATION) public class DefaultGrailsDomainClassInjector implements ASTTransformation { //GrailsDomainClassInjector { //private static final Log LOG = LogFactory.getLog(DefaultGrailsDomainClassInjector.class); public void visit(ASTNode[] nodes, SourceUnit source) { performInjection((ClassNode) nodes[1]); } public void performInjection(ClassNode classNode) { if(shouldInject(classNode)) { injectIdProperty(classNode); injectVersionProperty(classNode); injectToStringMethod(classNode); injectAssociations(classNode); } } public boolean shouldInject(URL url) { return true; //return GrailsResourceUtils.isDomainClass(url); } private boolean shouldInject(ClassNode classNode) { //String fullName = GrailsASTUtils.getFullName(classNode); //String mappingFile = GrailsDomainConfigurationUtil.getMappingFileName(fullName); //if(getClass().getResource(mappingFile)!=null) { //if(LOG.isDebugEnabled()) { //LOG.debug("[GrailsDomainInjector] Mapping file ["+mappingFile+"] found. Skipping property injection."); //} //return false; //} return true; } private void injectAssociations(ClassNode classNode) { List properties = classNode.getProperties(); List propertiesToAdd = new ArrayList(); for (Iterator p = properties.iterator(); p.hasNext();) { PropertyNode pn = (PropertyNode) p.next(); final boolean isHasManyProperty = pn.getName().equals(/*GrailsDomainClassProperty.*/RELATES_TO_MANY) || pn.getName().equals(/*GrailsDomainClassProperty.*/HAS_MANY); if(isHasManyProperty) { Expression e = pn.getInitialExpression(); propertiesToAdd.addAll(createPropertiesForHasManyExpression(e,classNode)); } final boolean isBelongsTo = pn.getName().equals(/*GrailsDomainClassProperty.*/BELONGS_TO); if(isBelongsTo) { Expression e = pn.getInitialExpression(); propertiesToAdd.addAll(createPropertiesForBelongsToExpression(e,classNode)); } } injectAssociationProperties(classNode,propertiesToAdd); } private Collection createPropertiesForBelongsToExpression(Expression e, ClassNode classNode) { List properties = new ArrayList(); if(e instanceof MapExpression) { MapExpression me = (MapExpression)e; List mapEntries = me.getMapEntryExpressions(); for (Iterator i = mapEntries.iterator(); i.hasNext();) { MapEntryExpression mme = (MapEntryExpression) i.next(); String key = mme.getKeyExpression().getText(); String type = mme.getValueExpression().getText(); properties.add(new PropertyNode(key,Modifier.PUBLIC, ClassHelper.make(type) , classNode, null,null,null)); } } return properties; } private void injectAssociationProperties(ClassNode classNode, List propertiesToAdd) { for (Iterator i = propertiesToAdd.iterator(); i.hasNext();) { PropertyNode pn = (PropertyNode) i.next(); if(!/*GrailsASTUtils.*/hasProperty(classNode, pn.getName())) { //if(LOG.isDebugEnabled()) { // LOG.debug("[GrailsDomainInjector] Adding property [" + pn.getName() + "] to class [" + classNode.getName() + "]"); //} classNode.addProperty(pn); } } } private List createPropertiesForHasManyExpression(Expression e, ClassNode classNode) { List properties = new ArrayList(); if(e instanceof MapExpression) { MapExpression me = (MapExpression)e; List mapEntries = me.getMapEntryExpressions(); for (Iterator j = mapEntries.iterator(); j.hasNext();) { MapEntryExpression mee = (MapEntryExpression) j.next(); Expression keyExpression = mee.getKeyExpression(); String key = keyExpression.getText(); addAssociationForKey(key,properties,classNode); } } return properties; } private void addAssociationForKey(String key, List properties, ClassNode classNode) { properties.add(new PropertyNode(key, Modifier.PUBLIC, new ClassNode(Set.class), classNode,null, null, null)); } private void injectToStringMethod(ClassNode classNode) { final boolean hasToString = /*GrailsASTUtils.*/implementsZeroArgMethod(classNode, "toString"); if(!hasToString) { GStringExpression ge = new GStringExpression(classNode.getName() + " : ${id}"); ge.addString(new ConstantExpression(classNode.getName()+" : ")); ge.addValue(new VariableExpression("id")); Statement s = new ReturnStatement(ge); MethodNode mn = new MethodNode("toString",Modifier.PUBLIC,new ClassNode(String.class), new Parameter[0],new ClassNode[0],s); //if(LOG.isDebugEnabled()) { // LOG.debug("[GrailsDomainInjector] Adding method [toString()] to class [" + classNode.getName() + "]"); //} classNode.addMethod(mn); } } private void injectVersionProperty(ClassNode classNode) { final boolean hasVersion = /*GrailsASTUtils.*/hasProperty(classNode, /*GrailsDomainClassProperty.*/VERSION); if(!hasVersion) { //if(LOG.isDebugEnabled()) { // LOG.debug("[GrailsDomainInjector] Adding property [" + GrailsDomainClassProperty.VERSION + "] to class [" + classNode.getName() + "]"); //} classNode.addProperty( /*GrailsDomainClassProperty.*/VERSION, Modifier.PUBLIC, new ClassNode(Long.class), null, null, null); } } private void injectIdProperty(ClassNode classNode) { final boolean hasId = /*GrailsASTUtils.*/hasProperty(classNode,/*GrailsDomainClassProperty.*/IDENTITY); if(!hasId) { //if(LOG.isDebugEnabled()) { // LOG.debug("[GrailsDomainInjector] Adding property [" + GrailsDomainClassProperty.IDENTITY + "] to class [" + classNode.getName() + "]"); //} classNode.addProperty( /*GrailsDomainClassProperty.*/IDENTITY, Modifier.PUBLIC, new ClassNode(Long.class), null, null, null); } } //*************************************************************** // from GrailsASTUtils //*************************************************************** /** * Returns whether a classNode has the specified property or not * * @param classNode The ClassNode * @param propertyName The name of the property * @return True if the property exists in the ClassNode */ public static boolean hasProperty(ClassNode classNode, String propertyName) { if(classNode == null || propertyName == null || "".equals(propertyName.trim())) return false; List properties = classNode.getProperties(); for (Iterator i = properties.iterator(); i.hasNext();) { PropertyNode pn = (PropertyNode) i.next(); if(pn.getName().equals(propertyName)) return true; } return false; } /** * Tests whether the ClasNode implements the specified method name * * @param classNode The ClassNode * @param methodName The method name * @return True if it does implement the method */ public static boolean implementsZeroArgMethod(ClassNode classNode, String methodName) { return implementsMethod(classNode, methodName, new Class[0]); } /** * Tests whether the ClassNode implements the specified method name * * @param classNode The ClassNode * @param methodName The method name * @param argTypes * @return True if it implements the method */ private static boolean implementsMethod(ClassNode classNode, String methodName, Class[] argTypes) { List methods = classNode.getMethods(); if (argTypes == null || argTypes.length ==0) { for (Iterator i = methods.iterator(); i.hasNext();) { MethodNode mn = (MethodNode) i.next(); boolean methodMatch = mn.getName().equals(methodName); if(methodMatch)return true; // TODO Implement further parameter analysis } } return false; } //*************************************************************** // from GrailsDomainClassProperty //*************************************************************** private static final String RELATES_TO_MANY = "relatesToMany"; private static final String BELONGS_TO = "belongsTo"; private static final String HAS_MANY = "hasMany"; private static final String IDENTITY = "id"; private static final String VERSION = "version"; }