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 className
attribute
(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 FerretBean
, CatBean
and
DogBean
.
The attribute which contains the fully qualified class name can be configured through
the classNameAttribute
property of the BindingConfiguration
.
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
the 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) - Cat
, say.
In this case, Animal
would be the introspection time type but Cat
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 latinName
and call
and that Cat
is a subclass with an additional
colour
property. Consider a bean with a property returning an Animal
.
If the introspection time type is used, only the latinName
and call
properties of aCat
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 MappingDerivationStrategy
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 XMLIntrospector
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); }
The optional mappingDerivation
attribute on an <element>
element.
This attribute takes two enumerated values: bind
and introspection
.
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 pet
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 Collection
implementations)
special rules are applied. For collection implementations, these special
rules ensure that the contents are written and polymorphism is supported
for reading.
For example, suppose AlphaList extends ArrayList
. It usually
contains BetaBean
's and GammaBean
's. Use a
multi-mapping
contain
mappings for just BetaBean
and GammaBean
and not
AlphaList
. This will force the default mapping to be used.
When reading, AlphaList
should be registered after the
multimapping thus:
BeanReader reader = ... ... reader.registerMultiMapping(...); reader.registerBeanClass(AlphaList.class);