Using Kotlin extensions in Groovy

Extensions in Kotlin and GroovyKotlin and Groovy have mechanisms for extending existing classes without using inheritance or decorators. In both languages, the mechanisms are called extension methods. Their source code looks different, but generated by…

Using Kotlin extensions in Groovy

Extensions in Kotlin and Groovy

Kotlin and Groovy have mechanisms for extending existing classes without using inheritance or decorators. In both languages, the mechanisms are called extension methods. Their source code looks different, but generated bytecode is quite similar. Thanks to that, Groovy is able to use Kotlin extensions just like its own.

Why would I want to use such extensions in Groovy? The main reason is that I want to test my extensions using the best testing framework available for the JVM – Spock Framework.

Code is available here.

Extensions in Kotlin

There are many types of extensions in Kotlin. I decided to focus only on extension functions and properties.

As an example, I extend the java.lang.String class. First, I create an extension function skipFirst, which skips first N characters:

fun String.skipFirst(n: Int) = if (length > n) this.substring(n) else ""

 

Next, I create an extension property answer, which is the Answer to the Ultimate Question of Life, the Universe, and Everything:

val String.answer
    get() = 42

Both extensions are declared in package com.github.alien11689.extensions, in file called StringExtensions. However, the generated class in target directory is named StringExtensionsKt and this is the name that must be used when accessing from other languages. Specific class name can be forced by annotation @file:JvmName.

Using Kotlin extensions in Groovy

There are two ways for using extensions in Groovy that are supported by good IDEs. First, you can declare scope where the extensions are available by use method:

def "should use extension method"() {
    expect:
        use(StringExtensionsKt) {
            input.skipFirst(n) == expected
        }
    where:
        input  | n | expected
        "abcd" | 3 | "d"
        "abcd" | 6 | ""
        ""     | 3 | ""
}

def "should use extension property"() {
    expect:
        use(StringExtensionsKt) {
            "abcd".answer == 42
        }
}

It is acceptable, but is not very convenient. The second and much better way is to use an extension module definition. The extension module is defined in file org.codehaus.groovy.runtime.ExtensionModule in directory src/main/resources/META-INF/services/. The same directory is monitored by ServiceLoader, but the file format is completely different:

moduleName=string-extension-module
moduleVersion=1.0.0
extensionClasses=com.github.alien11689.extensions.StringExtensionsKt

The tests look much better now:

def "should use extension method"() {
    expect:
        input.skipFirst(n) == expected
    where:
        input  | n | expected
        "abcd" | 3 | "d"
        "abcd" | 6 | ""
        ""     | 3 | ""
}

def "should use extension property"() {
    expect:
        "abcd".answer == 42
}
You May Also Like

New HTTP Logger Grails plugin

I've wrote a new Grails plugin - httplogger. It logs:

  • request information (url, headers, cookies, method, body),
  • grails dispatch information (controller, action, parameters),
  • response information (elapsed time and body).

It is mostly useful for logging your REST traffic. Full HTTP web pages can be huge to log and generally waste your space. I suggest to map all of your REST controllers with the same path in UrlMappings, e.g. /rest/ and configure this plugin with this path.

Here is some simple output just to give you a taste of it.

17:16:00,331 INFO  filters.LogRawRequestInfoFilter  - 17:16:00,340 INFO  filters.LogRawRequestInfoFilter  - 17:16:00,342 INFO  filters.LogGrailsUrlsInfoFilter  - 17:16:00,731 INFO  filters.LogOutputResponseFilter  - >> #1 returned 200, took 405 ms.
17:16:00,745 INFO filters.LogOutputResponseFilter - >> #1 responded with '{count:0}'
17:18:55,799 INFO  filters.LogRawRequestInfoFilter  - 17:18:55,799 INFO  filters.LogRawRequestInfoFilter  - 17:18:55,800 INFO  filters.LogRawRequestInfoFilter  - 17:18:55,801 INFO  filters.LogOutputResponseFilter  - >> #2 returned 404, took 3 ms.
17:18:55,802 INFO filters.LogOutputResponseFilter - >> #2 responded with ''

Official plugin information can be found on Grails plugins website here: http://grails.org/plugins/httplogger or you can browse code on github: TouK/grails-httplogger.