package net.sourceforge.javautil.common.xml;
import java.util.LinkedHashMap;
import java.util.Map;
import net.sourceforge.javautil.common.reflection.ObjectManager;
import net.sourceforge.javautil.common.reflection.cache.ClassCache;
import net.sourceforge.javautil.common.reflection.cache.ClassDescriptor;
import net.sourceforge.javautil.common.reflection.cache.ClassProperty;
import net.sourceforge.javautil.common.xml.annotation.XmlClassRegistry;
import net.sourceforge.javautil.common.xml.annotation.XmlCollection;
import net.sourceforge.javautil.common.xml.annotation.XmlMap;
import net.sourceforge.javautil.common.xml.annotation.XmlParent;
import net.sourceforge.javautil.common.xml.annotation.XmlTag;
import net.sourceforge.javautil.common.xml.annotation.XmlCollection.CollectionType;
import net.sourceforge.javautil.common.xml.annotation.XmlMap.MapType;
import net.sourceforge.javautil.common.xml.annotation.XmlTag.ElementType;
/**
* The definition of an {@link XMLDocumentElement} and how it should be
* interpreted.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: XMLDocumentElementDefinition.java 2466 2010-10-24 11:43:57Z ponderator $
*/
public class XMLDocumentElementDefinition implements Comparable<XMLDocumentElementDefinition> {
/**
* The class property this element is related to.
*/
protected String propertyName;
protected Class<?> propertyType;
protected ClassProperty property;
protected XMLDocumentElementDefinition parent;
protected String tagName;
protected String tagNamespace;
protected Class<?> tagType;
protected ClassDescriptor<?> tagTypeDescriptor;
protected Map<String, XMLDocumentElementDefinition> properties = new LinkedHashMap<String, XMLDocumentElementDefinition>();
protected boolean searchedAll = false;
protected XmlTag tagDef;
protected XmlCollection collection;
protected XmlMap map;
protected XmlClassRegistry classRegistry;
protected CollectionType collectionType;
protected ElementType elementType = ElementType.Unspecified;
protected String actualTagName;
protected String before;
protected String after;
protected XMLTranslationGuide guide;
protected boolean xml = true;
public XMLDocumentElementDefinition(Class<?> clazz) {
this(null, clazz, null);
}
public XMLDocumentElementDefinition(XMLDocumentElementDefinition parent, Class<?> clazz, String name) {
this.parent = parent;
if (clazz == XMLDocumentMapEntry.class && parent != null) {
this.tagDef = parent.tagDef;
this.propertyName = parent.propertyName;
this.propertyType = parent.propertyType;
this.tagName = parent.tagName;
this.tagNamespace = parent.tagNamespace;
this.tagType = clazz;
this.tagTypeDescriptor = ClassCache.getFor(clazz);
this.elementType = ElementType.Complex;
this.map = parent.map;
this.actualTagName = name;
this.properties.put(map.keyName(), new XMLDocumentElementDefinition(this, this.tagTypeDescriptor.getProperty("key")));
this.properties.put(name == null ? map.valueName() : name, new XMLDocumentElementDefinition(this, this.tagTypeDescriptor.getProperty("value")));
this.properties.get(map.keyName()).before = map.valueName();
this.searchedAll = true;
} else {
this.describe(parent, clazz);
}
}
public XMLDocumentElementDefinition(XMLDocumentElementDefinition parent, ClassProperty property) {
this.property = property;
if (parent.map != null) {
if ("key".equals( property.getName() )) {
this.describe(parent, parent.map.keyClass());
this.tagName = parent.map.keyName();
this.propertyName = "key";
} else {
this.describe(parent, parent.map.valueClass());
this.tagName = parent.map.valueName();
this.propertyName = "value";
}
} else {
this.propertyName = property.getName();
this.propertyType = property.getType();
this.parent = parent;
this.describe(property);
}
}
public XMLDocumentElementDefinition(XMLDocumentElementDefinition original, ElementType newType, Class concrete) {
this.propertyName = original.propertyName;
this.propertyType = original.propertyType;
this.parent = original.parent;
this.map = original.map;
this.collection = original.collection;
this.collectionType = original.collectionType;
this.properties = original.properties;
this.searchedAll = original.searchedAll;
this.tagDef = original.tagDef;
this.tagName = original.tagName;
this.tagNamespace = original.tagNamespace;
this.tagType = concrete;
this.tagTypeDescriptor = ClassCache.getFor(concrete);
this.elementType = newType;
}
public ClassProperty getProperty() { return property; }
public String getPropertyName() { return propertyName; }
public Class<?> getPropertyType() { return propertyType; }
/**
* @return A new instance of the property type
*/
public Object newInstance () { return this.tagTypeDescriptor.newInstance(); }
/**
* @return The type of property
*/
public Class getClassType() { return propertyType; }
/**
* @return The property name, which may differ from the tag name
*/
public String getName () { return this.propertyName; }
/**
* @return The type of element this is
*/
public ElementType getElementType() { return elementType; }
/**
* @return The real tag name to use
*/
public String getTagName() { return tagName; }
/**
* @return The tag namespace
*/
public String getTagNamespace() { return tagNamespace; }
/**
* @return The real type of the tag
*/
public Class getTagType() { return tagType; }
/**
* @return The corresponding tag def, or none if this is a default element
*/
public XmlTag getTagDef() { return tagDef; }
/**
* @return True if this property is to be used with XML, otherwise false
*/
public boolean isXml () { return tagDef != null ? !tagDef.ignore() : xml; }
/**
* @return The collection info if this is {@link ElementType#Collection}
*/
public XmlCollection getCollection() { return collection; }
/**
* @return The map info if this is {@link ElementType#Map}
*/
public XmlMap getMap() { return map; }
/**
* @return The type of collection
*/
public CollectionType getCollectionType() { return collectionType; }
public String getActualTagName() { return actualTagName; }
public XmlClassRegistry getClassRegistry() { return classRegistry; }
public XMLTranslationGuide getGuide () { return guide; }
/**
* This will use a {@link ClassDescriptor} for the {@link #getTagType()}
* in order to lookup properties.
*
* @param name The name of the property
* @return A corresponding definition, or null if there is no such property
*/
public XMLDocumentElementDefinition getComplexProperty (String name) {
if (!this.properties.containsKey(name) && !this.searchedAll) {
Map<String, ClassProperty> properties = this.tagTypeDescriptor.getProperties();
for (String propertyName : properties.keySet()) {
ClassProperty property = this.tagTypeDescriptor.getProperty(propertyName);
XMLDocumentElementDefinition xmlDef = new XMLDocumentElementDefinition(this, property);
if (xmlDef.isXml()) {
this.properties.put(xmlDef.getTagName(), xmlDef);
if (name.equals(xmlDef.getTagName())) return xmlDef;
}
}
this.searchedAll = true;
}
return this.properties.get(name);
}
/**
* @return The collection definition for a collection type element
*/
public XMLDocumentElementDefinition getCollectionDefinition (String tagName) {
return this.map == null ? new XMLDocumentElementDefinition(this, this.collection.value(), null) : this.getMapDefinition(tagName);
}
/**
* @return The collection definition for a collection type element
*/
public XMLDocumentElementDefinition getMapDefinition (String tagName) {
return map.mapType() == MapType.CONTENT_KEY ?
new XMLDocumentElementDefinition(this, XMLDocumentMapEntry.class, null) :
new XMLDocumentElementDefinition(this, XMLDocumentMapEntry.class, tagName);
}
public int compareTo(XMLDocumentElementDefinition o) {
if (o == null) return 1;
if (o.before != null && !"".equals(o.before) && o.before.equals(propertyName)) return 1;
if (o.after != null && !"".equals(o.after) && o.after.equals(propertyName)) return -1;
if (this.before != null && !"".equals(this.before) && this.before.equals(o.propertyName)) return -1;
if (this.after != null && !"".equals(this.after) && this.after.equals(o.propertyName)) return 1;
return propertyName.compareTo(o.propertyName);
}
protected void describe (XMLDocumentElementDefinition parent, Class<?> clazz) {
this.tagDef = clazz.getAnnotation(XmlTag.class);
this.propertyName = parent == null ? clazz.getSimpleName() : parent.propertyName;
this.propertyType = clazz;
this.classRegistry = tagDef != null && tagDef.classRegistry().length > 0 ? tagDef.classRegistry()[0] : null;
if (tagDef == null || (tagDef != null && "".equals(tagDef.name()))) {
XmlTag tag = null;
Class<?> c = clazz.getSuperclass();
while (tag == null && c != null) {
tag = c.getAnnotation(XmlTag.class);
if (tag != null && !"".equals(tag.name())) {
if (tagDef == null) tagDef = tag;
tagName = tag.name(); break;
}
c = c.getSuperclass();
}
}
if (tagName == null) {
tagName = tagDef != null && !"".equals(tagDef.name()) ? tagDef.name() : (parent == null ? this.propertyName : parent.tagName);
}
this.tagNamespace = tagDef != null ? tagDef.namespace() : "";
this.tagType = clazz;
this.tagTypeDescriptor = ClassCache.getFor(clazz);
if (tagDef != null) {
this.elementType = tagDef.elementType();
if (tagDef.translation().length > 0) {
guide = ClassCache.getFor( tagDef.translation()[0].value() ).newInstance();
}
}
if (this.elementType == ElementType.Unspecified) {
this.elementType = XMLUtil.isSimpleType(clazz) ? ElementType.Simple : ElementType.Complex;
}
}
/**
* This will analyze the {@link #property} and detect annotations
* and using basic defaults determine what kind of element this is
* supposed to be.
*/
protected void describe (ClassProperty property) {
XmlParent parent = property.getAnnotation(XmlParent.class);
if (parent != null) {
this.xml = false;
return;
}
// The property overrides all
this.tagDef = property.getAnnotation(XmlTag.class);
// Then we check the property type annotations
if (tagDef == null) tagDef = property.getType().getAnnotation(XmlTag.class);
if (tagDef != null) {
if (Object.class != tagDef.concreteType()) this.tagType = tagDef.concreteType(); else this.tagType = property.getType();
// if (tagDef.translation().length > 0) {
// XMLTranslationGuide guide = ClassCache.getFor( tagDef.translation()[0].value() ).newInstance();
// guide.
// }
XmlTag typeDef = null;
if (tagType.getAnnotation(XmlTag.class) != null) typeDef = tagType.getAnnotation(XmlTag.class);
if (tagDef.collection().length > 0) {
this.collection = tagDef.collection()[0];
this.collectionType = this.collection.type();
this.tagType = this.collection.value();
if (this.collectionType != CollectionType.WRAPPED)
typeDef = this.collection.value().getAnnotation(XmlTag.class);
}
else if (tagDef.map().length > 0) {
this.map = tagDef.map()[0];
this.collectionType = this.map.type();
if (this.collectionType != CollectionType.WRAPPED)
typeDef = (XmlTag) this.map.valueClass().getAnnotation(XmlTag.class);
else
this.tagType = tagDef.concreteType() == Object.class ? LinkedHashMap.class : this.tagDef.concreteType();
} else {
if (this.elementType == ElementType.Unspecified)
this.elementType = tagDef.elementType() == ElementType.Unspecified && typeDef != null ? typeDef.elementType() : tagDef.elementType();
}
this.tagName = "".equals( tagDef.name() ) && typeDef != null ? typeDef.name() : tagDef.name();
if ("".equals(tagName)) tagName = property.getName();
this.tagNamespace = "".equals( tagDef.namespace() ) && typeDef != null ? typeDef.namespace() : tagDef.namespace();
this.classRegistry = tagDef.classRegistry().length > 0 ? tagDef.classRegistry()[0] :
(typeDef != null && typeDef.classRegistry().length > 0 ? typeDef.classRegistry()[0] : null);
if (this.elementType == ElementType.Unspecified) {
if (XMLUtil.isSimpleType(this.tagType)) {
this.elementType = ElementType.Simple;
} else {
this.elementType = ElementType.Complex;
}
}
this.before = tagDef.before();
this.after = tagDef.after();
} else {
this.tagName = property.getName();
this.tagNamespace = "";
this.tagType = property.getType();
if (XMLUtil.isSimpleType(this.tagType)) {
this.elementType = ElementType.Simple;
} else {
this.elementType = ElementType.Complex;
}
}
this.tagTypeDescriptor = property.getDescriptor().getCache().getDescriptor(this.tagType);
}
}