blob: 848ebf3a90251900fad36e994633844b2fcad477 [file] [log] [blame]
/*
* 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";
}