[Practicalxml-commits] SF.net SVN: practicalxml:[110] branches/dev-1.1/src
Brought to you by:
kdgregory
|
From: Auto-Generated S. C. M. <pra...@li...> - 2009-08-17 17:26:48
|
Revision: 110
http://practicalxml.svn.sourceforge.net/practicalxml/?rev=110&view=rev
Author: kdgregory
Date: 2009-08-17 17:26:40 +0000 (Mon, 17 Aug 2009)
Log Message:
-----------
repackage
Modified Paths:
--------------
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanConverter.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlAppenders.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlDriver.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanConverter.java
branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestXml2BeanDriver.java
Added Paths:
-----------
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/
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/Bean2XmlDriver.java
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlOptions.java
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/Xml2BeanHandler.java
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanOptions.java
Removed Paths:
-------------
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean2xml/
branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/xml2bean/
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-08-17 17:13:36 UTC (rev 109)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanConverter.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -16,8 +16,8 @@
import org.w3c.dom.Document;
-import net.sf.practicalxml.converter.bean2xml.Bean2XmlDriver;
-import net.sf.practicalxml.converter.bean2xml.Bean2XmlOptions;
+import net.sf.practicalxml.converter.bean.Bean2XmlDriver;
+import net.sf.practicalxml.converter.bean.Bean2XmlOptions;
/**
Copied: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlAppenders.java (from rev 100, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean2xml/Bean2XmlAppenders.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlAppenders.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlAppenders.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -0,0 +1,235 @@
+// 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.util.EnumSet;
+
+import javax.xml.XMLConstants;
+
+import org.w3c.dom.Element;
+
+import net.sf.practicalxml.DomUtil;
+
+
+/**
+ * Packaging class used for XML output appenders. This class is a temporary
+ * hack, as I move intelligence into {@link Bean2XmlDriver}; the contained
+ * classes will end up in a new package, once I figure out what the package
+ * structure should be.
+ */
+public abstract class Bean2XmlAppenders
+{
+ /**
+ * An <code>Appender</code> appends children to a single node of the
+ * output tree. The driver is responsible for creating new appenders
+ * for each compound element, including the root, and providing those
+ * appenders with options to control output generation.
+ */
+ public interface Appender
+ {
+ /**
+ * Appends a value element to the current element. Value elements have
+ * associated text, but no other children.
+ *
+ * @param name Name to be associated with the node.
+ * @param type Type to be associated with the node; may or may
+ * not be written to the output, depending on the
+ * appender's options.
+ * @param value The node's value. May be <code>null</code>, in
+ * which case the appender decides whether or not
+ * to actually append the node.
+ *
+ * @return The appended element. This is a convenience for subclasses,
+ * which may want to set additional attributes after their
+ * super has done the work of appending the element.
+ * @throws ConversionException if unable to append the node.
+ */
+ public Element appendValue(String name, String type, String value);
+
+
+ /**
+ * Appends a container element to the current element. Container
+ * elements have other elements as children, and may have a type,
+ * but do not have an associated value.
+ */
+ public Element appendContainer(String name, String type);
+ }
+
+
+ /**
+ * Base class for XML appenders, providing helper methods for subclasses.
+ */
+ private static abstract class AbstractAppender
+ implements Appender
+ {
+ private EnumSet<Bean2XmlOptions> _options;
+
+ public AbstractAppender(EnumSet<Bean2XmlOptions> options)
+ {
+ _options = options;
+ }
+
+ protected boolean isOptionSet(Bean2XmlOptions option)
+ {
+ return _options.contains(option);
+ }
+
+ protected boolean shouldSkip(Object value)
+ {
+ return (value == null) && !_options.contains(Bean2XmlOptions.XSI_NIL);
+ }
+
+ protected void setType(Element elem, String type)
+ {
+ if (isOptionSet(Bean2XmlOptions.XSI_TYPE))
+ elem.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type", type);
+ }
+
+ protected void setValue(Element elem, String value)
+ {
+ if (value != null)
+ DomUtil.setText(elem, value);
+ else if (isOptionSet(Bean2XmlOptions.XSI_NIL))
+ elem.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil", "true");
+ }
+ }
+
+
+ /**
+ * Basic appender, which appends new elements to a parent.
+ */
+ public static class BasicAppender
+ extends AbstractAppender
+ {
+ private Element _parent;
+
+ public BasicAppender(Element parent, EnumSet<Bean2XmlOptions> options)
+ {
+ super(options);
+ _parent = parent;
+ }
+
+ public Element appendValue(String name, String type, String value)
+ {
+ if (shouldSkip(value))
+ return null;
+
+ Element child = DomUtil.appendChildInheritNamespace(_parent, name);
+ setType(child, type);
+ setValue(child, value);
+ return child;
+ }
+
+ public Element appendContainer(String name, String type)
+ {
+ Element child = DomUtil.appendChildInheritNamespace(_parent, name);
+ setType(child, type);
+ return child;
+ }
+ }
+
+
+ /**
+ * Appender for children of an indexed/iterated item (array, list, or set).
+ * Each element will have an incremented <code>index</code> attribute that
+ * indicates the position of the element within the iteration.
+ */
+ public static class IndexedAppender
+ extends BasicAppender
+ {
+ int _index = 0;
+
+ public IndexedAppender(Element parent, EnumSet<Bean2XmlOptions> options)
+ {
+ super(parent, options);
+ }
+
+
+ @Override
+ public Element appendValue(String name, String type, String value)
+ {
+ Element child = super.appendValue(name, type, value);
+ if (child != null)
+ child.setAttribute("index", String.valueOf(_index++));
+ return child;
+ }
+ }
+
+
+ /**
+ * Appender for children of a <code>Map</code>. Depending on options,
+ * will either create children named after the key, or a generic "data"
+ * child with the key as an attribute.
+ */
+ public static class MapAppender
+ extends BasicAppender
+ {
+ public MapAppender(Element parent, EnumSet<Bean2XmlOptions> options)
+ {
+ super(parent, options);
+ }
+
+
+ @Override
+ public Element appendValue(String name, String type, String value)
+ {
+ Element child = null;
+ if (isOptionSet(Bean2XmlOptions.INTROSPECT_MAPS))
+ {
+ child = super.appendValue(name, type, value);
+ }
+ else
+ {
+ child = super.appendValue("data", type, value);
+ if (child != null)
+ child.setAttribute("key", name);
+ }
+ return child;
+ }
+ }
+
+
+ /**
+ * An appender that sets values directly on the "parent" element. Used for
+ * the conversion root element.
+ */
+ public static class DirectAppender
+ extends AbstractAppender
+ {
+ private Element _elem;
+
+ public DirectAppender(Element elem, EnumSet<Bean2XmlOptions> options)
+ {
+ super(options);
+ _elem = elem;
+ }
+
+ public Element appendValue(String name, String type, String value)
+ {
+ if (!shouldSkip(value))
+ {
+ setType(_elem, type);
+ setValue(_elem, value);
+ }
+ return _elem;
+ }
+
+ public Element appendContainer(String name, String type)
+ {
+ setType(_elem, type);
+ return _elem;
+ }
+ }
+}
Copied: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java (from rev 108, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean2xml/Bean2XmlDriver.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlDriver.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -0,0 +1,259 @@
+// 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 javax.xml.XMLConstants;
+
+import org.w3c.dom.Element;
+
+import net.sf.practicalxml.DomUtil;
+import net.sf.practicalxml.converter.ConversionException;
+import net.sf.practicalxml.converter.ConversionHelper;
+import net.sf.practicalxml.converter.bean.Bean2XmlAppenders.*;
+
+
+/**
+ * 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 ConversionHelper _helper;
+ private EnumSet<Bean2XmlOptions> _options = EnumSet.noneOf(Bean2XmlOptions.class);
+
+ public Bean2XmlDriver(Bean2XmlOptions... options)
+ {
+ for (Bean2XmlOptions option : options)
+ _options.add(option);
+ _helper = new ConversionHelper(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.XSI_TYPE);
+ }
+
+
+ private String getJavaXsiType(Object obj)
+ {
+ return "java:" + obj.getClass().getName();
+ }
+
+
+ /**
+ * 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.XSI_TYPE))
+ {
+ root.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil", "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, getJavaXsiType(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, "data", childAppender);
+ }
+ return true;
+ }
+
+
+ private boolean tryToConvertAsMap(Object obj, String name, Appender appender)
+ {
+ if (!(obj instanceof Map))
+ return false;
+
+ Element parent = appender.appendContainer(name, getJavaXsiType(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, getJavaXsiType(obj));
+ Appender childAppender = new IndexedAppender(parent, _options);
+ for (Object value : (Collection<?>)obj)
+ {
+ convert(value, "data", childAppender);
+ }
+ return true;
+ }
+
+
+ private boolean tryToConvertAsBean(Object bean, String name, Appender appender)
+ {
+ Element parent = appender.appendContainer(name, getJavaXsiType(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/Bean2XmlOptions.java (from rev 100, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean2xml/Bean2XmlOptions.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlOptions.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Bean2XmlOptions.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -0,0 +1,55 @@
+// 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;
+
+
+/**
+ * Options used by {@link Bean2XmlHandler} to control the structure of the
+ * DOM tree.
+ */
+public enum Bean2XmlOptions
+{
+ /**
+ * Outputs values using formats defined by XML Schema, rather than Java's
+ * <code>String.valueOf()</code> method. Note that these formats are not
+ * flagged in the element, so sender and receiver will have to agree on
+ * the format.
+ */
+ XSD_FORMAT,
+
+ /**
+ * Will add an <code>xsi:type</code> attribute to each element. For values
+ * covered by the XML Schema simple types, this attribute's value will be
+ * "<code>xsd:XXX</code>", where XXX is the XSD type name. For complex
+ * types, this attribute's value will be "<code>java:XXX</code>", where
+ * XXX is the fully-qualified classname.
+ * <p>
+ * <em>This option implies {@link #XSD_FORMAT} for simple types</em>.
+ */
+ XSI_TYPE,
+
+ /**
+ * Report null values using the <code>xsi:nil="true"</code> attribute. If
+ * not present, null values are ignored, and not added to DOM tree.
+ */
+ XSI_NIL,
+
+ /**
+ * Output maps in an "introspected" format, where the name of each item
+ * is the map key (rather than "data"), and the "key" attribute is omitted.
+ * If any key is not a valid XML identifier, the converter will throw.
+ */
+ INTROSPECT_MAPS
+}
Copied: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java (from rev 109, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/xml2bean/Xml2BeanDriver.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanDriver.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -0,0 +1,447 @@
+// 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 javax.xml.XMLConstants;
+
+import net.sf.practicalxml.DomUtil;
+import net.sf.practicalxml.converter.ConversionException;
+import net.sf.practicalxml.converter.ConversionHelper;
+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.
+ * <p>
+ * This class assumes that the source XML will be in the form produced by
+ * {@link Bean2XmlDriver}: each element either contains child elements, or
+ * a text node containing the element's value. However, conversion is
+ * driven by the parameters passed to {@link #convert}, not by the content
+ * of the XML document; this can lead to some unexpected behavior:
+ * <ul>
+ * <li> Bean classes are introspected, and recursively processed base on
+ * the property descriptors. If the bean has multiple setter methods
+ * for a property, the method selected is arbitrarily chosen by the
+ * JavaBeans introspector. You can pass an option that looks for a
+ * setters using <code>String</code>, but this is not recommended
+ * from a performance perspective.
+ * <li> JDK collection types do not carry type information, so the only
+ * way to properly convert them is using an <code>xsi:type</code>
+ * attribute on the child elements. If this attribute is missing
+ * or cannot be interpreted, the element will be processed as if
+ * it is a <code>String</code>.
+ * <li> The converter will to pick an appropriate implementation class
+ * if given one of the JDK collections interfaces: <code>
+ * ArrayList</code> for <code>List</code> or <code>Collection</code>,
+ * <code>TreeSet</code> for <code>SortedSet</code>, <code>HashSet</code>
+ * for any other <code>Set</code>, and likewise <code>TreeMap</code> and
+ * <code>HashMap</code> for <code>SortedMap</code> and <code>Map</code>.
+ * </ul>
+ */
+public class Xml2BeanDriver
+{
+ private EnumSet<Xml2BeanOptions> _options;
+ private ConversionHelper _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 ConversionHelper(_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))
+ {
+ String attr = elem.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil");
+ if (!attr.equals("true"))
+ throw new ConversionException(
+ "missing xsi:nil: " + DomUtil.getAbsolutePath(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: " + DomUtil.getAbsolutePath(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("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;
+ }
+
+
+ /**
+ * Returns the <code>xsi:type</code> attribute value, <code>null</code> if
+ * it's not set.
+ */
+ private String getXsiType(Element elem)
+ {
+ String xsiType = elem.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type");
+ return StringUtils.isEmpty(xsiType)
+ ? null
+ : xsiType;
+ }
+
+
+ /**
+ * 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 = getXsiType(elem);
+ if (xsiType == null)
+ return null;
+
+ if (xsiType.startsWith("java:"))
+ {
+ String javaType = xsiType.substring(5);
+ try
+ {
+ return Class.forName(javaType);
+ }
+ catch (ClassNotFoundException ee)
+ {
+ throw new ConversionException(
+ "invalid Java type specification (" + javaType + "): "
+ + DomUtil.getAbsolutePath(elem),
+ ee);
+ }
+ }
+ return _helper.getJavaType(xsiType);
+ }
+
+
+ private void validateXsiType(Element elem, Class<?> klass)
+ {
+ if (!_options.contains(Xml2BeanOptions.REQUIRE_XSI_TYPE))
+ return;
+
+ String xsiType = elem.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type");
+ if (StringUtils.isEmpty(xsiType))
+ throw new ConversionException(
+ "missing xsi:type: " + DomUtil.getAbsolutePath(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() + "): "
+ + DomUtil.getAbsolutePath(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 in " + beanKlass.getName() + ": "
+ + DomUtil.getAbsolutePath(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 e)
+ {
+ throw new ConversionException("unable to introspect", e);
+ }
+
+ _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: " + DomUtil.getAbsolutePath(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: " + DomUtil.getAbsolutePath(elem),
+ ee);
+ }
+ }
+}
Copied: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanHandler.java (from rev 105, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/xml2bean/Xml2BeanHandler.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanHandler.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanHandler.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -0,0 +1,194 @@
+// 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 net.sf.practicalxml.DomUtil;
+import net.sf.practicalxml.XmlException;
+import net.sf.practicalxml.XmlUtil;
+import net.sf.practicalxml.converter.ConversionException;
+import net.sf.practicalxml.converter.ConversionHelper;
+import net.sf.practicalxml.internal.StringUtils;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.xml.XMLConstants;
+
+import org.w3c.dom.Element;
+
+
+/**
+ * Invoked by {@link Xml2BeanDriver} to convert a DOM <code>Element</code>
+ * into the appropriate Java object. Unlike {@link Bean2XmlHandler}, there
+ * will only be one instance of this object created during conversion; all
+ * intermediate data can be held on the stack.
+ */
+public class Xml2BeanHandler
+{
+ private EnumSet<Xml2BeanOptions> _options;
+ private ConversionHelper _primitiveHelper;
+
+
+ /**
+ * Public constructor, allowing various options specifications.
+ */
+ public Xml2BeanHandler(Xml2BeanOptions... options)
+ {
+ _options = EnumSet.noneOf(Xml2BeanOptions.class);
+ for (Xml2BeanOptions option : options)
+ _options.add(option);
+
+ _primitiveHelper = new ConversionHelper(true);
+ }
+
+
+//----------------------------------------------------------------------------
+// Public Methods
+//----------------------------------------------------------------------------
+
+ public Boolean convertBoolean(Element elem)
+ {
+ return (Boolean)_primitiveHelper.parse(getText(elem), Boolean.class);
+ }
+
+
+ public Byte convertByte(Element elem)
+ {
+ return (Byte)_primitiveHelper.parse(getText(elem), Byte.class);
+ }
+
+
+ public Character convertCharacter(Element elem)
+ {
+ return (Character)_primitiveHelper.parse(getText(elem), Character.class);
+ }
+
+
+ public Date convertDate(Element elem)
+ {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+
+ public Double convertDouble(Element elem)
+ {
+ return (Double)_primitiveHelper.parse(getText(elem), Double.class);
+ }
+
+
+ public Float convertFloat(Element elem)
+ {
+ return (Float)_primitiveHelper.parse(getText(elem), Float.class);
+ }
+
+
+ public Integer convertInteger(Element elem)
+ {
+ return (Integer)_primitiveHelper.parse(getText(elem), Integer.class);
+ }
+
+
+ public Long convertLong(Element elem)
+ {
+ return (Long)_primitiveHelper.parse(getText(elem), Long.class);
+ }
+
+
+ public Number convertNumber(Element elem)
+ {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+
+ public Short convertShort(Element elem)
+ {
+ return (Short)_primitiveHelper.parse(getText(elem), Short.class);
+ }
+
+
+ public String convertString(Element elem)
+ {
+ return (String)_primitiveHelper.parse(getText(elem), String.class);
+ }
+
+
+ public List<?> convertList(Element elem)
+ {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+
+ public Set<?> convertSet(Element elem)
+ {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+
+ public Map<?,?> convertMap(Element elem)
+ {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+
+ public Object convertObject(Element elem)
+ {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+
+ public Object[] convertObjectArray(Element elem)
+ {
+ throw new UnsupportedOperationException("not implemented yet");
+ }
+
+
+//----------------------------------------------------------------------------
+// Internals
+//----------------------------------------------------------------------------
+
+ /**
+ * Returns the text from a passed element, applying any low-level options
+ * along the way.
+ */
+ private String getText(Element elem)
+ {
+ String text = DomUtil.getText(elem);
+ if (_options.contains(Xml2BeanOptions.CONVERT_BLANK_AS_NULL) && StringUtils.isBlank(text))
+ text = null;
+ validateXsiNil(elem, text);
+ return text;
+ }
+
+
+ /**
+ * Checks for elements that require <code>xsi:nil</code>, and throws if missing.
+ */
+ private void validateXsiNil(Element elem, String text)
+ {
+ if (text != null)
+ return;
+ if (!_options.contains(Xml2BeanOptions.REQUIRE_XSI_NIL))
+ return;
+
+ String attr = elem.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil");
+ if ((attr == null) || !attr.equals("true"))
+ throw new ConversionException("empty element without required xsi:nil");
+ }
+
+}
Copied: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanOptions.java (from rev 109, branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/xml2bean/Xml2BeanOptions.java)
===================================================================
--- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanOptions.java (rev 0)
+++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/bean/Xml2BeanOptions.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -0,0 +1,69 @@
+// 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;
+
+
+
+/**
+ * Options used by {@link Xml2BeanHandler} to control the way that DOM trees
+ * are translated to Java beans.
+ */
+public enum Xml2BeanOptions
+{
+ /**
+ * If present, the converter will treat all elements with empty text nodes
+ * as if they were empty elements -- in other words, <code>null</code>.
+ * Note that this flag will interact with <code>REQUIRE_XSI_NIL</code>.
+ */
+ CONVERT_BLANK_AS_NULL,
+
+
+ /**
+ * Expect data (in particular, dates) to be formatted per XML Schema spec.
+ */
+ EXPECT_XSD_FORMAT,
+
+
+ /**
+ * If present, the converter ignores elements that don't correspond to
+ * settable properties of the bean.
+ */
+ IGNORE_MISSING_PROPERTIES,
+
+
+ /**
+ * If present, the converter will look for a setter method taking a
+ * <code>String</code>, in preference to a non-string method returned
+ * from the bean introspector.
+ */
+ PREFER_STRING_SETTER,
+
+
+ /**
+ * If present, the converter requires an <code>xsi:nil</code> attribute
+ * on any empty nodes, and will throw if it's not present. Default is to
+ * treat empty nodes as <code>null</code>.
+ */
+ REQUIRE_XSI_NIL,
+
+
+ /**
+ * If present, the converter requires an <code>xsi:type</code> attribute
+ * on each element, and will throw if it's not present. Default behavior
+ * uses the <code>xsi:type</code> value to choose between different setter
+ * methods, but otherwise ignores it.
+ */
+ REQUIRE_XSI_TYPE
+}
Modified: branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlAppenders.java
===================================================================
--- branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlAppenders.java 2009-08-17 17:13:36 UTC (rev 109)
+++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlAppenders.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -20,9 +20,9 @@
import org.w3c.dom.Element;
import net.sf.practicalxml.DomUtil;
-import net.sf.practicalxml.converter.bean2xml.Bean2XmlOptions;
+import net.sf.practicalxml.converter.bean.Bean2XmlOptions;
-import static net.sf.practicalxml.converter.bean2xml.Bean2XmlAppenders.*;
+import static net.sf.practicalxml.converter.bean.Bean2XmlAppenders.*;
public class TestBean2XmlAppenders
Modified: branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlDriver.java
===================================================================
--- branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlDriver.java 2009-08-17 17:13:36 UTC (rev 109)
+++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBean2XmlDriver.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -27,8 +27,8 @@
import net.sf.practicalxml.DomUtil;
import net.sf.practicalxml.OutputUtil;
-import net.sf.practicalxml.converter.bean2xml.Bean2XmlDriver;
-import net.sf.practicalxml.converter.bean2xml.Bean2XmlOptions;
+import net.sf.practicalxml.converter.bean.Bean2XmlDriver;
+import net.sf.practicalxml.converter.bean.Bean2XmlOptions;
import net.sf.practicalxml.junit.DomAsserts;
Modified: branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanConverter.java
===================================================================
--- branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanConverter.java 2009-08-17 17:13:36 UTC (rev 109)
+++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanConverter.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -16,7 +16,7 @@
import net.sf.practicalxml.DomUtil;
import net.sf.practicalxml.OutputUtil;
-import net.sf.practicalxml.converter.bean2xml.Bean2XmlOptions;
+import net.sf.practicalxml.converter.bean.Bean2XmlOptions;
import javax.xml.XMLConstants;
import org.w3c.dom.Document;
Modified: branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestXml2BeanDriver.java
===================================================================
--- branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestXml2BeanDriver.java 2009-08-17 17:13:36 UTC (rev 109)
+++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestXml2BeanDriver.java 2009-08-17 17:26:40 UTC (rev 110)
@@ -27,8 +27,8 @@
import javax.xml.XMLConstants;
import org.w3c.dom.Element;
-import net.sf.practicalxml.converter.xml2bean.Xml2BeanDriver;
-import net.sf.practicalxml.converter.xml2bean.Xml2BeanOptions;
+import net.sf.practicalxml.converter.bean.Xml2BeanDriver;
+import net.sf.practicalxml.converter.bean.Xml2BeanOptions;
import static net.sf.practicalxml.builder.XmlBuilder.*;
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|