Skip to content
Skip to breadcrumbs
Skip to header menu
Skip to action menu
Skip to quick search
Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Sign Up
Dashboard
Groovy
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Page Layout
No Layout
Two column (simple)
Two column (simple, left sidebar)
Two column (simple, right sidebar)
Three column (simple)
Two column
Two column (left sidebar)
Two column (right sidebar)
Three column
Three column (left and right sidebars)
Undo
Redo
Find/Replace
Keyboard Shortcuts Help
<p>Groovy gives us a wide variety of choices for meta-programming. We've looked at Categories and Interceptors, which change the behavior of objects within a selected block and current thread only, in other tutorials. In this tutorial, we'll learn about more ways of meta-programming in Groovy.</p> <h3>Intercepting Method Calls and Property Accesses</h3> <p>We can add a special method called 'invokeMethod' to a class definition that executes calls to undefined methods:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class MyClass{ def hello(){ 'invoked hello directly' } def invokeMethod(String name, Object args){ return "unknown method $name(${args.join(', ')})" } } def mine= new MyClass() assert mine.hello() == 'invoked hello directly' assert mine.foo("Mark", 19) == 'unknown method foo(Mark, 19)' </pre></td></tr></table> <p>If our class implements GroovyInterceptable, invokeMethod is called for all method invocations whether they exist or not:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class MyClass implements GroovyInterceptable{ def hello(){ 'invoked hello() directly' } def invokeMethod(String name, Object args){ "invoked method $name(${args.join(', ')})" } } def mine= new MyClass() assert mine.hello() == 'invoked method hello()' assert mine.foo('Mark', 19) == 'invoked method foo(Mark, 19)' assert mine.&hello() == 'invoked hello() directly' //we can still invoke a method directly using .& syntax </pre></td></tr></table> <p>We can get and set properties using special method names:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class MyClass{ def greeting= 'accessed greeting directly' Object getProperty(String property){ "read from property $property" } void setProperty(String property, Object newValue){ throw new Exception("wrote to property $property") } } def mine= new MyClass() assert mine.greeting == 'read from property greeting' try{ mine.greeting= 'hi' }catch(e){ assert e.message == 'wrote to property greeting' } assert mine.@greeting == 'accessed greeting directly' //we can access a property directly using .@ syntax </pre></td></tr></table> <p>When there's a field of some name, refering to that name still considers it to be a property unless the syntax .@ is used:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class MyClass{ public greeting= 'accessed field greeting (directly)' //field, not property Object getProperty(String property){ "read from property $property" } } def mine= new MyClass() assert mine.greeting == 'read from property greeting' assert mine.@greeting == 'accessed field greeting (directly)' </pre></td></tr></table> <p>We can call methods and access properties directly, both statically and dynamically, from within the class using various syntaxes:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class MyClass implements GroovyInterceptable{ def greeting= 'accessed greeting' def id= 'White: ' Object getProperty(String property){ try{ return this.@id + //access field directly 'indirectly ' + this.@"$property" //access field directly and dynamically }catch(e){ return "no such property $property" } } def hello(Object[] args){ "invoked hello with (${args.join(', ')})" } def id(){ 'Green: ' } def invokeMethod(String name, Object args){ try{ return this.&id() + //call method directly 'indirectly ' + this.&"$name"(args) //call method directly and dynamically }catch(e){ return "no such method $name" } } } def mine= new MyClass() assert mine.greeting == 'White: indirectly accessed greeting' assert mine.farewell == 'no such property farewell' assert mine.hello(1, 'b', 3) == 'Green: indirectly invoked hello with (1, b, 3)' assert mine.foo('Mark', 19) == 'no such method foo' </pre></td></tr></table> <p>If we add such 'invokeMethod', 'getProperty', or 'setProperty' methods to an object using Expando or Category syntax, they act just like normal methods. Not many supplied classes have 'invokeMethod' and such defined. For such cases, we need to use MetaClasses.</p> <h3>MetaClasses</h3> <p>We've seen how classes behave with the default MetaClass:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class A{ def bark(){ 'A: invoked bark()' } def invokeMethod(String name, Object args){ "A: missing $name(${args.join(', ')})" } } def a= new A() assert a.bark() == 'A: invoked bark()' assert a.bleet() == 'A: missing bleet()' </pre></td></tr></table> <p>We can create our own MetaClass which wraps around the existing one. DelegatingMetaClass provides the infrastructure for this, so we only need extend it with our own logic. We can do so on an instance-by-instance basis:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> public class MyMetaClass extends DelegatingMetaClass{ MyMetaClass(Class theClass){ super(theClass) } Object invokeMethod(Object object, String methodName, Object[] arguments){ "MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}" } } public class MyOtherMetaClass extends DelegatingMetaClass{ MyOtherMetaClass(Class theClass){ super(theClass) } Object invokeMethod(Object object, String methodName, Object[] arguments){ "MyOtherMetaClass: ${super.invokeMethod(object, methodName, arguments)}" } } class A{ def bark(){ 'A: invoked bark()' } def invokeMethod(String name, Object args){ "A: missing $name(${args.join(', ')})" } } def amc= new MyMetaClass(A) amc.initialize() def a= new A() a.metaClass= amc //using metaClass property on an instance affects only that instance... def amc2= new MyOtherMetaClass(A) amc2.initialize() def a2= new A() a2.metaClass= amc2 assert a.bark() == 'MyMetaClass: A: invoked bark()' assert a2.bark() == 'MyOtherMetaClass: A: invoked bark()' Thread.start{ //...even in a new thread assert a.bark() == 'MyMetaClass: A: invoked bark()' assert a2.bark() == 'MyOtherMetaClass: A: invoked bark()' } assert new A().bark() == 'A: invoked bark()' //new instances don't have new MetaClass assert a.bleet() == 'A: missing bleet()' //MetaClass invokeMethod() NOT called here </pre></td></tr></table> <p>Or we can do so on a class-wide basis:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> public class MyMetaClass extends DelegatingMetaClass{ MyMetaClass(Class theClass){ super(theClass) } Object invokeMethod(Object object, String methodName, Object[] arguments){ "MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}" } } class A{ def bark(){ 'A: invoked bark()' } def invokeMethod(String name, Object args){ "A: missing $name(${args.join(', ')})" } } def amc= new MyMetaClass(A) amc.initialize() def a= new A() import org.codehaus.groovy.runtime.InvokerHelper InvokerHelper.instance.metaRegistry.setMetaClass(A, amc) //all newly-created instances of A after this call will be affected assert a.bark() == 'A: invoked bark()' //created before so old MetaClass used assert a.bleet() == 'A: missing bleet()' assert new A().bark() == 'MyMetaClass: A: invoked bark()' //new MetaClass used Thread.start{ assert a.bark() == 'A: invoked bark()' //old MetaClass used assert new A().bark() == 'MyMetaClass: A: invoked bark()' //new MetaClass used } </pre></td></tr></table> <p>Classes we define ourselves return a MetaClass when accessing the metaClass property, but many Groovy-supplied classes don't. There's only one instance of a MetaClass in such cases:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class A{} assert new A().metaClass.class == MetaClassImpl assert new ArrayList().metaClass.class == ArrayList //class itself returned </pre></td></tr></table> <p>When we use Groovy-supplied classes without their own MetaClass, both already-created and newly-created classes are affected by changes to the MetaClass:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> public class MyMetaClass extends DelegatingMetaClass{ MyMetaClass(Class theClass){ super(theClass) } Object invokeMethod(Object object, String methodName, Object[] arguments){ "MyMetaClass: ${super.invokeMethod(object, methodName, arguments)}" } } def amc= new MyMetaClass(ArrayList) amc.initialize() def list1= [1, 2, 3] import org.codehaus.groovy.runtime.InvokerHelper InvokerHelper.instance.metaRegistry.setMetaClass(ArrayList, amc) //all instances of ArrayList will be affected, even already created ones assert list1.join(',') == 'MyMetaClass: 1,2,3' //new MetaClass used with already created ArrayList def list2= [4, 5, 6] assert list2.join(',') == 'MyMetaClass: 4,5,6' //new MetaClass used with newly created ArrayList //even in new Thread... Thread.start{ assert list1.join(',') == 'MyMetaClass: 1,2,3' //new MetaClass used assert list2.join(',') == 'MyMetaClass: 4,5,6' //new MetaClass used assert [7, 8, 9].join(',') == 'MyMetaClass: 7,8,9' //new MetaClass used } </pre></td></tr></table> <p>Other methods besides invokeMethod are available on the MetaClass:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> Object invokeStaticMethod(Object object, String methodName, Object[] arguments) Object invokeConstructor(Object[] arguments) Object getProperty(Object object, String property) void setProperty(Object object, String property, Object newValue) Object getAttribute(Object object, String attribute) void setAttribute(Object object, String attribute, Object newValue) Class getTheClass() </pre></td></tr></table> <p>For example, making the constructor return an instance of something other than what we called the constructor on:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> public class MyMetaClass extends DelegatingMetaClass{ MyMetaClass(Class theClass){ super(theClass) } Object invokeConstructor(Object[] arguments){ [] } } class A{} def amc= new MyMetaClass(A) amc.initialize() import org.codehaus.groovy.runtime.InvokerHelper InvokerHelper.instance.metaRegistry.setMetaClass(A, amc) def a= new A() assert a.class == ArrayList assert ( a << 1 << 2 << 3 ).size() == 3 </pre></td></tr></table> <h3>ExpandoMetaClass</h3> <p>There's some easy-to-use facilities available through the MetaClass, known as ExpandoMetaClass, to which we can add properties and methods easily:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class A{ String text } def a1= new A(text: 'aBCdefG') assert a1.metaClass.adaptee.class == MetaClassImpl //usual MetaClass type A.metaClass.inSameCase= {-> text.toUpperCase()} //triggers conversion of MetaClass of A to ExpandoMetaClass //then adds new instance method 'inUpperCase' to class def a2= new A(text: 'hiJKLmnOp') assert a2.metaClass.adaptee.getClass() == ExpandoMetaClass //MetaClass of A changed for instances created after conversion trigger only assert a2.inSameCase() == 'HIJKLMNOP' assert a1.metaClass.adaptee.class == MetaClassImpl //still usual MetaClass type try{ println a1.inSameCase(); assert false } catch(e){ assert e in MissingMethodException } //new method not available A.metaClass.inLowerCase= {-> text.toLowerCase()} assert a2.inLowerCase() == 'hijklmnop' //we can replace the method definition with another A.metaClass.inSameCase= {-> text.toLowerCase()} assert a2.inSameCase() == 'hijklmnop' //we can add static methods... A.metaClass.'static'.inSameCase= { it.toLowerCase()} assert A.inSameCase('qRStuVwXyz') == 'qrstuvwxyz' </pre></td></tr></table> <p>We can also add properties and constructors:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class A{} //we can let ExpandoMetaClass manage the properties... A.metaClass.character = 'Cat in the Hat' //add property 'character' def a1= new A() assert a1.character == 'Cat in the Hat' //...or we can manage the properties ourselves... def ourProperties = Collections.synchronizedMap([:]) //see tutorial on Multi-Threading to learn about synchronized objects A.metaClass.setType= { String value -> ourProperties[ "${delegate}Type" ] = value } A.metaClass.getType= {-> ourProperties[ "${delegate}Type" ] } a1.type= 'Hatted Cat' assert a1.type == 'Hatted Cat' //we can add our own constructors... def a2= new A() A.metaClass.constructor= {-> new A() } try{ a2= new A() //be careful when overriding default or existing constructors assert false }catch(Error e){ assert e in StackOverflowError } A.metaClass.constructor= {-> new A() } try{ A.metaClass.constructor << {-> new A() } // << notation doesn't allow overriding assert false }catch(e){ assert e in GroovyRuntimeException } A.metaClass.constructor= { String s-> new A(character: s) } a2 = new A("Thing One") //We can quote method and property names... A.metaClass.'changeCharacterToThingTwo'= {-> delegate.character = 'Thing Two' } a2.character= 'Cat in the Hat' a2.changeCharacterToThingTwo() assert a2.character == 'Thing Two' //...which is handy for dynamically constructing method/property names... ['Hatted Cat', 'Thing', 'Boy', 'Girl', 'Mother'].each{p-> A.metaClass."changeTypeTo${p}"= {-> delegate.type= p} } a2.changeTypeToBoy() assert a2.type == 'Boy' a2.'changeTypeToHatted Cat'() assert a2.type == 'Hatted Cat' </pre></td></tr></table> <p>We can also add methods for supplied Groovy classes, ones we don't define ourselves:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> ExpandoMetaClass.enableGlobally() //call 'enableGlobally' method before adding to supplied class List.metaClass.sizeDoubled = {-> delegate.size() * 2 } //add method to an interface def list = [] << 1 << 2 assert list.sizeDoubled() == 4 </pre></td></tr></table> <p>We can override MetaClass class methods such as 'invokeMethod' and 'getProperty' using ExpandoMetaClass's easy syntax:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> class Bird{ def name= 'Tweety' def twirp(){ 'i taught i saw a puddy cat' } } Bird.metaClass.invokeMethod= {name, args-> def metaMethod= Bird.metaClass.getMetaMethod(name, args) //'getMetaMethod' gets method, which may be an added or an existing one metaMethod? metaMethod.invoke(delegate,args): 'no such method' } def a= new Bird() assert a.twirp() == 'i taught i saw a puddy cat' assert a.bleet() == 'no such method' Bird.metaClass.getProperty= {name-> def metaProperty= Bird.metaClass.getMetaProperty(name) //'getMetaProperty' gets property, which may be an added or an existing one metaProperty? metaProperty.getProperty(delegate): 'no such property' } def b= new Bird() assert b.name == 'Tweety' assert b.filling == 'no such property' </pre></td></tr></table>
Please type the word appearing in the picture.
Attachments
Labels
Location
Watch this page
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced