[Practicalxml-commits] SF.net SVN: practicalxml:[140] branches/dev-1.1/src
Brought to you by:
kdgregory
|
From: Auto-Generated S. C. M. <pra...@li...> - 2009-09-18 18:46:45
|
Revision: 140
http://practicalxml.svn.sourceforge.net/practicalxml/?rev=140&view=rev
Author: kdgregory
Date: 2009-09-18 18:45:53 +0000 (Fri, 18 Sep 2009)
Log Message:
-----------
BeanConverter now contains only static methods, is a facade
Renamed Bean2XmlDriver and Xml2BeanDriver to XXXConverter
Moved conversion docs into package
Modified Paths:
--------------
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanConverter.java
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlAppenders.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/bean/TestBeanConverter.java
Added Paths:
-----------
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlConverter.java
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanConverter.java
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/package.html
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/bean/TestBean2XmlConverter.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/bean/TestXml2BeanConverter.java
Removed Paths:
-------------
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/bean/TestBean2XmlDriver.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/bean/TestXml2BeanDriver.java
Modified: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanConverter.java
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanConverter.java 2009-09-18 15:51:32 UTC (rev 139)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanConverter.java 2009-09-18 18:45:53 UTC (rev 140)
@@ -17,175 +17,32 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import net.sf.practicalxml.converter.bean.Bean2XmlDriver;
+import net.sf.practicalxml.converter.bean.Bean2XmlConverter;
import net.sf.practicalxml.converter.bean.Bean2XmlOptions;
-import net.sf.practicalxml.converter.bean.Xml2BeanDriver;
+import net.sf.practicalxml.converter.bean.Xml2BeanConverter;
import net.sf.practicalxml.converter.bean.Xml2BeanOptions;
/**
* Converts Java objects (not just beans) to or from an XML representation.
* Originally developed to support simple web services, without the overhead
- * (schema definitions and/or annotations) required by JAXB.
+ * (schema definitions and/or annotations) required by JAXB. See the {@link
+ * net.sf.practicalxml.converter.bean package docs} for specifics.
* <p>
- * A single instance is intended for conversions in one direction only. This
- * is a consequence of specifying options as varargs, but fits well with
- * "normal" application design, in which input and output are handled by
- * different parts of the code. You won't get an error if you try to convert
- * the "wrong" way, you'll simply get default behavior. Note that, depending
- * on options settings, output generated by one converter may not be valid
- * input for another.
+ * This class provides static facade methods for
+ * {@link net.sf.practicalxml.converter.bean.Bean2XmlConverter} and
+ * {@link net.sf.practicalxml.converter.bean.Xml2BeanConverter}. If static
+ * methods and throwaway objects offend you then use those classes directly.
* <p>
- * The basic structure of XML produced/consumed by this class is that the
- * root element represents the object being converted, and its child nodes
- * represent the elements/properties of the object. Nodes representing complex
- * objects have child elements, those representing simple (primitive) objects
- * have a single text child. Objects are processed recursively, and cycles are
- * <em>not</em> detected (although this may change).
- * <p>
- * A namespace and qualified name may be provided to {@link #convertToXml},
- * and all elements will inherit the namespace and prefix. Namespaces are
- * ignored on input; all property matches uses the element's local name.
- * <p>
- * Each element may have an <code>xsi:type</code> attribute (where <code>xsi
- * </code> references to the XML Schema instance namespace). This attribute
- * is optional for both output and input; if used for output, it is merely
- * informational, but for input will be examined to validate that the XML is
- * appropriate for the desired object type. For primitive types, wrappers,
- * and strings, it takes the form "<code>xsd:TYPE</code>", where <code>TYPE
- * </code> is one of the simple types defined by XML schema. For other Java
- * types, it takes the form "<code>java:TYPE</code>", where <code>TYPE</code>
- * is the fully qualified Java classname.
- * <p>
- * On input, the desired type is specified by the caller or by introspection
- * (except in the case of collection elements; see below). The <code>xsi:type
- * </code> value, if any, is ignored except for validation.
- * <p>
- * Additional conversion rules are as follows:
- *
- * <table border="1">
- * <tr><th>Java Object Type
- * <th>{@link #convertToXml}
- * <th>{@link #convertToJava}
- * <tr><td>Primitives, Wrapper objects, and String
- * <td>
- * <td>
- * <tr><td>Arrays
- * <td>Elements of the array are written in sequence to the DOM, using the
- * element name "<code>data</code>". Elements are given an attribute
- * named "<code>index</code>", which contains the element's position
- * within the array.
- * <td>Elements of the array are processed in sequence, ignoring both
- * element name and "index" attribute.
- * <tr><td>Lists and Sets
- * <td>The collection is iterated, and elements are written in sequence to
- * the DOM, using the element name "<code>data</code>". Elements are
- * given an attribute named "<code>index</code>", which contains the
- * element's position within the iteration (meaningful only for lists).
- * <td>Elements of the are processed in sequence, ignoring both element
- * name and "index" attribute.
- * <p>
- * If an <code>xsi:type</code> attribute is present, it will be used
- * to drive conversion of the element. Otherwise, the element will be
- * converted as a <code>String</code> (which will fail for complex
- * types, because string conversion assumes a single text node).
- * <p>
- * Where the caller specifies an interface as the conversion class, the
- * converter will choose an appropriate implementation class:
- * <ul>
- * <li> <code>ArrayList</code> for <code>List</code> or <code>Collection</code>
- * <li> <code>TreeSet</code> for <code>SortedSet</code>
- * <li> <code>HashSet</code> for <code>Set</code>
- * </ul>
- * <tr><td>Maps
- * <td>The collection is iterated, and elements are written in sequence to
- * the DOM. Depending on the output options, either the entry key will
- * be used as the element name, or the name will be "<code>data</code>"
- * and the key stored in an attribute named "<code>key</code>". The
- * former option is only permitted if all keys in the map are valid
- * XML element names; otherwise the converter will throw.
- * <td>Elements are processed in sequence. The converter first looks for
- * a "<code>key</code>" attribute, and will use it as the entry key
- * if found. Otherwise, it will use the element name. If your maps
- * are being reduced to a single entry, look for a missing attribute.
- * <p>
- * If an <code>xsi:type</code> attribute is present, it will be used
- * to drive conversion of the element. Otherwise, the element will be
- * converted as a <code>String</code> (which will fail for complex
- * types, because string conversion assumes a single text node).
- * <p>
- * Where the caller specifies an interface as the conversion class,
- * the converter will choose an appropriate implementation class:
- * <code>TreeMap</code> for <code>SortedMap</code>, and <code>HashMap
- * </code> for <code>Map</code>.
- * <tr><td>Bean-structured Objects
- * <td>The object is introspected, and properties are written in the order
- * provided by the <code>Introspector</code>.
- * <td>The bean class must provide a no-argument constructor (otherwise it
- * doesn't follow the bean spec, and we can't use it).
- * <p>
- * The converter relies on <code>java.beans.Introspector</code> to find
- * property setter methods for an object. If the object provides
- * multiple methods for the property, the converter will use whichever
- * one the introspector provides.
- * <p>
- * Elements are processed in order, and the element's localname is used
- * to find the associated object property. If the XML does not contain
- * an element corresponding to a bean property, that property is left
- * with its default value (ie, we don't try to find an element based
- * on property name).
- * <p>
- * If the XML contains an element that does not correspond to any bean
- * property, the converter will either throw or ignore the element,
- * depending on options settings.
- * <tr><td>Other Objects
- * <td>not supported
- * <td>not supported
- * </table>
- *
* <strong>Warning</strong>:
- * <code>java.beans.Introspector</code> holds a cache of introspected objects.
- * If you use this converter in an app-server you should call
- * <code>Introspector.flushCaches()</code> during deploy.
+ * Bean-to-XML conversion uses <code>java.beans.Introspector</code>, which
+ * holds a cache of introspected objects. If you use this conversion in an
+ * app-server you should call <code>Introspector.flushCaches()</code> during
+ * deploy.
*/
public class BeanConverter
{
- private Bean2XmlDriver _outputDriver;
- private Xml2BeanDriver _inputDriver;
-
-
/**
- * Creates an instance with all options disabled. This can be used for
- * conversions in either direction, and is meant primarily for testing.
- */
- public BeanConverter()
- {
- _outputDriver = new Bean2XmlDriver();
- _inputDriver = new Xml2BeanDriver();
- }
-
-
- /**
- * Creates an instance to be used for Bean -> XML conversion,
- */
- public BeanConverter(Bean2XmlOptions... options)
- {
- _outputDriver = new Bean2XmlDriver(options);
- _inputDriver = new Xml2BeanDriver();
- }
-
-
- /**
- * Creates an instance to be used for XML -> Bean conversion.
- */
- public BeanConverter(Xml2BeanOptions options)
- {
- _outputDriver = new Bean2XmlDriver();
- _inputDriver = new Xml2BeanDriver(options);
- }
-
-
- /**
* Creates a new DOM document from the passed bean, in which all elements
* are members of the specified namespace and will inherit the root's
* prefix (if any).
@@ -197,10 +54,13 @@
* @param rootName The qualified name given to the root element of the
* generated document. If a qualified name, all child
* elements will inherit its prefix.
+ * @param options Conversion options.
*/
- public Document convertToXml(Object bean, String nsUri, String rootName)
+ public static Document convertToXml(
+ Object bean, String nsUri, String rootName, Bean2XmlOptions... options)
{
- return _outputDriver.convert(bean, nsUri, rootName)
+ return new Bean2XmlConverter(options)
+ .convert(bean, nsUri, rootName)
.getOwnerDocument();
}
@@ -212,10 +72,13 @@
* bean, collection, or simple type.
* @param rootName The name given to the root element of the produced
* document.
+ * @param options Conversion options.
*/
- public Document convertToXml(Object bean, String rootName)
+ public static Document convertToXml(
+ Object bean, String rootName, Bean2XmlOptions... options)
{
- return _outputDriver.convert(bean, rootName)
+ return new Bean2XmlConverter(options)
+ .convert(bean, rootName)
.getOwnerDocument();
}
@@ -227,10 +90,12 @@
* @param dom The source document.
* @param klass The desired class to instantiate and fill from this
* document.
+ * @param options Conversion options.
*/
- public <T> T convertToJava(Document dom, Class<T> klass)
+ public static <T> T convertToJava(
+ Document dom, Class<T> klass, Xml2BeanOptions... options)
{
- return convertToJava(dom.getDocumentElement(), klass);
+ return convertToJava(dom.getDocumentElement(), klass, options);
}
@@ -243,9 +108,11 @@
* root element of its document.
* @param klass The desired class to instantiate and fill from this
* document.
+ * @param options Conversion options.
*/
- public <T> T convertToJava(Element root, Class<T> klass)
+ public static <T> T convertToJava(
+ Element root, Class<T> klass, Xml2BeanOptions... options)
{
- return _inputDriver.convert(root, klass);
+ return new Xml2BeanConverter(options).convert(root, klass);
}
}
Modified: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlAppenders.java
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlAppenders.java 2009-09-18 15:51:32 UTC (rev 139)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlAppenders.java 2009-09-18 18:45:53 UTC (rev 140)
@@ -25,7 +25,7 @@
/**
* Packaging class used for XML output appenders. This class is a temporary
- * hack, as I move intelligence into {@link Bean2XmlDriver}; the contained
+ * hack, as I move intelligence into {@link Bean2XmlConverter}; the contained
* classes will end up in a new package, once I figure out what the package
* structure should be.
*/
Copied: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlConverter.java (from rev 130, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlConverter.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlConverter.java 2009-09-18 18:45:53 UTC (rev 140)
@@ -0,0 +1,253 @@
+// Copyright 2008-2009 severally by the contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.sf.practicalxml.converter.bean;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Map;
+
+import org.w3c.dom.Element;
+
+import net.sf.practicalxml.DomUtil;
+import net.sf.practicalxml.converter.ConversionException;
+import net.sf.practicalxml.converter.bean.Bean2XmlAppenders.*;
+import net.sf.practicalxml.converter.internal.ConversionStrings;
+import net.sf.practicalxml.converter.internal.DomUtilToo;
+import net.sf.practicalxml.converter.internal.PrimitiveConversionHelper;
+
+
+/**
+ * Driver class for converting a Java bean into an XML DOM. Normal usage is
+ * to create a single instance of this class with desired options, then use
+ * it for multiple conversions. This class is thread-safe.
+ */
+public class Bean2XmlConverter
+{
+ private PrimitiveConversionHelper _helper;
+ private EnumSet<Bean2XmlOptions> _options = EnumSet.noneOf(Bean2XmlOptions.class);
+
+ public Bean2XmlConverter(Bean2XmlOptions... options)
+ {
+ for (Bean2XmlOptions option : options)
+ _options.add(option);
+ _helper = new PrimitiveConversionHelper(shouldUseXsdFormatting());
+ }
+
+
+//----------------------------------------------------------------------------
+// Public methods
+//----------------------------------------------------------------------------
+
+ /**
+ * Creates an XML DOM with the specified root element name, and fills it
+ * by introspecting the passed object (see {@link #introspect} for
+ * treatment of simple objects).
+ */
+ public Element convert(Object obj, String rootName)
+ {
+ return convert(obj, null, rootName);
+ }
+
+
+ /**
+ * Creates an XML DOM with the specified root element name and namespace
+ * URI, and fills it by introspecting the passed object (see {@link
+ * #introspect} for treatment of simple objects). The namespace URI (and
+ * prefix, if provided) will be used for all child elements.
+ */
+ public Element convert(Object obj, String nsUri, String rootName)
+ {
+ Element root = DomUtil.newDocument(nsUri, rootName);
+ doXsiNamespaceHack(root);
+ convert(obj, "", new DirectAppender(root, _options));
+ return root;
+ }
+
+
+ /**
+ * Introspects the passed object, and appends its contents to the output.
+ * This method is public to allow non-standard conversions, such as
+ * appending into an existing tree, or (in the future, if we introduce an
+ * appender factory) producing non-XML output.
+ */
+ public void convert(Object obj, String name, Appender appender)
+ {
+ // these methods have side effects!
+ // empty blocks and comments are there to keep Eclipse happy
+ if (tryToConvertAsPrimitiveOrNull(obj, null, name, appender))
+ { /* it was converted */ }
+ else if (tryToConvertAsArray(obj, name, appender))
+ { /* it was converted */ }
+ else if (tryToConvertAsMap(obj, name, appender))
+ { /* it was converted */ }
+ else if (tryToConvertAsCollection(obj, name, appender))
+ { /* it was converted */ }
+ else if (tryToConvertAsBean(obj, name, appender))
+ { /* it was converted */ }
+ else
+ throw new ConversionException("unable to convert: " + obj.getClass().getName());
+ }
+
+
+//----------------------------------------------------------------------------
+// Internals
+//----------------------------------------------------------------------------
+
+ private boolean shouldUseXsdFormatting()
+ {
+ return _options.contains(Bean2XmlOptions.XSD_FORMAT)
+ || _options.contains(Bean2XmlOptions.ADD_XSI_TYPE);
+ }
+
+
+ /**
+ * Introduces the XML Schema Instance namespace into the DOM tree using a
+ * meaningless attribute. The Xerces serializer does not attempt to promote
+ * namespace definitions above the subtree in which they first appear, which
+ * means that the XSI definition could be repeated many times throughout the
+ * serialized tree, adding bulk to the serialized representation.
+ * <p>
+ * By putting "nil=false" at the root element, we will keep the serializer
+ * from inserting all these definitions. This has to happen <em>before</em>
+ * any actual conversion, in case some bozo passes <code>null</code> to
+ * the top-level conversion routine.
+ * <p>
+ * Note that we only do this if <code>xsi:nil</code> is enabled by itself.
+ * If <code>xsi:type</code> is enabled, the converter will attach that
+ * attribute to the root instead, thereby establishing the namespace context.
+ */
+ private void doXsiNamespaceHack(Element root)
+ {
+ if (_options.contains(Bean2XmlOptions.XSI_NIL)
+ && !_options.contains(Bean2XmlOptions.ADD_XSI_TYPE))
+ {
+ DomUtilToo.setXsiNil(root, false);
+ }
+ }
+
+
+ private boolean tryToConvertAsPrimitiveOrNull(
+ Object obj, Class<?> klass, String name, Appender appender)
+ {
+ if (obj != null)
+ klass = obj.getClass();
+
+ String objType = _helper.getXsdType(klass);
+ if ((obj == null) || (objType != null))
+ {
+ appender.appendValue(name, objType, _helper.stringify(obj));
+ return true;
+ }
+
+ return false;
+ }
+
+
+ private boolean tryToConvertAsArray(Object array, String name, Appender appender)
+ {
+ if (!array.getClass().isArray())
+ return false;
+
+ Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(array));
+ Appender childAppender = new IndexedAppender(parent, _options);
+ int length = Array.getLength(array);
+ for (int idx = 0 ; idx < length ; idx++)
+ {
+ Object value = Array.get(array, idx);
+ convert(value, ConversionStrings.EL_COLLECTION_ITEM, childAppender);
+ }
+ return true;
+ }
+
+
+ private boolean tryToConvertAsMap(Object obj, String name, Appender appender)
+ {
+ if (!(obj instanceof Map))
+ return false;
+
+ Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(obj));
+ Appender childAppender = new MapAppender(parent, _options);
+ for (Map.Entry<?,?> entry : ((Map<?,?>)obj).entrySet())
+ {
+ convert(entry.getValue(), String.valueOf(entry.getKey()), childAppender);
+ }
+ return true;
+ }
+
+
+ private boolean tryToConvertAsCollection(Object obj, String name, Appender appender)
+ {
+ if (!(obj instanceof Collection))
+ return false;
+
+ Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(obj));
+ Appender childAppender = new IndexedAppender(parent, _options);
+ for (Object value : (Collection<?>)obj)
+ {
+ convert(value, ConversionStrings.EL_COLLECTION_ITEM, childAppender);
+ }
+ return true;
+ }
+
+
+ private boolean tryToConvertAsBean(Object bean, String name, Appender appender)
+ {
+ Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(bean));
+ Appender childAppender = new BasicAppender(parent, _options);
+ try
+ {
+ BeanInfo info = Introspector.getBeanInfo(bean.getClass(), Object.class);
+ PropertyDescriptor[] props = info.getPropertyDescriptors();
+ for (int ii = 0 ; ii < props.length ; ii++)
+ convertBeanProperty(bean, props[ii], childAppender);
+ }
+ catch (IntrospectionException ee)
+ {
+ throw new ConversionException("introspection failure", ee);
+ }
+ return true;
+ }
+
+
+ private void convertBeanProperty(
+ Object bean, PropertyDescriptor propDesc, Appender appender)
+ {
+ String name = propDesc.getName();
+ Class<?> type = propDesc.getPropertyType();
+ Object value;
+ try
+ {
+ Method getter = propDesc.getReadMethod();
+ if (getter == null)
+ return;
+ value = getter.invoke(bean);
+ }
+ catch (Exception ee)
+ {
+ throw new ConversionException("unable to retrieve bean value", ee);
+ }
+
+ if (value == null)
+ tryToConvertAsPrimitiveOrNull(value, type, name, appender);
+ else
+ convert(value, name, appender);
+ }
+}
Deleted: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java 2009-09-18 15:51:32 UTC (rev 139)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java 2009-09-18 18:45:53 UTC (rev 140)
@@ -1,253 +0,0 @@
-// Copyright 2008-2009 severally by the contributors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package net.sf.practicalxml.converter.bean;
-
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Array;
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.Map;
-
-import org.w3c.dom.Element;
-
-import net.sf.practicalxml.DomUtil;
-import net.sf.practicalxml.converter.ConversionException;
-import net.sf.practicalxml.converter.bean.Bean2XmlAppenders.*;
-import net.sf.practicalxml.converter.internal.ConversionStrings;
-import net.sf.practicalxml.converter.internal.DomUtilToo;
-import net.sf.practicalxml.converter.internal.PrimitiveConversionHelper;
-
-
-/**
- * Driver class for converting a Java bean into an XML DOM. Normal usage is
- * to create a single instance of this class with desired options, then use
- * it for multiple conversions. This class is thread-safe.
- */
-public class Bean2XmlDriver
-{
- private PrimitiveConversionHelper _helper;
- private EnumSet<Bean2XmlOptions> _options = EnumSet.noneOf(Bean2XmlOptions.class);
-
- public Bean2XmlDriver(Bean2XmlOptions... options)
- {
- for (Bean2XmlOptions option : options)
- _options.add(option);
- _helper = new PrimitiveConversionHelper(shouldUseXsdFormatting());
- }
-
-
-//----------------------------------------------------------------------------
-// Public methods
-//----------------------------------------------------------------------------
-
- /**
- * Creates an XML DOM with the specified root element name, and fills it
- * by introspecting the passed object (see {@link #introspect} for
- * treatment of simple objects).
- */
- public Element convert(Object obj, String rootName)
- {
- return convert(obj, null, rootName);
- }
-
-
- /**
- * Creates an XML DOM with the specified root element name and namespace
- * URI, and fills it by introspecting the passed object (see {@link
- * #introspect} for treatment of simple objects). The namespace URI (and
- * prefix, if provided) will be used for all child elements.
- */
- public Element convert(Object obj, String nsUri, String rootName)
- {
- Element root = DomUtil.newDocument(nsUri, rootName);
- doXsiNamespaceHack(root);
- convert(obj, "", new DirectAppender(root, _options));
- return root;
- }
-
-
- /**
- * Introspects the passed object, and appends its contents to the output.
- * This method is public to allow non-standard conversions, such as
- * appending into an existing tree, or (in the future, if we introduce an
- * appender factory) producing non-XML output.
- */
- public void convert(Object obj, String name, Appender appender)
- {
- // these methods have side effects!
- // empty blocks and comments are there to keep Eclipse happy
- if (tryToConvertAsPrimitiveOrNull(obj, null, name, appender))
- { /* it was converted */ }
- else if (tryToConvertAsArray(obj, name, appender))
- { /* it was converted */ }
- else if (tryToConvertAsMap(obj, name, appender))
- { /* it was converted */ }
- else if (tryToConvertAsCollection(obj, name, appender))
- { /* it was converted */ }
- else if (tryToConvertAsBean(obj, name, appender))
- { /* it was converted */ }
- else
- throw new ConversionException("unable to convert: " + obj.getClass().getName());
- }
-
-
-//----------------------------------------------------------------------------
-// Internals
-//----------------------------------------------------------------------------
-
- private boolean shouldUseXsdFormatting()
- {
- return _options.contains(Bean2XmlOptions.XSD_FORMAT)
- || _options.contains(Bean2XmlOptions.ADD_XSI_TYPE);
- }
-
-
- /**
- * Introduces the XML Schema Instance namespace into the DOM tree using a
- * meaningless attribute. The Xerces serializer does not attempt to promote
- * namespace definitions above the subtree in which they first appear, which
- * means that the XSI definition could be repeated many times throughout the
- * serialized tree, adding bulk to the serialized representation.
- * <p>
- * By putting "nil=false" at the root element, we will keep the serializer
- * from inserting all these definitions. This has to happen <em>before</em>
- * any actual conversion, in case some bozo passes <code>null</code> to
- * the top-level conversion routine.
- * <p>
- * Note that we only do this if <code>xsi:nil</code> is enabled by itself.
- * If <code>xsi:type</code> is enabled, the converter will attach that
- * attribute to the root instead, thereby establishing the namespace context.
- */
- private void doXsiNamespaceHack(Element root)
- {
- if (_options.contains(Bean2XmlOptions.XSI_NIL)
- && !_options.contains(Bean2XmlOptions.ADD_XSI_TYPE))
- {
- DomUtilToo.setXsiNil(root, false);
- }
- }
-
-
- private boolean tryToConvertAsPrimitiveOrNull(
- Object obj, Class<?> klass, String name, Appender appender)
- {
- if (obj != null)
- klass = obj.getClass();
-
- String objType = _helper.getXsdType(klass);
- if ((obj == null) || (objType != null))
- {
- appender.appendValue(name, objType, _helper.stringify(obj));
- return true;
- }
-
- return false;
- }
-
-
- private boolean tryToConvertAsArray(Object array, String name, Appender appender)
- {
- if (!array.getClass().isArray())
- return false;
-
- Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(array));
- Appender childAppender = new IndexedAppender(parent, _options);
- int length = Array.getLength(array);
- for (int idx = 0 ; idx < length ; idx++)
- {
- Object value = Array.get(array, idx);
- convert(value, ConversionStrings.EL_COLLECTION_ITEM, childAppender);
- }
- return true;
- }
-
-
- private boolean tryToConvertAsMap(Object obj, String name, Appender appender)
- {
- if (!(obj instanceof Map))
- return false;
-
- Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(obj));
- Appender childAppender = new MapAppender(parent, _options);
- for (Map.Entry<?,?> entry : ((Map<?,?>)obj).entrySet())
- {
- convert(entry.getValue(), String.valueOf(entry.getKey()), childAppender);
- }
- return true;
- }
-
-
- private boolean tryToConvertAsCollection(Object obj, String name, Appender appender)
- {
- if (!(obj instanceof Collection))
- return false;
-
- Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(obj));
- Appender childAppender = new IndexedAppender(parent, _options);
- for (Object value : (Collection<?>)obj)
- {
- convert(value, ConversionStrings.EL_COLLECTION_ITEM, childAppender);
- }
- return true;
- }
-
-
- private boolean tryToConvertAsBean(Object bean, String name, Appender appender)
- {
- Element parent = appender.appendContainer(name, DomUtilToo.getXsiTypeForJavaObject(bean));
- Appender childAppender = new BasicAppender(parent, _options);
- try
- {
- BeanInfo info = Introspector.getBeanInfo(bean.getClass(), Object.class);
- PropertyDescriptor[] props = info.getPropertyDescriptors();
- for (int ii = 0 ; ii < props.length ; ii++)
- convertBeanProperty(bean, props[ii], childAppender);
- }
- catch (IntrospectionException ee)
- {
- throw new ConversionException("introspection failure", ee);
- }
- return true;
- }
-
-
- private void convertBeanProperty(
- Object bean, PropertyDescriptor propDesc, Appender appender)
- {
- String name = propDesc.getName();
- Class<?> type = propDesc.getPropertyType();
- Object value;
- try
- {
- Method getter = propDesc.getReadMethod();
- if (getter == null)
- return;
- value = getter.invoke(bean);
- }
- catch (Exception ee)
- {
- throw new ConversionException("unable to retrieve bean value", ee);
- }
-
- if (value == null)
- tryToConvertAsPrimitiveOrNull(value, type, name, appender);
- else
- convert(value, name, appender);
- }
-}
Copied: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanConverter.java (from rev 130, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanConverter.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanConverter.java 2009-09-18 18:45:53 UTC (rev 140)
@@ -0,0 +1,396 @@
+// Copyright 2008-2009 severally by the contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.sf.practicalxml.converter.bean;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import net.sf.practicalxml.DomUtil;
+import net.sf.practicalxml.converter.ConversionException;
+import net.sf.practicalxml.converter.internal.ConversionStrings;
+import net.sf.practicalxml.converter.internal.DomUtilToo;
+import net.sf.practicalxml.converter.internal.PrimitiveConversionHelper;
+import net.sf.practicalxml.internal.StringUtils;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+
+/**
+ * Driver class for converting an XML DOM into a Java bean. Normal usage is
+ * to create a single instance of this class with desired options, then use
+ * it for multiple conversions. This class is thread-safe.
+ */
+public class Xml2BeanConverter
+{
+ private EnumSet<Xml2BeanOptions> _options;
+ private PrimitiveConversionHelper _helper;
+ private Map<Class<?>,Map<String,Method>> _introspectedClasses;
+
+
+ public Xml2BeanConverter(Xml2BeanOptions... options)
+ {
+ _options = EnumSet.noneOf(Xml2BeanOptions.class);
+ for (Xml2BeanOptions option : options)
+ _options.add(option);
+
+ _helper = new PrimitiveConversionHelper(_options.contains(Xml2BeanOptions.EXPECT_XSD_FORMAT));
+ _introspectedClasses = new HashMap<Class<?>,Map<String,Method>>();
+ }
+
+
+//----------------------------------------------------------------------------
+// Public Methods
+//----------------------------------------------------------------------------
+
+ /**
+ * Attempts to convert the passed DOM subtree into an object of the
+ * specified class.
+ */
+ public <T> T convert(Element elem, Class<T> klass)
+ {
+ return klass.cast(convertWithoutCast(elem, klass));
+ }
+
+
+//----------------------------------------------------------------------------
+// Internal Conversion Methods
+//----------------------------------------------------------------------------
+
+ /**
+ * Attempts to convert the passed DOM subtree into an object of the
+ * specified class. Note that this version does not use generics,
+ * and does not try to cast the result, whereas the public version
+ * does. Internally, we want to treat <code>Integer.TYPE</code> the
+ * same as <code>Integer.class</code>, and the cast prevents that.
+ */
+ public Object convertWithoutCast(Element elem, Class<?> klass)
+ {
+ validateXsiType(elem, klass);
+ if (isAllowableNull(elem))
+ return null;
+
+ Object obj = tryConvertAsPrimitive(elem, klass);
+ if (obj == null)
+ obj = tryConvertAsArray(elem, klass);
+ if (obj == null)
+ obj = tryConvertAsSimpleCollection(elem, klass);
+ if (obj == null)
+ obj = tryConvertAsMap(elem, klass);
+ if (obj == null)
+ obj = tryConvertAsBean(elem, klass);
+ return obj;
+ }
+
+
+ private boolean isAllowableNull(Element elem)
+ {
+ String text = getText(elem);
+ if ((text != null) || hasElementChildren(elem))
+ return false;
+
+ if (_options.contains(Xml2BeanOptions.REQUIRE_XSI_NIL))
+ {
+ if (!DomUtilToo.getXsiNil(elem))
+ throw new ConversionException("missing/false xsi:nil", elem);
+ }
+
+ return true;
+ }
+
+
+ private Object tryConvertAsPrimitive(Element elem, Class<?> klass)
+ {
+ if (_helper.getXsdType(klass) == null)
+ return null;
+
+ if (hasElementChildren(elem))
+ throw new ConversionException("expecting primitive; has children", elem);
+
+ return _helper.parse(getText(elem), klass);
+ }
+
+
+ private Object tryConvertAsArray(Element elem, Class<?> klass)
+ {
+ Class<?> childKlass = klass.getComponentType();
+ if (childKlass == null)
+ return null;
+
+ List<Element> children = DomUtil.getChildren(elem);
+ Object result = Array.newInstance(childKlass, children.size());
+ int idx = 0;
+ for (Element child : children)
+ {
+ Array.set(result, idx++, convertWithoutCast(child, childKlass));
+ }
+ return result;
+ }
+
+
+ private Object tryConvertAsSimpleCollection(Element elem, Class<?> klass)
+ {
+ Collection<Object> result = instantiateCollection(klass);
+ if (result == null)
+ return null;
+
+ List<Element> children = DomUtil.getChildren(elem);
+ for (Element child : children)
+ {
+ Class<?> childClass = getClassFromXsiType(child);
+ if (childClass == null)
+ childClass = String.class;
+ result.add(convertWithoutCast(child, childClass));
+ }
+ return result;
+ }
+
+
+ private Object tryConvertAsMap(Element elem, Class<?> klass)
+ {
+ Map<Object,Object> result = instantiateMap(klass);
+ if (result == null)
+ return null;
+
+ List<Element> children = DomUtil.getChildren(elem);
+ for (Element child : children)
+ {
+ String key = child.getAttribute(ConversionStrings.AT_MAP_KEY);
+ if (StringUtils.isEmpty(key))
+ key = DomUtil.getLocalName(child);
+ Class<?> childClass = getClassFromXsiType(child);
+ if (childClass == null)
+ childClass = String.class;
+ result.put(key, convertWithoutCast(child, childClass));
+ }
+ return result;
+ }
+
+
+ private Object tryConvertAsBean(Element elem, Class<?> klass)
+ {
+ Object bean = instantiateBean(elem, klass);
+
+ List<Element> children = DomUtil.getChildren(elem);
+ for (Element child : children)
+ {
+ Method setter = getSetterMethod(klass, child);
+ if (setter == null)
+ continue;
+
+ Class<?> childClass = setter.getParameterTypes()[0];
+ Object childValue = convertWithoutCast(child, childClass);
+ invokeSetter(elem, bean, setter, childValue);
+ }
+ return bean;
+ }
+
+
+//----------------------------------------------------------------------------
+// Other Internals
+//----------------------------------------------------------------------------
+
+ /**
+ * Returns the text content of an element, applying appropriate options.
+ */
+ private String getText(Element elem)
+ {
+ String text = DomUtil.getText(elem);
+ if (StringUtils.isBlank(text)
+ && _options.contains(Xml2BeanOptions.CONVERT_BLANK_AS_NULL))
+ text = null;
+ return text;
+ }
+
+
+ /**
+ * Examines an element's <code>xsi:type</code> attribute, if any, and
+ * returns the Java class corresponding to it. Used when converting
+ * collection types, which don't have type information that can be
+ * introspected, and also to validate non-XSD types.
+ */
+ private Class<?> getClassFromXsiType(Element elem)
+ {
+ String xsiType = DomUtilToo.getXsiType(elem);
+ if (xsiType == null)
+ return null;
+
+ String javaType = DomUtilToo.getJavaClassFromXsiType(xsiType);
+ if (javaType != null)
+ {
+ try
+ {
+ return Class.forName(javaType);
+ }
+ catch (ClassNotFoundException ee)
+ {
+ throw new ConversionException(
+ "invalid Java type specification: " + javaType, elem, ee);
+ }
+ }
+
+ return _helper.getJavaType(xsiType);
+ }
+
+
+ private void validateXsiType(Element elem, Class<?> klass)
+ {
+ if (!_options.contains(Xml2BeanOptions.REQUIRE_XSI_TYPE))
+ return;
+
+ String xsiType = DomUtilToo.getXsiType(elem);
+ if (xsiType == null)
+ throw new ConversionException("missing xsi:type", elem);
+
+ if (xsiType.equals(_helper.getXsdType(klass)))
+ return;
+
+ Class<?> xsiKlass = getClassFromXsiType(elem);
+ if (klass.isAssignableFrom(xsiKlass))
+ return;
+
+ throw new ConversionException(
+ "invalid xsi:type: \"" + xsiType + "\" for " + klass.getName(),
+ elem);
+ }
+
+
+ private boolean hasElementChildren(Element elem)
+ {
+ Node child = elem.getFirstChild();
+ while (child != null)
+ {
+ if (child instanceof Element)
+ return true;
+ child = child.getNextSibling();
+ }
+ return false;
+ }
+
+
+ private Method getSetterMethod(Class<?> beanKlass, Element child)
+ {
+ Map<String,Method> methodMap = _introspectedClasses.get(beanKlass);
+ if (methodMap == null)
+ methodMap = introspect(beanKlass);
+
+ Method setter = methodMap.get(DomUtil.getLocalName(child));
+ if ((setter == null) && !_options.contains(Xml2BeanOptions.IGNORE_MISSING_PROPERTIES))
+ {
+ throw new ConversionException("can't find property setter", child);
+ }
+
+ return setter;
+ }
+
+
+ private Map<String,Method> introspect(Class<?> klass)
+ {
+ Map<String,Method> methodMap = new HashMap<String,Method>();
+ try
+ {
+ BeanInfo info = Introspector.getBeanInfo(klass, Object.class);
+ for (PropertyDescriptor propDesc : info.getPropertyDescriptors())
+ {
+ Method setter = propDesc.getWriteMethod();
+ if (setter != null)
+ methodMap.put(propDesc.getName(), setter);
+ }
+ }
+ catch (IntrospectionException ee)
+ {
+ throw new ConversionException("unable to introspect", ee);
+ }
+
+ _introspectedClasses.put(klass, methodMap);
+ return methodMap;
+ }
+
+
+ /**
+ * Attempts to create a <code>Collection</code> instance appropriate for
+ * the passed class, returns <code>null</code> if unable.
+ */
+ private Collection<Object> instantiateCollection(Class<?> klass)
+ {
+ if (SortedSet.class.isAssignableFrom(klass))
+ return new TreeSet<Object>();
+ else if (Set.class.isAssignableFrom(klass))
+ return new HashSet<Object>();
+ else if (List.class.isAssignableFrom(klass))
+ return new ArrayList<Object>();
+ else if (Collection.class.isAssignableFrom(klass))
+ return new ArrayList<Object>();
+ else
+ return null;
+ }
+
+
+ /**
+ * Attempts to create a <code>Map</code> instance appropriate for the
+ * passed class, returns <code>null</code> if unable.
+ */
+ private Map<Object,Object> instantiateMap(Class<?> klass)
+ {
+ if (SortedMap.class.isAssignableFrom(klass))
+ return new TreeMap<Object,Object>();
+ else if (Map.class.isAssignableFrom(klass))
+ return new HashMap<Object,Object>();
+ else
+ return null;
+ }
+
+
+ private Object instantiateBean(Element elem, Class<?> klass)
+ {
+ try
+ {
+ return klass.newInstance();
+ }
+ catch (Exception ee)
+ {
+ throw new ConversionException("unable to instantiate bean", elem, ee);
+ }
+ }
+
+
+ private void invokeSetter(Element elem, Object bean, Method setter, Object value)
+ {
+ try
+ {
+ setter.invoke(bean, value);
+ }
+ catch (Exception ee)
+ {
+ throw new ConversionException("unable to set property", elem, ee);
+ }
+ }
+}
Deleted: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java 2009-09-18 15:51:32 UTC (rev 139)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java 2009-09-18 18:45:53 UTC (rev 140)
@@ -1,396 +0,0 @@
-// Copyright 2008-2009 severally by the contributors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package net.sf.practicalxml.converter.bean;
-
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Array;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import net.sf.practicalxml.DomUtil;
-import net.sf.practicalxml.converter.ConversionException;
-import net.sf.practicalxml.converter.internal.ConversionStrings;
-import net.sf.practicalxml.converter.internal.DomUtilToo;
-import net.sf.practicalxml.converter.internal.PrimitiveConversionHelper;
-import net.sf.practicalxml.internal.StringUtils;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-
-/**
- * Driver class for converting an XML DOM into a Java bean. Normal usage is
- * to create a single instance of this class with desired options, then use
- * it for multiple conversions. This class is thread-safe.
- */
-public class Xml2BeanDriver
-{
- private EnumSet<Xml2BeanOptions> _options;
- private PrimitiveConversionHelper _helper;
- private Map<Class<?>,Map<String,Method>> _introspectedClasses;
-
-
- public Xml2BeanDriver(Xml2BeanOptions... options)
- {
- _options = EnumSet.noneOf(Xml2BeanOptions.class);
- for (Xml2BeanOptions option : options)
- _options.add(option);
-
- _helper = new PrimitiveConversionHelper(_options.contains(Xml2BeanOptions.EXPECT_XSD_FORMAT));
- _introspectedClasses = new HashMap<Class<?>,Map<String,Method>>();
- }
-
-
-//----------------------------------------------------------------------------
-// Public Methods
-//----------------------------------------------------------------------------
-
- /**
- * Attempts to convert the passed DOM subtree into an object of the
- * specified class.
- */
- public <T> T convert(Element elem, Class<T> klass)
- {
- return klass.cast(convertWithoutCast(elem, klass));
- }
-
-
-//----------------------------------------------------------------------------
-// Internal Conversion Methods
-//----------------------------------------------------------------------------
-
- /**
- * Attempts to convert the passed DOM subtree into an object of the
- * specified class. Note that this version does not use generics,
- * and does not try to cast the result, whereas the public version
- * does. Internally, we want to treat <code>Integer.TYPE</code> the
- * same as <code>Integer.class</code>, and the cast prevents that.
- */
- public Object convertWithoutCast(Element elem, Class<?> klass)
- {
- validateXsiType(elem, klass);
- if (isAllowableNull(elem))
- return null;
-
- Object obj = tryConvertAsPrimitive(elem, klass);
- if (obj == null)
- obj = tryConvertAsArray(elem, klass);
- if (obj == null)
- obj = tryConvertAsSimpleCollection(elem, klass);
- if (obj == null)
- obj = tryConvertAsMap(elem, klass);
- if (obj == null)
- obj = tryConvertAsBean(elem, klass);
- return obj;
- }
-
-
- private boolean isAllowableNull(Element elem)
- {
- String text = getText(elem);
- if ((text != null) || hasElementChildren(elem))
- return false;
-
- if (_options.contains(Xml2BeanOptions.REQUIRE_XSI_NIL))
- {
- if (!DomUtilToo.getXsiNil(elem))
- throw new ConversionException("missing/false xsi:nil", elem);
- }
-
- return true;
- }
-
-
- private Object tryConvertAsPrimitive(Element elem, Class<?> klass)
- {
- if (_helper.getXsdType(klass) == null)
- return null;
-
- if (hasElementChildren(elem))
- throw new ConversionException("expecting primitive; has children", elem);
-
- return _helper.parse(getText(elem), klass);
- }
-
-
- private Object tryConvertAsArray(Element elem, Class<?> klass)
- {
- Class<?> childKlass = klass.getComponentType();
- if (childKlass == null)
- return null;
-
- List<Element> children = DomUtil.getChildren(elem);
- Object result = Array.newInstance(childKlass, children.size());
- int idx = 0;
- for (Element child : children)
- {
- Array.set(result, idx++, convertWithoutCast(child, childKlass));
- }
- return result;
- }
-
-
- private Object tryConvertAsSimpleCollection(Element elem, Class<?> klass)
- {
- Collection<Object> result = instantiateCollection(klass);
- if (result == null)
- return null;
-
- List<Element> children = DomUtil.getChildren(elem);
- for (Element child : children)
- {
- Class<?> childClass = getClassFromXsiType(child);
- if (childClass == null)
- childClass = String.class;
- result.add(convertWithoutCast(child, childClass));
- }
- return result;
- }
-
-
- private Object tryConvertAsMap(Element elem, Class<?> klass)
- {
- Map<Object,Object> result = instantiateMap(klass);
- if (result == null)
- return null;
-
- List<Element> children = DomUtil.getChildren(elem);
- for (Element child : children)
- {
- String key = child.getAttribute(ConversionStrings.AT_MAP_KEY);
- if (StringUtils.isEmpty(key))
- key = DomUtil.getLocalName(child);
- Class<?> childClass = getClassFromXsiType(child);
- if (childClass == null)
- childClass = String.class;
- result.put(key, convertWithoutCast(child, childClass));
- }
- return result;
- }
-
-
- private Object tryConvertAsBean(Element elem, Class<?> klass)
- {
- Object bean = instantiateBean(elem, klass);
-
- List<Element> children = DomUtil.getChildren(elem);
- for (Element child : children)
- {
- Method setter = getSetterMethod(klass, child);
- if (setter == null)
- continue;
-
- Class<?> childClass = setter.getParameterTypes()[0];
- Object childValue = convertWithoutCast(child, childClass);
- invokeSetter(elem, bean, setter, childValue);
- }
- return bean;
- }
-
-
-//----------------------------------------------------------------------------
-// Other Internals
-//----------------------------------------------------------------------------
-
- /**
- * Returns the text content of an element, applying appropriate options.
- */
- private String getText(Element elem)
- {
- String text = DomUtil.getText(elem);
- if (StringUtils.isBlank(text)
- && _options.contains(Xml2BeanOptions.CONVERT_BLANK_AS_NULL))
- text = null;
- return text;
- }
-
-
- /**
- * Examines an element's <code>xsi:type</code> attribute, if any, and
- * returns the Java class corresponding to it. Used when converting
- * collection types, which don't have type information that can be
- * introspected, and also to validate non-XSD types.
- */
- private Class<?> getClassFromXsiType(Element elem)
- {
- String xsiType = DomUtilToo.getXsiType(elem);
- if (xsiType == null)
- return null;
-
- String javaType = DomUtilToo.getJavaClassFromXsiType(xsiType);
- if (javaType != null)
- {
- try
- {
- return Class.forName(javaType);
- }
- catch (ClassNotFoundException ee)
- {
- throw new ConversionException(
- "invalid Java type specification: " + javaType, elem, ee);
- }
- }
-
- return _helper.getJavaType(xsiType);
- }
-
-
- private void validateXsiType(Element elem, Class<?> klass)
- {
- if (!_options.contains(Xml2BeanOptions.REQUIRE_XSI_TYPE))
- return;
-
- String xsiType = DomUtilToo.getXsiType(elem);
- if (xsiType == null)
- throw new ConversionException("missing xsi:type", elem);
-
- if (xsiType.equals(_helper.getXsdType(klass)))
- return;
-
- Class<?> xsiKlass = getClassFromXsiType(elem);
- if (klass.isAssignableFrom(xsiKlass))
- return;
-
- throw new ConversionException(
- "invalid xsi:type: \"" + xsiType + "\" for " + klass.getName(),
- elem);
- }
-
-
- private boolean hasElementChildren(Element elem)
- {
- Node child = elem.getFirstChild();
- while (child != null)
- {
- if (child instanceof Element)
- return true;
- child = child.getNextSibling();
- }
- return false;
- }
-
-
- private Method getSetterMethod(Class<?> beanKlass, Element child)
- {
- Map<String,Method> methodMap = _introspectedClasses.get(beanKlass);
- if (methodMap == null)
- methodMap = introspect(beanKlass);
-
- Method setter = methodMap.get(DomUtil.getLocalName(child));
- if ((setter == null) && !_options.contains(Xml2BeanOptions.IGNORE_MISSING_PROPERTIES))
- {
- throw new ConversionException("can't find property setter", child);
- }
-
- return setter;
- }
-
-
- private Map<String,Method> introspect(Class<?> klass)
- {
- Map<String,Method> methodMap = new HashMap<String,Method>();
- try
- {
- BeanInfo info = Introspector.getBeanInfo(klass, Object.class);
- for (PropertyDescriptor propDesc : info.getPropertyDescriptors())
- {
- Method setter = propDesc.getWriteMethod();
- if (setter != null)
- methodMap.put(propDesc.getName(), setter);
- }
- }
- catch (IntrospectionException ee)
- {
- throw new ConversionException("unable to introspect", ee);
- }
-
- _introspectedClasses.put(klass, methodMap);
- return methodMap;
- }
-
-
- /**
- * Attempts to create a <code>Collection</code> instance appropriate for
- * the passed class, returns <code>null</code> if unable.
- */
- private Collection<Object> instantiateCollection(Class<?> klass)
- {
- if (SortedSet.class.isAssignableFrom(klass))
- return new TreeSet<Object>();
- else if (Set.class.isAssignableFrom(klass))
- return new HashSet<Object>();
- else if (List.class.isAssignableFrom(klass))
- return new ArrayList<Object>();
- else if (Collection.class.isAssignableFrom(klass))
- return new ArrayList<Object>();
- else
- return null;
- }
-
-
- /**
- * Attempts to create a <code>Map</code> instance appropriate for the
- * passed class, returns <code>null</code> if unable.
- */
- private Map<Object,Object> instantiateMap(Class<?> klass)
- {
- if (SortedMap.class.isAssignableFrom(klass))
- return new TreeMap<Object,Object>();
- else if (Map.class.isAssignableFrom(klass))
- return new HashMap<Object,Object>();
- else
- return null;
- }
-
-
- private Object instantiateBean(Element elem, Class<?> klass)
- {
- try
- {
- return klass.newInstance();
- }
- ...
[truncated message content] |