Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

MetaProgramming with Groovy I

DZone's Guide to

MetaProgramming with Groovy I

· Java Zone
Free Resource

Just released, a free O’Reilly book on Reactive Microsystems: The Evolution of Microservices at Scale. Brought to you in partnership with Lightbend.

Groovy is a dynamic language for the JVM not only because it supports dynamic typing (and static typing as well) but because it lets you modify and enhance classes with new methods and properties at runtime. The first part of this series lays out the basics of getting started with Groovy's meta programming.

Introduction

You probably know by now that Groovy is a language for the JVM that has great integration with the Java language and platform, you can even write Groovy using Java's syntax (see From Java to Groovy in a few easy steps) and that among other things it lets you write some wacky things like the following

// look ma, I'm calling a method on an integer!
3.times { println "hello!" }

List aList = []
// what's that wacky operator doing to aList ?
aList << "a string"
assert aList[0] == "a string"

Yes, Groovy let's you do wacky things like calling methods on an integer and operator overloading. But how does it do it? last time I checked java.lang.Integer is declared final, how can methods be added to Integer instances then? Well it turns out Groovy has something called Meta Object Protocol which allows what you have seen and more. Every object in groovy (not matter if it comes from a java class or a groovy class) has a MetaClass, responsible for holding information about the object's class. Everytime you invoke a method on an object, Groovy's dispatch mechanism routes the call through the MetaClass related to the object, so if you can alter the MetaClass in any way you may change the object's behavior at runtime, and to do that Groovy has offers a couple of options, let's explore the first one: categories.

Categorize me this

Categories are very simple things really, they can be any class that adhere to the following conventions

  • any public static method is a candidate to be a category method
  • the type of the first parameter is the target type that may have the new methods
  • there is no need to extend a particular class or implement an interface

Alright let's follow the rules and see what comes up

class Pouncer {
static pounces( Integer self ){
(0..<self).inject("") { s, n ->
s += "boing! "
}
}
}

use( Pouncer ) {
assert 3.pounces() == "boing! boing! boing! "
}

The magic part happens in lines 9 through 11, where we use a method available to all objects use(). You can use any class (or a list of classes for that matter) as a parameter for use(), the point is that all methods on that class are candidates to be categorized methods if they follow the afore mentioned rules, in our case the method pounces() will be added to Integer because it is static and the first parameter is of type Integer. Calling pounce() on an integer yields a string with as many "boing !" strings concatenated in a single string, which starts as an empty string. The pounces() methods will be available only in the scope of the use() block, if you try to call it before or after the block you'll get an exception.

Oh yes, lines 3 to 5, that is where the other part of the magic happens. If you are not familiar with some of Groovy's data types and operators (0..<self) is an exclusive range from integer 0 to integer (self - 1). Ranges have many methods in common with Lists, one of them being inject(), which takes two parameters: an starting value and a closure. It's behavior is to iterate over all values of the range, using the starting value as seed, and applying the closure to each element; you will usually return the updated value based on the seeded one, in order to be processed by the next element in the subsequent iteration.

Here is another example of a regular class used as a category. The Apache Commons project hosts many useful small projects, commons-lang being one of them. Commons-lang has some utility classes like ClassUtils and WordUtils that provide methods many think should be in the JDK. (Note: the commons project was started before jdk 1.4 appeared, some methods did make it into later versions of the JDK). Those utility classes are a collection of static methods, making them ideal for categorization.

import org.apache.commons.lang.ClassUtils
import org.apache.commons.lang.StringUtils
import org.apache.commons.lang.WordUtils

use( ClassUtils, StringUtils, WordUtils ) {
assert "java.lang.Class".packageName == "java.lang"
assert "java.lang.Class".shortClassName == "Class"
assert Map.Entry.isInnerClass()

assert "CamelCase".swapCase() == "cAMELcASE"
assert "ALL CAPS".uncapitalize() == "aLL cAPS"
assert "GROOVY JAVA".initials() == "GJ"

assert "FOO_BAR_BAZ".split("_").collect { word ->
word.toLowerCase().capitalize()
}.join("") == "FooBarBaz"
}

 

The Catch

Categories add methods to classes for a limited period of time using a ThreadLocal variable to store the new methods, so using them in a multithread environment is not a good choice, you would need a more lasting and performant solution. You also require a call to use(), there is no transparent way to work with categories without it, unless you wrap your code in a manner that when your app boostraps it runs inside a category block (which is somewhat how groovlets work).

You are probably wondering if ranges have many methods in common with lists they probably share some new ones as inject(), and what's more I previously stated that all objects have an use() method. Well those methods belong to what is called the GDK. These methods are also available through metaclasses, in fact you can even override most of them using categories if needed, the difference strives that those methods are added in another way, when the Groovy runtime boostraps, so they are always available.

Conclusion

Categories are a simple way to start your meta programming experiments with Groovy, in the next part we will explore ExpandoMetaClass, a more powerful and configurable way to enhance your classes at runtime.

 

Strategies and techniques for building scalable and resilient microservices to refactor a monolithic application step-by-step, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}