package org.jboss.xml.binding;
import org.jboss.logging.Logger;
import org.jboss.util.Classes;
import org.jboss.util.NestedRuntimeException;
import org.xml.sax.Attributes;
import org.apache.xerces.xs.XSTypeDefinition;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
public class MappingObjectModelFactory
implements GenericObjectModelFactory
{
private final static Logger log = Logger.getLogger(MappingObjectModelFactory.class);
private final Map elementToClassMapping = new HashMap();
private final Map elementToFieldMapping = new HashMap();
public void mapElementToClass(String element, Class cls)
{
ElementToClassMapping mapping = new ElementToClassMapping(element, cls);
addElementToClassMapping(mapping);
if(log.isTraceEnabled())
{
log.trace(mapping);
}
}
public void mapElementToField(String element, Class cls, String field, TypeConverter converter)
{
ElementToFieldMapping mapping = new ElementToFieldMapping(element, cls, field, converter);
addElementToFieldMapping(mapping);
if(log.isTraceEnabled())
{
log.trace(mapping);
}
}
public Object newRoot(Object root,
ContentNavigator navigator,
String namespaceURI,
String localName,
Attributes attrs)
{
if(log.isTraceEnabled())
{
log.trace("newRoot root=" +
root +
" navigator=" +
navigator +
" namespaceURI=" +
namespaceURI +
" localName=" +
localName +
" attributes=" +
attrs
);
}
if(root == null)
{
ElementToClassMapping mapping = (ElementToClassMapping)elementToClassMapping.get(localName);
if(mapping != null)
{
if(log.isTraceEnabled())
{
log.trace("creating root using " + mapping);
}
root = newInstance(mapping.cls);
}
else
{
root = create(namespaceURI, localName, navigator.getType());
}
if(root == null)
{
throw new IllegalStateException(
"Failed to resolve Java type binding for root element: ns=" + namespaceURI + ", local=" + localName
);
}
}
if(attrs != null)
{
for(int i = 0; i < attrs.getLength(); ++i)
{
try
{
if(attrs.getLocalName(i).length() > 0)
{
if(!attrs.getQName(i).startsWith("xsi:")) {
setAttribute(root, attrs.getLocalName(i), attrs.getValue(i), navigator.getType());
}
}
}
catch(Exception e)
{
String msg = "Failed to set attribute " + attrs.getQName(i) + "=" + attrs.getValue(i);
log.error(msg, e);
throw new IllegalStateException(msg + ": " + e.getMessage());
}
}
}
return root;
}
public Object newChild(Object o,
ContentNavigator navigator,
String namespaceURI,
String localName,
Attributes attrs)
{
if(log.isTraceEnabled())
{
log.trace("newChild object=" +
o +
" navigator=" +
navigator +
" namespaceURI=" +
namespaceURI +
" localName=" +
localName +
" attributes=" +
attrs
);
}
if(o == null)
{
throw new RuntimeException("Attempt to add a new child to a null parent localName=" + localName);
}
Object child = null;
ElementToClassMapping mapping = (ElementToClassMapping)elementToClassMapping.get(localName);
XSTypeDefinition type = navigator.getType();
if(mapping != null)
{
if(log.isTraceEnabled())
{
log.trace("newChild using mapping " + mapping);
}
try
{
if(!(o instanceof Collection))
{
Method getter;
ElementToFieldMapping fieldMapping = (ElementToFieldMapping)elementToFieldMapping.get(
new ElementToFieldMappingKey(localName, o.getClass())
);
if(fieldMapping != null)
{
getter = fieldMapping.getter;
}
else
{
String getterStr = Util.xmlNameToGetMethodName(localName, true);
getter = o.getClass().getMethod(getterStr, null);
}
child = get(o, localName, getter);
}
if(child == null)
{
child = newInstance(mapping.cls);
}
if(attrs != null)
{
for(int i = 0; i < attrs.getLength(); ++i)
{
if(attrs.getLocalName(i).length() > 0)
{
if(!attrs.getQName(i).startsWith("xsi:")) {
setAttribute(child, attrs.getLocalName(i), attrs.getValue(i), type);
}
}
}
}
}
catch(IllegalStateException e)
{
throw e;
}
catch(Exception e)
{
throw new NestedRuntimeException("newChild failed for o=" +
o +
", uri=" +
namespaceURI +
", local="
+ localName + ", attrs=" + attrs, e
);
}
}
else
{
if(o instanceof Collection)
{
child = create(namespaceURI, localName, type);
}
else
{
Class oCls;
if(o instanceof ImmutableContainer)
{
oCls = ((ImmutableContainer)o).cls;
}
else
{
oCls = o.getClass();
}
String getterStr = Util.xmlNameToGetMethodName(localName, true);
Method getter;
try
{
getter = oCls.getMethod(getterStr, null);
}
catch(NoSuchMethodException e)
{
throw new IllegalStateException("newChild failed for o=" +
o +
", uri=" +
namespaceURI +
", local="
+ localName + ", attrs=" + attrs + ": no getter"
);
}
Class childType = getter.getReturnType();
if(Collection.class.isAssignableFrom(childType))
{
child = get(o, localName, getter);
Object item = null;
if(type == null || type != null && type.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE)
{
item = create(namespaceURI, localName, type);
}
if(item != null)
{
if(child == null)
{
setChild(new ArrayList(), o, localName);
}
child = item;
}
else
{
if(child == null)
{
child = new ArrayList();
}
}
}
else if(!Util.isAttributeType(childType))
{
ElementToFieldMapping fieldMapping = (ElementToFieldMapping)elementToFieldMapping.get(
new ElementToFieldMappingKey(localName, o.getClass())
);
TypeConverter converter = fieldMapping == null ? null : fieldMapping.converter;
if(converter == null)
{
child = newInstance(childType);
}
}
}
}
return child;
}
public void addChild(Object parent,
Object child,
ContentNavigator navigator,
String namespaceURI,
String localName)
{
if(log.isTraceEnabled())
{
log.trace("addChild parent=" +
parent +
" child=" +
child +
" navigator=" +
navigator +
" namespaceURI=" +
namespaceURI +
" localName=" +
localName
);
}
if(child instanceof ImmutableContainer)
{
child = ((ImmutableContainer)child).newInstance();
}
setChild(child, parent, localName);
}
public void setValue(Object o, ContentNavigator navigator, String namespaceURI, String localName, String value)
{
if(log.isTraceEnabled())
{
log.trace("setValue object=" +
o +
" navigator=" +
navigator +
" namespaceURI=" +
namespaceURI +
" localName=" +
localName +
" value=" +
value
);
}
setAttribute(o, localName, value, navigator.getType());
}
public Object completedRoot(Object root, ContentNavigator navigator, String namespaceURI, String localName)
{
if(log.isTraceEnabled())
{
log.trace("completedRoot root=" +
root +
" navigator=" +
navigator +
" namespaceURI=" +
namespaceURI +
" localName=" +
localName
);
}
if(root instanceof ImmutableContainer)
{
root = ((ImmutableContainer)root).newInstance();
}
return root;
}
private void addElementToClassMapping(ElementToClassMapping mapping)
{
elementToClassMapping.put(mapping.element, mapping);
}
private void addElementToFieldMapping(ElementToFieldMapping mapping)
{
elementToFieldMapping.put(mapping.key, mapping);
}
private void setChild(Object child, Object parent, String localName)
{
boolean trace = log.isTraceEnabled();
Object value = child;
if(parent instanceof Collection)
{
if(trace)
{
log.trace("Add " + value + " to collection " + parent);
}
((Collection)parent).add(value);
}
else
{
Method setter = null;
final ElementToFieldMapping fieldMapping = (ElementToFieldMapping)elementToFieldMapping.get(
new ElementToFieldMappingKey(localName, parent.getClass())
);
if(fieldMapping != null)
{
if(trace)
{
log.trace("Add " + value + " to " + parent + " using field mapping " + fieldMapping);
}
setter = fieldMapping.setter;
set(parent, value, localName, setter);
}
else
{
final String xmlToCls = Util.xmlNameToClassName(localName, true);
if(trace)
{
log.trace("Add " + value + " to xml mapped class " + xmlToCls);
}
Method getter = null;
Class parentCls;
if(parent instanceof ImmutableContainer)
{
parentCls = ((ImmutableContainer)parent).cls;
}
else
{
parentCls = parent.getClass();
}
try
{
getter = parentCls.getMethod("get" + xmlToCls, null);
}
catch(NoSuchMethodException e)
{
log.warn("no getter found for " + localName + " in " + parent);
}
if(getter != null)
{
if(!(child instanceof Collection) && Collection.class.isAssignableFrom(getter.getReturnType()))
{
Object o = get(parent, localName, getter);
Collection col = (Collection)o;
if(trace)
{
log.trace("Add " + value + " to collection " + col + " retrieved from getter " + getter);
}
col.add(child);
}
else
{
try
{
setter = parentCls.getMethod("set" + xmlToCls, new Class[]{getter.getReturnType()});
}
catch(NoSuchMethodException e)
{
log.warn("No setter for " + localName + " in " + parentCls);
}
set(parent, value, localName, setter);
}
}
}
}
}
private void setAttribute(Object o, String localName, String value, XSTypeDefinition type)
{
if(o instanceof Collection)
{
if(type == null)
{
log.warn("Type is not available for collection item " + localName + "=" + value + " -> adding as string.");
((Collection)o).add(value);
}
else
{
if(type.getName() == null)
{
throw new IllegalStateException("Name is null for simple type?!");
}
Object trgValue = TypeBinding.unmarshal(type.getName(), value);
((Collection)o).add(trgValue);
}
}
else
{
Method setter = null;
Object fieldValue = null;
final ElementToFieldMapping fieldMapping = (ElementToFieldMapping)elementToFieldMapping.get(
new ElementToFieldMappingKey(localName, o.getClass())
);
if(fieldMapping != null)
{
fieldValue = fieldMapping.converter.unmarshal(value);
setter = fieldMapping.setter;
}
else
{
Class oCls;
if(o instanceof ImmutableContainer)
{
oCls = ((ImmutableContainer)o).cls;
}
else
{
oCls = o.getClass();
}
try
{
final String xmlToCls = Util.xmlNameToClassName(localName, true);
Method getter = oCls.getMethod("get" + xmlToCls, null);
fieldValue = TypeBinding.unmarshal(value, getter.getReturnType());
setter = oCls.getMethod("set" + xmlToCls, new Class[]{getter.getReturnType()});
}
catch(NoSuchMethodException e)
{
log.warn("no setter found for " + localName + " in " + oCls);
}
}
set(o, fieldValue, localName, setter);
}
}
private static Object create(String namespaceURI, String localName, XSTypeDefinition type)
{
Object o = null;
String clsName = type != null && type.getName() != null ?
Util.xmlNameToClassName(namespaceURI, type.getName(), true) :
Util.xmlNameToClassName(namespaceURI, localName, true);
Class cls = null;
try
{
cls = Thread.currentThread().getContextClassLoader().loadClass(clsName);
}
catch(ClassNotFoundException e)
{
if(log.isTraceEnabled())
{
log.trace("create: failed to load class " + clsName);
}
}
if(cls != null)
{
o = newInstance(cls);
}
return o;
}
private static Object get(Object o, String localName, Method getter)
{
if(log.isTraceEnabled())
{
log.trace("get object=" + o + " localName=" + localName + " getter=" + getter);
}
Object value;
if(o instanceof ImmutableContainer)
{
ImmutableContainer con = ((ImmutableContainer)o);
value = con.getChild(localName);
}
else
{
try
{
value = getter.invoke(o, null);
}
catch(Exception e)
{
throw new NestedRuntimeException("Failed to invoke " + getter + " on " + o, e);
}
}
return value;
}
private static void set(Object parent, Object child, String localName, Method setter)
{
if(log.isTraceEnabled())
{
log.trace("set parent=" + parent + " child=" + child + " localName=" + localName + " setter=" + setter);
}
if(setter != null)
{
try
{
setter.invoke(parent, new Object[]{child});
}
catch(Exception e)
{
throw new NestedRuntimeException("Failed to set attribute value " +
child +
" with setter " +
setter
+ " on " + parent + ": ", e
);
}
}
else if(parent instanceof ImmutableContainer)
{
((ImmutableContainer)parent).addChild(localName, child);
}
else
{
throw new IllegalStateException("setter is null and it's not an immutable container: parent=" +
parent.getClass() +
", localName" + localName + ", parent=" + parent + ", child=" + child
);
}
}
private static Object newInstance(Class cls)
{
if(log.isTraceEnabled())
{
log.trace("new " + cls.getName());
}
Object instance;
try
{
Constructor ctor = cls.getConstructor(null);
instance = ctor.newInstance(null);
}
catch(NoSuchMethodException e)
{
log.warn("No no-arg constructor in " + cls);
instance = new ImmutableContainer(cls);
}
catch(Exception e)
{
throw new IllegalStateException("Failed to create an instance of " +
cls +
" with the no-arg constructor: "
+ e.getMessage()
);
}
return instance;
}
private class ElementToClassMapping
{
public final String element;
public final Class cls;
public ElementToClassMapping(String element, Class cls)
{
this.element = element;
this.cls = cls;
}
public String toString()
{
StringBuffer buffer = new StringBuffer();
buffer.append("ElementToClass@").append(System.identityHashCode(this));
buffer.append("{element=").append(element);
if(cls != null)
{
buffer.append(" class=").append(cls.getName());
}
buffer.append("}");
return buffer.toString();
}
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof ElementToClassMapping))
{
return false;
}
final ElementToClassMapping classMapping = (ElementToClassMapping)o;
if(cls != null ? !cls.equals(classMapping.cls) : classMapping.cls != null)
{
return false;
}
return true;
}
public int hashCode()
{
return (cls != null ? cls.hashCode() : 0);
}
}
private class ElementToFieldMappingKey
{
public final String element;
public final Class cls;
public ElementToFieldMappingKey(String element, Class cls)
{
this.element = element;
this.cls = cls;
}
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof ElementToFieldMappingKey))
{
return false;
}
final ElementToFieldMappingKey elementToFieldMappingKey = (ElementToFieldMappingKey)o;
if(cls != null ? !cls.equals(elementToFieldMappingKey.cls) : elementToFieldMappingKey.cls != null)
{
return false;
}
if(element != null ?
!element.equals(elementToFieldMappingKey.element) :
elementToFieldMappingKey.element != null)
{
return false;
}
return true;
}
public int hashCode()
{
int result;
result = (element != null ? element.hashCode() : 0);
result = 29 * result + (cls != null ? cls.hashCode() : 0);
return result;
}
}
private class ElementToFieldMapping
{
public final String element;
public final Class cls;
public final String field;
public final TypeConverter converter;
public final ElementToFieldMappingKey key;
public final Method getter;
public final Method setter;
public ElementToFieldMapping(String element, Class cls, String field, TypeConverter converter)
{
this.element = element;
this.cls = cls;
this.field = field;
this.converter = converter;
key = new ElementToFieldMappingKey(element, cls);
try
{
getter = Classes.getAttributeGetter(cls, field);
}
catch(NoSuchMethodException e)
{
throw new IllegalStateException("Getter not found for " + field + " in class " + cls.getName());
}
try
{
setter = Classes.getAttributeSetter(cls, field, getter.getReturnType());
}
catch(NoSuchMethodException e)
{
throw new IllegalStateException("Setter not found for " + field + " in class " + cls.getName());
}
}
public String toString()
{
StringBuffer buffer = new StringBuffer();
buffer.append("ElementToField@").append(System.identityHashCode(this));
buffer.append("{element=").append(element);
if(cls != null)
{
buffer.append(" class=").append(cls.getName());
}
buffer.append(" field=").append(field);
buffer.append(" getter=").append(getter);
buffer.append(" setter=").append(setter);
if(converter != null)
{
buffer.append(" convertor=").append(converter.getClass().getName());
}
buffer.append("}");
return buffer.toString();
}
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof ElementToFieldMapping))
{
return false;
}
final ElementToFieldMapping elementToFieldMapping = (ElementToFieldMapping)o;
if(cls != null ? !cls.equals(elementToFieldMapping.cls) : elementToFieldMapping.cls != null)
{
return false;
}
if(element != null ? !element.equals(elementToFieldMapping.element) : elementToFieldMapping.element != null)
{
return false;
}
if(field != null ? !field.equals(elementToFieldMapping.field) : elementToFieldMapping.field != null)
{
return false;
}
return true;
}
public int hashCode()
{
int result;
result = (element != null ? element.hashCode() : 0);
result = 29 * result + (cls != null ? cls.hashCode() : 0);
result = 29 * result + (field != null ? field.hashCode() : 0);
return result;
}
}
private static class ImmutableContainer
{
private final Class cls;
private final List names = new ArrayList();
private final List values = new ArrayList();
public ImmutableContainer(Class cls)
{
this.cls = cls;
if(log.isTraceEnabled())
{
log.trace("created immutable container for " + cls);
}
}
public void addChild(String localName, Object child)
{
if(!names.isEmpty() && names.get(names.size() - 1).equals(localName))
{
throw new IllegalStateException("Attempt to add duplicate element " + localName);
}
names.add(localName);
values.add(child);
if(log.isTraceEnabled())
{
log.trace("added child " + localName + " for " + cls + ": " + child);
}
}
public Object getChild(String localName)
{
return names.get(names.size() - 1).equals(localName) ? values.get(values.size() - 1) : null;
}
public Object[] getValues()
{
return values.toArray();
}
public Class[] getValueTypes()
{
Class[] types = new Class[values.size()];
for(int i = 0; i < values.size(); ++i)
{
types[i] = values.get(i).getClass();
}
return types;
}
public Object newInstance()
{
Constructor ctor = null;
Constructor[] ctors = cls.getConstructors();
if(ctors == null || ctors.length == 0)
{
throw new JBossXBRuntimeException("The class has no declared constructors: " + cls);
}
for(int i = 0; i < ctors.length; ++i)
{
Class[] types = ctors[i].getParameterTypes();
if(types == null || types.length == 0)
{
throw new IllegalStateException("Found no-arg constructor for immutable " + cls);
}
if(types.length == values.size())
{
ctor = ctors[i];
int typeInd = 0;
while(typeInd < types.length)
{
if(!types[typeInd].isAssignableFrom(values.get(typeInd++).getClass()))
{
ctor = null;
break;
}
}
if(ctor != null)
{
break;
}
}
}
if(ctor == null)
{
throw new IllegalStateException("No constructor in " + cls + " that would take arguments " + values);
}
try
{
return ctor.newInstance(values.toArray());
}
catch(Exception e)
{
throw new IllegalStateException("Failed to create immutable instance of " +
cls +
" using arguments: "
+ values + ": " + e.getMessage()
);
}
}
}
}