[Warning: This is long]
So a month ago to the day, I wrote asking about handling for mapping
java.util.Map in Betwixt, but I didn't really have a good idea of
what a better solution would look like. I've been thinking about the
problem off and on, and I'm ready to propose a possible solution.
Let's consider the current behavior of Betwixt with an example.
Consider first our bean:
public class IdMap {
private Map _ids = new HashMap();
public Map getIds() { reutrn _ids; }
public void addId(String key, Integer value ) { _ids.put
(key,value); }
}
Consider an instantiated IdMap:
IdMap ids = new IdMap();
ids.addId("Alice", new Integer(10) );
ids.addId("Bob", new Integer(20) );
Serialized to XML, we get:
<id-map>
<ids>
<entry>
<key>Alice</key>
<value>10</value>
</entry>
<entry>
<key>Bob</key>
<value>20</value>
</entry>
</ids>
</id-map>
Definitely reasonable. But how could I get something like this instead?
<id-map>
<ids>
<entry key="Alice" value="10" />
<entry key="Bob" value="20" />
</ids>
</id-map>
That's not currently possible in Betwixt, because the handling for
Maps is currently hard-coded. While the default behavior is a little
restrictive, it's pretty-much useless when it comes to polymorphic
values. Consider a new example:
public interface IPet {
public String getName();
}
public abstract class AbstractPet implements IPet {
private String _name;
public AbstractPet(String name) { _name = name; }
public String getName() { return _name; }
}
public class Cat extends AbstractPet {
public Cat(String name) { super(name); }
}
public class Dog extends AbstractPet {
public Dog(String name) { super(name); }
}
public class PetDatabase {
private Map _pets = new HashMap();
public Map getPets() { return _pets; }
public void addPet(Integer key, IPet pet) { _pets.put(key,
pet); }
}
Instantiate as:
PetDatabase pets = new PetDatabase();
pets.addPet( new Integer(10), new Cat("Garfield") );
pets.addPet( new Integer(20), new Dog("Odie") );
Serialized to XML, we get:
<PetDatabase>
<pets>
<entry>
<key>20</key>
<value>
<name>Odi</name>
</value>
</entry>
<entry>
<key>10</key>
<value>
<name>Garfield</name>
</value>
</entry>
</pets>
</PetDatabase>
This is problematic, because the element for our Pet value is always
named "value", regardless of the type of pet, making it pretty
difficult to determine the type of the Pet after the fact.
Effectively, the default class mapping for each Map$Entry is:
<class name="Map$Entry">
<element name="entry">
<element name="key" property="key"/>
<element name="value" property="value"/>
</element>
</class>
What we'd like is the ability to supply our own mappings. In the case
of our polymorphic example, we might like something like:
<class name="Map$Entry">
<element name="entry">
<attribute name="key" property="key" />
<element property="value"/>
</element>
</class>
Which would give us the following XML:
<PetDatabase>
<pets>
<entry key="20">
<dog>
<name>Odi</name>
</dog>
</entry>
<entry key="10">
<cat>
<name>Garfield</name>
</cat>
</entry>
</pets>
</PetDatabase>
We could define our own class mapping for Map$Entry, but this has the
unfortunate effect of changing the mapping of Map$Entry everywhere it
is used. What if two classes, both containing Maps, need to be
mapped in different ways? This gets at a bigger issue that I've
always had with Betwixt: it's not easy to easy to define different
"flavors" of class mappings. That is to say, there are often cases
when you'd like one class to mapped in different ways depending on
context. My solution to the Map problem is a solution to the bigger
problem of class "flavors". Let's reconsider our PetDatabase
example. What if I could define a mapping in the following way?
<class name="PetDatabase">
<element name="PetDatabase">
<element name="pet" property="pets">
<attribute name="key" property="key" />
<element property="value"/>
</element>
</element>
</class>
According to current Betwixt behavior, the "key" and "value"
properties would attempt to evaluate their properties against the Pet
Database. My proposed change behavior change is to have the <element
name="pet" property="pets"> element to change the active bean context
such that "key" and "value" would evaluate against the Map$Entry
object instead. Serialized to XML, we would get:
<PetDatabase>
<pets>
<pet key="20">
<dog>
<name>Odi</name>
</dog>
</pet>
<pet key="10">
<cat>
<name>Garfield</name>
</cat>
</pet>
</pets>
</PetDatabase>
Implementation issues aside, does this change in behavior break
existing Betwixt mappings? The question can be restated as "Does
anyone use a mapping like the follwing?":
<class name="SomeBean">
<element name="some-bean">
<element name="p1" property="property1">
<element name="p2" property="property2"/>
</element>
</element>
</class>
where they expect both "property1" and "property2" to evaluate
against "SomeBean". My feeling is that this behavior might be
expected for writing beans to XML but not for reading XML to a
beans. That's because when reading XML, the current behavior would
be call "setProperty2()" against whatever object is created for the
"property1" getter/setter. So if anything, there is a contradiction
in the current Betwixt behavior. I think my proposed changes would
rectify that behavior, introduce mapping flexibility for
java.util.Map instances, and indeed provide powerful new flexibility
to Betwixt. All the possible use cases described above could be
handled with a few custom lines in the class mapping file. In
addition, these custom sub-mappings could use applied to classes of
all types for lots of nifty behavior.
Ok, so that was a lot. Thanks for reading this far. Any comments?
I've started on implementation and can show some JUnit test cases if
there is interest.
Thanks,
Brian Ferris
On Nov 17, 2005, at 2:29 PM, robert burrell donkin wrote:
On Thu, 2005-11-17 at 12:13 -0800, Brian Ferris wrote:
It seems that the rules for mapping a java.util.Map are determined by
the BeanProperty.createDescriptorForMap() method. Is there any way
to override this method or change the behavior of java.util.Map
mapping. After examining the parent method
BeanProperty.createXMLDescriptor(), it doesn't look like there is an
easy way to do it.
the support for maps is one of the few areas which hasn't really been
refactored (so it's a bit rubbish). needs a redesign.
i work best from concrete use cases so maybe we could develop a better
design now...
- robert
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]