June 19, 2018

Groovy Goodness: Add Map Constructor With Annotation

Since the early days of Groovy we can create POGO (Plain Old Groovy Objects) classes that will have a constructor with a Map argument. Groovy adds the constructor automatically in the generated class. We can use named arguments to create an instance of a POGO, because of the Map argument constructor. This only works if we don't add our own constructor and the properties are not final. Since Groovy 2.5.0 we can use the @MapConstrutor AST transformation annotation to add a constructor with a Map argument. Using the annotation we can have more options to customize the generated constructor. We can for example let Groovy generate the constructor with Map argument and add our own constructor. Also properties can be final and we can still use a constructor with Map argument.

First we look at the default behaviour in Groovy when we create a POGO:

// Simple POGO.
// Groovy adds Map argument
// constructor to the class.
class Person {
    String name
    String alias
    List<String> likes
}

// Create Person object using
// the Map argument constructor.
// We can use named arguments, 
// with the name of key being
// the property name. Groovy
// converts this to Map.
def mrhaki = 
    new Person(
        alias: 'mrhaki',
        name: 'Hubert Klein Ikkink',
        likes: ['Groovy', 'Gradle'])
        
assert mrhaki.alias == 'mrhaki'
assert mrhaki.name == 'Hubert Klein Ikkink'
assert mrhaki.likes == ['Groovy', 'Gradle']


// Sample class with already
// a constructor. Groovy cannot
// create a Map argument constructor now.
class Student {
    String name
    String alias
    
    Student(String name) {
        this.name = name
    }
}


import static groovy.test.GroovyAssert.shouldFail

// When we try to use named arguments (turns into a Map)
// in the constructor we get an exception.
def exception = shouldFail(GroovyRuntimeException) {
    def student = 
        new Student(
            name: 'Hubert Klein Ikkink', 
            alias: 'mrhaki')
}

assert exception.message.startsWith('failed to invoke constructor: public Student(java.lang.String) with arguments: []')
assert exception.message.endsWith('reason: java.lang.IllegalArgumentException: wrong number of arguments')

Now let's use the @MapConstructor annotation in our next example:

import groovy.transform.MapConstructor

@MapConstructor
class Person {
    final String name // AST transformation supports read-only properties.
    final String alias
    List<String> likes
}

// Create object using the Map argument constructor.
def mrhaki = 
    new Person(
        name: 'Hubert Klein Ikkink', 
        alias: 'mrhaki', 
        likes: ['Groovy', 'Gradle'])
        
assert mrhaki.name == 'Hubert Klein Ikkink'
assert mrhaki.alias == 'mrhaki'
assert mrhaki.likes == ['Groovy', 'Gradle']

// Using the annotation the Map argument
// constructor is added, even though we
// have our own constructor as well.
@MapConstructor
class Student {
    String name
    String alias
    
    Student(String name) {
        this.name = name
    }
}

def student = 
    new Student(
        name: 'Hubert Klein Ikkink', 
        alias: 'mrhaki')
        
assert student.name == 'Hubert Klein Ikkink'
assert student.alias == 'mrhaki'

The AST transformation supports several attributes. We can use the attributes includes and excludes to include or exclude properties that will get a value in the Map argument constructor. In the following example we see how we can use the includes attribute:

import groovy.transform.MapConstructor

@MapConstructor(includes = 'name')
class Person {
    final String name 
    final String alias
    List<String> likes
}

// Create object using the Map argument constructor.
def mrhaki = 
    new Person(
        name: 'Hubert Klein Ikkink', 
        alias: 'mrhaki', 
        likes: ['Groovy', 'Gradle'])
        
assert mrhaki.name == 'Hubert Klein Ikkink'
assert !mrhaki.alias 
assert !mrhaki.likes 

We can add custom code that is executed before or after the generated code by the AST transformation using the attributes pre and post. We assign a Closure to these attributes with the code that needs to be executed.

In the next example we set the pre attribute with code that calculates the alias property value if it is not set via the constructor:

// If alias is set in constructor use it, otherwise
// calculate alias value based on name value.
@MapConstructor(post = { alias = alias ?: name.split().collect { it[0] }.join() })
class Person {
    final String name // AST transformation supports read-only properties.
    final String alias
    List<String> likes
}

// Set alias in constructor.
def mrhaki = 
    new Person(
        name: 'Hubert Klein Ikkink', 
        alias: 'mrhaki', 
        likes: ['Groovy', 'Gradle'])
        
assert mrhaki.name == 'Hubert Klein Ikkink'
assert mrhaki.alias 
assert mrhaki.likes == ['Groovy', 'Gradle']

// Don't set alias via constructor.
def hubert = 
    new Person(
        name: 'Hubert A. Klein Ikkink')
        
assert hubert.name == 'Hubert A. Klein Ikkink'
assert hubert.alias == 'HAKI'
assert !hubert.likes

Written with Groovy 2.5.0.

June 15, 2018

Groovy Goodness: Implement Interface And Abstract Methods Automatically

A lot of new AST transformation annotations are added in Groovy 2.5.0. One of them is the @AutoImplement annotation. If we apply this annotation to our class dummy implementations for abstract methods in superclasses or methods in implemented interfaces are created. This can be useful to have something in place and then gradually write real implementations for the abstract or interface methods. The transformation will not alter any method that is already implemented by custom code.

When we apply the @AutoImplement annotation the default implementation for an abstract method from a superclass or method from a interface is simple. If the method has a return type the default value of that return type is returned. For example false for a boolean and null for an object type. But the @AutoImplement annotation has some attributes we can use to change the default implementation. We can set the exception attribute and assign a exception type. The implementation of the methods is than to throw that exception when the method is invoked. With the optional message attribute we can set the exception message. Finally we can use the code attribute to define a Closure with statements that will be called as the implementation of abstract and interface methods.

In the following example we have an interface Creator and create several classes that implement this interface and apply the @AutoImplement annotation with different attribute values:

import groovy.transform.AutoImplement
import static groovy.test.GroovyAssert.shouldFail

// Sample class with two simple properties.
@groovy.transform.Canonical
class Course {
    String name, location
}

// Interface with a single method to 
// create an object based on Map argument.
interface Creator<R> {
    R create(Map args)
}


// Use AutoImplement annotation to create object
// implementing the Creator interface. The compiled
// class will have an implementation for the create
// method. The return value is the default of the
// return type, in our case null.
@AutoImplement
class DefaultCourseCreator implements Creator<Course> { }

def defaultCreator = new DefaultCourseCreator()
assert defaultCreator.create(name: 'Groovy', location: 'Tilburg') == null


// When we use the AutoImplement annotation, only
// methods that are not implemented by ourselves
// will have an auto implementation.
@AutoImplement
class ImplCourseCreator implements Creator<Course> { 
    Course create(Map args) {
        new Course(args)
    }
}

def creator = new ImplCourseCreator()
assert creator.create(name: 'Groovy', location: 'Tilburg') == new Course(name: 'Groovy', location: 'Tilburg')


// Instead of returning the default value for the return type,
// we can throw an exception as default implementation.
// We can specify the type of exception and optionally the
// exception message.
@AutoImplement(exception = UnsupportedOperationException, 
               message = 'Not supported by NotSupportedCourseCreator')
class NotSupportedCourseCreator implements Creator<Course> { }

def creatorUnsupported = new NotSupportedCourseCreator()

def exception = shouldFail(UnsupportedOperationException) {
    creatorUnsupported.create(name: 'Groovy 101')
}
assert exception.message == 'Not supported by NotSupportedCourseCreator'


// We can use the code attribute of the AutoImplement annotation to
// specify a Closure. The Closure is used as implementation for the
// method from the interface.
// In this case we log a warning and return null as value. Notice
// we can access the log variable added by the Log annotation in 
// the Closure of the code attribute.
@groovy.util.logging.Log
@AutoImplement(code = { log.warning('Method needs implementation'); return null })
class ImplementCodeCourseCreator implements Creator<Course> { }

def creatorWarning = new ImplementCodeCourseCreator()
assert !creatorWarning.create(name: 'Groovy') // Prints log message "WARNING: Method needs implementation"

Written with Groovy 2.5.0.

June 14, 2018

Groovy Goodness: Customizing JSON Output

Groovy 2.5.0 adds the possibility to customize JSON output via a JsonGenerator instance. The easiest way to turn an object into a JSON string value is via JsonOutput.toJson. This method uses a default JsonGenerator with sensible defaults for JSON output. But we can customize this generator and create JSON output using the custom generator. To create a custom generator we use a builder accessible via JsonGenerator.Options. Via a fluent API we can for example ignore fields with null values in the output, change the date format for dates and ignore fields by their name or type of the value. And we can add a custom converter for types via either an implementation of the conversion as Closure or implementation of the JsonGenerator.Converter interface. To get the JSON string we simple invoke the toJson method of our generator.

In the following example Groovy code we have a Map with data and we want to convert it to JSON. First we use the default generator and then we create our own to customize the JSON output:

// Sample class to be used in JSON.
@groovy.transform.TupleConstructor
class Student { 
    String firstName, lastName
}

def data = 
    [student: new Student('Hubert', 'Klein Ikkink'),
     dateOfBirth: Date.parse('yyyyMMdd', '19730709'),
     website: 'https://www.mrhaki.com'.toURL(),
     password: 'IamSecret',
     awake: Optional.empty(),
     married: Optional.of(true), 
     location: null,
     currency: '\u20AC' /* Unicode EURO */]
     

import groovy.json.JsonGenerator
import groovy.json.JsonGenerator.Converter
        
// Default JSON generator. This generator is used by
// Groovy to create JSON if we don't specify our own. 
// For this example we define the default generator 
// explicitly to see the default output.       
def jsonDefaultOutput = new JsonGenerator.Options().build()
        
// Use generator to create JSON string.
def jsonDefaultResult = jsonDefaultOutput.toJson(data) // Or use JsonOutput.toJson(data)

assert jsonDefaultResult == '{"student":{"firstName":"Hubert","lastName":"Klein Ikkink"},' + 
    '"dateOfBirth":"1973-07-08T23:00:00+0000","website":"https://www.mrhaki.com","password":"IamSecret",' + 
    '"awake":{"present":false},"married":{"present":true},"location":null,"currency":"\\u20ac"}'


// Define custom rules for JSON that will be generated.
def jsonOutput = 
    new JsonGenerator.Options()
        .excludeNulls()  // Do not include fields with value null.
        .dateFormat('EEEE dd-MM-yyyy', new Locale('nl', 'NL')) // Set format for dates.
        .timezone('Europe/Amsterdam') // Set timezone to be used for formatting dates.
        .excludeFieldsByName('password')  // Exclude fields with given name(s). 
        .excludeFieldsByType(URL)  // Exclude fields of given type(s).
        .disableUnicodeEscaping()  // Do not escape UNICODE.
        .addConverter(Optional) { value -> value.orElse('UNKNOWN') } // Custom converter for given type defined as Closure.
        .addConverter(new Converter() {  // Custom converter implemented via Converter interface.
        
            /**
             * Indicate which type this converter can handle.
             */
            boolean handles(Class<?> type) { 
                return Student.isAssignableFrom(type)
            }
            
            /**
             * Logic to convert Student object.
             */
            Object convert(Object student, String key) {
                "$student.firstName $student.lastName"
            }
            
        })
        .build()  // Create the converter instance.

// Use generator to create JSON from Map data structure.
def jsonResult = jsonOutput.toJson(data)

assert jsonResult == '{"student":"Hubert Klein Ikkink",' + 
    '"dateOfBirth":"maandag 09-07-1973",' + 
    '"awake":"UNKNOWN","married":true,"currency":"€"}'

The JsonBuilder and StreamingJsonBuilder classes now also support the use of a JsonGenerator instance. The generator is used when the JSON output needs to be created. The internal data structure of the builder is not altered by using a custom generator.

In the following example we use the custom generator of the previous example and apply it with a JsonBuilder and StreamingJsonBuilder instance:

import groovy.json.JsonBuilder

// We can use a generator instance as constructor argument
// for JsonBuilder. The generator is used when we create the
// JSON string. It will not effecct the internal JSON data structure.
def jsonBuilder = new JsonBuilder(jsonOutput)
jsonBuilder {
    student new Student('Hubert', 'Klein Ikkink')
    dateOfBirth Date.parse('yyyyMMdd', '19730709')
    website 'https://www.mrhaki.com'.toURL()
    password 'IamSecret'
    awake Optional.empty()
    married Optional.of(true)
    location null
    currency  '\u20AC' 
}

def jsonBuilderResult = jsonBuilder.toString()

assert jsonBuilderResult == '{"student":"Hubert Klein Ikkink",' + 
    '"dateOfBirth":"maandag 09-07-1973",' + 
    '"awake":"UNKNOWN","married":true,"currency":"€"}'

// The internal structure is unaffected by the generator.
assert jsonBuilder.content.password == 'IamSecret'
assert jsonBuilder.content.website.host == 'www.mrhaki.com'


import groovy.json.StreamingJsonBuilder

new StringWriter().withWriter { output -> 

    // As with JsonBuilder we can provide a custom generator via
    // the constructor for StreamingJsonBuilder.
    def jsonStreamingBuilder = new StreamingJsonBuilder(output, jsonOutput)
    jsonStreamingBuilder {
        student new Student('Hubert', 'Klein Ikkink')
        dateOfBirth Date.parse('yyyyMMdd', '19730709')
        website 'https://www.mrhaki.com'.toURL()
        password 'IamSecret'
        awake Optional.empty()
        married Optional.of(true)
        location null
        currency  '\u20AC' 
    }

    def jsonStreamingBuilderResult = output.toString()
    
    assert jsonStreamingBuilderResult == '{"student":"Hubert Klein Ikkink",' + 
        '"dateOfBirth":"maandag 09-07-1973",' + 
        '"awake":"UNKNOWN","married":true,"currency":"€"}'
}

Written with Groovy 2.5.0.

June 13, 2018

Groovy Goodness: Remove Last Item From List Using RemoveLast Method (And Pop/Push Methods Reimplemented)

Versions of Groovy before 2.5.0 implemented pop and push methods for the List class for items at the end of a List object. The pop method removed the last item of a List and push added a item to the List. Groovy 2.5.0 reimplemented the methods so they now work on the first item of a List instance. To remove an item from the end of the list we can use the newly added method removeLast.

In the following example Groovy code we use the removeLast and add methods to remove and add items to the end of the list. And with the pop and push methods we remove and add items to the beginnen of the list:

def list = ['Groovy', 'is', 'great!']
 
// Remove last item from list
// with removeLast().
assert list.removeLast() == 'great!'
assert list == ['Groovy', 'is']
 
// Remove last item which is now 'is'.
list.removeLast()
 
// Add new item to end of the list.
list.add 'rocks!'
 
assert list.join(' ') == 'Groovy rocks!'


/* IMPORTANT */
/* pop() and push() implementations has changed */
/* in Groovy 2.5.0. They now work on the first */
/* item in a List instead of the last. */

// Using pop() we remove the first item
// of a List.
assert list.pop() == 'Groovy'

// And with push we add item to 
// beginning of a List.
list.push 'Spock'

assert list.join(' ') == 'Spock rocks!'

Written with Groovy 2.5.0.

Groovy Goodness: Getting All Init And Tail Values Recursively

For a long time we could get the tail or init values for a collection. Groovy 2.5.0 adds the methods inits and tails for Iterable objects. These methods return a List with List values where the first element is the original collection and the next is the result of init or tail on the previous element. This is repeated until the result of init or tail is an empty List.

In the next example script we have a original collection of letters. We first run the init and tail methods (without the s). Next we look at the result of invoking inits and tails:

def letters = ('a'..'d').toList()

assert letters == ['a', 'b', 'c', 'd']

assert letters.init() == ['a', 'b', 'c']
assert letters.tail() == ['b', 'c', 'd']

// Inits returns collection of all init()
// results for an Iterable. The first element
// has the original values, the next element
// the result of init()
// of the previous element and so on until
// an empty List is the result.
assert letters.inits() == [
    ['a', 'b', 'c', 'd'], 
    ['a', 'b', 'c'], 
    ['a', 'b'], 
    ['a'], 
    []]

// Tails returns collection of all tail()
// results for an Iterable. The first element
// has the original values, the next element
// the result of tail()
// of the previous element and so on until
// an empty List is the result.
assert letters.tails() == [
    ['a', 'b', 'c', 'd'], 
    ['b', 'c', 'd'], 
    ['c', 'd'], 
    ['d'], 
    []]

Written with Groovy 2.5.0.

Groovy Goodness: Truncate And Round BigDecimal Values

Groovy 2.5.0 adds round and truncate methods to the BigDecimal class. These methods were already available on Double and Float classes. The methods can take an argument to denote the number of decimals the rounding or truncating must be applied to.

In the following example we see the methods with and without arguments:

def bigDecimal = 42.576135

// Groovy uses BigDecimal for decimal 
// numbers by default.
assert bigDecimal.class.name == 'java.math.BigDecimal'

assert bigDecimal.round() == 43
assert bigDecimal.round(2) == 42.58

assert bigDecimal.trunc() == 42
assert bigDecimal.trunc(2) == 42.57

Written with Groovy 2.5.0.

June 12, 2018

Groovy Goodness: Intersect Collections With Custom Comparator

In a previous post we learned about the intersect method added to collections in Groovy. Since Groovy 2.5.0 we can supply a custom Comparator to the intersect method to define our own rules for the intersection.

In the following example we first apply the intersect method with the default Comparator. Then we create a new Comparator using a closure where we check if the value is in both collections and if the value starts with the letter M:

def stuff = ['Groovy', 'Gradle', 'Grails', 'Spock', 'Micronaut', 'Ratpack'] as Set
def micro = ['Ratpack', 'Micronaut', 'SpringBoot', 'Microservice']

// Using default comparator to get values
// that are in both collections.
assert stuff.intersect(micro) == ['Ratpack', 'Micronaut'] as Set
assert micro.intersect(stuff) == ['Micronaut', 'Ratpack']

// Comparator to check if value is in
// both collection and starts with a 'M'.
def microName = { a, b -> a <=> b == 0 && a[0] == 'M' ? 0 : -1 } as Comparator

// This time we use the Comparator and
// end up with all elements in both
// collections that start with a 'M'.
assert stuff.intersect(micro, microName) == ['Micronaut'] as Set
assert micro.intersect(stuff, microName) == ['Micronaut']

Written with Groovy 2.5.0.

Groovy Goodness: Easy Object Creation With Tap Method

Groovy 2.5.0 adds the tap method to all objects and changes the method signature of the with method. In a previous post we already learned about the with method. In Groovy 2.5.0 we can add an extra boolean argument to the with method. If the value is false (is default) the with method must return the same value as what the closure invocation returns. If the value is true the object instance on which the with method is invoked is returned. The new tap method is an alias for with(true), so it will always return the object instance.

In the first example we use the tap method to create a new Sample object and set property values and invoke methods of the Sample class:

/** 
 * Sample class with some properties
 * and a method.
 */
class Sample {
    
    String username, email
    
    List<String> labels = []
    
    void addLabel(value) { 
        labels << value 
    }
    
}

// Use tap method to create instance of 
// Sample and set properties and invoke methods. 
def sample = 
        new Sample().tap {
            assert delegate.class.name == 'Sample'
            
            username = 'mrhaki'
            email = 'email@host.com'
            addLabel 'Groovy'
            addLabel 'Gradle'
            
            // We use tap, an alias for with(true), 
            // so the delegate of the closure, 
            // the Sample object, is returned.
        }

assert sample.labels == ['Groovy', 'Gradle']
assert sample.username == 'mrhaki'
assert sample.email == 'email@host.com'

In the following example we use the with method to demonstrate the differences for several invocations using different argument values:

/** 
 * Sample class with some properties
 * and a method.
 */
class Sample {
    
    String username, email
    
    List<String> labels = []
    
    void addLabel(value) { 
        labels << value 
    }
    
}

// Use with method to create instance of 
// Sample and set properties and invoke methods. 
def sample1 = 
        new Sample().with {
            assert delegate.class.name == 'Sample'

            username = 'mrhaki'
            email = 'email@host.com'
            addLabel 'Groovy'
            addLabel 'Gradle'   
        }
       
// With method returns the result 
// from the closure. In the previous
// case the return result is null,
// because the last statement addLabel
// is used as return value. addLabel has
// return type void.
assert !sample1


// Use with method to create instance of 
// Sample and set properties and invoke methods. 
def sample2 = 
        new Sample().with {
            assert delegate.class.name == 'Sample'

            username = 'mrhaki'
            email = 'email@host.com'
            addLabel 'Groovy'
            addLabel 'Gradle'
            
            // Explicitly return delegate of
            // closure, which is the Sample object.
            return delegate
        }

assert sample2.labels == ['Groovy', 'Gradle']
assert sample2.username == 'mrhaki'
assert sample2.email == 'email@host.com'


// Use with method to create instance of 
// Sample and set properties and invoke methods. 
def sample3 = 
        new Sample().with(true) {
            assert delegate.class.name == 'Sample'

            username = 'mrhaki'
            email = 'email@host.com'
            addLabel 'Groovy'
            addLabel 'Gradle'
            
            // We use with(true), so the 
            // delegate of the closure, the Sample
            // object, is returned.
        }

assert sample3.labels == ['Groovy', 'Gradle']
assert sample3.username == 'mrhaki'
assert sample3.email == 'email@host.com'

A good use case for using the with method is to transform an object to another type using values from the object. In the next example we use values from a Sample objects to create a new String:

/** 
 * Sample class with some properties
 * and a method.
 */
class Sample {
    
    String username, email
    
    List<String> labels = []
    
    void addLabel(value) { 
        labels << value 
    }
    
}

def sample = 
        new Sample().tap {
            username = 'mrhaki'
            email = 'email@host.com'
            addLabel 'Groovy'
            addLabel 'Gradle'
        }

// The with method can be very useful to
// transform object to another type using
// values from the object.
def user = sample.with { "$username likes ${labels.join(', ')}." }

assert user == 'mrhaki likes Groovy, Gradle.'

Written with Groovy 2.5.0.

Groovy Goodness: Where Is My Class?

Groovy 2.5.0 makes it possible to get the location of a Class file by adding the method getLocation to the Class class. If the Class is part of the JDK the location returned is null, but otherwise we get the location of the JAR file or source file (if available) with the Class file.

In the following example we get the location for the internal JDK String class and the Groovy utility class ConfigSlurper:

// Internal JDK class location is null.
assert String.location == null


// Import ConfigSlurper with alias.
import groovy.util.ConfigSlurper as ConfigReader

// Location of Groovy JAR file.
def groovyJarFile = 'file:/Users/mrhaki/.sdkman/candidates/groovy/2.5.0/lib/groovy-2.5.0.jar'.toURL()   

// ConfigSlurper is located in the Groovy JAR file.
assert ConfigSlurper.location == groovyJarFile

// Works also for aliased class.
assert ConfigReader.location == groovyJarFile                                                 

Written with Groovy 2.5.0.

Groovy Goodness: Calculate MD5 And SHA Hash Values

Groovy adds a lot of useful methods to the String class. Since Groovy 2.5.0 we can even calculate MD5 and SHA hash values using the methods md5 and digest. The md5 method create a hash value using the MD5 algorithm. The digest method accepts the name of the algorithm as value. These values are dependent on the available algorithms on our Java platform. For example the algorithms MD2, MD5, SHA-1, SHA-256, SHA-384 and SHA-512 are by default available.

In the next example we use the md5 and digest methods on a String value:

def value = 'IamASecret'

def md5 = value.md5()

// We can provide hash algorithm with digest method.
def md2 = value.digest('MD2')
def sha1 = value.digest('SHA-1')
def sha256 = value.digest('SHA-256')

assert md5 == 'a5f3147c32785421718513f38a20ca44'
assert md2 == '832cbe3966e186194b1203c00ef47488'
assert sha1 == '52ebfed118e0a411e9d9cbd60636fc9dea718928'
assert sha256 == '4f5e3d486d1fd6c822a81aa0b93d884a2a44daf2eb69ac779a91bc76de512cbe'

Written with Groovy 2.5.0.