Groovy 2.0 brought us extension modules. An extension module is a JAR file with classes that provide extra methods to existing other classes like in the JDK or third-party libraries. Groovy uses this mechanism to add for example extra methods to the File
class. We can implement our own extension module to add new extension methods to existing classes. Once we have written the module we can add it to the classpath of our code or application and all the new methods are immediately available.
We define the new extension methods in helper classes, which are part of the module. We can create instance and static extension methods, but we need a separate helper class for each type of extension method. We cannot mix static and instance extension methods in one helper class. First we create a very simple class with an extension method for the String
class. The first argument of the extension method defines the type or class we want the method to be added to. The following code shows the method likeAPirate
. The extension method needs to be public
and static
even though we are creating an instance extension method.
// File: src/main/groovy/com/mrhaki/groovy/PirateExtension.groovy package com.mrhaki.groovy class PirateExtension { static String likeAPirate(final String self) { // List of pirate language translations. def translations = [ ["hello", "ahoy"], ["Hi", "Yo-ho-ho"], ['are', 'be'], ['am', 'be'], ['is', 'be'], ['the', "th'"], ['you', 'ye'], ['your', 'yer'], ['of', "o'"] ] // Translate the original String to a // pirate language String. String result = self translations.each { translate -> result = result.replaceAll(translate[0], translate[1]) } result } }
Next we need to create an extension module descriptor file. In this file we define the name of the helper class, so Groovy will know how to use it. The descriptor file needs to be placed in the META-INF/services
directory of our module archive or classpath. The name of the file is org.codehaus.groovy.runtime.ExtensionModule
. In the file we define the name of our module, version and the name of the helper class. The name of the helper class is defined with the property extensionClasses
:
# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = pirate-module moduleVersion = 1.0 extensionClasses = com.mrhaki.groovy.PirateExtension
Now are extension module is ready. The easiest way to distribute the module is by packaging the code and descriptor file in a JAR file and put it in a artifact repository manager. Other developers can then use build tools like Gradle or Maven to include the extension module in their projects and applications. If we use Gradle to create a JAR file we only needs this small build script:
// File: build.gradle apply plugin: 'groovy' repositories.mavenCentral() dependencies { // Since Gradle 1.4 we don't use the groovy configuration // to define dependencies. We can simply use the // compile and testCompile configurations. compile 'org.codehaus.groovy:groovy-all:2.0.6' }
Now we can invoke $ gradle build
and we got ourselves an extension module.
Let's add a test for our new extension method. Because we use Gradle the test classpath already will contain our extension module helper class and descriptor file. In our test we can simply invoke the method and test the results. We are going to use Spock to write a simple specification:
// File: src/test/groovy/com/mrhaki/groovy/PirateExtensionSpec.groovy package com.mrhaki.groovy import spock.lang.Specification class PirateExtensionSpec extends Specification { def "likeAPirate method should work as instance method on a String value"() { given: final String originalText = "Hi, Groovy is the greatest language of the JVM." expect: originalText.likeAPirate() == "Yo-ho-ho, Groovy be th' greatest language o' th' JVM." } }
We add the dependency to Spock in our Gradle build file:
// File: build.gradle apply plugin: 'groovy' repositories.mavenCentral() dependencies { // Since Gradle 1.4 we don't use the groovy configuration // to define dependencies. We can simply use the // compile and testCompile configurations. compile 'org.codehaus.groovy:groovy-all:2.0.6' testCompile 'org.spockframework:spock-core:0.7-groovy-2.0' }
We can run $ gradle test
to run the Spock specification and test our new extension method.
To add a static method to an existing class we need to add an extra helper class to our extension module and an extra property to our descriptor file to register the helper class. The first argument of the extension method define the type we want to add a static method to. In the following helper class we add the extension method talkLikeAPirate()
to the String
class.
// File: src/main/groovy/com/mrhaki/groovy/PirateStaticExtension.groovy package com.mrhaki.groovy class PirateStaticExtension { static String talkLikeAPirate(final String type) { "Arr, me hearty," } }
We change the descriptor file and add the staticExtensionClasses
property:
# File: src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = pirate-module moduleVersion = 1.0 extensionClasses = com.mrhaki.groovy.PirateExtension staticExtensionClasses = com.mrhaki.groovy.PirateStaticExtension
In our Spock specification we add an extra test for our new static method talkLikeAPirate()
on the String
class:
// File: src/test/groovy/com/mrhaki/groovy/PirateExtensionSpec.groovy package com.mrhaki.groovy import spock.lang.Specification class PirateExtensionSpec extends Specification { def "likeAPirate method should work as instance method on a String value"() { given: final String originalText = "Hi, Groovy is the greatest language of the JVM." expect: originalText.likeAPirate() == "Yo-ho-ho, Groovy be th' greatest language o' th' JVM." } def "talkLikeAPirate method should work as static method on String class"() { expect: "Arr, me hearty, Groovy rocks!" == String.talkLikeAPirate() + " Groovy rocks!" } }
Written with Groovy 2.1