Skip to content

Toro Cloud Dev Center


Creating and registering custom Groovy extension modules

A Groovy extension module allows you to add new methods to existing classes, including classes which are precompiled, like classes from the JDK.1 With extension modules, you can conveniently extend the behavior of an existing class without changing the definition of the class.

Groovy itself ships with a default extension module and it is because of this extension module that you are able to use code like the following:

1
2
3
assert [1, 2, 3, 4, 5, 6].sum() == 21
assert ['a', 'b', 'c', 'd', 'e'].sum() == 'abcde'
assert [['a', 'b'], ['c', 'd']].sum() == ['a', 'b', 'c', 'd']

The sum() method is not defined in the ArrayList class

At first glance, sum() may look like an instance method but it is in fact a public static method with multiple implementations found in the DefaultGroovyMethods class, where many default extension methods are defined.

Groovy's default extension module adds a suite of extra methods to Files, Collections, Maps, Strings, primitive arrays, and more.

Groovy's enhancements on the JDK

Learn more about Groovy's changes to the JDK by visiting their API documentation.

In addition to what Groovy offers by default, Martini also provides other extension modules intended to help you code and build your applications faster. These extension modules contain the so-called 'functions' – handy utility methods that help solve common application problems in just one line of code. But aside from the extension modules provided by Groovy and Martini, you can also add your own. Continue reading the following section to learn how.

Procedure

To define and register your own extension module, you must:

  1. Create a new project2 and then create the class which will contain your extension methods. You can create multiple classes if you want.
  2. Create the extension module descriptor file.
  3. Pack the files you have created in a JAR.
  4. Store the JAR in your Martini package's lib directory.

Alternatively, you can opt to keep your extension module classes (their source code) in the Martini package3 where you will use it so you won't have to create a JAR file; all you need is to place the descriptor file in the META-INF/services directory.

Reuse Groovy extension modules from other projects

Groovy extension modules are not just a Martini thing. You can grab the extension module JAR of another project and plug it to a Martini package so you can use it in Martini. Likewise, you can get a copy of your extension module JAR from Martini and use it for your projects outside of Martini (assuming it has no dependencies on Martini classes).

Creating the extension module's classes and methods

All you need to do is create a class containing the methods you want to add. These methods must:

  • Have the modifiers public and static
  • Have at least one parameter
  • Ensure that the first parameter's type is also the class that will be extended

Check out other guides!

We recommend reading Hubert Klein Ikkink's (mrhaki) and Andre Steingress' blog posts about Groovy extension modules. Both articles thoroughly explain the process of creating a Groovy extension module.

To help you get started, here's an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package io.toro.martini

/**
  * A class containing functions for common math operations.
  */
class MathMethods {

    /**
     * <p>Computes the greatest common factor of two numbers.</p>
     * @param x the first number
     * @param y the second number
     * @return the greatest common factor
     */
    static int gcf(int x, int y) {
        return (y == 0) ? x.abs() : gcf(y, x % y);
    }

    /**
     * <p>Computes the greatest common factor of a given set of numbers.</p>
     * @param self the set of numbers
     * @return the greatest common factor
     * @throws NullPointerException if {@code self == null}
     * @throws IllegalArgumentException if {@code self.length < 2} or if all numbers are zeros
     */
    static int gcf(int... self) {
        Objects.requireNonNull(self, "Must provide numbers")
        if (self.length < 2) {
            throw new IllegalArgumentException("Must have at least two numbers")
        }

        return self.inject(0) { int result, int current -> gcf(result, current) }
    }

}

Where's the public modifier?

By default, Groovy considers classes and methods public. So you don’t have to use the public modifier everywhere something is public. Only if it’s not public should you put a visibility modifier.4

The class above defines two extension methods:

  • MathMethods.gcf(int, int)

    An extra method for the int class (inferred from the first parameter's type) for finding the greatest common factor of two integers.

  • MathMethods.gcf(int...)

    An extra method for the int[] class (inferred from the first parameter's type) for finding the greatest common factor of an array of integers.

Common practices

When creating extension module classes, it is common practice to:

  • Not define static and instance extension methods together in a single class
  • Create separate extension modules per target type

Creating the descriptor file

In the META-INF/services directory of your module archive or classpath, create the org.codehaus.groovy.runtime.ExtensionModule file. We will use this file to define the extension module, like so:

1
2
3
4
moduleName = extras-module
moduleVersion = 1.0
extensionClasses = io.toro.martini.MathMethods
staticExtensionClasses =

Multiple extension classes

You can add multiple extension classes on a single extension module. Simply delimit each class name with a comma.

If you want to add instance methods to the target type5, then use the extensionClasses property; if you want to add static methods instead, use the staticExtensionClasses property.

Creating the JAR

Typically, the JAR file creation process is tied to the project's lifecycle. For those using build tools, this can be as simple as executing a single build command. As an alternative, you can also rely on Java's native commands to package a JAR for you, or use IDEs capable of producing JARs using an export function.

Usage

After moving the JAR or extension module descriptor file under your Martini package's META-INF/services directory, you may now use your extension methods.

To use instance extension methods (like that seen in this example), call the method on an instance of the first parameter's type, like so:

1
2
144.gcf(32)
[144, 64, 32].gcf()

For static extension methods, you must call the method on the initial parameter's type (like you would normally call static methods); for example:

1
<Class>.method(<arguments...>)

  1. See extension modules

  2. A separate project that does not reside in Martini. 

  3. Particularly in the code directory. 

  4. See public by default

  5. The target type is the type of the extension method's first parameter.