Working with IO (TBD)

Working with collections (TBD)

Handy utilities

ConfigSlurper

ConfigSlurper is a utility class for reading configuration files defined in the form of Groovy scripts. Like it is the case with Java *.properties files, ConfigSlurper allows a dot notation. But in addition, it allows for Closure scoped configuration values and arbitrary object types.

def config = new ConfigSlurper().parse('''
    app.date = new Date()  (1)
    app.age  = 42
    app {                  (2)
        name = "Test${42}"
    }
''')

assert config.app.date instanceof Date
assert config.app.age == 42
assert config.app.name == 'Test42'
1 Usage of the dot notation
2 Usage of Closure scopes as an alternative to the dot notation

As can be seen in the above example, the parse method can be used to retrieve groovy.util.ConfigObject instances. The ConfigObject is a specialized java.util.Map implementation that either returns the configured value or a new ConfigObject instance but never null.

def config = new ConfigSlurper().parse('''
    app.date = new Date()
    app.age  = 42
    app.name = "Test${42}"
''')

assert config.test != null   (1)
1 config.test has not been specified yet it returns a ConfigObject when being called.

In the case of a dot being part of a configuration variable name, it can be escaped by using single or double quotes.

def config = new ConfigSlurper().parse('''
    app."person.age"  = 42
''')

assert config.app."person.age" == 42

In addition, ConfigSlurper comes with support for environments. The environments method can be used to hand over a Closure instance that itself may consist of a several sections. Let’s say we wanted to create a particular configuration value for the development environment. When creating the ConfigSlurper instance we can use the ConfigSlurper(String) constructor to specify the target environment.

def config = new ConfigSlurper('development').parse('''
  environments {
       development {
           app.port = 8080
       }

       test {
           app.port = 8082
       }

       production {
           app.port = 80
       }
  }
''')

assert config.app.port == 8080
The ConfigSlurper environments aren’t restricted to any particular environment names. It solely depends on the ConfigSlurper client code what value are supported and interpreted accordingly.

The environments method is built-in but the registerConditionalBlock method can be used to register other method names in addition to the environments name.

def slurper = new ConfigSlurper()
slurper.registerConditionalBlock('myProject', 'developers')   (1)

def config = slurper.parse('''
  sendMail = true

  myProject {
       developers {
           sendMail = false
       }
  }
''')

assert !config.sendMail
1 Once the new block is registered ConfigSlurper can parse it.

For Java integration purposes the toProperties method can be used to convert the ConfigObject to a java.util.Properties object that might be stored to a *.properties text file. Be aware though that the configuration values are converted to String instances during adding them to the newly created Properties instance.

def config = new ConfigSlurper().parse('''
    app.date = new Date()
    app.age  = 42
    app {
        name = "Test${42}"
    }
''')

def properties = config.toProperties()

assert properties."app.date" instanceof String
assert properties."app.age" == '42'
assert properties."app.name" == 'Test42'

Expando

The Expando class can be used to create a dynamically expandable object. Despite its name it does not use the ExpandoMetaClass underneath. Each Expando object represents a standalone, dynamically-crafted instance that can be extended with properties (or methods) at runtime.

def expando = new Expando()
expando.name = 'John'

assert expando.name == 'John'

A special case occurs when a dynamic property registers a Closure code block. Once being registered it can be invoked as it would be done with a method call.

def expando = new Expando()
expando.toString = { -> 'John' }
expando.say = { String s -> "John says: ${s}" }

assert expando as String == 'John'
assert expando.say('Hi') == 'John says: Hi'

Observable list, map and set

Groovy comes with observable lists, maps and sets. Each of these collections trigger java.beans.PropertyChangeEvent events when elements are added, removed or changed. Note that a PropertyChangeEvent is not only signaling that a certain event has occurred, moreover, it holds information on the property name and the old/new value a certain property has been changed to.

Depending on the type of change that has happened, observable collections might fire more specialized PropertyChangeEvent types. For example, adding an element to an observable list fires an ObservableList.ElementAddedEvent event.


def event                                       (1)
def listener = {
    if (it instanceof ObservableList.ElementEvent)  {  (2)
        event = it
    }
} as PropertyChangeListener


def observable = [1, 2, 3] as ObservableList    (3)
observable.addPropertyChangeListener(listener)  (4)

observable.add 42                               (5)

assert event instanceof ObservableList.ElementAddedEvent

def elementAddedEvent = event as ObservableList.ElementAddedEvent
assert elementAddedEvent.changeType == ObservableList.ChangeType.ADDED
assert elementAddedEvent.index == 3
assert elementAddedEvent.oldValue == null
assert elementAddedEvent.newValue == 42
1 Declares a PropertyChangeEventListener that is capturing the fired events
2 ObservableList.ElementEvent and its descendant types are relevant for this listener
3 Registers the listener
4 Creates an ObservableList from the given list
5 Triggers an ObservableList.ElementAddedEvent event
Be aware that adding an element in fact causes two events to be triggered. The first is of type ObservableList.ElementAddedEvent, the second is a plain PropertyChangeEvent that informs listeners about the change of property size.

The ObservableList.ElementClearedEvent event type is another interesting one. Whenever multiple elements are removed, for example when calling clear(), it holds the elements being removed from the list.

def event
def listener = {
    if (it instanceof ObservableList.ElementEvent)  {
        event = it
    }
} as PropertyChangeListener


def observable = [1, 2, 3] as ObservableList
observable.addPropertyChangeListener(listener)

observable.clear()

assert event instanceof ObservableList.ElementClearedEvent

def elementClearedEvent = event as ObservableList.ElementClearedEvent
assert elementClearedEvent.values == [1, 2, 3]
assert observable.size() == 0

To get an overview of all the supported event types the reader is encouraged to have a look at the JavaDoc documentation or the source code of the observable collection in use.

ObservableMap and ObservableSet come with the same concepts as we have seen for ObservableList in this section.