commons-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Brian Ferris <bdfer...@cs.washington.edu>
Subject Re: [betwixt] mapping java.util.Map
Date Sat, 17 Dec 2005 22:06:06 GMT
[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: commons-user-unsubscribe@jakarta.apache.org
> For additional commands, e-mail: commons-user-help@jakarta.apache.org
>


Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message