Search

Dark theme | Light theme
Showing posts with label GroovyGoodness:Annotations. Show all posts
Showing posts with label GroovyGoodness:Annotations. Show all posts

May 28, 2013

Groovy Goodness: @DelegatesTo For Type Checking DSL

Groovy 2.1 introduced the @DelegatesTo annotation. With this annotation we can document a method and tell which class is responsible for executing the code we pass into the method. If we use @TypeChecked or @CompileStatic then the static type checker of the compiler will use this information to check at compile-time if the code is correct. And finally this annotation allows an IDE to give extra support like code completion.

Suppose we have the following class Reservation with the method submit(). The method accepts a closure with methods that need to be applied with the instance of the Reservation class. This is a very common pattern for writing simple DSLs in Groovy.

class Reservation {
    private Date date
    private String event
    private String attendee
    
    void date(final Date date) { this.date = date }
    void event(final String event) { this.event = event }
    void attendee(final String attendee) { this.attendee = attendee }
    
    /** Submit a reservation. 
      * @param config Configuration for reservation, invoking method class on Reservation.
      */
    static void submit(final Closure config) {
        final Reservation reservation = new Reservation()
        reservation.with config
    }

}

class Event {
    /** Use Reservation configuration DSL to submit a reservation. */
    void submitReservation() {
        Reservation.submit {
            date Date.parse('yyyyMMdd', '20130522')
            event 'Gr8Conf'
            attendee 'mrhaki'
            reserved true
        }
    }
}

final event = new Event()
event.submitReservation()

When we look at the code we might already see there is an error. In the Event.submitReservation() method we have the line reserved true, which will try to invoke the reserve() method of the Reservation class. But that method is not defined. When we run the application we get the expected error:

Exception thrown
May 28, 2013 6:58:36 AM org.codehaus.groovy.runtime.StackTraceUtils sanitize
WARNING: Sanitizing stacktrace:
groovy.lang.MissingMethodException: No signature of method: Reservation.reserved() is applicable for argument types: (java.lang.Boolean) values: [true]

To get an error for this line at compile-time we must add some annotation so the Groovy compiler can do static type checking on our code. We change the code and get the following sample:

import groovy.transform.*

class Reservation {
    private Date date
    private String event
    private String attendee
    
    void date(final Date date) { this.date = date }
    void event(final String event) { this.event = event }
    void attendee(final String attendee) { this.attendee = attendee }
    
    /** Submit a reservation. 
      * @param config Configuration for reservation, invoking method class on Reservation.
      */
    static void submit(@DelegatesTo(Reservation) final Closure config) {
        final Reservation reservation = new Reservation()
        reservation.with config
    }

}

@TypeChecked
// @CompileStatic - will also do static type checking
class Event {
    /** Use Reservation configuration DSL to submit a reservation. */
    void submitReservation() {
        Reservation.submit {
            date Date.parse('yyyyMMdd', '20130522')
            event 'Gr8Conf'
            attendee 'mrhaki'
            reserved true
        }
    }
}

final event = new Event()
event.submitReservation()

When the code is compiled we immediately get a compilation error:

1 compilation error:

[Static type checking] - Cannot find matching method Event#reserved(boolean). Please check if the declared type is right and if the method exists.
 at line: 26, column: 13

Wow, this useful! We find errors in our DSL before the code is run.

When we create this code in an IDE like IntelliJ IDEA we also get code completion in the Reservation.submit() method invocation in the Event class. The following screenshot shows code completion and a red font for reserved to indicate the compilation error.


Code written in Groovy 2.1.3

July 15, 2010

Groovy Goodness: Create Indexed Property Getter and Setter Methods

In Groovy we can define properties in our class and automatically the getter and setter methods for these properties are generated in the class file. If we have a Collection type property we normally get the get/set method for this property. But according to the JavaBean specification we can define a Collection type property as indexed property. This means we need an extra get/set method with an index parameter, so we can set the value of a element in the property directly:

//Methods to access individual values
public PropertyElement getPropertyName(int index)
public void setPropertyName(int index, PropertyElement element)

/Methods to access the entire indexed property array
public PropertyElement[] getPropertyName()
public void setPropertyName(PropertyElement element[])

Normally if we use our class in Groovy code we don't need those extra methods, because we can GPath to access and set elements in the Collection typed property. But suppose our class needs to be accessed from Java code or by an IDE, we want those extra methods. We only have to add the @IndexedProperty annotation to our property and we get the extra getter and setter methods we want:

import groovy.transform.IndexedProperty

class Group {
    String name
    List members = []
}

class IndexedGroup {
    String name
    @IndexedProperty List members = []
}

def group = new Group(name: 'Groovy')
group.members[0] = 'mrhaki'
group.members[1] = 'Hubert'
assert 2 == group.members.size()
assert ['mrhaki', 'Hubert'] == group.members

try {
    group.setMembers(0, 'hubert') // Not index property
} catch (MissingMethodException e) {
    assert e
}

def indexedGroup = new IndexedGroup(name: 'Grails')
indexedGroup.members[0] = 'mrhaki'
indexedGroup.setMembers 1, 'Hubert'
assert 2 == indexedGroup.members.size()
assert 'mrhaki' == indexedGroup.getMembers(0)
assert 'Hubert' == indexedGroup.members[1]

June 14, 2010

Groovy Goodness: Inherit Constructors From Parent Classes

We have a new transformation annotation in Groovy 1.7.3 to safe us a lot of typing when we define new classes that extend from parent classes with multiple constructors: @InheritConstructors. When we apply this transformation to our class we automatically get all constructors from the super class. This is very useful if we for example extend from java.lang.Exception, because otherwise we would have to define four constructors ourselves. The transformation adds these constructors for us in our class file. This works also for our own created classes.

import groovy.transform.InheritConstructors

@InheritConstructors
class MyException extends Exception {
}

def e = new MyException()
def e1 = new MyException('message')   // Other constructors are available.
assert 'message' == e1.message


class Person {
    String name
    
    Person(String name) {
        this.name = name
    }
}

@InheritConstructors
class Child extends Person {}

def child = new Child('Liam')
assert 'Liam' == child.name

October 1, 2009

Groovy Goodness: Create a Singleton Class

Creating a singleton class in Groovy is simple. We only have to use the @Singleton transformation annotation and a complete singleton class is generated for us.

package com.mrhaki.blog

// Old style singleton class.
public class StringUtil {
    private static final StringUtil instance = new StringUtil();

    private StringUtil() {
    }

    public static StringUtil getInstance() { 
        return instance;
    }
    
    int count(text) { 
        text.size() 
    }
}

assert 6 == StringUtil.instance.count('mrhaki')

// Use @Singleton to create a valid singleton class.
// We can also use @Singleton(lazy=true) for a lazy loading
// singleton class.
@Singleton 
class Util {
    int count(text) {
        text.size()
    }
}

assert 6 == Util.instance.count("mrhaki")

try {
    new Util()
} catch (e) {
    assert e instanceof RuntimeException
    assert "Can't instantiate singleton com.mrhaki.blog.Util. Use com.mrhaki.blog.Util.instance" == e.message
}

September 30, 2009

Groovy Goodness: Newify to Create New Instances

The @Newify transformation annotation allows other ways to create a new instance of a class. We can use a new() method on the class or even omit the whole new keyword. The syntax is copied from other languages like Ruby and Python. If we use the @Newify annotation we get a slightly more readable piece of code (in some situations). We can use parameters in the annotation to denote all those classes we want to be instantiated with the new() method or without the new keyword.

class Author {
    String name
    List books
}
class Book { 
    String title 
}

def createKing() {
    new Author(name: 'Stephen King', books: [
        new Book(title: 'Carrie'),
        new Book(title: 'The Shining'),
        new Book(title: 'It')
    ])
}

assert 3 == createKing().books.size()
assert 'Stephen King' == createKing().name
assert 'Carrie' == createKing().books.getAt(0).title

@Newify 
def createKingRuby() {
    Author.new(name: 'Stephen King', books: [
        Book.new(title: 'Carrie'),
        Book.new(title: 'The Shining'),
        Book.new(title: 'It')
    ])
}

assert 3 == createKingRuby().books.size()
assert 'Stephen King' == createKingRuby().name
assert 'Carrie, The Shining, It' == createKingRuby().books.title.join(', ')

@Newify([Author, Book]) 
def createKingPython() {
    Author(name: 'Stephen King', books: [
        Book(title: 'Carrie'),
        Book(title: 'The Shining'),
        Book(title: 'It')
    ])
}

assert 3 == createKingPython().books.size()
assert 'Stephen King' == createKingPython().name
assert 'It' == createKingPython().books.title.find { it == 'It' }

September 26, 2009

Groovy Goodness: Grab That Dependency

In Groovy scripts we can automatically download dependencies with Grape. Grape can be used as annotation (for a method or parameter) or with a plain method call. Apacy Ivy is used by Grape to find and download the dependencies. This means we can also use all Maven2 dependency repositories. If we want to add new repositories we must create a file ~/.groovy/grapeConfig.xml which is an Ivy configuration file and in it we can add a new repository. The downloaded files are saved in ~/.groovy/grapes.

The command-line grape command can be used to download dependencies or to list the already downloaded dependencies. For example with $ grape list we get a list of all dependencies on our local computer.

import org.apache.commons.lang.SystemUtils

@Grab(group='commons-lang', module='commons-lang', version='2.4')
def printInfo() {
    if (SystemUtils.isJavaVersionAtLeast(5)) {
        println 'We are ready to use annotations in our Groovy code.'
    } else {
        println 'We cannot use annotations in our Groovy code.'
    }
}

printInfo()

Default Grape configuration file:

<ivysettings>
  <settings defaultResolver="downloadGrapes"/>
  <resolvers>
    <chain name="downloadGrapes">
      <filesystem name="cachedGrapes">
        <ivy pattern="${user.home}/.groovy/grapes/[organisation]/[module]/ivy-[revision].xml"/>
        <artifact pattern="${user.home}/.groovy/grapes/[organisation]/[module]/[type]s/[artifact]-[revision](-[classifier]).[ext]"/>
      </filesystem>
      <!-- todo add 'endorsed groovy extensions' resolver here -->
      <ibiblio name="codehaus" root="http://repository.codehaus.org/" m2compatible="true"/>
      <ibiblio name="ibiblio" m2compatible="true"/>
      <ibiblio name="java.net2" root="http://download.java.net/maven/2/" m2compatible="true"/>
    </chain>
  </resolvers>
</ivysettings>

September 17, 2009

Groovy Goodness: Making a Class Immutable

Immutable objects are created and cannot change after creation. This makes immutable objects very usable in concurrent and functional programming. To define a Java class as immutable we must define all properties as readonly and private. Only the constructor can set the values of the properties. The Groovy documentation has a complete list of the rules applying to immutable objects. The Java code to make a class immutable is verbose, especially since the hashCode(), equals() and toString() methods need to be overridden.

Groovy has the @Immutable transformation to do all the work for us. We only have to define @Immutable in our class definition and any object we create for this class is an immutable object. Groovy generates a class file following the rules for immutable objects. So all properties are readonly, constructors are created to set the properties, implementations for the hashCode(), equals() and toString() methods are generated, and more.

@Immutable class User {
    String username, email
    Date created = new Date()
    Collection roles
}

def first = new User(username: 'mrhaki', email: 'email@host.com', roles: ['admin', 'user'])
assert 'mrhaki' == first.username
assert 'email@host.com' == first.email
assert ['admin', 'user'] == first.roles
assert new Date().after(first.created)
try {
    // Properties are readonly.
    first.username = 'new username'
} catch (ReadOnlyPropertyException e) {
    assert 'Cannot set readonly property: username for class: User' == e.message
}
try {
    // Collections are wrapped in immutable wrapper classes, so we cannot
    // change the contents of the collection.
    first.roles << 'new role'
} catch (UnsupportedOperationException e) {
    assert true
}


def date = new Date(109, 8, 16)
def second = new User('user', 'test@host.com', date, ['user'])
assert 'user' == second.username
assert 'test@host.com' == second.email
assert ['user'] == second.roles
assert '2009/08/16' == second.created.format('yyyy/MM/dd')
assert date == second.created
assert !date.is(second.created)  // Date, Clonables and arrays are deep copied.
// toString() implementation is created.
assert 'User(user, test@host.com, Wed Sep 16 00:00:00 UTC 2009, [user])' == second.toString() 


def third = new User(username: 'user', email: 'test@host.com', created: date, roles: ['user'])
// equals() method is also generated by the annotation and is based on the
// property values.
assert third == second

August 29, 2009

Groovy Goodness: Delegate to Simplify Code

Groovy supports the @Delegate annotation. With this annotation we can import all the methods of the class the annotation is used for. For example if we use the delegate annotation for the Date class we get all the methods of the Date class in our class. Just like that. This is best explained with a little sample in which we use the @Delegate annotation for properties of type Date and List:

class SimpleEvent {
    @Delegate Date when
    @Delegate List<String> attendees = []
    int maxAttendees = 0
    String description
}

def event = new SimpleEvent(when: new Date() + 7, description: 'Small Groovy seminar', maxAttendees: 2)

assert 0 == event.size()  // Delegate to List.size()
assert event.after(new Date())  // Delegate to Date.after()
assert 'Small Groovy seminar' == event.description
assert 2 == event.maxAttendees

event << 'mrhaki' << 'student1'  // Delegate to List.leftShift()
assert 2 == event.size()
assert 'mrhaki' == event[0]

event -= 'student1'  // Delegate to List.minus()
assert 1 == event.size()

We have used the @Delegate annotations and as by magic the SimpleEvent has all methods of both the Date class and List interface. The code reads naturally and the meaning is obvious. Because the SimpleEvent class has all methods from the List interface we can override the methods as well. In our sample we override the add() so we can check if the number of attendees doesn't exceed the maximum number of attendees allowed:

class SimpleEvent {
    @Delegate Date when
    @Delegate List<String> attendees = []
    int maxAttendees = 0
    String description

    boolean add(Object value) {
        if (attendees.size() < maxAttendees) {
            return attendees.add(value)
        } else {
            throw new IllegalArgumentException("Maximum of ${maxAttendees} attendees exceeded.")
        }
    }
}

def event = new SimpleEvent(when: new Date() + 7, description: 'Small Groovy seminar', maxAttendees: 2)
event << 'mrhaki' << 'student1'

try {
    event << 'three is a crowd.'
    assert false
} catch (IllegalArgumentException e) {
    assert 'Maximum of 2 attendees exceeded.' == e.message
}