An easy way to specify the class to be loaded is for the class name to be
specified by an xml attribute. By default, the
(if present) is assumed to contain the name of the class to be loaded.
For example the following xml:
<Animals> <animal className='org.apache.commons.betwixt.io.read.FerretBean'> <call>Dook</call> <colour>albino</colour> <latinName>Mustela putoris furo</latinName> <name>Lector</name> </animal> <animal className='org.apache.commons.betwixt.io.read.CatBean'> <call>Meow</call> <colour>black</colour> <latinName>Felis catus</latinName> <name>Sam</name> </animal> <animal className='org.apache.commons.betwixt.io.read.DogBean'> <breed>mongrol</breed> <call>Woof</call> <latinName>Canis familiaris</latinName> <name>Bobby</name> <pedigree>false</pedigree> </animal> </Animals>
will construct objects of type
The attribute which contains the fully qualified class name can be configured through
classNameAttribute property of the
The classname mapping is implemented by a
ChainedBeanCreator. If this
feature is not required, a custom bean creation chain should be used.
A common variation on the above occurs when the java and the xml should be decoupled. So, no attributes with class names permitted. A common use case requires that different elements are associated with different types of bean. For example:
<animals> <ferret> <call>Dook</call> <colour>albino</colour> <latinName>Mustela putoris furo</latinName> <name>Lector</name> </ferret> <cat> <call>Meow</call> <colour>black</colour> <latinName>Felis catus</latinName> <name>Sam</name> </cat> <dog> <breed>mongrol</breed> <call>Woof</call> <latinName>Canis familiaris</latinName> <name>Bobby</name> <pedigree>false</pedigree> </dog> </animals>
To read this xml, the class corresponding to each element must be known. Of course, this is a mixed collection and so needs to be mapped as such. Then a suitable polymophoric mapping needs to be created. For example:
<betwixt-config> <class name='org.apache.commons.betwixt.io.read.Animals'> <element name='animals'> <!-- Note that an adder must be set but addDefaults will add a name (and so prevent a polymorphic mapping) and so should be avoided. --> <element property='animals' updater='addAnimal'/> </element> </class> <class name='org.apache.commons.betwixt.io.read.FerretBean'> <element name='ferret'> <addDefaults/> </element> </class> <class name='org.apache.commons.betwixt.io.read.CatBean'> <element name='cat'> <addDefaults/> </element> </class> <class name='org.apache.commons.betwixt.io.read.DogBean'> <element name='dog'> <addDefaults/> </element> </class> </betwixt-config>
This multi mapping
should be registered with
XMLIntrospector before the beans are written or read. For example (read):
BeanReader reader = new BeanReader(); ... reader.getXMLIntrospector().register(new InputSource(new StringReader(MAPPING))); reader.registerBeanClass(Animals.class); Animals animals = (Animals) reader.parse(in);
The introspection time type is the (expected) type as discovered during introspection.
The bind time type is the actual type of the object found as Betwixt runs. For example,
a property might have type
Animal but when the getter of that property is
called, the object returned might be a subclass (if
Animal is a class)
or an implementation (if
Animal is an interface) -
In this case,
Animal would be the introspection time type but
the bind time type.
There are two approaches to deal with this situation: either the object can be mapped on the basis of it's bind time type (the actual class of the object) or on the basis of it's introspection time type. In the first case, all the properties present will be mapped, in the second only a subset.
For example, say that
Animal has properties
call and that
Cat is a subclass with an additional
colour property. Consider a bean with a property returning an
If the introspection time type is used, only the
call properties of a
Cat bean will be read and written
whereas when bind time typing is used, all properties will be.
There are typical use cases which demonstrate the need for each approach. Betwixt supports both in a flexible way, for both reading and writing but defaults to bind time typing (which is typically more intuitive).
Whether the bind time type or the introspection time type should determine the mapping
is configurable in two different ways. The
implementation set on the introspection configuration allows powerful but course-grained
control over which types of property should have their mappings determine in which ways.
Per-element control is also available through the dot betwixt document. This is typically
used for more finely grained refinements. The dot betwixt settings override the strategy.
For example, the following code snippet configures an
so that (unless overridden by a dot betwixt document setting), introspection time types
will determine the mapping:
XMLIntrospector xmlIntrospector = ...; xmlIntrospector.getConfiguration() .setMappingDerivationStrategy( MappingDerivationStrategy.USE_INTROSPECTION_TIME_TYPE); }
mappingDerivation attribute on an
This attribute takes two enumerated values:
As might be expected,
bind indicates that the element should use
the bind time type whereas
introspection indicates that the introspection time
type should be used.
For example, when the following dot betwixt document is used, the
property's mapping will be determined by the introspection time type mapping.
<?xml version='1.0'?> <info> <element name='pet-record'> <element name='pet' property='pet' mappingDerivation='introspection'/>" + </element> </info>" }
Note that the mapping derivations are added together with all of the other defaults
and so all elements without the
mappingDerivation attribute will
default to bind time typing unless and
addDefaults is present.
The default mapping for a collection is the one created automatically
by Betwixt. This is controlled by a number of general configuration settings.
For some classes (including
special rules are applied. For collection implementations, these special
rules ensure that the contents are written and polymorphism is supported
For example, suppose
AlphaList extends ArrayList. It usually
GammaBean's. Use a
mappings for just
GammaBean and not
AlphaList. This will force the default mapping to be used.
AlphaList should be registered after the
BeanReader reader = ... ... reader.registerMultiMapping(...); reader.registerBeanClass(AlphaList.class);