commons-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Simon Kitching <skitch...@apache.org>
Subject Re: [Digester] parameter value from a parent node attribute
Date Wed, 26 Apr 2006 11:32:49 GMT
Hi Valerio,

If I understand you correctly, your input has:
  <committer>
    <element id="xx">
      <children>
        <subelement refid="y1"/>
        <subelement refid="y2"/>
etc.

A ConfigurationDescriptor is created at <committer> only. This class has
methods
  addElementType(String id);
  addChildForType(String id, String refId)

It would be far easier if you had an object that represented an element
with a particular id (ie something was created for each <element> tag)
rather than an object that contains a "map". Your object model and xml
model would then be similar, and you could have:
  class Element
    setId(String id)
    addChild(String refId)
which is *much* easier to write digester rules for, and just seems a
nicer object model to me.

As an alternative, if you can alter your ConfigurationDescriptor class
to have a concept of "current id", then this would also make digester
rules easier (though I don't think it's so elegant):
  class ConfigurationDescriptor
    setCurrentElementType(String id)
    addChild(String refId); // uses current element type

If you just can't or won't alter your model, you could possibly use a
digester "named stack" to keep track of the element id:

public class ElementRule {
  public void begin(..., attrs) {
    // just save away info for use by ChildElementRule
    String id = attrs.get("id");
    digester.push(CUSTOM_STACK_NAME, id);
  }

  public void end(...) {
    digester.pop(CUSTOM_STACK_NAME);
  }
}

public class ChildElementRule {
  public void begin(..., attrs) {

    String refId = attrs.get("refId");
    String currentElementId = (String) digester.peek(CUSTOM_STACK_NAME);

    ConfigurationDescriptor hd = 
      (ConfigrationDescriptor) digester.peek();

    hd.addChildForType(currentElementId, refId);
  }
}

You could do something similar without using named stacks, but would
then have to use 
   digester.peek(1)
to "skip" over the id when accessing the ConfigurationDescriptor object
etc.

I may have misunderstood this code, as I don't see why you are
pushing/popping a Map containing the id instead of just using a String
containing the id..

Regardless, if you do alter any digester stack (and esp. the main one),
you should only ever push things in begin(), and only ever pop them in
end().

Regards,

Simon

On Wed, 2006-04-26 at 11:46 +0200, Valerio Schiavoni wrote:
> Hello Simon,
> thanks for the explanation you provided. I thought about writing a
> custom rule, and it actually worked for all but one test case, and
> this is why i'm replying.

> 
> 
> On 4/22/06, Simon Kitching <skitching@apache.org> wrote:
>         > <root>
>         > <element id="A">
>         >    <subelement id="B"/>
>         > </element>
>         > </root>
> 
> in the following code, root == committer; and the path
> root/element/subelement 
> is committer/element/children/subelement (but same semantics);
> 
> digester.addObjectCreate("committer", ConfigurationDescriptor.class);
> digester.addRule("committer/element",new ElementRule()); 
> where this first custom rule looks like:
> 
> private class ElementRule extends Rule {
>         public void begin( String ns, String name, 
>                 Attributes att) {
>             Map<String,String> ctx = new HashMap<String,String>(); 
>             String value = att.getValue("id");
>             ConfigurationDescriptor hd = 
> (ConfigurationDescriptor) getDigester().peek();
>             hd.addElementType(value);
>             ctx.put("ROOT_ID", value); 
>             getDigester().push( ctx);
>           }
>     }
>  
> digester.addRule("committer/element/children/element", new
> ChildElementRule());
> private class ChildElementRule extends Rule{
>         
>         public void begin( String ns, String name, Attributes att) {
>             
>             Map<String,String> ctx = ( Map) getDigester().pop();
>             String parentId =  ctx.get("ROOT_ID"); 
>             String childElementId = att.getValue("refid");
>             
>             ConfigurationDescriptor hd = (ConfigurationDescriptor)
> getDigester().peek();
>             hd.addChildForType(parentId, childElementId); 
>             
>           }
>     }
> 
> and everything goes fine until i run a test with the following input:
> rootCategory_twoChildrenType.xml : 
> 
> <committer>
> <element id="category"> 
> <children>
> <element refid="client" maxocc="-1" />
> <element refid="folder" maxocc="-1" />
> </children>
> </element>
> <element id="client"/> 
> </committer>
> 
> and I run a test like:
> public void testExactlyTwoChildrenElement() throws Throwable {
>         ConfigurationDescriptor hd =
> hr.read(rootCategory_twoChildrenType);
>         assertEquals("Assert on childrens by ID"+ROOT_TYPE, 
>                 2, hd.typeChildrenForType(ROOT_TYPE).size());
>     }
> 
> and i get a ClassCastException in the ChildElementRule when I do
> something like:
> Map<String,String> ctx = ( Map) getDigester().pop(); 
> where it founds a ConfigurationDescriptor class instead.
> 
> the code of the digester is here: http://veleno.pastebin.com/682606
> and i get a stacktrace: http://veleno.pastebin.com/682612
> 
> probably somewhere I should do a pop() but i don't understand where :(
> 
> 
> 
>         >
>         > whenever I match path element/subelement, i need to invoke a
>         2-parameters
>         > method  defined on the object on the stack
>         > ,call it twoParamMethod(String idElement,String
>         idSubElement).
>         >
>         > i was able to code up to this code: 
>         >
>         > digester.addCallMethod("root/element/subelement",
>         "twoParamMethod", 2);
>         > //HOW DO I GET THE VALUE FOR THE FIRST PARAMETER ?
>         
>         You can get the "previous object on the stack" via: 
>           // See addCallParam(String pattern, int paramIndex, int
>         stackIndex)
>           digester.addCallParam("root/element/subelement", 0, 1);
>         
>         Assuming you have an ObjectCreateRule for <element> and
>         <subelement> 
>         this will pass the <element> as the 0th parameter.
>         
>         However the CallMethodRule is probably not what you want; by
>         default it
>         calls the object that is on the top of the stack. So in your
>         case,
>         you'll end up invoking methods on the object created by the 
>         ObjectCreateRule for <subelement>. Maybe you want SetTopRule?
>         Or maybe
>         you want one of the CallMethodRule constructors that take a
>         targetOffset
>         parameter (NB: these don't have factory methods on Digester
>         because they 
>         are only rarely needed).
>         
>         >  digester.addCallParam("root/element/subelement", 1,
>         "id"); //value for the
>         > second parameter
>         >
>         > i need to this to fill in a map (stored in the root object)
>         key="element 
>         > id", values="list of id-s of subelements";
>         
>         Before doing any further implementation, though, I suggest you
>         think
>         very carefully about your object model. When people need to
>         start doing
>         such tricky things with stack offsets it usually means that
>         the java
>         object model they are building doesn't correspond properly to
>         the xml.
>         Often the solution is to fix the object model rather than
>         write tricky
>         digester rules. 
>         
>         Regards,
>         
>         Simon
>         
>         
>         
>         ---------------------------------------------------------------------
>         To unsubscribe, e-mail:
>         commons-user-unsubscribe@jakarta.apache.org
>         For additional commands, e-mail:
>         commons-user-help@jakarta.apache.org
>         
> 
> 
> 
> -- 
> http://valerioschiavoni.blogspot.com
> http://jroller.com/page/vschiavoni


---------------------------------------------------------------------
To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-user-help@jakarta.apache.org


Mime
View raw message