Groovy per instance meta classes and ExpandoMetaClass DSL

So far Groovy allow per instance meta classes only for objects, which implements GroovyObject interface (like any class defined in Groovy). It can be illustrated by following simple test case:

   // for java.lang.String
   "test string".metaClass.toString = { "modified string" }
   assertEquals( "modified string", "test string".toString() )
   // but any other string will have the same behavior
   assertEquals( "modified string", "any other string".toString() )

At the same time

   // for GroovyObject
   class MyBean {
      String toString () { "bean" }
   }

   def bean = new MyBean ()
   def emc = new ExpandoMetaClass(MyBean,false,true)
   emc.toString = { -> "modified bean" }
   bean.metaClass = emc
   assertEquals("modified bean", bean.toString())
   // but any other bean will keep default behavior
   assertEquals("bean", new MyBean().toString())
   // if we want to modify behavior of all MyBean objects
   MyBean.metaClass = emc
   assertEquals("modified bean", new MyBean().toString())

Notice that we use different technique to add methods to Java and Groovy objects. As getMetaClass () for Java object always returns the same meta class we can use .metaClass notation, each GroovyObject has it is own meta class (of course, it is the same object by default) so we act differently depending on what we want to modify (whole class or just one Object)

Now I have patch which solves all these problems.

First of all we can assigned per instance meta class for any object. Of course, for Java objects it kept in WeakHashMap which involves some performance penalties. But now our first sample looks much more natural

   // for java.lang.String
   "test string".metaClass.toString = { "modified string" }
   assertEquals( "modified string", "test string".toString() )
   // but any other string will have natural behavior
   assertEquals( "any other string", "any other string".toString() )

Groovy objects also has benefits – we don’t need to create ExpandoMetaClass manually.


   def bean = new MyBean ()
   bean.metaClass.toString = { -> "modified bean" }
   assertEquals("modified bean", bean.toString())
   // but any other bean will keep default behavior
   assertEquals("bean", new MyBean().toString())
   // if we want to modify behavior of all MyBean objects
   MyBean.metaClass = bean.metaClass
   assertEquals("modified bean", new MyBean().toString())

But now, when we have unified behavior for Java and Groovy objects we can use simply and powerful ExpandoMetaClass DSL. Here is example


        // add behavior to meta class for Integer
        Integer.metaClass {
           // block of static methods
           'static' {
                fib { Number n ->
                    n.fib ()
                }
                // we can use both = or << if needed
                unusedStatic << { -> }
            }

            // one more syntax for static methods
            static.unusedStatic2 = { -> }

            // property definition
            ZERO = 0

            // method definition
            fib { ->
                def n = delegate
                if (n == ZERO)
                  return 1;
                if (n == 1)
                  return 1
                else
                  return fib(n-1) + fib(n-2)
            }

            // of course we can use both = or << if needed as well
            unusedMethod << { -> }
        }
        // and now we have it
        assertEquals( 3, 3.fib())

        // but we can also modify meta class for particular number
        4.metaClass {
            fib { ->
                10
            }
        }

        // and it works!
        assertEquals( 13, Integer.fib(5))

You know what… I like it. And thanks a lot for Graeme for inspirations and advices while I worked on that. Hope it will be accepted well and commited to the trunk.

Advertisements

One Response to Groovy per instance meta classes and ExpandoMetaClass DSL

  1. […] dynamic stateless mixins Last week I wrote about per-instance meta-classes and ExpandoMetaClass DSL. At that point it was just prototyped code in my working copy and I was not even sure if it will […]

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: