Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

Extension modules for extension methods

What is an extension method?

Since version 2.0, Groovy supports the notion of extension module. Basically, an extension module allows you to plugin extension methods to existing classes, just like regular Groovy does. For example, when you write:

Code Block
titleStandard extension method
linenumberstrue
languagegroovy
def file = new File(...)
def contents = file.getText('utf-8')

The getText method doesn't exist on the File class. However, Groovy knows it because it is defined in a special class ResourceGroovyMethods (formely, the method was defined in DefaultGroovyMethods):

Code Block
titleResourceGroovyMethods.java
languagejava
public static String getText(File file, String charset) throws IOException {
	return IOGroovyMethods.getText(newReader(file, charset));
}

You may notice that the extension method is defined using a static method in a "helper" class (where various extension methods are defined). The first argument of the getText method corresponds to the receiver, while additional parameters correspond to the arguments of the extension method. So here, we are defining a method called getText on the File class (because the first argument is a File), which takes a single argument as a parameter (the encoding String).

Definining your own module

Concept

Thanks to improved modularity, Groovy 2 now adds support for your custom extension methods. For that, you only have to write two things:

  1. your extension class, just like DefaultGroovyMethods, IOGroovyMethods, ...
  2. a descriptor file for your module

Then you just need to pack your extension module into a jar file and add it to the classpath! Groovy will automatically recognize the module at startup.

Writing your extension class

As we saw before, adding methods to an existing class is as simple as writing a helper class which will only contain static methods. Imagine we want to add a method called reverseToUpperCase on the String class. This method would reverse the string then turn it to upper case. Then, you can write the following helper class:

Code Block
titleStringExtension
languagejava
package com.example;
 
/**
 * An extension class for the String class.
 */
public class StringExtension {
   public static String reverseToUpperCase(String self) {
      StringBuilder sb = new StringBuilder(self);
      sb.reverse();
      return sb.toString().toUpperCase();
   }
}

That's all! Now imagine that you want to extend a class with a static method. For example, we would like to add an randomUUID() method to the String class which would return a random UUID string. Then, we must declare another extension class. The important thing to understand is that instance extension methods and static extension methods must be defined in two separate files. The only thing that will make a difference is the descriptor (see below). Then, the static extension helper class would look like this:

Code Block
titleStringStaticExtension.java
languagejava
import java.util.UUID;


/**
 * Static extension methods for the String class
 */
public class StringStaticExtension {
    public static String randomUUID(String selfType) {
        return UUID.randomUUID().toString();
    }
}

The module descriptor

For Groovy to be able to load your extension methods, you must declare your extension helper classes. You must create a file named org.codehaus.groovy.runtime.ExtensionModule into the META-INF/services directory:

Code Block
titleorg.codehaus.groovy.runtime.ExtensionModule
languagenone
moduleName=string-module
moduleVersion=1.0-test
extensionClasses=com.example.StringExtension
staticExtensionClasses=com.example.StringStaticExtension

The module descriptor requires 4 keys:

  • moduleName : the name of your module
  • moduleVersion: the version of your module. Note that version number is only used to check that you don't load the same module in two different versions.
  • extensionClasses: the list of extension helper classes for instance methods. You can provide several classes, given that they are comma separated.
  • staticExtensionClasses: the list of extension helper classes for static methods. You can provide several classes, given that they are comma separated.

Note that it is not required for a module to define both static helpers and instance helpers, and that you may add several classes to a single module. You can also extend different classes in a single module without problem. It is even possible to use different classes in a single extension class, but it is recommended to group extension methods into classes by feature set.

Advanced modules

Technically, the previous description about how to add extension methods to an existing class is a user-friendly way of doing it, but Groovy doesn't force you to use this standard mechanism. If you want to, you can write your own extension module implementation class, given that a module returns a list of MetaMethods to be added to a class. However, implementing such a module is not easy, and won't be covered by this tutorial. If you need to do that, please refer to the org.codehaus.groovy.runtime.m12n.ExtensionModule and org.codehaus.groovy.runtime.m12n.ExtensionModuleRegistry classes.

FAQ

Are extension modules compatible with type checking?

Yes, if the extension descriptor and the extension class is found on compilation classpath.

Are extension modules compatible with static compilation ?

Yes, if the extension descriptor and the extension class is found on compilation classpath.

When I compile my project using Gradle and @CompileStatic, my code using an extension module fails compilation.

As of Gradle 1.4, Gradle's native Groovy compiler integration doesn't seem to be compatible with extension modules. A fix is available in Groovy 2.1.2. As a workaround, you can use the Ant compiler integration:

Code Block
compileGroovy.groovyOptions.useAnt = true
compileTestGroovy.groovyOptions.useAnt = true

See https://jira.codehaus.org/browse/GROOVY-6008