
com.tangosol.run.xml.XmlBean Maven / Gradle / Ivy
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.run.xml;
import com.tangosol.io.ExternalizableLite;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.SafeHashMap;
import com.tangosol.util.ExternalizableHelper;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.Externalizable;
import java.io.IOException;
import java.io.NotActiveException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
/**
* This is a base class for building XmlSerializable value objects.
*
* The following property types are supported using standard property
* adapters:
*
* 1) XmlValue types:
* TYPE_BOOLEAN - boolean, java.lang.Boolean
* TYPE_INT - byte, char, short, int, java.lang.Byte,
* java.lang.Character, java.lang.Short,
* java.lang.Integer
* TYPE_LONG - long, java.lang.Long
* TYPE_DOUBLE - float, double, java.lang.Float, java.lang.Double
* TYPE_DECIMAL - java.math.BigDecimal, java.math.BigInteger
* TYPE_STRING - java.lang.String
* TYPE_BINARY - com.tangosol.util.Binary, byte[]
* TYPE_DATE - java.sql.Date
* TYPE_TIME - java.sql.Time
* TYPE_DATETIME - java.sql.Timestamp, java.util.Date
*
* 2) Objects implementing XmlSerializable (including XmlBean subclasses)
*
* 3) Objects implementing Serializable
*
* 4) Collections of any of the above:
* Java arrays
* java.util.Collection
* java.util.Set
* java.util.List
* java.util.Map
* java.util.SortedSet
* java.util.SortedMap
*
*
* Each XmlBean must have a corresponding XML declaration file that provides
* the necessary information to parse XML into the XML bean and to format the
* XML bean into XML. The declaration file should be located in the same package
* (directory) as the class itself.
*
* For example, here is an XmlBean subclass with an int property "Id" and a
* String property "Name":
*
* public class Person extends XmlBean {
* public Person(int nId, String sName) {...}
* public int getId() {...}
* public void setId(int nId) {...}
* public String getName() {...}
* public void setName(String sName) {...}
* }
*
*
* The Person XML bean example above would have an XML declaration file that
* resembles the following:
*
* <xml-bean>
* <name>person</name>
* <property>
* <name>Id</name>
* <xml-name>person-id</xml-name>
* </property>
* <property>
* <name>Name</name>
* <xml-name>full-name</xml-name>
* </property>
* </xml-bean>
*
*
* Consider the following code:
* System.out.println(new Person(15, "John Smith").toString());
*
* The output would be:
*
* <person>
* <person-id>15</person-id>
* <full-name>John Smith</full-name>
* </person>
*
*
* To specify namespace information for an XML bean, add an "xmlns" element to
* the bean's descriptor:
*
*
* <xml-bean>
* <name>person</name>
* <xmlns>
* <uri>the-schema-URI-goes-here</uri>
* <prefix>the-default-namespace-prefix-goes-here</prefix>
* <xmlns>
* <property>
* ...
* </property>
* </xml-bean>
*
*
* @version 1.2
*
* @author cp 2000.11.10
* @author gg 2002.05.17 anonymous element and XML Namespaces support
* @author cp 2003.03.27 ExternalizableLite support
*/
public abstract class XmlBean
extends ExternalizableHelper
implements Cloneable, Externalizable, ExternalizableLite, XmlSerializable
{
// ----- constructors ---------------------------------------------------
/**
* Construct a value object.
*/
protected XmlBean()
{
}
// ----- accessors ------------------------------------------------------
/**
* Obtain the XmlBean that contains this XmlBean.
*
* @return the containing XmlBean, or null if there is none
*/
public XmlBean getParentXmlBean()
{
return m_parent;
}
/**
* Specify the XmlBean that contains this XmlBean.
*
* @param parent the XmlBean that contains this XmlBean
*/
protected void setParentXmlBean(XmlBean parent)
{
XmlBean parentOrig = m_parent;
if (parentOrig != null && parentOrig != parent)
{
throw new IllegalStateException("ParentXmlBean is immutable.");
}
m_parent = parent;
}
/**
* Helper to adopt a Map of XmlBean objects.
*
* @param map a Map that may contain keys and/or values that are XmlBeans
*/
protected void adopt(Map map)
{
if (map != null && !map.isEmpty())
{
adopt(map.keySet());
adopt(map.entrySet());
}
}
/**
* Helper to adopt a Collection of XmlBean objects.
*
* @param coll a Collection that may contain XmlBeans
*/
protected void adopt(Collection coll)
{
if (coll != null && !coll.isEmpty())
{
adopt(coll.iterator());
}
}
/**
* Helper to adopt a collection of XmlBean objects.
*
* @param iter an Iterator that may contain XmlBeans
*/
protected void adopt(Iterator iter)
{
if (iter != null)
{
while (iter.hasNext())
{
Object o = iter.next();
if (o instanceof XmlBean)
{
adopt((XmlBean) o);
}
}
}
}
/**
* Helper to adopt a collection of XmlBean objects.
*
* @param ao an array that may contain XmlBeans
*/
protected void adopt(Object[] ao)
{
if (ao != null)
{
for (int i = 0, c = ao.length; i < c; ++i)
{
Object o = ao[i];
if (o instanceof XmlBean)
{
adopt((XmlBean) o);
}
}
}
}
/**
* When an XmlBean adds a contained XmlBean, it should invoke this method
* to relate the contained XmlBean with this XmlBean.
*
* @param child the XmlBean that is being contained within this XmlBean
*/
protected void adopt(XmlBean child)
{
child.setParentXmlBean(this);
}
/**
* Determine if this value can be modified. If the value can not be
* modified, all mutating methods are required to throw an
* UnsupportedOperationException.
*
* @return true if this value can be modified, otherwise false to
* indicate that this value is read-only
*/
public boolean isMutable()
{
return m_fMutable;
}
/**
* Specify whether this value can be modified or not.
*
* @param fMutable true to allow this value to be modified, otherwise false
* to indicate that this value is read-only
*/
protected void setMutable(boolean fMutable)
{
m_fMutable = fMutable;
}
/**
* Make sure that this XML bean is mutable.
*
* @return this XmlBean if it is mutable, otherwise a mutable copy of this
* XmlBean
*/
public XmlBean ensureMutable()
{
if (isMutable())
{
return this;
}
XmlBean that = (XmlBean) this.clone();
azzert(that.isMutable());
return that;
}
/**
* Make sure that this value is read-only (immutable).
*/
public void ensureReadOnly()
{
setMutable(false);
}
/**
* Verify that this XmlBean is mutable. This method is designed to be
* called by all mutator methods of an XmlBean to ensure that the bean
* fulfills the contract provided by the Mutable property.
*/
protected void checkMutable()
{
if (!isMutable())
{
throw new IllegalStateException(getBeanInfo().getName() +
" is immutable");
}
// a child XmlBean is immutable if its parent is immutable
XmlBean parent = getParentXmlBean();
if (parent != null)
{
parent.checkMutable();
}
}
/**
* Get the cached hash code. Value objects whose hash code is supposed
* to change must override the hashCode implementation.
*
* @return the cached hash code
*/
protected int getHashCode()
{
return m_nHash;
}
/**
* Set the cached hash code. Value objects whose hash code is supposed
* to change must override the hashCode implementation.
*
* @param nHash the hash code
*/
protected void setHashCode(int nHash)
{
m_nHash = nHash;
}
/**
* Obtain the BeanInfo for this XmlBean object, or create and configure
* a BeanInfo if one does not exist.
*
* @return the BeanInfo that describes this XmlBean
*/
public BeanInfo getBeanInfo()
{
BeanInfo info = m_info;
if (info == null)
{
info = findBeanInfo();
azzert(info != null);
m_info = info;
}
return info;
}
/**
* Obtain the PropertyAdapter objects for this XmlBean.
*
* @return the PropertyAdapter objects that handle the properties of
* this XmlBean
*/
public PropertyAdapter[] getAdapters()
{
return getBeanInfo().getAdapters();
}
// ----- Object methods -------------------------------------------------
/**
* Determine if this value object is equal to the passed value object.
*
* @param o the other value object to compare to
*
* @return true if the other value object is equal to this
*/
public boolean equals(Object o)
{
// optimization: incompatible class
if (!(o instanceof XmlBean))
{
return false;
}
// optimization: same object
XmlBean that = (XmlBean) o;
if (this == that)
{
return true;
}
// optimization: check cached hash codes
int nThis = this.getHashCode();
int nThat = that.getHashCode();
if (nThis != 0 && nThat != 0 && nThis != nThat)
{
return false;
}
// no optimization available; check each property for inequality
PropertyAdapter[] aAdapter = getAdapters();
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
Object oThis = adapter.get(this);
Object oThat = adapter.get(that);
if (!adapter.equalsValue(oThis, oThat))
{
return false;
}
}
// no inequality found; objects are equal
return true;
}
/**
* Determine a hash code for this value object. For value objects with
* multiple properties, the hash code is calculated from the xor of the
* hash codes for each property.
*
* @return a hash code for this value object
*/
public int hashCode()
{
// check to see if the hashcode is cached
int n = getHashCode();
if (n == 0)
{
// calculate the hash code as the xor of the hashcode of each
// property
PropertyAdapter[] aAdapter = getAdapters();
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
n ^= adapter.hash(adapter.get(this));
}
// don't allow a hash of zero because it would be recalculated
// on every call
if (n == 0)
{
n = -1;
}
// cache the hash code
setHashCode(n);
}
return n;
}
/**
* To assist in debugging, provide a clear indication of the key's
* state.
*
* @return a String representing this key object
*/
public String toString()
{
// default implementation of toString is to convert the XML bean to
// an XML format and return that as a String
return XmlHelper.toString(this);
}
/**
* Clone the value object.
*
* @return a clone of this object
*/
public Object clone()
{
// start with a shallow clone
XmlBean that;
try
{
that = (XmlBean) super.clone();
}
catch (CloneNotSupportedException e)
{
throw ensureRuntimeException(e);
}
// the clone is not automatically a child of this XmlBean's parent
that.m_parent = null;
that.setMutable(true);
// clone any properties that need to be deep cloned; if no deep
// cloning is required, this step is skipped entirely
BeanInfo info = getBeanInfo();
if (info.requiresDeepClone())
{
PropertyAdapter[] aAdapter = info.getAdapters();
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
if (adapter.isCloneRequired() && adapter.getMutator() != null)
{
Object o = adapter.get(this);
if (o != null)
{
adapter.set(that, adapter.clone(o));
}
}
}
}
return that;
}
// ----- XmlSerializable methods ----------------------------------------
/**
* Serialize the object into an XmlElement.
*
* @return an XmlElement that contains the serialized form of the object
*/
public XmlElement toXml()
{
BeanInfo info = getBeanInfo();
PropertyAdapter[] aAdapter = info.getAdapters();
String sName = info.getName();
if (sName.length() == 0)
{
// the name must be discarded by the caller
XmlElement xml = new SimpleElement(
ClassHelper.getSimpleName(info.getType()));
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
Object o = adapter.get(this);
if (!adapter.isEmpty(o))
{
adapter.writeXml(xml, o);
// not more then one property for an anonymous [choice] element
// (see http://www.w3.org/TR/xmlschema-0/#element-choice)
break;
}
}
return xml;
}
else
{
String sNmsPrefix = info.getNamespacePrefix();
XmlElement xml;
if (sNmsPrefix == null)
{
xml = new SimpleElement(sName);
}
else
{
xml = new SimpleElement(sNmsPrefix + ':' + sName);
XmlHelper.ensureNamespace(xml, sNmsPrefix, info.getNamespaceUri());
}
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
Object o = adapter.get(this);
// adapters decide whether or not to process null values
adapter.writeXml(xml, o);
}
if (sNmsPrefix != null)
{
XmlHelper.purgeChildrenNamespace(xml);
}
return xml;
}
}
/**
* Deserialize the object from an XmlElement.
*
* This method can throw one of several RuntimeExceptions.
*
* @param xml an XmlElement that contains the serialized form of the
* object
*
* @throws UnsupportedOperationException if the operation is not supported
* @throws IllegalStateException if this is not an appropriate state
* @throws IllegalArgumentException if there is an illegal argument
*/
public void fromXml(XmlElement xml)
{
BeanInfo info = getBeanInfo();
PropertyAdapter[] aAdapter = info.getAdapters();
String sName = info.getName();
if (sName.length() == 0)
{
// for anonymous elements one and only one adapter may fit
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
if (adapter.getMutator() != null &&
(adapter.isAnonymous() || adapter.isElementMatch(xml)))
{
XmlElement xmlParent = new SimpleElement(
ClassHelper.getSimpleName(getClass()));
xmlParent.getElementList().add(xml);
// plug the dummy parent into the xml tree
// to preserve a Namespace context
List list = xml.getElementList();
list.add(xmlParent);
try
{
Object o = adapter.readXml(xmlParent);
if (o != null)
{
adapter.set(this, o);
}
}
finally
{
list.remove(xmlParent);
}
break;
}
}
}
else
{
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
if (adapter.getMutator() != null)
{
Object o = adapter.readXml(xml);
if (o != null)
{
adapter.set(this, o);
}
}
}
}
}
// ----- Externalizable interface ---------------------------------------
/**
* The object implements the readExternal method to restore its
* contents by calling the methods of DataInput for primitive
* types and readObject for objects, strings and arrays. The
* readExternal method must read the values in the same sequence
* and with the same types as were written by writeExternal.
*
* @param in the stream to read data from in order to restore the object
*
* @exception IOException if I/O errors occur
* @exception ClassNotFoundException if the class for an object being
* restored cannot be found.
*/
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException
{
readExternal((DataInput) in);
}
/**
* The object implements the writeExternal method to save its contents
* by calling the methods of DataOutput for its primitive values or
* calling the writeObject method of ObjectOutput for objects, strings,
* and arrays.
*
* @serialData Overriding methods should use this tag to describe
* the data layout of this Externalizable object.
* List the sequence of element types and, if possible,
* relate the element to a public/protected field and/or
* method of this Externalizable class.
*
* @param out the stream to write the object to
* @exception IOException includes any I/O exceptions that may occur
*/
public void writeExternal(ObjectOutput out)
throws IOException
{
writeExternal((DataOutput) out);
}
// ----- ExternalizableLite interface -----------------------------------
/**
* Restore the contents of this object by loading the object's state from
* the passed DataInput object.
*
* @param in the DataInput stream to read data from in order to restore
* the state of this object
*
* @exception IOException if an I/O exception occurs
* @exception NotActiveException if the object is not in its initial
* state, and therefore cannot be deserialized into
*/
public void readExternal(DataInput in)
throws IOException
{
boolean fMutable = in.readBoolean();
BeanInfo info = getBeanInfo();
PropertyAdapter[] aAdapter = info.getAdapters();
// the stream "in" is packed with accessor-index/value pairs,
// and terminated with a -1 index (with no corresponding value)
int i;
while ((i = readInt(in)) >= 0)
{
PropertyAdapter adapter = aAdapter[i];
Object o = adapter.readExternal(in);
if (o != null && adapter.getMutator() != null)
{
adapter.set(this, o);
}
}
// now that everything is set up, configure the read-only versus
// mutable attribute of the XML bean
m_fMutable = fMutable;
}
/**
* Save the contents of this object by storing the object's state into
* the passed DataOutput object.
*
* @param out the DataOutput stream to write the state of this object to
*
* @exception IOException if an I/O exception occurs
*/
public void writeExternal(DataOutput out)
throws IOException
{
out.writeBoolean(m_fMutable);
BeanInfo info = getBeanInfo();
PropertyAdapter[] aAdapter = info.getAdapters();
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
if (adapter.getMutator() != null) // only save what can be restored
{
Object o = adapter.get(this);
if (!adapter.isEmpty(o))
{
writeInt(out, i);
adapter.writeExternal(out, o);
}
}
}
writeInt(out, -1); // "EOF" aka "end of properties" marker
}
// ----- internal -------------------------------------------------------
/**
* For backwards compatibility only - loads reflection info.
*
* This method is intended to be called by the static initializer of each
* concrete sub-class.
*
* @param clz the class to initialize
* @param sName the name of the value object
* @param asProp the property names that make up the value object
*/
protected static void init(Class clz, String sName, String[] asProp)
{
XmlElement xml = new SimpleElement("xml-bean");
xml.addElement("name").setString(sName);
for (int i = 0, c = asProp.length; i < c; ++i)
{
XmlElement xmlProp = xml.addElement("property");
String sProp = asProp[i];
xmlProp.addElement("name") .setString(sProp);
xmlProp.addElement("xml-name").setString(sProp);
}
s_mapXml.put(clz, xml);
}
/**
* Obtain the BeanInfo object for this XML bean.
*
* @return the BeanInfo for this Object
*/
private BeanInfo findBeanInfo()
{
Class clz = getClass();
BeanInfo info = (BeanInfo) s_mapInfo.get(clz);
if (info == null)
{
info = initBeanInfo();
s_mapInfo.put(clz, info);
}
return info;
}
/**
* Initialize the Object, loading the XML Bean design information if
* necessary.
*
* @return a BeanInfo object
*/
protected BeanInfo initBeanInfo()
{
Class clzBean = getClass();
XmlElement xml = null;
Class clz = clzBean;
while (clz != null)
{
XmlElement xmlClz = (XmlElement) s_mapXml.get(clz);
if (xmlClz == null)
{
xmlClz = XmlHelper.loadXml(clz);
}
if (xml == null)
{
xml = xmlClz;
}
else if (xmlClz != null)
{
// insert the super class properties
List list = xml.getElementList();
int ix = 0;
for (Iterator iter = xmlClz.getElements("property"); iter.hasNext();)
{
XmlElement xmlProp = (XmlElement) iter.next();
String sProp = xmlProp.getElement("name").getName();
// check if a sub-class defined (over-rode) the
// property; if so, put it in the order defined by the
// superclass by removing it from its current position
// and re-inserting it with the other superclass
// properties
XmlElement xmlSub = XmlHelper.findElement(xml, "/property/name", sProp);
if (xmlSub != null)
{
xmlProp = xmlSub.getParent();
list.remove(xmlProp);
}
list.add(ix++, xmlProp);
}
}
clz = clz.getSuperclass();
}
azzert(xml != null, "Cannot find bean info for " + clzBean);
return new BeanInfo(clzBean, xml);
}
/**
* A BeanInfo contains information about the XML bean and its properties.
* One BeanInfo will be created for each specific class of XmlBean.
*/
public static class BeanInfo
{
/**
* Construct a BeanInfo.
*
* @param clzBean the class of the bean
* @param xml the xml descriptor
*/
protected BeanInfo(Class clzBean, XmlElement xml)
{
// store the class of the specific XML bean
m_clzBean = clzBean;
// determine the XML element name to use for the XML bean
String sName;
XmlElement xmlName = xml.getElement("name");
if (xmlName == null)
{
xmlName = xml.getElement("xml-name");
}
if (xmlName == null)
{
// default: use class name as XML element name
sName = ClassHelper.getSimpleName(clzBean);
}
else
{
sName = xmlName.getString();
}
azzert(sName != null);
m_sName = sName;
// determine the XML namespace URI and default prefix, if any
XmlElement xmlNms = xml.getElement("xmlns");
if (xmlNms != null)
{
m_sNmsUri = xmlNms.getSafeElement("uri") .getString(null);
m_sNmsPrefix = xmlNms.getSafeElement("prefix").getString(null);
}
// obtain a PropertyAdapter for each of the XML beans' properties
List list = new ArrayList();
for (Iterator iter = xml.getElements("property"); iter.hasNext(); )
{
XmlElement xmlProp = (XmlElement) iter.next();
String sProp = xmlProp.getSafeElement("name").getString();
azzert(sProp != null && sProp.length() > 0);
XmlElement xmlXmlName = xmlProp.getElement("xml-name");
String sXmlName = xmlXmlName == null ?
sProp : xmlXmlName.getString();
Class clzProp;
String sClass = xmlProp.getSafeElement("type").getString();
if (sClass != null && sClass.length() > 0)
{
clzProp = resolveClass(sClass);
}
else
{
// use reflection to find the property's type
Method method = null;
try
{
method = clzBean.getMethod("get" + sProp, NOPARAMS);
}
catch (NoSuchMethodException e)
{
try
{
method = clzBean.getMethod("is" + sProp, NOPARAMS);
}
catch (NoSuchMethodException e2)
{
}
}
if (method == null)
{
throw new RuntimeException("Unable to find accessor for "
+ sProp + " on " + clzBean.getName());
}
clzProp = method.getReturnType();
}
list.add(makeAdapter(clzProp, sProp, sXmlName, xmlProp));
}
PropertyAdapter[] aAdapter = (PropertyAdapter[])
list.toArray(new PropertyAdapter[list.size()]);
m_aAdapter = aAdapter;
// determine whether any of the properties causes the XML bean to
// require "deep" cloning
for (int i = 0, c = aAdapter.length; i < c; ++i)
{
PropertyAdapter adapter = aAdapter[i];
if (adapter.isCloneRequired() && adapter.getMutator() != null)
{
m_fDeepClone = true;
break;
}
}
if (USE_XMLBEAN_CLASS_CACHE)
{
m_nBeanId = XMLBEAN_CLASS_CACHE.getClassId(clzBean);
}
}
// ----- accessors ----------------------------------------
/**
* Get the class of the specific XML bean implementation.
*
* @return the type of the XML bean
*/
public Class getType()
{
return m_clzBean;
}
/**
* Get the serialization ID for the specific XML bean implementation.
*
* @return the XmlBean ID used by ExternalizableHelper, or -1 if this
* XmlBean does not have an ID assigned or if the ID
* optimization is not being used
*/
public int getBeanId()
{
return m_nBeanId;
}
/**
* Determine the element name that the XML bean will use when
* serializing to XML.
*
* @return the local XmlElement name for the bean
*/
public String getName()
{
return m_sName;
}
/**
* Obtain the namespace URI for this XML bean.
*
* @return the URI that qualifies the default Namespace for this bean
*/
public String getNamespaceUri()
{
return m_sNmsUri;
}
/**
* Obtain the default namespace prefix for this XML bean.
*
* @return the default Namespace prefix for this bean
*/
public String getNamespacePrefix()
{
return m_sNmsPrefix;
}
/**
* Set the default Namespace prefix for this XML bean.
*
* @param sPrefix the default namespace prefix
*/
public void setNamespacePrefix(String sPrefix)
{
m_sNmsPrefix = sPrefix;
}
/**
* Obtain the PropertyAdapter objects for the properties of this XML bean.
*
* @return the property adapters for this bean
*/
public PropertyAdapter[] getAdapters()
{
return m_aAdapter;
}
/**
* Determine if a clone of the XmlBean should be a deep clone, which
* typically means that at least one property value is mutable
* reference type.
*
* @return true if any of the property values must be "deep" cloned
* when the XmlBean is cloned
*/
public boolean requiresDeepClone()
{
return m_fDeepClone;
}
// ----- helpers ----------------------------------------------------
/**
* Generate a property adapter instance that will work on this bean class
* and will adapt for a property of the specified class and of the
* specified name.
*
* @param clz the class of the property
* @param sName the property name
* @param sXmlName the corresponding element name
* @param xml additional XML information
*
* @return an adapter that will handle the specified property
*/
protected PropertyAdapter makeAdapter(Class clz, String sName, String sXmlName, XmlElement xml)
{
// check if the adapter implementation is specified
String sAdapter = xml.getSafeElement("adapter").getString();
Class clzAdapter = sAdapter != null && sAdapter.length() > 0
? resolveClass(sAdapter)
: (Class) s_mapClassAdapters.get(clz);
if (clzAdapter == null)
{
if (XmlElement.class.isAssignableFrom(clz))
{
clzAdapter = XmlElementAdapter.class;
}
else if (XmlSerializable.class.isAssignableFrom(clz))
{
clzAdapter = XmlSerializableAdapter.class;
}
else if (Object[].class.isAssignableFrom(clz))
{
clzAdapter = ArrayAdapter.class;
}
else if (Collection.class.isAssignableFrom(clz))
{
clzAdapter = CollectionAdapter.class;
}
else if (Map.class.isAssignableFrom(clz))
{
clzAdapter = MapAdapter.class;
}
else if (Serializable.class.isAssignableFrom(clz))
{
clzAdapter = SerializableAdapter.class;
}
}
if (clzAdapter == null)
{
throw new RuntimeException("XmlBean: No suitable adapter for: "
+ clz.getName());
}
try
{
// instantiate the adapter
Constructor constructor = clzAdapter.getConstructor(ADAPTER_INIT_PARAMS);
Object[] aoParams = new Object[] {this, clz, sName, sXmlName, xml};
return (PropertyAdapter) constructor.newInstance(aoParams);
}
catch (Exception e)
{
throw ensureRuntimeException(e,
"Instantiating adapter: " + clzAdapter.getName());
}
}
/**
* Find a property adapter instance for the specified property.
*
* @param sName the property name
*
* @return an adapter that handles the specified property;
* null if none could be found
*/
public PropertyAdapter findAdapter(String sName)
{
PropertyAdapter[] aAdapter = getAdapters();
for (int i = 0, c = aAdapter.length; i < c; i++)
{
PropertyAdapter adapter = aAdapter[i];
if (adapter.getName().equals(sName))
{
return adapter;
}
}
return null;
}
/**
* Resolve a Class name into a Class object.
*
* @param sClass the Class name
*
* @return the Class object
*/
public Class resolveClass(String sClass)
{
Class clz = (Class) s_mapClassNames.get(sClass);
if (clz != null)
{
return clz;
}
if (sClass.endsWith("[]"))
{
clz = resolveClass(sClass.substring(0, sClass.length() - 2));
if (clz.isArray())
{
sClass = '[' + clz.getName();
}
else if (clz.isPrimitive())
{
sClass = "[" + s_mapPrimitiveNames.get(clz);
}
else
{
sClass = "[L" + clz.getName() + ';';
}
}
try
{
ClassLoader loader = m_clzBean.getClassLoader();
if (loader == null)
{
loader = ClassLoader.getSystemClassLoader();
}
return Class.forName(sClass, false, loader);
}
catch (Exception e)
{
throw ensureRuntimeException(e);
}
}
// ----- Object methods -------------------------------------------------
/**
* Debugging support.
*
* @return a String description of this Info object
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("BeanInfo for ")
.append(getName())
.append(", type=")
.append(getType().getName())
.append(", requiresDeepClone=")
.append(requiresDeepClone())
.append(", NamespaceUri=")
.append(getNamespaceUri())
.append(", NamespacePrefix=")
.append(getNamespacePrefix());
return sb.toString();
}
// ----- data members -----------------------------------------------
/**
* The class of the specific XML bean.
*/
protected Class m_clzBean;
/**
* The XML element name for the XML bean.
*/
protected String m_sName;
/**
* The property adapters for the XML bean.
*/
protected PropertyAdapter[] m_aAdapter;
/**
* Specifies whether the XML bean requires a deep clone.
*/
protected boolean m_fDeepClone;
/**
* Namespace URI.
*/
protected String m_sNmsUri;
/**
* Namespace prefix.
*/
protected String m_sNmsPrefix;
/**
* Serialization ID for the XmlBean class.
*/
protected int m_nBeanId = -1;
// ----- constants --------------------------------------------------
/**
* Parameters for finding no-parameter methods.
*/
protected static final Class[] NOPARAMS = new Class[0];
/**
* Parameters for finding the default adapter constructor.
*/
protected static Class[] ADAPTER_INIT_PARAMS = new Class[]
{
BeanInfo .class,
Class .class,
String .class,
String .class,
XmlElement.class,
};
/**
* Map from type name / short class name to actual class instance.
*/
protected static final Map s_mapClassNames = new HashMap();
static
{
Map map = s_mapClassNames;
map.put("boolean" , boolean .class);
map.put("byte" , byte .class);
map.put("char" , char .class);
map.put("short" , short .class);
map.put("int" , int .class);
map.put("long" , long .class);
map.put("float" , float .class);
map.put("double" , double .class);
map.put("Boolean" , Boolean .class);
map.put("Byte" , Byte .class);
map.put("Character" , Character .class);
map.put("Short" , Short .class);
map.put("Integer" , Integer .class);
map.put("Long" , Long .class);
map.put("Float" , Float .class);
map.put("Double" , Double .class);
map.put("BigDecimal", BigDecimal .class);
map.put("BigInteger", BigInteger .class);
map.put("String" , String .class);
map.put("Date" , Date .class);
map.put("Time" , Time .class);
map.put("Timestamp" , Timestamp .class);
}
/**
* Map from the class of a property type to the class of the adapter
* that handles the type.
*/
protected static final Map s_mapClassAdapters = new HashMap();
static
{
Map map = s_mapClassAdapters;
map.put(boolean.class , SimpleAdapter.BooleanAdapter.class);
map.put(byte .class , SimpleAdapter.ByteAdapter .class);
map.put(char .class , SimpleAdapter.CharAdapter .class);
map.put(short .class , SimpleAdapter.ShortAdapter .class);
map.put(int .class , SimpleAdapter.IntAdapter .class);
map.put(long .class , SimpleAdapter.LongAdapter .class);
map.put(float .class , SimpleAdapter.FloatAdapter .class);
map.put(double .class , SimpleAdapter.DoubleAdapter .class);
map.put(Boolean .class , SimpleAdapter.BooleanAdapter.class);
map.put(Byte .class , SimpleAdapter.ByteAdapter .class);
map.put(Character.class , SimpleAdapter.CharAdapter .class);
map.put(Short .class , SimpleAdapter.ShortAdapter .class);
map.put(Integer .class , SimpleAdapter.IntAdapter .class);
map.put(Long .class , SimpleAdapter.LongAdapter .class);
map.put(Float .class , SimpleAdapter.FloatAdapter .class);
map.put(Double .class , SimpleAdapter.DoubleAdapter .class);
map.put(boolean[].class , PrimitiveArrayAdapter.BooleanArrayAdapter.class);
map.put(byte [].class , PrimitiveArrayAdapter.ByteArrayAdapter .class);
map.put(char [].class , PrimitiveArrayAdapter.CharArrayAdapter .class);
map.put(short [].class , PrimitiveArrayAdapter.ShortArrayAdapter .class);
map.put(int [].class , PrimitiveArrayAdapter.IntArrayAdapter .class);
map.put(long [].class , PrimitiveArrayAdapter.LongArrayAdapter .class);
map.put(float [].class , PrimitiveArrayAdapter.FloatArrayAdapter .class);
map.put(double [].class , PrimitiveArrayAdapter.DoubleArrayAdapter .class);
map.put(String .class, SimpleAdapter.StringAdapter .class);
map.put(BigDecimal .class, SimpleAdapter.BigDecimalAdapter.class);
map.put(BigInteger .class, SimpleAdapter.BigIntegerAdapter.class);
map.put(Date .class, SimpleAdapter.DateAdapter .class);
map.put(Time .class, SimpleAdapter.TimeAdapter .class);
map.put(Timestamp .class, SimpleAdapter.TimestampAdapter .class);
map.put(java.util.Date.class, SimpleAdapter.OldDateAdapter .class);
}
/**
* Map from class of an intrinsic type to its JVM signature.
*/
protected static final Map s_mapPrimitiveNames = new HashMap();
static
{
Map map = s_mapPrimitiveNames;
map.put(boolean.class , "Z");
map.put(byte .class , "B");
map.put(char .class , "C");
map.put(short .class , "S");
map.put(int .class , "I");
map.put(long .class , "J");
map.put(float .class , "F");
map.put(double .class , "D");
}
}
// ----- data members ---------------------------------------------------
/**
* For backwards compatibility -- caches XML descriptors that wrap the
* old-style data used for XmlBean initialization.
*/
private static Map s_mapXml = new SafeHashMap();
/**
* Cache by class of reflection information.
*/
private static Map s_mapInfo = new SafeHashMap();
/**
* If this XmlBean is contained by another XmlBean, then the containing
* bean reference is held by this XmlBean.
*/
private transient XmlBean m_parent;
/**
* Mutable/read-only setting.
*/
private boolean m_fMutable = true;
/**
* Cached hash value.
*/
private transient int m_nHash;
/**
* Cached bean info (for this bean).
*/
private transient BeanInfo m_info;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy