5 min read

(For more resources on Groovy DSL, see here.)

In a nutshell, the term metaprogramming refers to writing code that can dynamically change its behavior at runtime. A Meta-Object Protocol (MOP) refers to the capabilities in a dynamic language that enable metaprogramming. In Groovy, the MOP consists of four distinct capabilities within the language: reflection, metaclasses, categories, and expandos.

The MOP is at the core of what makes Groovy so useful for defining DSLs. The MOP is what allows us to bend the language in different ways in order to meet our needs, by changing the behavior of classes on the fly. This section will guide you through the capabilities of MOP.

Reflection

To use Java reflection, we first need to access the Class object for any Java object in which are interested through its getClass() method. Using the returned Class object, we can query everything from the list of methods or fields of the class to the modifiers that the class was declared with. Below, we see some of the ways that we can access a Class object in Java and the methods we can use to inspect the class at runtime.

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Reflection {
public static void main(String[] args) {
String s = new String();
Class sClazz = s.getClass();
Package _package = sClazz.getPackage();
System.out.println("Package for String class: ");
System.out.println(" " + _package.getName());
Class oClazz = Object.class;
System.out.println("All methods of Object class:");
Method[] methods = oClazz.getMethods();
for(int i = 0;i System.out.println(" " + methods[i].getName());
try {
Class iClazz = Class.forName("java.lang.Integer");
Field[] fields = iClazz.getDeclaredFields();
System.out.println("All fields of Integer class:");
for(int i = 0; i System.out.println(" " + fields[i].getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

We can access the Class object from an instance by calling its Object.getClass() method. If we don’t have an instance of the class to hand, we can get the Class object by using .class after the class name, for example, String.class. Alternatively, we can call the static Class.forName, passing to it a fully-qualified class name.

Class has numerous methods, such as getPackage(), getMethods(), and getDeclaredFields() that allow us to interrogate the Class object for details about the Java class under inspection. The preceding example will output various details about String, Integer, and Double.

Metaprogramming and the Groovy MOP

Groovy Reflection shortcuts

Groovy, as we would expect by now, provides shortcuts that let us reflect classes easily. In Groovy, we can shortcut the getClass() method as a property access .class, so we can access the class object in the same way whether we are using the class name or an instance. We can treat the .class as a String, and print it directly without calling Class.getName(), as follows:

Metaprogramming and the Groovy MOP

The variable greeting is declared with a dynamic type, but has the type java.lang.String after the “Hello” String is assigned to it. Classes are first class objects in Groovy so we can assign String to a variable. When we do this, the object that is assigned is of type java.lang.Class. However, it describes the String class itself, so printing will report java.lang.String.

Groovy also provides shortcuts for accessing packages, methods, fields, and just about all other reflection details that we need from a class. We can access these straight off the class identifier, as follows:

println "Package for String class"
println " " + String.package
println "All methods of Object class:"
Object.methods.each { println " " + it }
println "All fields of Integer class:"
Integer.fields.each { println " " + it }

Incredibly, these six lines of code do all of the same work as the 30 lines in our Java example. If we look at the preceding code, it contains nothing that is more complicated than it needs to be. Referencing String.package to get the Java package of a class is as succinct as you can make it. As usual, String.methods and String.fields return Groovy collections, so we can apply a closure to each element with the each method. What’s more, the Groovy version outputs a lot more useful detail about the package, methods, and fields.

Metaprogramming and the Groovy MOP

When using an instance of an object, we can use the same shortcuts through the class field of the instance.

def greeting = "Hello"
assert greeting.class.package == String.package

Expandos

An Expando is a dynamic representation of a typical Groovy bean. Expandos support typical get and set style bean access but in addition to this they will accept gets and sets to arbitrary properties. If we try to access, a non-existing property, the Expando does not mind and instead of causing an exception it will return null. If we set a non-existent property, the Expando will add that property and set the value. In order to create an Expando, we instantiate an object of class groovy.util.Expando.

def customer = new Expando()

assert customer.properties == [:]
assert customer.id == null

assert customer.properties == [:]
customer.id = 1001
customer.firstName = "Fred"
customer.surname = "Flintstone"
customer.street = "1 Rock Road"

assert customer.id == 1001

assert customer.properties == [
id:1001, firstName:'Fred',
surname:'Flintstone', street:'1 Rock Road']customer.properties.each { println it }

The id field of customer is accessible on the Expando shown in the preceding example even when it does not exist as a property of the bean. Once a property has been set, it can be accessed by using the normal field getter: for example, customer.id. Expandos are a useful extension to normal beans where we need to be able to dump arbitrary properties into a bag and we don’t want to write a custom class to do so.

A neat trick with Expandos is what happens when we store a closure in a property. As we would expect, an Expando closure property is accessible in the same way as a normal property. However, because it is a closure we can apply function call syntax to it to invoke the closure. This has the effect of seeming to add a new method on the fly to the Expando.

customer.prettyPrint = {
println "Customer has following properties"
customer.properties.each {
if (it.key != 'prettyPrint')
println " " + it.key + ": " + it.value
}
}

customer.prettyPrint()

Here we appear to be able to add a prettyPrint() method to the customer object, which outputs to the console:

Customer has following properties
surname: Flintstone
street: 1 Rock Road
firstName: Fred
id: 1001


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here