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