org.jdom2.contrib.beans.BeanMapper Maven / Gradle / Ivy
Show all versions of jdom Show documentation
/*--
Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin & Alex Chaffee.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer that follows
these conditions in the documentation and/or other materials
provided with the distribution.
3. The name "JDOM" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact .
4. Products derived from this software may not be called "JDOM", nor
may "JDOM" appear in their name, without prior written permission
from the JDOM Project Management .
In addition, we request (but do not require) that you include in the
end-user documentation provided with the redistribution and/or in the
software itself an acknowledgement equivalent to the following:
"This product includes software developed by the
JDOM Project (http://www.jdom.org/)."
Alternatively, the acknowledgment may be graphical using the logos
available at http://www.jdom.org/images/logos.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
This software consists of voluntary contributions made by many
individuals on behalf of the JDOM Project and was originally
created by Jason Hunter and
Brett McLaughlin . For more information
on the JDOM Project, please see .
*/
package org.jdom2.contrib.beans;
import java.lang.reflect.*;
import java.util.*;
import java.beans.*;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Attribute;
import org.jdom2.Namespace;
/**
* Maps a JavaBean to an XML tree and vice versa. (Yes, it's yet
* another XML Data Binding solution.) Given a JavaBean, it will
* produce a JDOM tree whose elements correspond to the bean's
* property values. Given a JDOM tree, it will return a new instance
* of that bean whose properties have been set using the corresponding
* values in the JDOM tree.
*
* By default, it assumes each element maps to a property of the same
* name, subject to normal capitalization rules. That is, an element
* <foo> will map to the methods setFoo and getFoo. You can
* change this behavior by calling the various addMapping
* methods. For instance, to map a an element <date> to the
* property "birthDate" (using methods setBirthDate and getBirthDate),
* call
mapper.addMapping("birthDate", "date");
You can
* also map a property to an attribute, either of a child or of the
* parent (see JavaDoc for
*
* addMapping(String property, String element, String attribute)
* for details).
*
* During Bean -> JDOM conversion, if a BeanInfo object is found, it
* will be respected. See JavaDoc for java.beans.Introspector.
*
* If a given property, element, or attribute is to be skipped
* (ignored) during conversion, call the appropriate ignore method
* (ignoreProperty, ignoreElement, or ignoreAttribute). This is also
* appropriate if your bean has multiple accessors (properties) for
* the same underlying data.
*
* Support for Namespaces is rudimentary at best and has not been
* well-tested; if you specify a Namespace then all created elements
* and attributes will be in that namespace (or all elements not in
* that namespace will be skipped, for JDOM->Bean mapping).
*
* If a bean property type is a Java array, its items will be mapped
* to multiple child elements of the bean element (multiple children
* at the base level, not one child). This is to provide an easier
* transition for XML documents whose elements contain multiple
* children with the same name.
*
* Please try this out on your own beans. If there is a case that
* fails to do what you like (for instance, properties with custom
* class types), let me know and I'll try to work it out.
*
*
TODO:
* support list properties (collections other than Java arrays)
* allow lists/arrays to map to either scattered elements or a single nested element
* sort XML elements in some order other than random BeanInfo order
* support for BeanInfoSearchPaths
* multiple packages searched for beans
* better support for Namespaces (hard)
* allow stringconverter to map object -> string (not just string -> object)
* id/idref support for cyclical references
* more type converters
* custom property converters
* known issue: inner class bean can't be found jdom->bean (workaround: define mapping containing class name)
*
*
* Example:
* TestBean
*
* public class TestBean implements java.io.Serializable {
* public String getName() { ... }
* public int getAge() { ... }
* public Date getBirthdate() { ... }
* public TestBean getFriend() { ... }
* public void setName(String name) { ... }
* public void setAge(int age) { ... }
* public void setBirthdate(Date birthdate) { ... }
* public void setFriend(TestBean friend) { ... }
* public String toString() { ... }
* }
*
* XML Representation
* <testBean>
* <dob age="31">Fri Aug 08 00:00:00 EDT 1969</dob>
* <name>Alex</name>
* <friend>
* <testBean>
* <dob age="25">Thu May 01 00:00:00 EDT 1975</dob>
* <name>Amy</name>
* </testBean>
* </friend>
* </testBean>
*
* Mapping code
* BeanMapper mapper = new BeanMapper();
* mapper.addMapping("birthdate", "dob"); // element mapping
* mapper.addMapping("age", "dob", "age"); // attribute mapping
* mapper.setBeanPackage("org.jdom2.contrib.beans");
*
* Converting Bean to JDOM
* Document doc = mapper.toDocument(alex);
* Converting JDOM to Bean
* TestBean alex = mapper.toBean(doc);
*
* @author Alex Chaffee ([email protected])
**/
@SuppressWarnings("javadoc")
public class BeanMapper {
protected String beanPackage;
protected Namespace namespace;
protected boolean ignoreMissingProperties = false;
protected boolean ignoreNullProperties = true;
protected List mappings = new ArrayList();
protected StringConverter stringconverter = new StringConverter();
/**
* Default constructor. If you are only doing bean -> XML
* mapping, you may use the mapper immediately. Otherwise, you
* must call setBeanPackage.
**/
public BeanMapper()
{
}
// Properties
/**
* @param beanPackage the name of the package in which to find the
* JavaBean classes to instantiate
**/
public void setBeanPackage(String beanPackage)
{
this.beanPackage = beanPackage;
}
/**
* Use this namespace when creating the XML element and all
* child elements.
**/
public void setNamespace(Namespace namespace)
{
this.namespace = namespace;
}
/**
* Get the object responsible for converting a string to a known
* type. Once you get this, you can call its setFactory() method
* to add a converter for a custom type (for which toString() does
* not suffice). The default string converter has a factory that
* recognizes several date formats (including ISO8601 -
* e.g. "2000-09-18 18:51:22-0600" or some substring thereof).
**/
public StringConverter getStringConverter() {
return stringconverter;
}
/**
* Set a custom string converter.
**/
public void setStringConverter(StringConverter stringconverter) {
this.stringconverter = stringconverter;
}
/**
* In mapping from Bean->JDOM, if we encounter an property with a
* null value, should
* we ignore it or add an empty child element/attribute (default: true)?
* @param b true = ignore, false = empty element
**/
public void setIgnoreNullProperties(boolean b) {
ignoreNullProperties = b;
}
/**
* In mapping from JDOM->Bean, if we encounter an element or
* attribute without a corresponding property in the bean, should
* we ignore it or throw an exception (default: false)?
* @param b true = ignore, false = throw exception
**/
public void setIgnoreMissingProperties(boolean b) {
ignoreMissingProperties = b;
}
// Bean -> JDOM Mapping
/**
* Converts the given bean to a JDOM Document.
* @param bean the bean from which to extract values
**/
public Document toDocument(Object bean) throws BeanMapperException {
return toDocument(bean, null);
}
/**
* Converts the given bean to a JDOM Document.
* @param bean the bean from which to extract values
* @param name the name of the root element (null => use bean class name)
**/
public Document toDocument(Object bean, String elementName)
throws BeanMapperException {
Element root = toElement(bean, elementName);
Document doc = new Document(root);
return doc;
}
/**
* Converts the given bean to a JDOM Element.
* @param bean the bean from which to extract values
* @param elementName the name of the element (null => use bean class name)
**/
public Element toElement(Object bean) throws BeanMapperException
{
return toElement(bean, null);
}
/**
* Converts the given bean to a JDOM Element.
* @param bean the bean from which to extract values
* @param elementName the name of the element (null => use bean class name)
**/
public Element toElement(Object bean, String elementName)
throws BeanMapperException {
BeanInfo info;
try {
// cache this?
info = Introspector.getBeanInfo(bean.getClass());
}
catch (IntrospectionException e) {
throw new BeanMapperException("Mapping bean " + bean, e);
}
// create element
Element element;
String beanname;
if (elementName != null) {
element = createElement(elementName);
}
else {
Class> beanclass = info.getBeanDescriptor().getBeanClass();
beanname = unpackage(beanclass.getName());
element = createElement(beanname);
}
// get all properties, set as child-elements
PropertyDescriptor[] properties = info.getPropertyDescriptors();
for (int i=0; i type = value.getClass();
String classname = type.getName();
// todo: allow per-type callback to convert (if toString() is
// inadequate) -- extend stringconverter?
if (classname.startsWith("java.lang.") ||
classname.equals("java.util.Date")
)
{
result = value.toString();
}
else if (type.isArray()) {
// it's an array - use java.lang.reflect.Array to extract
// items (or wrappers thereof)
List