blob: 4e2d50968ab06a245fa8f230a9c2bf312676b421 [file] [log] [blame]
/**
*
* Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable.
*
* 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.apache.xbean.spring.generator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.BeanProperty;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.JavaSource;
import com.thoughtworks.qdox.model.Type;
/**
* @author Dain Sundstrom
* @version $Id$
* @since 1.0
*/
public class QdoxMappingLoader implements MappingLoader {
public static final String XBEAN_ANNOTATION = "org.apache.xbean.XBean";
public static final String PROPERTY_ANNOTATION = "org.apache.xbean.Property";
public static final String INIT_METHOD_ANNOTATION = "org.apache.xbean.InitMethod";
public static final String DESTROY_METHOD_ANNOTATION = "org.apache.xbean.DestroyMethod";
public static final String FACTORY_METHOD_ANNOTATION = "org.apache.xbean.FactoryMethod";
public static final String MAP_ANNOTATION = "org.apache.xbean.Map";
public static final String FLAT_PROPERTY_ANNOTATION = "org.apache.xbean.Flat";
public static final String FLAT_COLLECTION_ANNOTATION = "org.apache.xbean.FlatCollection";
public static final String ELEMENT_ANNOTATION = "org.apache.xbean.Element";
private static final Log log = LogFactory.getLog(QdoxMappingLoader.class);
private final String defaultNamespace;
private final File[] srcDirs;
private Type listType;
public QdoxMappingLoader(String defaultNamespace, File[] srcDirs) {
this.defaultNamespace = defaultNamespace;
this.srcDirs = srcDirs;
}
public String getDefaultNamespace() {
return defaultNamespace;
}
public File[] getSrcDirs() {
return srcDirs;
}
public Set loadNamespaces() throws IOException {
JavaDocBuilder builder = new JavaDocBuilder();
log.debug("Source directories: ");
for (int it = 0; it < srcDirs.length; it++) {
File sourceDirectory = srcDirs[it];
if (!sourceDirectory.isDirectory()) {
log.warn("Specified source directory isn't a directory: '" + sourceDirectory.getAbsolutePath() + "'.");
}
log.debug(" - " + sourceDirectory.getAbsolutePath());
builder.addSourceTree(sourceDirectory);
}
listType = builder.getClassByName("java.util.List").asType();
Set namespaces = loadNamespaces(builder);
return namespaces;
}
private Set loadNamespaces(JavaDocBuilder builder) {
// load all of the elements
List elements = loadElements(builder);
// index the elements by namespace and find the root element of each namespace
Map elementsByNamespace = new HashMap();
Map namespaceRoots = new HashMap();
for (Iterator iterator = elements.iterator(); iterator.hasNext();) {
ElementMapping element = (ElementMapping) iterator.next();
String namespace = element.getNamespace();
Set namespaceElements = (Set) elementsByNamespace.get(namespace);
if (namespaceElements == null) {
namespaceElements = new HashSet();
elementsByNamespace.put(namespace, namespaceElements);
}
namespaceElements.add(element);
if (element.isRootElement()) {
if (namespaceRoots.containsKey(namespace)) {
log.warn("Multiple root elements found for namespace " + namespace);
}
namespaceRoots.put(namespace, element);
}
}
// build the NamespaceMapping objects
Set namespaces = new TreeSet();
for (Iterator iterator = elementsByNamespace.entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String namespace = (String) entry.getKey();
Set namespaceElements = (Set) entry.getValue();
ElementMapping rootElement = (ElementMapping) namespaceRoots.get(namespace);
NamespaceMapping namespaceMapping = new NamespaceMapping(namespace, namespaceElements, rootElement);
namespaces.add(namespaceMapping);
}
return Collections.unmodifiableSet(namespaces);
}
private List loadElements(JavaDocBuilder builder) {
JavaSource[] javaSources = builder.getSources();
List elements = new ArrayList();
for (int i = 0; i < javaSources.length; i++) {
JavaClass javaClass = javaSources[i].getClasses()[0];
ElementMapping element = loadElement(javaClass);
if (element != null && !javaClass.isAbstract()) {
elements.add(element);
} else {
log.debug("No XML annotation found for type: " + javaClass.getFullyQualifiedName());
}
}
return elements;
}
private ElementMapping loadElement(JavaClass javaClass) {
DocletTag xbeanTag = javaClass.getTagByName(XBEAN_ANNOTATION);
if (xbeanTag == null) {
return null;
}
String element = getElementName(javaClass, xbeanTag);
String description = getProperty(xbeanTag, "description");
if (description == null) {
description = javaClass.getComment();
}
String namespace = getProperty(xbeanTag, "namespace", defaultNamespace);
boolean root = getBooleanProperty(xbeanTag, "rootElement");
String contentProperty = getProperty(xbeanTag, "contentProperty");
Map mapsByPropertyName = new HashMap();
List flatProperties = new ArrayList();
Map flatCollections = new HashMap();
Set attributes = new HashSet();
Map attributesByPropertyName = new HashMap();
for (JavaClass jClass = javaClass; jClass != null; jClass = jClass.getSuperJavaClass()) {
BeanProperty[] beanProperties = jClass.getBeanProperties();
for (int i = 0; i < beanProperties.length; i++) {
BeanProperty beanProperty = beanProperties[i];
// we only care about properties with a setter
if (beanProperty.getMutator() != null) {
AttributeMapping attributeMapping = loadAttribute(beanProperty, "");
if (attributeMapping != null) {
attributes.add(attributeMapping);
attributesByPropertyName.put(attributeMapping.getPropertyName(), attributeMapping);
}
JavaMethod acc = beanProperty.getAccessor();
if (acc != null) {
DocletTag mapTag = acc.getTagByName(MAP_ANNOTATION);
if (mapTag != null) {
MapMapping mm = new MapMapping(mapTag.getNamedParameter("entryName"),
mapTag.getNamedParameter("keyName"));
mapsByPropertyName.put(beanProperty.getName(), mm);
}
DocletTag flatColTag = acc.getTagByName(FLAT_COLLECTION_ANNOTATION);
if (flatColTag != null) {
String childName = flatColTag.getNamedParameter("childElement");
if (childName == null)
throw new InvalidModelException("Flat collections must specify the childElement attribute.");
flatCollections.put(beanProperty.getName(), childName);
}
DocletTag flatPropTag = acc.getTagByName(FLAT_PROPERTY_ANNOTATION);
if (flatPropTag != null) {
flatProperties.add(beanProperty.getName());
}
}
}
}
}
String initMethod = null;
String destroyMethod = null;
String factoryMethod = null;
for (JavaClass jClass = javaClass; jClass != null; jClass = jClass.getSuperJavaClass()) {
JavaMethod[] methods = javaClass.getMethods();
for (int i = 0; i < methods.length; i++) {
JavaMethod method = methods[i];
if (method.isPublic() && !method.isConstructor()) {
if (initMethod == null && method.getTagByName(INIT_METHOD_ANNOTATION) != null) {
initMethod = method.getName();
}
if (destroyMethod == null && method.getTagByName(DESTROY_METHOD_ANNOTATION) != null) {
destroyMethod = method.getName();
}
if (factoryMethod == null && method.getTagByName(FACTORY_METHOD_ANNOTATION) != null) {
factoryMethod = method.getName();
}
}
}
}
List constructorArgs = new ArrayList();
JavaMethod[] methods = javaClass.getMethods();
for (int i = 0; i < methods.length; i++) {
JavaMethod method = methods[i];
JavaParameter[] parameters = method.getParameters();
if (isValidConstructor(factoryMethod, method, parameters)) {
List args = new ArrayList(parameters.length);
for (int j = 0; j < parameters.length; j++) {
JavaParameter parameter = parameters[j];
String parameterType = parameter.getType().toString();
AttributeMapping attributeMapping = (AttributeMapping) attributesByPropertyName.get(parameter.getName());
if (attributeMapping == null) {
attributeMapping = loadParameter(parameter);
attributes.add(attributeMapping);
attributesByPropertyName.put(attributeMapping.getPropertyName(), attributeMapping);
}
if (!parameterType.equals(attributeMapping.getType().getName())) {
throw new InvalidModelException("Type mismatch:" +
" The construction method " + toMethodLocator(parameter.getParentMethod()) +
" declared parameter " + parameter.getName() + " as a " + parameterType +
" but the bean property type is " + attributeMapping.getType().getName());
}
args.add(attributeMapping);
}
constructorArgs.add(Collections.unmodifiableList(args));
}
}
return new ElementMapping(namespace,
element,
javaClass.getFullyQualifiedName(),
description,
root,
initMethod,
destroyMethod,
factoryMethod,
contentProperty,
attributes,
constructorArgs,
flatProperties,
mapsByPropertyName,
flatCollections);
}
private String getElementName(JavaClass javaClass, DocletTag tag) {
String elementName = getProperty(tag, "element");
if (elementName == null) {
String className = javaClass.getFullyQualifiedName();
int index = className.lastIndexOf(".");
if (index > 0) {
className = className.substring(index + 1);
}
// strip off "Bean" from a spring factory bean
if (className.endsWith("FactoryBean")) {
className = className.substring(0, className.length() - 4);
}
elementName = Utils.decapitalise(className);
}
return elementName;
}
private AttributeMapping loadAttribute(BeanProperty beanProperty, String defaultDescription) {
DocletTag propertyTag = getPropertyTag(beanProperty);
if (getBooleanProperty(propertyTag, "hidden")) {
return null;
}
String attribute = getProperty(propertyTag, "alias", beanProperty.getName());
String attributeDescription = getAttributeDescription(beanProperty, propertyTag, defaultDescription);
String defaultValue = getProperty(propertyTag, "default");
boolean fixed = getBooleanProperty(propertyTag, "fixed");
boolean required = getBooleanProperty(propertyTag, "required");
String nestedType = getProperty(propertyTag, "nestedType");
return new AttributeMapping(attribute,
beanProperty.getName(),
attributeDescription,
toMappingType(beanProperty.getType(), nestedType),
defaultValue,
fixed,
required);
}
private static DocletTag getPropertyTag(BeanProperty beanProperty) {
JavaMethod accessor = beanProperty.getAccessor();
if (accessor != null) {
DocletTag propertyTag = accessor.getTagByName(PROPERTY_ANNOTATION);
if (propertyTag != null) {
return propertyTag;
}
}
JavaMethod mutator = beanProperty.getMutator();
if (mutator != null) {
DocletTag propertyTag = mutator.getTagByName(PROPERTY_ANNOTATION);
if (propertyTag != null) {
return propertyTag;
}
}
return null;
}
private String getAttributeDescription(BeanProperty beanProperty, DocletTag propertyTag, String defaultDescription) {
String description = getProperty(propertyTag, "description");
if (description != null && description.trim().length() > 0) {
return description.trim();
}
JavaMethod accessor = beanProperty.getAccessor();
if (accessor != null) {
description = accessor.getComment();
if (description != null && description.trim().length() > 0) {
return description.trim();
}
}
JavaMethod mutator = beanProperty.getMutator();
if (mutator != null) {
description = mutator.getComment();
if (description != null && description.trim().length() > 0) {
return description.trim();
}
}
return defaultDescription;
}
private AttributeMapping loadParameter(JavaParameter parameter) {
String parameterName = parameter.getName();
String parameterDescription = getParameterDescription(parameter);
// first attempt to load the attribute from the java beans accessor methods
JavaClass javaClass = parameter.getParentMethod().getParentClass();
BeanProperty beanProperty = javaClass.getBeanProperty(parameterName);
if (beanProperty != null) {
AttributeMapping attributeMapping = loadAttribute(beanProperty, parameterDescription);
// if the attribute mapping is null, the property was tagged as hidden and this is an error
if (attributeMapping == null) {
throw new InvalidModelException("Hidden property usage: " +
"The construction method " + toMethodLocator(parameter.getParentMethod()) +
" can not use a hidded property " + parameterName);
}
return attributeMapping;
}
// create an attribute solely based on the parameter information
return new AttributeMapping(parameterName,
parameterName,
parameterDescription,
toMappingType(parameter.getType(), null),
null,
false,
false);
}
private String getParameterDescription(JavaParameter parameter) {
String parameterName = parameter.getName();
DocletTag[] tags = parameter.getParentMethod().getTagsByName("param");
for (int k = 0; k < tags.length; k++) {
DocletTag tag = tags[k];
if (tag.getParameters()[0].equals(parameterName)) {
String parameterDescription = tag.getValue().trim();
if (parameterDescription.startsWith(parameterName)) {
parameterDescription = parameterDescription.substring(parameterName.length()).trim();
}
return parameterDescription;
}
}
return null;
}
private boolean isValidConstructor(String factoryMethod, JavaMethod method, JavaParameter[] parameters) {
if (!method.isPublic() || parameters.length == 0) {
return false;
}
if (factoryMethod == null) {
return method.isConstructor();
} else {
return method.getName().equals(factoryMethod);
}
}
private static String getProperty(DocletTag propertyTag, String propertyName) {
return getProperty(propertyTag, propertyName, null);
}
private static String getProperty(DocletTag propertyTag, String propertyName, String defaultValue) {
String value = null;
if (propertyTag != null) {
value = propertyTag.getNamedParameter(propertyName);
}
if (value == null) {
return defaultValue;
}
return value;
}
private boolean getBooleanProperty(DocletTag propertyTag, String propertyName) {
return toBoolean(getProperty(propertyTag, propertyName));
}
private static boolean toBoolean(String value) {
if (value != null) {
return new Boolean(value).booleanValue();
}
return false;
}
private org.apache.xbean.spring.generator.Type toMappingType(Type type, String nestedType) {
if (type.isArray()) {
return org.apache.xbean.spring.generator.Type.newArrayType(type.getValue(), type.getDimensions());
} else if (type.isA(listType)) {
if (nestedType == null) nestedType = "java.lang.Object";
return org.apache.xbean.spring.generator.Type.newCollectionType(type.getValue(),
org.apache.xbean.spring.generator.Type.newSimpleType(nestedType));
} else {
return org.apache.xbean.spring.generator.Type.newSimpleType(type.getValue());
}
}
private static String toMethodLocator(JavaMethod method) {
StringBuffer buf = new StringBuffer();
buf.append(method.getParentClass().getFullyQualifiedName());
if (!method.isConstructor()) {
buf.append(".").append(method.getName());
}
buf.append("(");
JavaParameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
JavaParameter parameter = parameters[i];
if (i > 0) {
buf.append(", ");
}
buf.append(parameter.getName());
}
buf.append(") : ").append(method.getLineNumber());
return buf.toString();
}
}