[Practicalxml-commits] SF.net SVN: practicalxml:[87] branches/dev-1.1/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2009-07-15 19:23:58
|
Revision: 87 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=87&view=rev Author: kdgregory Date: 2009-07-15 19:23:56 +0000 (Wed, 15 Jul 2009) Log Message: ----------- Initial Commit - BeanConverter (output only) Added 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/BeanOutputDriver.java branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputHandler.java branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputOptions.java branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanConverter.java branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanOutputHandler.java Added: 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 (rev 0) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanConverter.java 2009-07-15 19:23:56 UTC (rev 87) @@ -0,0 +1,112 @@ +// 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; + +import net.sf.practicalxml.DomUtil; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * Converts Java objects (not just beans) to and from an XML representation. + * This was originally developed to support RESTful web services, without the + * class-generation hassle of JAXB. + * <p> + * The XML generated/consumed by this class obeys the following rules, with + * options controlled by {@link BeanOutputOptions}: + * <ul> + * <li> All elements inherit their parent's namespace and namespace prefix. + * <li> Elements of sets are output using the element name "data". + * <li> Elements of lists and arrays are output using the element name "data", + * with an attribute "index" that contains the numeric index. + * <li> Elements of maps are by default output using the name "data", with an + * attribute "name" containing the actual map key. The output option + * <code>INTROSPECT_MAPS</code> will change this behavior, but will throw + * if the map keys are not valid XML element names. + * <li> Values are output using <code>toString()</code>, except as overridden + * by options. + * <li> Null values do not generate output, unless the <code>XSI_NIL</code> + * option is in effect. + * </ul> + */ +public class BeanConverter +{ + /** + * Creates a new DOM document from the passed bean, in which all elements + * are members of the specified namespace. + * + * @param bean The source object. This can be any Java object: + * bean, collection, or simple type. + * @param nsUri The namespace of the root element. This will be + * inherited by all child elements. + * @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 Options controlling output. +. + */ + public static Document generateXml(Object bean, String nsUri, String rootName, + BeanOutputOptions... options) + { + Element root = DomUtil.newDocument(nsUri, "temp"); + BeanOutputDriver.dispatch(rootName, bean, new BeanOutputHandler(root, options)); + return replaceRoot(root); + } + + + /** + * Creates a new DOM document from the passed bean, without namespace. + * + * @param bean The source object. This can be any Java object: + * bean, collection, or simple type. + * @param rootName The name given to the root element of the produced + * document. + * @param options Options controlling output. +. + */ + public static Document generateXml(Object bean, String rootName, + BeanOutputOptions... options) + { + Element root = DomUtil.newDocument("temp"); + BeanOutputDriver.dispatch(rootName, bean, new BeanOutputHandler(root, options)); + return replaceRoot(root); + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Replaces the passed element as the root of the document by its first + * element child. This is a remarkably ugly hack, but it lets me avoid + * writing a worse hack into the output handler to determine whether or + * not it is handling the root element. + * <p> + * This works for the Sun (Xerces) DOM implementation. It might not work + * for other implementations (indeed, the <code>Node</code> JavaDoc + * indicates that it might not be allowed). + */ + private static Document replaceRoot(Element oldRoot) + { + Element newRoot = DomUtil.getChildren(oldRoot).get(0); + oldRoot.removeChild(newRoot); + + Document dom = oldRoot.getOwnerDocument(); + dom.replaceChild(newRoot, oldRoot); + return dom; + } +} Added: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputDriver.java =================================================================== --- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputDriver.java (rev 0) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputDriver.java 2009-07-15 19:23:56 UTC (rev 87) @@ -0,0 +1,207 @@ +// 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; + +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.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * Driver class for bean conversion. This class uses an {@link OutputHandler} + * to produce actual output, and provides two methods that interact with that + * handler: + * + * <ul> + * <li> {@link #dispatch} examines the type of the passed object, and calls + * the appropriate output handler method. Objects may be dispatched + * with or without a name associated. + * <li> {@link #introspect} examines compound objects that are either beans + * or Java collection objects, and calls <code>dispatch()</code> for + * each member. This method is meant to be called by the handler. + * </ul> + */ +public class BeanOutputDriver +{ + /** + * Dispatches a named object to the output handler. + */ + public static void dispatch(String name, Object obj, BeanOutputHandler handler) + { + if (obj == null) + handler.convertNull(name); + else if (obj instanceof String) + handler.convert(name, (String)obj); + else if (obj instanceof Integer) + handler.convert(name, (Integer)obj); + else if (obj instanceof Long) + handler.convert(name, (Long)obj); + else if (obj instanceof Short) + handler.convert(name, (Short)obj); + else if (obj instanceof Byte) + handler.convert(name, (Byte)obj); + else if (obj instanceof Double) + handler.convert(name, (Double)obj); + else if (obj instanceof Float) + handler.convert(name, (Float)obj); + else if (obj instanceof Boolean) + handler.convert(name, (Boolean)obj); + else if (obj instanceof Character) + handler.convert(name, (Character)obj); + else if (obj instanceof Date) + handler.convert(name, (Date)obj); + else if (obj instanceof Number) + handler.convert(name, (Number)obj); + else if (obj instanceof List) + handler.convert(name, (List<?>)obj); + else if (obj instanceof Set) + handler.convert(name, (Set<?>)obj); + else if (obj instanceof Map) + handler.convert(name, (Map<?,?>)obj); + else if (obj instanceof Object[]) + handler.convert(name, (Object[])obj); + else + handler.convert(name, obj); + } + + + /** + * Dispatches an unnamed object to the output handler (actually + * dispatches with a null name). + */ + public static void dispatch(Object obj, BeanOutputHandler handler) + { + dispatch(null, obj, handler); + } + + + /** + * Introspects the passed compound object, and dispatches each of its + * components. If the object is not a compound object, will dispatch + * the object itself. If the object is <code>null</code>, does nothing. + */ + public static void introspect(Object obj, BeanOutputHandler handler) + { + if (obj == null) + return; + + if (isSimpleObject(obj)) + dispatch(obj, handler); + else if (obj.getClass().isArray()) + introspectPrimitiveArray(obj, handler); + else if (obj instanceof Object[]) + introspectObjectArray((Object[])obj, handler); + else if (obj instanceof Collection<?>) + introspectCollection((Collection<?>)obj, handler); + else if (obj instanceof Map<?,?>) + introspectMap((Map<?,?>)obj, handler); + else + introspectBean(obj, handler); + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + private static boolean isSimpleObject(Object obj) + { + return (obj instanceof String) + || (obj instanceof Integer) + || (obj instanceof Long) + || (obj instanceof Byte) + || (obj instanceof Short) + || (obj instanceof Double) + || (obj instanceof Float) + || (obj instanceof Character) + || (obj instanceof Boolean) + || (obj instanceof Number) + || (obj instanceof Date); + } + + + private static void introspectPrimitiveArray(Object array, BeanOutputHandler handler) + { + int length = Array.getLength(array); + for (int ii = 0 ; ii < length ; ii++) + dispatch(Array.get(array, ii), handler); + } + + + private static void introspectObjectArray(Object[] array, BeanOutputHandler handler) + { + for (int ii = 0 ; ii < array.length ; ii++) + dispatch(array[ii], handler); + } + + + private static void introspectCollection(Collection<?> coll, BeanOutputHandler handler) + { + for (Object member : coll) + dispatch(member, handler); + } + + + private static void introspectMap(Map<?,?> map, BeanOutputHandler handler) + { + for (Map.Entry<?,?> entry : map.entrySet()) + dispatch(String.valueOf(entry.getKey()), entry.getValue(), handler); + } + + + private static void introspectBean(Object bean, BeanOutputHandler handler) + { + try + { + BeanInfo info = Introspector.getBeanInfo(bean.getClass(), Object.class); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (int ii = 0 ; ii < props.length ; ii++) + beanIntrospectionHelper(bean, props[ii], handler); + } + catch (IntrospectionException ee) + { + throw new RuntimeException("introspection failure", ee); + } + } + + + private static void beanIntrospectionHelper( + Object bean, PropertyDescriptor propDesc, BeanOutputHandler handler) + { + Method getter = propDesc.getReadMethod(); + if (getter == null) + return; + + Object value = null; + try + { + value = getter.invoke(bean); + } + catch (Exception ee) + { + throw new RuntimeException("unable to retrieve bean value", ee); + } + + dispatch(propDesc.getName(), value, handler); + } +} Added: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputHandler.java =================================================================== --- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputHandler.java (rev 0) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputHandler.java 2009-07-15 19:23:56 UTC (rev 87) @@ -0,0 +1,390 @@ +// 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; + +import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.XmlUtil; + +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.Document; +import org.w3c.dom.Element; + + +/** + * {@link OutputHandler} that builds a DOM tree. The structure of this + * tree is customized by passing values from {@link BeanOutputOptions}. + * <p> + * Instances of this class are constructed around an <code>Element</code>, + * which represents the object to be introspected by {@link BeanOutputDriver}; + * this simplifies recursive calls. + * <p> + * This class is not threadsafe (but then, neither are the JDK's DOM classes). + */ +public class BeanOutputHandler +{ + private EnumSet<BeanOutputOptions> _options; + private Appender _appender; + + + /** + * Public constructor, which uses default appender and allows individual + * options specifiers. + */ + public BeanOutputHandler(Element appendTo, BeanOutputOptions... options) + { + _appender = new Appender(appendTo); + _options = EnumSet.noneOf(BeanOutputOptions.class); + for (BeanOutputOptions option : options) + _options.add(option); + } + + + /** + * Internal constructor, used by container classes for recursive calls. + */ + protected BeanOutputHandler(BeanOutputHandler parent, Appender appender) + { + _appender = appender; + _options = parent._options; + } + + +//---------------------------------------------------------------------------- +// Public methods +//---------------------------------------------------------------------------- + + /** + * Convenience method to create a new document and invoke conversion. + * This document will not namespace its elements. + * + * @param bean The object to be converted. + * @param rootName The name of the root element of the new document. + * @param options Options to control the output structure. Options + * are additive, and are either on (passed here) or + * off. + */ + public static Document newDocument(Object bean, String rootName, BeanOutputOptions... options) + { + return newDocument(bean, null, rootName, options); + } + + + /** + * Convenience method to create a new document and invoke conversion, + * with all elements belonging to a specified namespace. + * + * @param bean The object to be converted. + * @param nsUri Namespace for this document; all elements will + * have the same namespace. If <code>null</code>, + * the elements will not be namespaced. + * @param rootName The name of the root element of the new document. + * @param options Options to control the output structure. Options + * are additive, and are either on (passed here) or + * off. + */ + public static Document newDocument(Object bean, String nsUri, String rootName, + BeanOutputOptions... options) + { + Element root = DomUtil.newDocument(nsUri, rootName); + return root.getOwnerDocument(); + } + + +//---------------------------------------------------------------------------- +// OutputHandler implementation +//---------------------------------------------------------------------------- + + public void convert(String name, Boolean value) + { + Object formatted = value; + if (shouldFormatAsXsd()) + formatted = value.booleanValue() ? "true" : "false"; + + _appender.append(name, "xsd:boolean", formatted); + } + + + public void convert(String name, Byte value) + { + _appender.append(name, "xsd:byte", value); + } + + + public void convert(String name, Character value) + { + _appender.append(name, "xsd:string", value); + } + + + public void convert(String name, Date value) + { + Object formatted = value; + if (shouldFormatAsXsd()) + formatted = XmlUtil.formatXsdDatetime(value); + + _appender.append(name, "xsd:dateTime", formatted); + } + + + public void convert(String name, Double value) + { + Object formatted = value; + if (shouldFormatAsXsd()) + formatted = XmlUtil.formatXsdDecimal(value); + + _appender.append(name, "xsd:decimal", formatted); + } + + + public void convert(String name, Float value) + { + Object formatted = value; + if (shouldFormatAsXsd()) + formatted = XmlUtil.formatXsdDecimal(value); + + _appender.append(name, "xsd:decimal", formatted); + } + + + public void convert(String name, Integer value) + { + _appender.append(name, "xsd:int", value); + } + + + public void convert(String name, Long value) + { + _appender.append(name, "xsd:long", value); + } + + + public void convert(String name, Number value) + { + _appender.append(name, "xsd:decimal", value); + } + + + public void convert(String name, Short value) + { + _appender.append(name, "xsd:short", value); + } + + + public void convert(String name, String value) + { + _appender.append(name, "xsd:string", value); + } + + + public void convert(String name, List<?> value) + { + Element container = _appender.append(name, javaXsiType(value), null); + appendSequence(container, value.iterator(), true); + } + + + public void convert(String name, Set<?> value) + { + Element container = _appender.append(name, javaXsiType(value), null); + appendSequence(container, value.iterator(), false); + } + + + public void convert(String name, Map<?,?> map) + { + Element container = _appender.append(name, javaXsiType(map), null); + BeanOutputHandler childHandler = new BeanOutputHandler(this, new MapAppender(container)); + BeanOutputDriver.introspect(map, childHandler); + } + + + public void convert(String name, Object value) + { + Element container = _appender.append(name, javaXsiType(value), null); + Appender childAppender = value.getClass().isArray() + ? new IndexedAppender(container) + : new Appender(container); + BeanOutputHandler childHandler = new BeanOutputHandler(this, childAppender); + BeanOutputDriver.introspect(value, childHandler); + } + + + public void convert(String name, Object[] array) + { + Element container = _appender.append(name, javaXsiType(array), null); + appendSequence(container, Arrays.asList(array).iterator(), true); + } + + + public void convertNull(String name) + { + if (!_options.contains(BeanOutputOptions.XSI_NIL)) + return; + + Element child = _appender.append(name, "", null); + child.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:nil", "true"); + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + protected boolean shouldFormatAsXsd() + { + return _options.contains(BeanOutputOptions.XSD_FORMAT) + || _options.contains(BeanOutputOptions.XSI_TYPE); + } + + + /** + * Returns the <code>xsi:type</code> value for a Java object (should not + * be called for primitive wrappers, as these are covered by "xsd" types). + */ + private String javaXsiType(Object obj) + { + return "java:" + obj.getClass().getName(); + } + + + /** + * Common code for sequenced objects -- arrays, lists, and sets. + */ + private void appendSequence(Element parent, Iterator<?> itx, boolean isIndexed) + { + Appender childAppender = isIndexed ? new IndexedAppender(parent) + : new Appender(parent); + BeanOutputHandler childHandler = new BeanOutputHandler(this, childAppender); + while (itx.hasNext()) + BeanOutputDriver.dispatch("data", itx.next(), childHandler); + } + + + /** + * This class is responsible for adding nodes to the DOM tree. Each + * instance of <code>XmlOutputHandler</code> is constructed around one + * of these (or a subclass). Subclasses may override the {@link #append} + * method to add additional information to the appended nodes. + * <p> + * A little bit of weirdness: as an inner class, this is constructed in + * the context of an <code>XmlOutputHandler</code>, and has access to + * members defined by that handler. However, in a recursive call, it is + * actually held and used by a different handler. Since the second handler + * copies members of the first, I don't see this as a problem ... until + * some member doesn't get copied. + */ + private class Appender + { + private Element _appendTo; + + public Appender(Element appendTo) + { + _appendTo = appendTo; + } + + /** + * Appends a child to the element we manage. + * + * @param name Local-name for the new element. As long as you're + * dealing with Java, property names and element names + * are interchangeable. If handling a <code>Map</code>, + * you'll need to ensure that keys are appropriate XML + * element names. + * @param xsiType The value to insert in an <code>xsi:type</code> + * attribute. This is required, even if the output-type + * option isn't turned on. + * @param value If not-null, will be stringified and attached to the + * element as a text node. Apply any fancy formatting + * before coming here. If null, will be ignored. + */ + public Element append(String name, String xsiType, Object value) + { + if ((name == null) || (name.length() == 0)) + name = "data"; + + Element child = DomUtil.appendChildInheritNamespace(_appendTo, name); + + if (_options.contains(BeanOutputOptions.XSI_TYPE)) + child.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:type", xsiType); + + if (value != null) + child.setTextContent(String.valueOf(value)); + + return child; + } + } + + + /** + * An appender for array/list processing, that attaches an index attribute + * to the appended node. + */ + private class IndexedAppender + extends Appender + { + int _index = 0; + + public IndexedAppender(Element appendTo) + { + super(appendTo); + } + + @Override + public Element append(String name, String xsiType, Object value) + { + Element child = super.append(name, xsiType, value); + child.setAttribute("index", String.valueOf(_index++)); + return child; + } + } + + + /** + * An appender for maps, which either uses the passed name as the + * element name, or as the value of a "key" attribute. + */ + private class MapAppender + extends Appender + { + public MapAppender(Element appendTo) + { + super(appendTo); + } + + @Override + public Element append(String name, String xsiType, Object value) + { + Element child = null; + if (_options.contains(BeanOutputOptions.INTROSPECT_MAPS)) + { + child = super.append(name, xsiType, value); + } + else + { + child = super.append("data", xsiType, value); + child.setAttribute("key", name); + } + return child; + } + } +} Added: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputOptions.java =================================================================== --- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputOptions.java (rev 0) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/BeanOutputOptions.java 2009-07-15 19:23:56 UTC (rev 87) @@ -0,0 +1,41 @@ +// Copyright (c) 2009 Keith D Gregory +package net.sf.practicalxml.converter; + +/** + * Options used by {@link BeanOutputHandler} to control the structure of the + * DOM tree. + */ +public enum BeanOutputOptions +{ + /** + * 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 +} Added: 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 (rev 0) +++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanConverter.java 2009-07-15 19:23:56 UTC (rev 87) @@ -0,0 +1,74 @@ +// 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; + +import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.OutputUtil; + +import javax.xml.XMLConstants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import junit.framework.TestCase; + +/** + * Tests for the top-level <code>BeanConverter</code> methods. These tend to + * be minimal; the detailed testing happens in {@link TestBeanOutputHandler} + * and {@link TestBeanInputHandler}. + */ +public class TestBeanConverter +extends TestCase +{ +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + public void testGenerateSimpleContentSansNamespace() throws Exception + { + String rootName = "argle"; + String value = "foo"; + + Document dom = BeanConverter.generateXml(value, rootName, BeanOutputOptions.XSI_TYPE); +// System.out.println(OutputUtil.compactString(dom)); + + Element root = dom.getDocumentElement(); + assertEquals(rootName, root.getNodeName()); + assertEquals(value, root.getTextContent()); + assertEquals("xsd:string", root.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type")); + assertEquals(0, DomUtil.getChildren(root).size()); + } + + + public void testGenerateSimpleContentWithNamespace() throws Exception + { + String nsUri = "urn:fibble"; + String rootName = "argle:bargle"; + String value = "foo"; + + Document dom = BeanConverter.generateXml(value, nsUri, rootName, BeanOutputOptions.XSI_TYPE); +// System.out.println(OutputUtil.compactString(dom)); + + Element root = dom.getDocumentElement(); + assertEquals(nsUri, root.getNamespaceURI()); + assertEquals(rootName, root.getNodeName()); + assertEquals(value, root.getTextContent()); + assertEquals("xsd:string", root.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type")); + assertEquals(0, DomUtil.getChildren(root).size()); + } +} \ No newline at end of file Added: branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanOutputHandler.java =================================================================== --- branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanOutputHandler.java (rev 0) +++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/TestBeanOutputHandler.java 2009-07-15 19:23:56 UTC (rev 87) @@ -0,0 +1,574 @@ +// 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; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.xml.XMLConstants; + +import org.w3c.dom.Element; + +import junit.framework.TestCase; + +import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.OutputUtil; +import net.sf.practicalxml.junit.DomAsserts; +import net.sf.practicalxml.xpath.XPathWrapper; + + +public class TestBeanOutputHandler +extends TestCase +{ +//---------------------------------------------------------------------------- +// Test Data / Classes +//---------------------------------------------------------------------------- + + /** + * An array of simple value types, along with their XSD type name and + * XSD-formatted value. + */ + private final static Object[][] SIMPLE_VALUES = new Object[][] + { + new Object[] { Byte.valueOf((byte)123), "xsd:byte", "123" }, + new Object[] { Short.valueOf((short)4567), "xsd:short", "4567" }, + new Object[] { Integer.valueOf(12345678), "xsd:int", "12345678" }, + new Object[] { Long.valueOf(12345678901234L), "xsd:long", "12345678901234" }, + new Object[] { Float.valueOf((float)1234), "xsd:decimal", "1234.0" }, + new Object[] { Double.valueOf(1234567890.5), "xsd:decimal", "1234567890.5" }, + new Object[] { Boolean.TRUE, "xsd:boolean", "true" }, + new Object[] { Boolean.FALSE, "xsd:boolean", "false" }, + new Object[] { Character.valueOf('A'), "xsd:string", "A" }, + new Object[] { "this is a test", "xsd:string", "this is a test" }, + new Object[] { new Date(1247551703704L), "xsd:dateTime", "2009-07-14T06:08:23" }, + new Object[] { new BigInteger("123456789012345"), "xsd:decimal", "123456789012345" }, + new Object[] { new BigDecimal("123456789012345.123456789012345"), "xsd:decimal", "123456789012345.123456789012345" } + }; + + + // has to be public so that we can introspect it + public static class SimpleBean + { + private String _sval; + private int _ival; + private BigDecimal _dval; + private boolean _bval; + + public SimpleBean() + { + // nothign to see here + } + + public SimpleBean(String sval, int ival, BigDecimal dval, boolean bval) + { + _sval = sval; + _ival = ival; + _dval = dval; + _bval = bval; + } + + public String getSval() { return _sval; } + public void setSval(String sval) { _sval = sval; } + + public int getIval() { return _ival; } + public void setIval(int ival) { _ival = ival; } + + public BigDecimal getDval() { return _dval; } + public void setDval(BigDecimal dval) { _dval = dval; } + + public boolean isBval() { return _bval; } + public void setBval(boolean bval) { _bval = bval; } + } + + + public static class CompoundBean + { + private SimpleBean _simple; + private int[] _primArray; + private List<String> _stringList; + + public CompoundBean() + { + // nothing here + } + + public CompoundBean(SimpleBean simple, int[] primArray, List<String> stringList) + { + super(); + _simple = simple; + _primArray = primArray; + _stringList = stringList; + } + + public SimpleBean getSimple() { return _simple; } + public void setSimple(SimpleBean simple) { _simple = simple; } + + public int[] getPrimArray() { return _primArray; } + public void setPrimArray(int[] primArray) { _primArray = primArray; } + + public List<String> getStringList() { return _stringList; } + public void setStringList(List<String> list) { _stringList = list; } + } + + +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + + /** + * Asserts that an element has the expected number of children (verifies + * that we didn't append to the wrong element). + */ + private void assertChildCount(String message, Element parent, int expected) + { + List<Element> children = DomUtil.getChildren(parent); + assertEquals(message + " child count:", expected, children.size()); + } + + + /** + * Asserts the name, type, and value of an element. If any of the expected + * values are null, the associated test isn't performed. + */ + private void assertNameTypeValue( + String message, Element elem, + String expectedName, String expectedType, String expectedValue) + { + if (expectedName != null) + assertEquals(message + " name:", expectedName, + elem.getNodeName()); + if (expectedType != null) + assertEquals(message + " type:", expectedType, + elem.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type")); + if (expectedValue != null) + assertEquals(message + " value:", expectedValue, + DomUtil.getText(elem)); + } + + + /** + * Asserts the name and type of a compound element, where the type is a Java classname. + */ + private void assertContainerNameAndType(Element elem, String expectedName, Class<?> expectedType) + { + assertNameTypeValue("container", elem, expectedName, "java:" + expectedType.getName(), null); + } + + + /** + * A variety of assertions about added children. Assumes that we'll add + * a single child node per parent, and that we aren't using namespaces. + */ + private void assertAppend(String message, Element parent, + String expectedName, + String expectedType, + String expectedValue) + { + assertChildCount(message, parent, 1); + assertNameTypeValue(message, DomUtil.getChildren(parent).get(0), + expectedName, expectedType, expectedValue); + } + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + // this test is essentially repeated in the DispatchSimpleValues tests + // it exists here as an initial verification of appended element structure + public void testDispatch() throws Exception + { + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", "bar", new BeanOutputHandler(root)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + assertAppend("", root, "foo", "", "bar"); + } + + + public void testDispachWithNullName() throws Exception + { + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch(null, "bar", new BeanOutputHandler(root)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + assertAppend("", root, "data", "", "bar"); + } + + + public void testNamespaceInheritance() throws Exception + { + Element root = DomUtil.newDocument("urn:zippy", "argle:bargle"); + BeanOutputDriver.dispatch("foo", "bar", new BeanOutputHandler(root)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + List<Element> children = DomUtil.getChildren(root); + assertEquals("child count", 1, children.size()); + + Element child = children.get(0); + assertEquals("urn:zippy", child.getNamespaceURI()); + assertEquals("argle", child.getPrefix()); + assertEquals("foo", child.getLocalName()); + } + + + public void testDispatchNullValue() throws Exception + { + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", null, new BeanOutputHandler(root)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + List<Element> children1 = DomUtil.getChildren(root); + assertEquals("null element added", 0, children1.size()); + + BeanOutputDriver.dispatch("foo", null, new BeanOutputHandler(root, BeanOutputOptions.XSI_NIL)); + List<Element> children2 = DomUtil.getChildren(root); + assertEquals("null element added", 1, children2.size()); + assertEquals("nil flag", "true", + children2.get(0).getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil")); + } + + + + public void testDispatchSimpleValuesWithDefaults() throws Exception + { + Element root = DomUtil.newDocument("test"); + for (int ii = 0 ; ii < SIMPLE_VALUES.length ; ii++) + { + Object value = SIMPLE_VALUES[ii][0]; + Element parent = DomUtil.appendChild(root, "value"); + BeanOutputDriver.dispatch("foo", value, new BeanOutputHandler(parent)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + assertAppend("value " + ii, parent, "foo", "", String.valueOf(value)); + } + } + + + public void testDispatchSimpleValuesWithXSDFormatting() throws Exception + { + Element root = DomUtil.newDocument("test"); + for (int ii = 0 ; ii < SIMPLE_VALUES.length ; ii++) + { + Element parent = DomUtil.appendChild(root, "value"); + BeanOutputDriver.dispatch("foo", SIMPLE_VALUES[ii][0], + new BeanOutputHandler(parent, BeanOutputOptions.XSD_FORMAT)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + assertAppend("value " + ii, parent, "foo", "", (String)SIMPLE_VALUES[ii][2]); + } + } + + + public void testDispatchSimpleValuesWithXSIType() throws Exception + { + Element root = DomUtil.newDocument("test"); + for (int ii = 0 ; ii < SIMPLE_VALUES.length ; ii++) + { + Element parent = DomUtil.appendChild(root, "value"); + BeanOutputDriver.dispatch("foo", SIMPLE_VALUES[ii][0], + new BeanOutputHandler(parent, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + assertAppend("value " + ii, parent, "foo", (String)SIMPLE_VALUES[ii][1], (String)SIMPLE_VALUES[ii][2]); + } + } + + + public void testDispatchObjectArray() throws Exception + { + Object[] data = new String[] { "argle", "bargle" }; + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", data, new BeanOutputHandler(root)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + assertAppend("container", root, "foo", "", null); + + Element container = DomUtil.getChild(root, "foo"); + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 2, children.size()); + assertNameTypeValue("child 0", children.get(0), "data", "", "argle"); + assertNameTypeValue("child 1", children.get(1), "data", "", "bargle"); + + DomAsserts.assertEquals("child 1 index", "0", root, "/test/foo/data[1]/@index"); + DomAsserts.assertEquals("child 2 index", "1", root, "/test/foo/data[2]/@index"); + } + + + public void testDispatchObjectArrayWithType() throws Exception + { + Object[] data = new String[] { "argle", "bargle" }; + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", data, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", data.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 2, children.size()); + assertNameTypeValue("child 0", children.get(0), "data", "xsd:string", "argle"); + assertNameTypeValue("child 1", children.get(1), "data", "xsd:string", "bargle"); + + DomAsserts.assertEquals("child 1 index", "0", root, "/test/foo/data[1]/@index"); + DomAsserts.assertEquals("child 2 index", "1", root, "/test/foo/data[2]/@index"); + } + + + public void testDispatchPrimitiveArrayWithType() throws Exception + { + int[] data = new int[] { 1, 2, 3 }; + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", data, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", data.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 3, children.size()); + assertNameTypeValue("child 1", children.get(0), "data", "xsd:int", "1"); + assertNameTypeValue("child 2", children.get(1), "data", "xsd:int", "2"); + assertNameTypeValue("child 3", children.get(2), "data", "xsd:int", "3"); + + DomAsserts.assertEquals("child 1 index", "0", root, "/test/foo/data[1]/@index"); + DomAsserts.assertEquals("child 2 index", "1", root, "/test/foo/data[2]/@index"); + DomAsserts.assertEquals("child 3 index", "2", root, "/test/foo/data[3]/@index"); + } + + + public void testDispatchListWithType() throws Exception + { + List<String> data = new ArrayList<String>(); + data.add("argle"); + data.add("bargle"); + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", data, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", data.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 2, children.size()); + assertNameTypeValue("child 0", children.get(0), "data", "xsd:string", "argle"); + assertNameTypeValue("child 1", children.get(1), "data", "xsd:string", "bargle"); + + DomAsserts.assertEquals("child 1 index", "0", root, "/test/foo/data[1]/@index"); + DomAsserts.assertEquals("child 2 index", "1", root, "/test/foo/data[2]/@index"); + } + + + public void testDispatchSetWithType() throws Exception + { + // use TreeSet so that order is guaranteed + Set<String> data = new TreeSet<String>(); + data.add("bargle"); + data.add("argle"); + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", data, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", data.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 2, children.size()); + assertNameTypeValue("child 0", children.get(0), "data", "xsd:string", "argle"); + assertNameTypeValue("child 1", children.get(1), "data", "xsd:string", "bargle"); + + DomAsserts.assertEquals("child 1 index", "", root, "/test/foo/data[1]/@index"); + DomAsserts.assertEquals("child 2 index", "", root, "/test/foo/data[2]/@index"); + } + + + public void testDispatchMapWithDefaultFormat() throws Exception + { + // use TreeMap so that order is guaranteed + Map<String,Object> data = new TreeMap<String,Object>(); + data.put("foo", Integer.valueOf(123)); + data.put("argle", "bargle"); + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", data, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", data.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 2, children.size()); + assertNameTypeValue("child 0", children.get(0), "data", "xsd:string", "bargle"); + assertNameTypeValue("child 1", children.get(1), "data", "xsd:int", "123"); + + DomAsserts.assertEquals("child 1 key", "argle", root, "/test/foo/data[1]/@key"); + DomAsserts.assertEquals("child 2 key", "foo", root, "/test/foo/data[2]/@key"); + } + + + public void testDispatchMapWithIntrospectFormat() throws Exception + { + // use TreeMap so that order is guaranteed + Map<String,Object> data = new TreeMap<String,Object>(); + data.put("foo", Integer.valueOf(123)); + data.put("argle", "bargle"); + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", data, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE, + BeanOutputOptions.INTROSPECT_MAPS)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", data.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 2, children.size()); + assertNameTypeValue("child 0", children.get(0), "argle", "xsd:string", "bargle"); + assertNameTypeValue("child 1", children.get(1), "foo", "xsd:int", "123"); + + DomAsserts.assertEquals("child 1 key", "", root, "/test/foo/argle/@key"); + DomAsserts.assertEquals("child 2 key", "", root, "/test/foo/foo/@key"); + } + + + public void testDispatchSimpleBean() throws Exception + { + SimpleBean bean = new SimpleBean("zippy", 123, new BigDecimal("456.78"), true); + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", bean, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", bean.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 4, children.size()); + + Element child1 = (Element)(new XPathWrapper("//sval").evaluate(root).get(0)); + assertNameTypeValue("sval", child1, "sval", "xsd:string", "zippy"); + + Element child2 = (Element)(new XPathWrapper("//ival").evaluate(root).get(0)); + assertNameTypeValue("ival", child2, "ival", "xsd:int", "123"); + + Element child3 = (Element)(new XPathWrapper("//dval").evaluate(root).get(0)); + assertNameTypeValue("dval", child3, "dval", "xsd:decimal", "456.78"); + + Element child4 = (Element)(new XPathWrapper("//bval").evaluate(root).get(0)); + assertNameTypeValue("bval", child4, "bval", "xsd:boolean", "true"); + } + + + public void testDispatchSimpleBeanIncludingNulls() throws Exception + { + SimpleBean bean = new SimpleBean(); + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", bean, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE, + BeanOutputOptions.XSI_NIL)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", bean.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 4, children.size()); + + Element child1 = (Element)(new XPathWrapper("//sval").evaluate(root).get(0)); + assertEquals("sval nil", "true", child1.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil")); + + Element child2 = (Element)(new XPathWrapper("//ival").evaluate(root).get(0)); + assertNameTypeValue("ival", child2, "ival", "xsd:int", "0"); + + Element child3 = (Element)(new XPathWrapper("//dval").evaluate(root).get(0)); + assertEquals("dval nil", "true", child3.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "nil")); + + Element child4 = (Element)(new XPathWrapper("//bval").evaluate(root).get(0)); + assertNameTypeValue("bval", child4, "bval", "xsd:boolean", "false"); + } + + + public void testDispatchSimpleBeanIgnoringNulls() throws Exception + { + SimpleBean bean = new SimpleBean(); + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", bean, + new BeanOutputHandler(root, BeanOutputOptions.XSI_TYPE)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + Element container = DomUtil.getChild(root, "foo"); + assertContainerNameAndType(container, "foo", bean.getClass()); + + List<Element> children = DomUtil.getChildren(container); + assertEquals("child count", 2, children.size()); + + Element child1 = (Element)(new XPathWrapper("//ival").evaluate(root).get(0)); + assertNameTypeValue("ival", child1, "ival", "xsd:int", "0"); + + Element child2 = (Element)(new XPathWrapper("//bval").evaluate(root).get(0)); + assertNameTypeValue("bval", child2, "bval", "xsd:boolean", "false"); + } + + + public void testDispatchCompoundBean() throws Exception + { + CompoundBean bean = new CompoundBean( + new SimpleBean("zippy", 123, new BigDecimal("456.78"), true), + new int[] { 1, 2, 3 }, + Arrays.asList("foo", "bar", "baz")); + + // at this point, I'm convinced the type output works, so we'll do default + // output and then use XPath for all assertions + + Element root = DomUtil.newDocument("test"); + BeanOutputDriver.dispatch("foo", bean, new BeanOutputHandler(root)); +// System.out.println(OutputUtil.compactString(root.getOwnerDocument())); + + DomAsserts.assertEquals("zippy", root, "/test/foo/simple/sval"); + DomAsserts.assertEquals("123", root, "/test/foo/simple/ival"); + DomAsserts.assertEquals("456.78", root, "/test/foo/simple/dval"); + DomAsserts.assertEquals("true", root, "/test/foo/simple/bval"); + + DomAsserts.assertEquals("1", root, "/test/foo/primArray/data[1]"); + DomAsserts.assertEquals("2", root, "/test/foo/primArray/data[2]"); + DomAsserts.assertEquals("3", root, "/test/foo/primArray/data[3]"); + + DomAsserts.assertEquals("0", root, "/test/foo/primArray/data[1]/@index"); + DomAsserts.assertEquals("1", root, "/test/foo/primArray/data[2]/@index"); + DomAsserts.assertEquals("2", root, "/test/foo/primArray/data[3]/@index"); + + DomAsserts.assertEquals("foo", root, "/test/foo/stringList/data[1]"); + DomAsserts.assertEquals("bar", root, "/test/foo/stringList/data[2]"); + DomAsserts.assertEquals("baz", root, "/test/foo/stringList/data[3]"); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |