package jadex.xml.writer;
import jadex.commons.SReflect;
import jadex.xml.AccessInfo;
import jadex.xml.AttributeInfo;
import jadex.xml.IContext;
import jadex.xml.IObjectStringConverter;
import jadex.xml.ObjectInfo;
import jadex.xml.SubobjectInfo;
import jadex.xml.TypeInfo;
import jadex.xml.TypeInfoTypeManager;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
/**
* Abstract base class for an object writer handler. Is object type agnostic and
* uses several abstract methods that have to be overridden by concrete handlers.
*/
public abstract class AbstractObjectWriterHandler implements IObjectWriterHandler
{
//-------- attributes --------
/** Control flag for generating container tags. */
protected boolean gentypetags;
/** Flag indicating if writing tags should be preferred wrt. attributes. */
protected boolean prefertags;
/** The flattening flag for tags, i.e. generate always new containing tags or use one. */
protected boolean flattening;
/** The type info manager. */
protected TypeInfoTypeManager titmanager;
//-------- constructors --------
/**
* Create a new writer handler.
*/
public AbstractObjectWriterHandler(boolean gentypetags, boolean prefertags, boolean flattening, Set typeinfos)
{
this.gentypetags = gentypetags;
this.prefertags = prefertags;
this.flattening = flattening;
this.titmanager = new TypeInfoTypeManager(typeinfos);
}
//-------- methods --------
/**
* Get the most specific mapping info.
* @param type The type.
* @param fullpath The full path.
* @return The most specific mapping info.
*/
public TypeInfo getTypeInfo(Object object, QName[] fullpath, IContext context)
{
TypeInfo ret = null;
Object type = getObjectType(object, context);
Set tis = titmanager.getTypeInfosByType(type);
if(tis!=null)
{
// Take not into account path if only one candidate
if(tis.size()==1)
{
ret = (TypeInfo)tis.iterator().next();
}
// Else disambiguate using path
else if(tis.size()>1)
{
ret = titmanager.findTypeInfo(tis, fullpath);
}
}
return ret;
// return titmanager.getTypeInfo(type, fullpath);
}
/**
* Get the titmanager.
* @return The titmanager.
*/
public TypeInfoTypeManager getTypeInfoManager()
{
return this.titmanager;
}
/**
* Get the object type
* @param object The object.
* @return The object type.
*/
public abstract Object getObjectType(Object object, IContext context);
/**
* Get write info for an object.
*/
public WriteObjectInfo getObjectWriteInfo(Object object, TypeInfo typeinfo, IContext context) throws Exception
{
// todo: conversion value to string
WriteObjectInfo wi = new WriteObjectInfo();
HashSet doneprops = new HashSet();
if(typeinfo!=null)
{
// Comment
Object info = typeinfo.getCommentInfo();
if(info!=null)
{
Object property = getProperty(info);//==null? AttributeInfo.COMMENT: getProperty(info);
if(property!=null)
{
doneprops.add(getPropertyName(property));
if(!(info instanceof AttributeInfo && ((AttributeInfo)info).isIgnoreWrite()))
{
Object value = getValue(object, property, context, info);
if(value!=null)
{
value = convertValue(info, value, context);
wi.setComment(value.toString());
}
}
}
}
// Content
info = typeinfo.getContentInfo();
if(info!=null)
{
Object property = getProperty(info);//==null? AttributeInfo.CONTENT: getProperty(info);
if(property!=null)
{
doneprops.add(getPropertyName(property));
if(!(info instanceof AttributeInfo && ((AttributeInfo)info).isIgnoreWrite()))
{
Object value = getValue(object, property, context, info);
if(value!=null)
{
value = convertValue(info, value, context);
wi.setContent(value.toString());
}
}
}
}
// Attributes
Collection attrinfos = typeinfo.getAttributeInfos();
if(attrinfos!=null)
{
for(Iterator it=attrinfos.iterator(); it.hasNext(); )
{
info = it.next();
Object property = getProperty(info);
if(property!=null)
{
doneprops.add(getPropertyName(property));
if(!(info instanceof AttributeInfo && ((AttributeInfo)info).isIgnoreWrite()))
{
Object value = getValue(object, property, context, info);
if(value!=null)
{
// When attribute is a IDREF then
// a) find type info for current value
// b) find id attribute
// c) find id attribute value
AttributeInfo attrinfo = (AttributeInfo)info;
if(AttributeInfo.IDREF.equals(attrinfo.getId()))
{
Set tis = titmanager.getTypeInfosByType(value.getClass());
if(tis==null || tis.size()!=1)
throw new RuntimeException("Could not determine type info for idref object: "+value);
TypeInfo ti = (TypeInfo)tis.iterator().next();
AttributeInfo idinfo = null;
if(ti.getAttributeInfos()!=null)
{
for(Iterator it2 = ti.getAttributeInfos().iterator(); idinfo==null && it2.hasNext(); )
{
AttributeInfo tmp = (AttributeInfo)it2.next();
if(AttributeInfo.ID.equals(tmp.getId()))
idinfo = tmp;
}
}
if(idinfo==null && (ti.getCommentInfo() instanceof AttributeInfo)
&& AttributeInfo.ID.equals(((AttributeInfo)ti.getCommentInfo()).getId()))
{
idinfo = ((AttributeInfo)ti.getCommentInfo());
}
if(idinfo==null && (ti.getContentInfo() instanceof AttributeInfo)
&& AttributeInfo.ID.equals(((AttributeInfo)ti.getContentInfo()).getId()))
{
idinfo = ((AttributeInfo)ti.getContentInfo());
}
if(idinfo==null)
throw new RuntimeException("Could not determine id attribute of type info: "+ti);
Object prop = getProperty(idinfo);
value = getValue(value, prop, context, idinfo);
// System.out.println("Found id value: "+value);
}
Object defval = getDefaultValue(info);
if(!value.equals(defval))
{
value = convertValue(info, value, context);
// Do we want sometimes to write default values?
Object xmlattrname = null;
if(info instanceof AttributeInfo)
xmlattrname = ((AttributeInfo)info).getXMLAttributeName();
if(xmlattrname==null)
xmlattrname = getPropertyName(property);
wi.addAttribute(xmlattrname, value.toString());
}
}
}
}
}
}
// Subobjects
Collection subobsinfos = typeinfo.getSubobjectInfos();
if(subobsinfos!=null)
{
for(Iterator it=subobsinfos.iterator(); it.hasNext(); )
{
SubobjectInfo soinfo = (SubobjectInfo)it.next();
AccessInfo ai = soinfo.getAccessInfo();
ObjectInfo obinfo = soinfo.getObjectInfo();
Object property = getProperty(soinfo);
if(property!=null)
{
String propname = getPropertyName(property);
doneprops.add(propname);
if(!(ai!=null && ai.isIgnoreWrite()))
{
Object value = getValue(object, property, context, soinfo);
if(value!=null)
{
QName[] xmlpath = soinfo.getXMLPathElements();
if(xmlpath==null)
xmlpath = new QName[]{QName.valueOf(propname)};
// Fetch elements directly if it is a multi subobject
// if(soinfo.getMulti()!=null && soinfo.getMulti().booleanValue())
if(soinfo.isMulti())
{
Iterator it2 = SReflect.getIterator(value);
boolean flat = soinfo.getFlattening()!=null? soinfo.getFlattening().booleanValue(): flattening;
while(it2.hasNext())
{
Object val = it2.next();
if(isTypeCompatible(val, obinfo, context))
{
QName[] path = createPath(xmlpath, val, context);
wi.addSubobject(path, val, flat);
}
}
}
else
{
if(isTypeCompatible(value, obinfo, context))
{
QName[] path = createPath(xmlpath, value, context);
boolean flat = soinfo.getFlattening()!=null? soinfo.getFlattening().booleanValue(): flattening;
wi.addSubobject(path, value, flat);
}
}
}
}
}
}
}
}
// Get properties from type inspection.
boolean includefields = false;
if(typeinfo!=null)
{
includefields = typeinfo.isIncludeFields();
}
else
{
// Hack!!! todo: must not be Java object (OAV) ?!
try
{
Field field = object.getClass().getField(Writer.XML_INCLUDE_FIELDS);
if(SReflect.getWrappedType(field.getType()).equals(Boolean.class))
{
includefields = ((Boolean)field.get(object)).booleanValue();
}
}
catch(Exception e)
{
}
}
Collection props = getProperties(object, context, includefields);
if(props!=null)
{
for(Iterator it=props.iterator(); it.hasNext(); )
{
Object property = it.next();
String propname = getPropertyName(property);
if(!doneprops.contains(propname))
{
doneprops.add(propname);
Object value = getValue(object, property, context, null);
if(value!=null)
{
// Make to an attribute when
// a) it is a basic type
// b) it can be decoded to the right object type
Boolean pt = typeinfo!=null && typeinfo.getMappingInfo()!=null? typeinfo.getMappingInfo().getPreferTags(): null;
boolean prefertags = pt!=null? pt.booleanValue(): this.prefertags;
if(!prefertags && isBasicType(property, value) && isDecodableToSameType(property, value, context))
{
if(!value.equals(getDefaultValue(property)))
wi.addAttribute(propname, value.toString());
}
else
{
// todo: remove
// Hack special case array, todo: support generically via typeinfo???
QName[] xmlpath = new QName[]{QName.valueOf(propname)};
QName[] path = createPath(xmlpath, value, context);
// todo: use some default for flattening
wi.addSubobject(path, value, flattening);
}
}
}
}
}
// Special case that no info about object was found.
// Hack?!
// if(typeinfo==null && wi.getAttributes()==null && wi.getSubobjects()==null
// && wi.getContent()==null && doneprops.size()==0)
// {
// // todo: use prewriter
// System.out.println("Special case for content: "+object+" "+object.getClass());
//// wi.setContent(object.toString());
// }
// System.out.println("wi: "+object+" "+wi.getContent()+" "+wi.getSubobjects());
return wi;
}
/**
* Create a qname path.
*/
protected QName[] createPath(QName[] xmlpath, Object value, IContext context)
{
QName[] ret = xmlpath;
if(gentypetags)
{
ret = new QName[xmlpath.length+1];
System.arraycopy(xmlpath, 0, ret, 0, xmlpath.length);
// QName tag = getTagName(value, context);
// ret[ret.length-1] = tag;
}
return ret;
}
/**
* Convert a value before writing.
*/
protected Object convertValue(Object info, Object value, IContext context)
{
Object ret = value;
if(info instanceof AttributeInfo)
{
IObjectStringConverter conv = ((AttributeInfo)info).getConverter();
if(conv!=null)
{
ret = conv.convertObject(value, context);
}
}
return ret;
}
/**
* Get the default value.
*/
protected Object getDefaultValue(Object property)
{
Object ret = null;
if(property instanceof AttributeInfo)
{
ret = ((AttributeInfo)property).getAccessInfo().getDefaultValue();
}
return ret;
}
/**
* Get a value from an object.
*/
protected abstract Object getValue(Object object, Object attr, IContext context, Object info) throws Exception;
/**
* Get the property.
*/
protected abstract Object getProperty(Object info);
/**
* Get the name of a property.
*/
protected abstract String getPropertyName(Object property);
/**
* Get the properties of an object.
*/
protected abstract Collection getProperties(Object object, IContext context, boolean includefields);
/**
* Test is a value is a basic type (and can be mapped to an attribute).
*/
protected abstract boolean isBasicType(Object property, Object value);
/**
* Test if a value is compatible with the defined typeinfo.
*/
protected abstract boolean isTypeCompatible(Object object, ObjectInfo info, IContext context);
/**
* Test if a value is decodable to the same type.
*/
protected abstract boolean isDecodableToSameType(Object property, Object value, IContext context);
}