[groovy-dev] Using Classes as well as Closures in Builders

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
13 messages Options
12
Reply | Threaded
Open this post in threaded view
|

[groovy-dev] Using Classes as well as Closures in Builders

tugwilson
At the moment the Builder Magic only works on closures:

System.out << new StreamingMarkupBuilder() {
        root {
                element {
etc.

This is fine for a lot of markup but there are certain sorts of  
documents which have the same construct appearing in several places.  
One example of this is in Atom feeds. I'm generating these at the  
moment and it's slightly awkward:

Atom has a Person construct which can be used in several places in a  
feed and is a <name> element and optional <url> and <email> elements.  
As it is used in several places it makes sense to put it in a  
separate closure as follows:

def atomPerson = { name, url = nuu, emai = null ->
   mkpDeclareNamespace(atom: "http://purl.org/atom/ns#")

   atom.name name
   if (url != null) atom.url url
   if (email != null) atom.email email
}


I can then use that closure in the main markup closure.

However actually using it is rather tedious...

def person = atomPerson.clone()
person.delegate = delegate
peerson "John Wilson"

I have made a proposal as part of the JSR (see the jsr mailing list  
for details)  process of fixing the name resolution rules which would  
mean that Builder Magic can apply to Classes as well.

If this proposal is accepted (or if the treatment of unbound variable  
part is accepted) then we could do the following:

Class Atom extends SomeBuilderBaseClass {
   atom(builder) {
     super(builder) // bind the class to this builder using a custom  
MetaClass
   }

   person(name, url = nuu, emai = null ) {
     mkpDeclareNamespace(atom: "http://purl.org/atom/ns#")

     atom.name name
     if (url != null) atom.url url
     if (email != null) atom.email email
   }

  feed() {
     mkpDeclareNamespace(atom: "http://purl.org/atom/ns#")

    atom.feed {
....

     person("John Wilson")
.....


This may look rather trivial but I think it's actually quite  
exciting. I can envisage us packaging up common XML vocabularies as  
Goovy classes:


System.out << new StreamingMarkupBuilder() {
   def soap = new Soap(it)
   def dc = new DublinCore(it)

....

   dc.title "My Title"

...

   soap.envelope {
.. body goes here
   }




John Wilson
The Wilson Partnership
http://www.wilson.co.uk


Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

Jochen Theodorou
John Wilson schrieb:
[...]
>   person(name, url = nuu, emai = null ) {
>     mkpDeclareNamespace(atom: "http://purl.org/atom/ns#")
>
>     atom.name name
>     if (url != null) atom.url url
>     if (email != null) atom.email email
>   }


    person(name, url = nuu, emai = null ) {
      builder.mkpDeclareNamespace(atom: "http://purl.org/atom/ns#")

      def atom = builder.atom
      atom.name name
      if (url != null) atom.url url
      if (email != null) atom.email email
    }


would it be today, if there is a field/propety named builder that
contains the builder.

> This may look rather trivial but I think it's actually quite  exciting.
> I can envisage us packaging up common XML vocabularies as  Goovy classes:
>
> System.out << new StreamingMarkupBuilder() {
>   def soap = new Soap(it)
>   def dc = new DublinCore(it)
>
> ....
>
>   dc.title "My Title"
>
> ...
>
>   soap.envelope {
> .. body goes here
>   }

this would not be changed, or what is it I don't see?

bye blackdrag
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

tugwilson

On 18 Nov 2005, at 11:27, Jochen Theodorou wrote:

>
>> This may look rather trivial but I think it's actually quite  
>> exciting. I can envisage us packaging up common XML vocabularies  
>> as  Goovy classes:
>> System.out << new StreamingMarkupBuilder() {
>>   def soap = new Soap(it)
>>   def dc = new DublinCore(it)
>> ....
>>   dc.title "My Title"
>> ...
>>   soap.envelope {
>> .. body goes here
>>   }
>
> this would not be changed, or what is it I don't see?
>


The changed behaviour happens inside the cd.title and soap.envelope  
methods. because the objects are bound to the builder object (it's  
passed in via the constructor) then they automatically emit their  
markup into the current document. With closures the binding is more  
awkward (clone and set the delegate). With classes the process is  
slightly simpler but the big win is that it happens for many items at  
once. If we used closures we would have to bind each closure  
individually the methods are bound in one go.

In adition the constructor of the method can do things like make  
namespace declarations which actually simplifies the code needed for  
each method:

Class Atom extends SomeBuilderBaseClass {
   atom(builder) {
     super(builder) // bind the class to this builder using a custom  
MetaClass
     mkp.DeclareNamespace(atom: "http://purl.org/atom/ns#")
   }

   person(name, url = nuu, emai = null ) {
     atom.name name
     if (url != null) atom.url url
     if (email != null) atom.email email
   }

  feed() {
    atom.feed {
....

     person("John Wilson")
.....

Note that in this versions the methods don't have to declare the atom  
namespace because they know it's already been declared.


John Wilson
The Wilson Partnership
http://www.wilson.co.uk


Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

Jochen Theodorou
John Wilson schrieb:

>
> On 18 Nov 2005, at 11:27, Jochen Theodorou wrote:
>
>>
>>> This may look rather trivial but I think it's actually quite  
>>> exciting. I can envisage us packaging up common XML vocabularies  as  
>>> Goovy classes:
>>> System.out << new StreamingMarkupBuilder() {
>>>   def soap = new Soap(it)
>>>   def dc = new DublinCore(it)
>>> ....
>>>   dc.title "My Title"
>>> ...
>>>   soap.envelope {
>>> .. body goes here
>>>   }
>>
>>
>> this would not be changed, or what is it I don't see?
>
> The changed behaviour happens inside the cd.title and soap.envelope  
> methods. because the objects are bound to the builder object (it's  
> passed in via the constructor) then they automatically emit their  
> markup into the current document. With closures the binding is more  
> awkward (clone and set the delegate). With classes the process is  
> slightly simpler but the big win is that it happens for many items at  
> once. If we used closures we would have to bind each closure  
> individually the methods are bound in one go.


so all it is about is to avoid overwriting invokeMethod in the class?

I am not talking about replacing your class with a closure, I am talking
about doing what you want with invokeMethod and get/setProperty. That's
possible, or not?

bye blackdrag
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

tugwilson

On 18 Nov 2005, at 11:58, Jochen Theodorou wrote:

>
>
> so all it is about is to avoid overwriting invokeMethod in the class?
>
> I am not talking about replacing your class with a closure, I am  
> talking about doing what you want with invokeMethod and get/
> setProperty. That's possible, or not?

The magic is done by replacing the object's MetaClass. The processing  
is done in the MetaClass' invokeThisMethod and get/setThisProperty.

Note this is done on a per object basis using setMetaClass - see  
groovy.util.slurpersupport.GPathResult for an example of this  
thechnique. It's used there to catch a.@p and interpret it as  
selection the XML attribute p on the element a.

The class itself does not overwrite invokeMethod().


John Wilson
The Wilson Partnership
http://www.wilson.co.uk


Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

Jochen Theodorou
John Wilson schrieb:

>
> On 18 Nov 2005, at 11:58, Jochen Theodorou wrote:
>
[...]
> The class itself does not overwrite invokeMethod().

yes, but can the same behaviour you intend to give the class by
replacing its MetaClass be simulated using the classes invokeMethod and
get/setProperty methods?

bye blackdrag
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

tugwilson

On 18 Nov 2005, at 12:20, Jochen Theodorou wrote:

> yes, but can the same behaviour you intend to give the class by  
> replacing its MetaClass be simulated using the classes invokeMethod  
> and get/setProperty methods?


Not at the moment because of the name resolution checks in Classes.

I have a proposal on the table about introducing different versions  
of invokeMethod etc ont the MataClass. If we were to implement that  
we would have to decide which calls were delegated to the  
GroovyObject methods.

We haven't had that discussion yet so we have no consensus position  
on the semantics. My starting point would be that only access  
extarnal to the object would go through the GroovyObject methods (e.g  
a.x() would go through GO#invokeMethod() but this.x() would not).

I have some issues about the behaviour of the GO methods when they  
fail to find a match. They throw an exception which is really quite  
nasty. I'd prefer it if they returned a sentinel result (like  
NOT_CALLED and NOT_FOUND). However that is a breaking change and may  
not find support within the JSR.
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

tugwilson
In reply to this post by Jochen Theodorou

On 18 Nov 2005, at 11:27, Jochen Theodorou wrote:

>    person(name, url = nuu, emai = null ) {
>      builder.mkpDeclareNamespace(atom: "http://purl.org/atom/ns#")
>
>      def atom = builder.atom
>      atom.name name
>      if (url != null) atom.url url
>      if (email != null) atom.email email
>    }
>
>
> would it be today, if there is a field/propety named builder that  
> contains the builder.


Sorry i missed that the first time round:)

Unfortunately this will not work. This aspect of the  
StreamingXBuilder implementation is a bit tricky I'm afraid!


John Wilson
The Wilson Partnership
http://www.wilson.co.uk


Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

Jochen Theodorou
In reply to this post by tugwilson
John Wilson schrieb:

>
> On 18 Nov 2005, at 12:20, Jochen Theodorou wrote:
>
>> yes, but can the same behaviour you intend to give the class by  
>> replacing its MetaClass be simulated using the classes invokeMethod  
>> and get/setProperty methods?
>
> Not at the moment because of the name resolution checks in Classes.

this.a is not checked, so wouldn't builder.a be checked

> I have a proposal on the table about introducing different versions  of
> invokeMethod etc ont the MataClass. If we were to implement that  we
> would have to decide which calls were delegated to the  GroovyObject
> methods.

the old version was/is that invokeMethod is only called for methods
which name does not exist in the class. So of course MetaClass is more
mighty because it gets all calls.

> We haven't had that discussion yet so we have no consensus position  on
> the semantics. My starting point would be that only access  extarnal to
> the object would go through the GroovyObject methods (e.g  a.x() would
> go through GO#invokeMethod() but this.x() would not).
 >
> I have some issues about the behaviour of the GO methods when they  fail
> to find a match. They throw an exception which is really quite  nasty.
> I'd prefer it if they returned a sentinel result (like  NOT_CALLED and
> NOT_FOUND). However that is a breaking change and may  not find support
> within the JSR.

I see.

bye blackdrag
Reply | Threaded
Open this post in threaded view
|

Re: [groovy-dev] Using Classes as well as Closures in Builders

tugwilson

On 18 Nov 2005, at 12:43, Jochen Theodorou wrote:

>> On 18 Nov 2005, at 12:20, Jochen Theodorou wrote:
>>> yes, but can the same behaviour you intend to give the class by  
>>> replacing its MetaClass be simulated using the classes  
>>> invokeMethod  and get/setProperty methods?
>> Not at the moment because of the name resolution checks in Classes.
>
> this.a is not checked, so wouldn't builder.a be checked

Certainly you can make the code work by prefixing all the calls with  
a defined name which is a variable which refers to the builder agent  
(strictly it's not the Builder but a special object which the builder  
creates and manages).

In some circumstances it's good to do this. When I have a small  
amount of markup on a lot of other logic  I use a markup. or xml.  
prefix as a code documentation aid so the fact that I'm emitting  
markup is apparent.

In other circumstances (which are more common, in my experience) the  
vast bulk of the code is emitting markup - in this case the prefix  
clutters the code with unnecessary information. In these cases it's  
far preferable to have unadorned names.


John Wilson
The Wilson Partnership
http://www.wilson.co.uk


12