You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@commons.apache.org by Christopher Bare <ch...@yahoo.com> on 2003/05/24 03:30:47 UTC

[digester] help with multi-argument constructor

Hi,

I'm trying to parse XML something like this:

  <user>
    <id>1</id>
    <name>chris</name>
  </user>

As a result, I would like the following constructor to
be called:

   public User( long id, String username )
   {
      //...
   }

I read in the javadocs that "FactoryCreateRule" is
used to create instances of classes without a
no-argument constructor, but, as far as I can tell, an
ObjectCreationFactory is set up to use attributes, and
not enclosed elements, for constructor parameters.
Please correct me if I'm wrong here.

Otherwise, is there a way to do this? Sorry if I'm
missing something totally obvious, and thanks for any
insight.

I know... I could just add "set" methods and an empty
constructor, but I hate to expose public "set" methods
for the world to use and abuse where it's not really
appropriate.

If there's not already an obvious way to do this, and
I'm just clueless, I think something like the
following would be cool:

   digester.addCallConstructor( "users/user",
User.class, 2 );
   digester.addCallParam( "users/user/id", 0 );
   digester.addCallParam( "users/user/name", 1 );


-Chris





__________________________________
Do you Yahoo!?
The New Yahoo! Search - Faster. Easier. Bingo.
http://search.yahoo.com

Re: [digester] help with multi-argument constructor

Posted by Simon Kitching <si...@ecnetwork.co.nz>.
On Tue, 2003-05-27 at 07:12, Christopher Bare wrote:
> If I understand correctly, the CallParam pushes values
> onto the stack and the CallMethod rule pops them off
> and finally invokes the method in the
> end(java.lang.String namespace, java.lang.String name)
> method of the rule.

Sort of.

There are actually two stacks: the stack of *objects* and the stack of
"parameter sets". The object stack is the "main" one. The "parameter set
stack" is a bit of a hack really, but it works.

When the start of a tag is encountered, the set of matching rules is
determined. The "start" method of each matching rule is immediately
invoked, passing the set of attributes defined for that tag. The set of
matching rules is then cached away, and all nested tags are processed.
When the end of the tag is encountered, the "body" method of each rule
is invoked, then after that the "end" method of each rule is invoked *in
reverse order*.

For the ObjectCreateRule, the start method creates an instance of the
target class and pushes it onto the object stack. This is typically
followed by a SetNextRule which calls a method on the second-to-top
object on the object stack, passing the top object as a parameter.

The nested tags are then processed; any rules triggered as a result will
see the object previously created on the top of the object stack.

The end tag is then processed; the ObjectCreateRule's end method is
called which pops the created object off the stack.

>  I was thinking that the
> CallConstructor rule could work the same way, and that
> the newly created object could be pushed onto the
> stack in the end(...) method. Then the SetNext rule
> could come along and pop do its thing.

It is not the SetNextRule which pops the created object off the stack,
but the end method of the ObjectCreateRule. Otherwise, if someone
doesn't want to use SetNextRule, then the stack wouldn't get cleaned up.


Clearly, we can't create the object in a start method, because the
parameters aren't yet available. And we can't create the object in an
end method, because the end method also has the responsibility of
cleaning up the stack, therefore the created object has zero time
resident on the stack.

But as you & Robert pointed out, a solution could involve creating the
object in the body method. Not sure what the implications would be
(whether rules would interact reasonably in this situation), but worth
considering....


> definitely push the new object first. Would body(...)
> definitely be called -after- the params were pushed
> onto the stack?

The body method would definitely be called after all parameters in the
"parameter set" associated with your rule had been populated.

NB: the "parameter set stack" is actually a stack where each object on
the stack is an array of objects. One array --> one set of parameters to
be passed to some target method. The CallParamRule instances populate
the elements of the array on top of the parameter stack.

Good luck,

Simon


Re: [digester] help with multi-argument constructor

Posted by robert burrell donkin <ro...@blueyonder.co.uk>.
On Monday, May 26, 2003, at 08:12 PM, Christopher Bare wrote:

> Thanks Simon and Robert for the insight into how these
> rules work. I understand now why my idea is somewhat
> at odds with the way digester is designed.
>
> You are correct that I want to use a setNext rule
> after creating the User 0bject to add each user to an
> aggregate object. Sorta like this:
>
>     digester.addObjectCreate( "users", Users.class );
>
>     digester.addCallConstructor( "users/user",
> User.class, 2 );
>     digester.addCallParam( "users/user/id", 0 );
>     digester.addCallParam( "users/user/name", 1 );
>
>     digester.addSetNext( "users/user", "addUser" );
>
> If I understand correctly, the CallParam pushes values
> onto the stack and the CallMethod rule pops them off
> and finally invokes the method in the
> end(java.lang.String namespace, java.lang.String name)
> method of the rule. I was thinking that the
> CallConstructor rule could work the same way, and that
> the newly created object could be pushed onto the
> stack in the end(...) method. Then the SetNext rule
> could come along and pop do its thing.
>
> The issue with this, I guess, is that you need to
> insure that the the constructor is called and the
> object pushed onto the stack before SetNext's end(...)
> is invoked. Or, is there more to it than that?

you need to ensure that the constructor is called and the object pushed 
onto the stack before SetNext's end method is called and then popped off 
the stack after it's called.

> If the
> Constructor was invoked in the body(...) of my
> hypothetical CallConstructorRule, that would
> definitely push the new object first. Would body(...)
> definitely be called -after- the params were pushed
> onto the stack?

yes.

what happens is that when the digester endElement implementation is called,
  all the body calls for all the matched rules are called and then all the 
end calls. endElement for the parent has to be called (by the SAX parser) 
after each endElement is called for each child element - therefore any 
params which are going to be pushed onto the stack will have been. (the 
only exception is where you're setting a parameter from the body text of 
the parent - in this case, you'd need to be careful about the ordering.)

- robert


Re: [digester] help with multi-argument constructor

Posted by Christopher Bare <ch...@yahoo.com>.
Thanks Simon and Robert for the insight into how these
rules work. I understand now why my idea is somewhat
at odds with the way digester is designed.

You are correct that I want to use a setNext rule
after creating the User 0bject to add each user to an
aggregate object. Sorta like this:

    digester.addObjectCreate( "users", Users.class );

    digester.addCallConstructor( "users/user",
User.class, 2 );
    digester.addCallParam( "users/user/id", 0 );
    digester.addCallParam( "users/user/name", 1 );

    digester.addSetNext( "users/user", "addUser" );

If I understand correctly, the CallParam pushes values
onto the stack and the CallMethod rule pops them off
and finally invokes the method in the
end(java.lang.String namespace, java.lang.String name)
method of the rule. I was thinking that the
CallConstructor rule could work the same way, and that
the newly created object could be pushed onto the
stack in the end(...) method. Then the SetNext rule
could come along and pop do its thing.

The issue with this, I guess, is that you need to
insure that the the constructor is called and the
object pushed onto the stack before SetNext's end(...)
is invoked. Or, is there more to it than that? If the
Constructor was invoked in the body(...) of my
hypothetical CallConstructorRule, that would
definitely push the new object first. Would body(...)
definitely be called -after- the params were pushed
onto the stack?

Maybe I'll grab some sack and code up a rule. In the
meantime, Simon's work-around is working well enough.

Thanks again. The jakarta project is a like a toy
store. There are so many nifty things tucked away in
various corners.

-Chris



__________________________________
Do you Yahoo!?
The New Yahoo! Search - Faster. Easier. Bingo.
http://search.yahoo.com

Re: [digester] help with multi-argument constructor

Posted by robert burrell donkin <ro...@blueyonder.co.uk>.
On Monday, May 26, 2003, at 06:09 AM, Simon Kitching wrote:

> On Sat, 2003-05-24 at 13:30, Christopher Bare wrote:
>> Hi,
>>
>> I'm trying to parse XML something like this:
>>
>>   <user>
>>     <id>1</id>
>>     <name>chris</name>
>>   </user>

<snip>

> Still, it would be nice to be able to construct an object with
> parameters based upon nested tags. Anyone else out there got any
> ideas????

i have a couple of ideas. (unfortunately i'm busy at the moment and so 
probably won't have time to code either of them any time soon.)

perhaps the object could be created and pushed onto the stack when the 
rule's body method is called and then removed (as per usual) when the end 
method is called. i think that (given some careful ordering), the object 
will be on the stack long enough for rules that take their effect in the 
end method (for example setNextRule) to act on it. i think that a rule 
like this should be straight forward to create and would satisfy many use 
cases (including the example Christopher gives).

a more and complex possibility would be to create rules that work together 
to call the constructor when all the parameters have been assembled. so, 
for the above example, the object would be constructed after the name 
element body content has been parsed. this would be more difficult to code 
and could not cope with XML maps whereby absence of child element implies 
that a null should be passed into the constructor.

of course, Christopher could relatively easily solve his specific problem 
by creating his own rule.

- robert


Re: [digester] help with multi-argument constructor

Posted by Simon Kitching <si...@ecnetwork.co.nz>.
On Sat, 2003-05-24 at 13:30, Christopher Bare wrote:
> Hi,
> 
> I'm trying to parse XML something like this:
> 
>   <user>
>     <id>1</id>
>     <name>chris</name>
>   </user>
> 
> As a result, I would like the following constructor to
> be called:
> 
>    public User( long id, String username )
>    {
>       //...
>    }
> 
> I read in the javadocs that "FactoryCreateRule" is
> used to create instances of classes without a
> no-argument constructor, but, as far as I can tell, an
> ObjectCreationFactory is set up to use attributes, and
> not enclosed elements, for constructor parameters.
> Please correct me if I'm wrong here.

I think you're correct here. The factory only works with attributes.

> 
> Otherwise, is there a way to do this? Sorry if I'm
> missing something totally obvious, and thanks for any
> insight.
> 
> I know... I could just add "set" methods and an empty
> constructor, but I hate to expose public "set" methods
> for the world to use and abuse where it's not really
> appropriate.
> 
> If there's not already an obvious way to do this, and
> I'm just clueless, I think something like the
> following would be cool:
> 
>    digester.addCallConstructor( "users/user",
> User.class, 2 );
>    digester.addCallParam( "users/user/id", 0 );
>    digester.addCallParam( "users/user/name", 1 );

The problem with what you are suggesting here is that the object you are
constructing will not be created until the end tag is encountered,
because all the subtags need to be seen first in order to gather the
required parameters (the Digester is based on SAX, not DOM).

Remember that the digester's central concept is a stack of objects. The
ObjectCreateRule and FactoryCreateRule both ensure that the object is
created as soon as the *start* tag for the element is processed. The
created object then resides on the object stack until the end tag is
encountered, allowing other rules which operate upon the top object of
the stack to refer to the instance.

Because what you are suggesting requires delaying the construction of
the target object until the *end* tag for the element is processed, this
results in the constructed object spending zero time upon the object
stack, making it impossible for other rules to ever reference it.

In particular, I expect that you will want to apply the "setNext" rule
in order to tell some parent object that your new "user" object exists.
This simply isn't possible with the proposed solution.



I assume you have some object representing a "set of users", which is
created by an ObjectCreateRule when the <users> tag is encountered. You
could therefore place a factory method on that class, and do:

digester.addCallMethod("users/user", "createUser", 2);
digester.addCallParam("users/user/id", 0);
digester.addCallParam("users/user/name", 1);



Still, it would be nice to be able to construct an object with
parameters based upon nested tags. Anyone else out there got any
ideas????



Regards,

Simon