commons-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Craig R. McClanahan" <craig...@apache.org>
Subject Re: [Digester]How do you populate a map?
Date Tue, 11 Feb 2003 00:33:59 GMT


On Mon, 10 Feb 2003, Baltz, Kenneth wrote:

> Date: Mon, 10 Feb 2003 15:04:48 -0800
> From: "Baltz, Kenneth" <Kbaltz@firstam.com>
> Reply-To: Jakarta Commons Users List <commons-user@jakarta.apache.org>
> To: Jakarta Commons Users List <commons-user@jakarta.apache.org>
> Subject: [Digester]How do you populate a map?
>
> I'm really embarrassed after posting a HowTo on populating a map, but I've
> found that it's not as clear as I thought it was.  I know how to do it with
> the following example:
>
> <?xml version='1.0'?>
> <map>
>   <key name='The key'/>
>   <value name='The value'/>
> </map>
>
> But how to do it with this?
>
> <?xml version='1.0'?>
> <map>
>   <key name='KeyA'/>
>   <value name='ValueA'/>
>   <key name='KeyB'/>
>   <value name='ValueB'/>
> </map>
>

My understanding of XML style guidelines is that the above is considered
poor form if you really want each key-value pair to be related to each
other.  The recommended pattern would be something like this instead:

  <?xml version='1.0'?>
  <map>
    <entry>
      <key name='KeyA'/>
      <value name='ValueA'/>
    </entry>
    <entry>
      <key name='KeyB'/>
      <value name='ValueB'/>
    </entry>
  </map>

where the relationship becomes obvious in the XML structure, *and* you can
easily use Digester's CallMethod rule to fire at the end of each <entry>,
with the parameter values coming from the nested <key> and <value>
elements:

  digester.addObjectCreate("map", "java.util.HashMap");
  digester.addCallMethod("map/entry",
                         "put", 2,
                         new String[] { "java.lang.Object",
                                        "java.lang.Object" });
  digester.addCallParam("map/entry/key", 0, "name");
  digester.addCallParam("map/entry/value", 1, "name");

If the <key> and <value> elements were modified to take their input from
the element body:

  <?xml version='1.0'?>
  <map>
    <entry>
      <key>KeyA</key>
      <value>ValueA'</value>
    </entry>
    <entry>
      <key>KeyB</key>
      <value>ValueB</value>
    </entry>
  </map>

then the latter two rules would become a little simpler:

  digester.addObjectCreate("map", "java.util.HashMap");
  digester.addCallMethod("map/entry",
                         "put", 2,
                         new String[] { "java.lang.Object",
                                        "java.lang.Object" });
  digester.addCallParam("map/entry/key", 0);
  digester.addCallParam("map/entry/value", 1);

but this change in your XML document structure is not as important as
nesting the things that go together.

> CallMethodRule is only invoked at the end of <map>, so you can't call it
> twice, once for each key, value pair.
> The situation I have that I actually need to solve looks more like this:
>
> <Library>
> 	<Book title="The Firm" />
> 	<Book title="The Cat in the Hat" />
> </Library>
>
> I would want Library to be a HashMap with titles for keys and Book objects
> for values.  You can't use a CallMethodRule on Book because the put() method
> belongs to Library, not Book.

I assume <Book> should create some sort of Book object, and you ultimately
want to call map.put(book.getTitle(), book), right?  If so, it sounds like
time to write your own custom Rule implementation -- easy and fun! :-)

  digester.addObjectCreate("Library", "java.util.Map");
  digester.addObjectCreate("Library/Book", "com.mycompany.MyBook");
  digester.addSetProperties("Library/Book"); // Sets "title" and others
  digester.addRule("Library/Book",
                   new AddBookRule());

with an AddBookRule class:

  public class AddBookRule extends Rule {

    public void end() throws Exception {
      MyBook book = (MyBook) digster.peek(0); // Top of stack
      Map library = (Map) digester.peek(1);   // Next-to-top of stack
      library.put(book.getTitle(), book);
    }

  }

Alternatively, if MyLibrary were a simple class like this:

  public class MyLibrary {
    private Map books = new HashMap();
    public void addBook(MyBook book) {
      books.put(book.getTitle(), book);
    }
    public Map getBooks() {
      return (books);
    }
  }

Then you could do without a custom Rule implementation:

  digester.addObjectCreate("Library", "com.mycompany.MyLibrary");
  digester.addObjectCreate("Library/Book", "com.mycompany.MyBook");
  digester.addSetProperties("Library/Book"); // Sets "title" and others
  digester.addSetNext("Library/Book",
                      "addBook", "com.mycompany.MyBook");

It's just a matter of where you want to add a little complexity.

>
> Help?
>
> BTW, I realize you can do this by creating proxy objects that call put() for
> you.  That's inelegant IMHO and I was hoping that Digester 1.4 had solved
> this issue.

Digester 1.0 actually solved that issue -- that's why it is very easy to
create your own Rule implementations :-).

>
> K.C.
>

Craig


Mime
View raw message