/*
* Copyright (c) 2005
* XDoclet Team
* All rights reserved.
*/
package org.xdoclet.plugin.ejb.entity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import org.generama.MergeableVelocityTemplateEngine;
import org.generama.WriterMapper;
import org.xdoclet.plugin.ejb.EjbConfig;
import org.xdoclet.plugin.ejb.EjbJavaClassBuilder;
import org.xdoclet.plugin.ejb.EjbJavaGeneratingPlugin;
import org.xdoclet.plugin.ejb.EjbJavaType;
import org.xdoclet.plugin.ejb.EjbRuntime;
import org.xdoclet.plugin.ejb.EjbUtils;
import org.xdoclet.plugin.ejb.qtags.EjbPkTag;
import org.xdoclet.plugin.ejb.qtags.TagLibrary;
import com.thoughtworks.qdox.model.BeanProperty;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.Type;
/**
* Primary key class plugin
*
* @author Diogo Quintela
* @version $Revision: 638 $
*/
public class PrimaryKeyClassPlugin extends EjbJavaGeneratingPlugin implements EjbJavaClassBuilder {
/** Cache for hierarchy resolver */
private Map pkHierarchyCache = new HashMap();
public PrimaryKeyClassPlugin(MergeableVelocityTemplateEngine templateEngine,
WriterMapper writerMapper, EjbConfig config) {
super(templateEngine, writerMapper, config);
EjbRuntime.setPlugin(this);
setPackageregex("beans");
setPackagereplace("util");
super.setFileregex(config.getEjbReplaceRegex());
setFilereplace("PK");
super.setMultioutput(true);
}
/**
* Don't let multioutput be changed
*/
public void setMultioutput(boolean multioutput) {
throw new RuntimeException("Can't set multioutput for plugin");
}
/**
* Don't let fileregex be changed
*/
public void setFileregex(String fileregex) {
throw new RuntimeException("Can't set fileregex for plugin. Try setting it in " + EjbConfig.class.getName());
}
public String getExtends(JavaClass javaClass) {
PkMetadata pkHierarchy = getMetaPk(javaClass);
// We'll go up twice, as the first above is the metadata from the class being generated
pkHierarchy = (pkHierarchy != null) ? pkHierarchy.getParent() : null;
return (pkHierarchy != null) ? pkHierarchy.getType().toString() : "java.lang.Object";
}
public String[] getImplements(JavaClass javaClass) {
Collection implementsLst = new ArrayList();
PkMetadata pkHierarchy = getMetaPk(javaClass);
implementsLst.addAll(Arrays.asList(pkHierarchy.getImplements()));
if (!implementsLst.contains("java.io.Serializable")) {
implementsLst.add("java.io.Serializable");
}
return (String[]) implementsLst.toArray(new String[0]);
}
/**
* JavaClass that describes the generated artifacts
*
* @return The generated artifacts JavaClass otherwise null
*/
public JavaClass[] buildFor(JavaClass metadata) {
if (!shouldGenerate(metadata)) {
return null;
}
JavaField field;
JavaMethod method;
JavaClass retVal = createDynamicJavaClass(getDestinationClassname(metadata), getDestinationPackage(metadata), null, getMetadataProvider());
retVal.setModifiers(new String[] { "public" });
PkMetadata metaPk = getMetaPk(metadata);
String extendz = getExtends(metadata);
JavaClass parentBuilded[];
JavaClass parent = metadata.getSuperJavaClass();
while (parent != null) {
parentBuilded = buildFor(parent);
if ((parentBuilded != null) && (parentBuilded[0].getFullyQualifiedName().equals(extendz))) {
retVal.setSuperClass(parentBuilded[0].asType());
break;
}
parent = parent.getSuperJavaClass();
}
String[] implementz = getImplements(metadata);
Type[] implementzTypes = new Type[implementz.length];
for (int i = 0; i < implementz.length; i++) {
implementzTypes[i] = new Type(implementz[i]);
}
retVal.setImplementz(implementzTypes);
BeanProperty[] properties = metaPk.getProperties();
for (int i = 0; (properties != null) && (i < properties.length); i++) {
field = new JavaField(properties[i].getType(), properties[i].getName());
field.setModifiers(new String[] { "public" });
retVal.addField(field);
}
method = new JavaMethod(getDestinationClassname(metadata));
method.setConstructor(true);
method.setModifiers(new String[] { "public" });
retVal.addMethod(method);
BeanProperty[] parentProperties = metaPk.getParentProperties();
if ((properties != null && properties.length > 0)
|| (parentProperties != null && parentProperties.length > 0)) {
method = new JavaMethod(getDestinationClassname(metadata));
method.setConstructor(true);
method.setModifiers(new String[] { "public" });
JavaParameter[] params = new JavaParameter[((properties != null) ? properties.length : 0) + ((parentProperties != null) ? parentProperties.length : 0)];
int idx = 0;
for (int i = 0; (properties != null) && (i < properties.length); i++) {
params[idx++] = new JavaParameter(properties[i].getType(), properties[i].getName());
}
for (int i = 0; (parentProperties != null) && (i < parentProperties.length); i++) {
params[idx++] = new JavaParameter(parentProperties[i].getType(), parentProperties[i].getName());
}
method.setParameters(params);
retVal.addMethod(method);
}
for (int i = 0; (properties != null) && (i < properties.length); i++) {
method = new JavaMethod(getSetterName(properties[i]));
method.setModifiers(new String[] { "public" });
method.setParameters(new JavaParameter[]{new JavaParameter(properties[i].getType(), properties[i].getName())});
retVal.addMethod(method);
method = new JavaMethod(properties[i].getType(), getGetterName(properties[i]));
method.setModifiers(new String[] { "public" });
retVal.addMethod(method);
}
method = new JavaMethod(new Type("int"), "hashCode");
method.setModifiers(new String[] { "public" });
retVal.addMethod(method);
method = new JavaMethod(new Type("boolean"), "equals");
method.setModifiers(new String[] { "public" });
method.setParameters(new JavaParameter[]{new JavaParameter(new Type("java.lang.Object"), "other")});
retVal.addMethod(method);
method = new JavaMethod(new Type("java.lang.String"), "toString");
method.setModifiers(new String[] { "public" });
retVal.addMethod(method);
return new JavaClass[]{retVal};
}
public boolean shouldGenerate(Object metadata) {
JavaClass javaClass = (JavaClass) metadata;
PkMetadata pkHierarchy = getMetaPk(javaClass);
boolean generate = (pkHierarchy != null) && pkHierarchy.getType().equals(getVirtualType(javaClass));
if (generate) generate = isDestinationDirty(javaClass);
if (generate && verbose) System.out.println(
"Generating Primary Key for " + javaClass.getName());
return generate;
}
public PkMetadata getMetaPk(JavaClass javaClass) {
PkMetadata pkHierarchy = collectPkHierarchy(javaClass);
if (pkHierarchy.isEmptyWithParent()) {
return pkHierarchy.getParent();
}
return null;
}
public List getHierarchy(JavaClass javaClass) {
List hierarchyLst = new ArrayList();
hierarchyLst.add(javaClass);
while ((javaClass = javaClass.getSuperJavaClass()) != null) {
hierarchyLst.add(javaClass);
}
return hierarchyLst;
}
private BeanProperty[] getPkProperties(JavaClass javaClass) {
JavaMethod[] methods = javaClass.getMethods();
JavaMethod method;
SortedSet propSet = new TreeSet(new Comparator() {
public int compare(Object o1, Object o2) {
BeanProperty p1 = (BeanProperty) o1;
BeanProperty p2 = (BeanProperty) o2;
return p1.getName().compareTo(p2.getName());
}
});
for (int i = 0; i < methods.length; i++) {
method = methods[i];
int metaFlags = ejbUtils.getMethodMetadata(javaClass, method);
if (EjbUtils.hasFlag(metaFlags, EjbUtils.METADATA_METHOD_PRIMARY_KEY_FIELD)) {
BeanProperty beanProperty = javaClass.getBeanProperty(method.getPropertyName());
if (beanProperty != null) {
if (propSet.add(beanProperty)) {
if (log.isDebugEnabled()) {
log.debug(beanProperty.getName() + " was added to the Set");
}
} else {
if (log.isDebugEnabled()) {
log.debug(beanProperty.getName() +
" wasn't added to the Set. There must be already a property with the same name");
}
}
} else {
String errorMessage = "Unexpected null property for " + method.getPropertyName() + " in " +
javaClass.getFullyQualifiedName();
log.error(errorMessage);
throw new Error(errorMessage);
}
}
}
return (BeanProperty[]) propSet.toArray(new BeanProperty[0]);
}
public PkMetadata collectPkHierarchy(JavaClass javaClass) {
PkMetadata retVal = null;
if (pkHierarchyCache.containsKey(javaClass)) {
retVal = (PkMetadata) pkHierarchyCache.get(javaClass);
} else {
List hierarchyLst = getHierarchy(javaClass);
Collections.reverse(hierarchyLst); // Top down scan
PkClass current = new PkClass();
EjbPkTag pkTag;
for (Iterator iter = hierarchyLst.iterator(); iter.hasNext();) {
JavaClass clazz = (JavaClass) iter.next();
pkTag = (EjbPkTag) clazz.getTagByName(TagLibrary.EJB_PK);
if (pkTag != null) {
String pkExtends = pkTag.getExtends();
if (pkExtends != null) {
// We need to test if so far we haven't collected any properties to allow to set top class
// If we don't check this, we could be loosing primary key's properties
// NOTE: xdoclet1 allowed this, although it generates non compiling class
if (current.isEmpty() && current.getParent() == null) {
current.setType(new EjbJavaType(pkExtends));
current = new PkClass(current);
} else {
throw ejbUtils.getErrorWithTagLocation(pkTag,
"Can't set \"extends\" property without loosing current primary key properties: " +
current);
}
}
String[] implementz = pkTag.getImplements();
if (implementz != null) {
current.addImplements(implementz);
}
}
current.addProperties(getPkProperties(clazz));
if (!current.isSimpleProperty() && !current.isEmpty() && ((pkTag == null) || pkTag.isGenerate())) {
current.setType(getVirtualType(clazz));
current = new PkClass(current);
}
}
// Set into cache
pkHierarchyCache.put(javaClass, current);
// Set return value
retVal = current;
}
return retVal;
}
/**
* Gets primary key field name if exists
*
* @param javaClass Entity bean
*
* @return The field name if exists
*/
public String pkField(JavaClass javaClass) {
PkMetadata pkClassHierarchy = collectPkHierarchy(javaClass);
if (pkClassHierarchy.isSimpleProperty()) {
return pkClassHierarchy.getProperties()[0].getName();
}
return null;
}
/**
* Get the primary key class specified for a given class. If a primary key field has been specified, using the
* <code>primkey-field</code> parameter on the <code>ejb.bean</code> tag, this will be the return type of that
* field's getter method.
* Otherwise, it will be determined by the various parameters of the <code>ejb.pk</code> tag
* and the subtask's settings for default pattern, packageSubstitution, etc.
*
* @param javaClass Entity bean
*
* @return The primary key for the entity
* @throws ClassNotFoundException
*/
public String pkClass(JavaClass javaClass) {
PkMetadata pkClassHierarchy = collectPkHierarchy(javaClass);
if (pkClassHierarchy.isSimpleProperty()) {
return pkClassHierarchy.getProperties()[0].getType().toString();
} else {
if (pkClassHierarchy.isEmptyWithParent()) {
pkClassHierarchy = pkClassHierarchy.getParent();
return pkClassHierarchy.getType().toString();
} else {
throw new Error("Cannot resolve primary key class name (" + javaClass.getFullyQualifiedName() +
"). Have you forgot to set @ejb.bean \"primkey-field\" or @ejb.pk-field ? (" + pkClassHierarchy +
")");
}
}
}
/**
* Get the primary key class specified for a given class. If a primary key field has been specified, using the
* <code>primkey-field</code> parameter on the <code>ejb.bean</code> tag, this will be the return type of that
* field's getter method.
* Otherwise, it will be determined by the various parameters of the <code>ejb.pk</code> tag
* and the subtask's settings for default pattern, packageSubstitution, etc.
*
* @param javaClass Entity bean
*
* @return The primary key for the entity
* @throws ClassNotFoundException
*/
public Type getPkClassType(JavaClass javaClass) {
PkMetadata pkClassHierarchy = collectPkHierarchy(javaClass);
if (pkClassHierarchy.isSimpleProperty()) {
return pkClassHierarchy.getProperties()[0].getType();
} else {
if (pkClassHierarchy.isEmptyWithParent()) {
pkClassHierarchy = pkClassHierarchy.getParent();
return new Type(pkClassHierarchy.getType().toString());
} else {
throw new Error("Cannot resolve primary key class name (" + javaClass.getFullyQualifiedName() +
"). Have you forgot to set @ejb.bean \"primkey-field\" or @ejb.pk-field ? (" + pkClassHierarchy +
")");
}
}
}
protected String getLocalyDefinedFullClassName(JavaClass clazz) {
EjbPkTag pkTag = (EjbPkTag) clazz.getTagByName(TagLibrary.EJB_PK);
return (pkTag != null) ? pkTag.getClass_() : null;
}
protected String getPatternBasedUnqualifiedName(JavaClass clazz) {
EjbPkTag pkTag = (EjbPkTag) clazz.getTagByName(TagLibrary.EJB_PK);
return (pkTag != null && pkTag.getPattern() != null) ? ejbUtils.expandPattern(pkTag.getPattern(), clazz) : null;
}
protected String getLocalyDefinedPackageName(JavaClass clazz) {
EjbPkTag pkTag = (EjbPkTag) clazz.getTagByName(TagLibrary.EJB_PK);
return (pkTag != null) ? pkTag.getPackage() : null;
}
public final static class PkClass implements PkMetadata {
private EjbJavaType type;
private List propertiesLst = new ArrayList();
private List implementsLst = new ArrayList();
private PkClass parent;
public PkClass(PkClass parent) {
this.parent = parent;
}
public PkClass() {
// no parent
}
public void addImplements(String[] implementz) {
implementsLst.addAll(Arrays.asList(implementz));
}
public String[] getImplements() {
return (String[]) implementsLst.toArray(new String[0]);
}
public void addProperty(BeanProperty prop) {
propertiesLst.add(prop);
}
public void addProperties(BeanProperty[] prop) {
propertiesLst.addAll(Arrays.asList(prop));
}
public BeanProperty[] getProperties() {
return (BeanProperty[]) propertiesLst.toArray(new BeanProperty[0]);
}
public BeanProperty[] getParentProperties() {
List retVal = new ArrayList();
if (parent != null) {
retVal.addAll(Arrays.asList(parent.getProperties()));
retVal.addAll(Arrays.asList(parent.getParentProperties()));
}
return (BeanProperty[]) retVal.toArray(new BeanProperty[0]);
}
public EjbJavaType getType() {
return type;
}
public PkMetadata getParent() {
return this.parent;
}
public void setParent(PkClass parent) {
this.parent = parent;
}
public void setType(EjbJavaType type) {
this.type = type;
}
public boolean isSimpleProperty() {
return !hasParent() && (propertiesLst.size() == 1) && (implementsLst.size() == 0);
}
public boolean isEmpty() {
return (type == null) && (propertiesLst.size() == 0) && (implementsLst.size() == 0);
}
public boolean hasParent() {
return (parent != null);
}
public boolean isEmptyWithParent() {
return isEmpty() && hasParent();
}
public String toString() {
StringBuffer retBuf = new StringBuffer();
retBuf.append('[');
retBuf.append("type=");
retBuf.append(type);
retBuf.append(", properties=");
retBuf.append('{');
for (Iterator iter = propertiesLst.iterator(); iter.hasNext();) {
BeanProperty prop = (BeanProperty) iter.next();
retBuf.append(prop.getName());
if (iter.hasNext()) {
retBuf.append(',');
}
}
retBuf.append('}');
retBuf.append(", implements=");
retBuf.append('{');
for (Iterator iter = implementsLst.iterator(); iter.hasNext();) {
String implementz = (String) iter.next();
retBuf.append(implementz);
if (iter.hasNext()) {
retBuf.append(',');
}
}
retBuf.append('}');
retBuf.append(", parent=");
retBuf.append(parent);
retBuf.append(']');
return retBuf.toString();
}
}
}