GContracts 1.2.0 Released!

I am proud to announce the release of GContracts 1.2.0 [0], the AST transformation framework enabling programmers to use contracts (aka Design by Contract) in Groovy. GContracts is completely done in Java without external dependencies to other frameworks/libraries. Although GContracts uses annotation closures, it supports Groovy 1.7 and above. 1.2.0 comes with a whole bunch of improvements, bug fixes and features. This announcement will give a short overview of the most important ones. As time goes by, i'll post a series of articles covering 1.2 features in greater detail.

Support for Groovy Interfaces

Before 1.2, @Requires and @Ensures have only been supported in concrete or abstract classes. The new version allows pre- and postconditions to appear in either concrete classes, abstract classes or interfaces. Example:
 
@Contracted
package batch

import org.gcontracts.annotations.*

interface Stackable {
    
    @Requires({ item != null })
    def push(def item)

    @Requires({ !isEmpty() })
    @Ensures({ result != null })
    def pop()

    def isEmpty()
}

class Batch implements Stackable {
    
    def myCards = []    

    def push(def card)  {
      myCards << card
    }    

    def pop()  { myCards[-1] }

    def isEmpty() { myCards.size() == 0 }
}

def batch = new Batch()
batch.push(null)

// results in 

org.gcontracts.PreconditionViolation: <org.gcontracts.annotations.Requires> Stackable.java.lang.Object push(java.lang.Object) 

item != null
|    |
null false
 
Cont'd Example:
 
class AnotherBatch extends Batch {
    def pop() { return null }
}

def batch = new AnotherBatch()
batch.push(1)
batch.pop()

// results in 


org.gcontracts.PostconditionViolation: <org.gcontracts.annotations.Ensures> Stackable.java.lang.Object pop() 

result != null
|      |
null   false
 

Enabling Contract Injection with @Contracted

Although GContracts supports Java's -ea and -da vm parameters to enable and disable assertions, 1.2 is more restrictive when it comes to applying AST transformations. Only classes or packages annotated with @Contracted will be processed during compilation runs. @Contracted can be applied at package- or type-level. If applied on a sub-type, the assertions of predecessors will still be evaluated.

The ContractGroovyDoc Ant Task

The ContractGroovyDoc class is based on Groovy's Groovydoc Ant task, but adds class-invariants, pre- and postconditions to the resulting HTML documentation. Example: ContractGroovyDoc Example Screen

Extending GContracts with Annotation Contracts and Annotation Processors

1.2 comes with the ability to define common pre- and postconditions as reusable annotations. These are called Annotation Contracts. An annotation contract is a Groovy annotation annotated with the following meta-annotations: Example:
 
    import org.gcontracts.annotations.*
    import org.gcontracts.annotations.meta.*
    import java.lang.annotation.*

    // ### Annotation Contract Definition

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)

    @Precondition
    @AnnotationContract({ it != null })
    public @interface NotNull {}

    // ### Applying the Annotation Contract

    class BankAccount {
        
        def deposit(@NotNull def param) {
           // ...

        }
    }
 
Since annotation contract capabilities are limited when it comes to defining complex assertions, an additional mechanism has been introduced: Annotation Processors. An annotation processor implements the AnnotationProcessor interface, and will be called automatically by GContracts during the AST transformation process, if applied on some annotation with the @AnnotationProcessorImplementation meta-annotation. Example:
 
// ### Annotation Processor Implementation

public class RequiresAnnotationProcessor extends AnnotationProcessor {

    @Override
    public void process(ProcessingContextInformation processingContextInformation, Contract contract, ClassNode classNode, MethodNode methodNode, BooleanExpression booleanExpression) {
        // ...

        // utilize GContracts domain model to add e.g. another precondition

        contract.preconditions().or(methodNode, new Precondition(booleanExpression));
    }
}

// ### Applying the Annotation Processor Implementation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})

@Precondition
@AnnotationProcessorImplementation(RequiresAnnotationProcessor.class)
public @interface Requires {
    Class value();
}
 

The Domain Model

As you might have noticed in the previous code sample of the AnnotationProcessor implementation, GContracts 1.2 comes with its own core domain classes, with Contract being the root aggregate entity. Currently, the domain model is rather simple. A contracts consists of a class invariant and multiple methods with pre- and postconditions. Example:
 
// the contract domain object is usually created internally ...

def myBooleanExpression = createMyBooleanExpression()
contract.preconditions().join(method, myBooleanExpression)
 
But the introduction of a separate domain model should lead to a better separation between low-level AST transformations and modification of certain assertion parts. Be aware that the domain model itself is not supposed to be stable in 1.2, interfaces might change and new domain classes might be introduced.

Lifecycle Hooks

In addition to annotation contracts and annotation processor implementations, there exists another way to hook into GContracts AST transformation process: using Lifecycle implementations. A Lifecycle implementation has to be specified in META-INF/services/org.gcontracts.common.spi.Lifecycle and is automatically instantiated before the AST transformation process starts. The interface provides a number of hooks which allow to inject arbitrary AST transformation logic.

Assertion Cycle Detection

Before 1.2, users could run into StackOverflowError exceptions due to cyclic assertion calls. Example:
 
class Box {
   
   @Ensures({ !isFull() })
   def isEmpty() { return true }
   
   @Ensures({ !isEmpty() })
   def isFull() { return false }
}

def box = new Box()
box.isFull()
 
1.2 keeps track of the method call stack in assertion statements and throws a CircularcAssertionCallException if it detects circular method calls from within assertion statements.
 
org.gcontracts.CircularAssertionCallException: Method 'isFull' has already been called from the current assertion - assertion call cycle detected!
 

Assertion Violation Tracking

Before 1.2, inherited preconditions have not been evaluated correctly. This has been solved by introducing the assertion violation tracking mechanism, which allows full evaluation of all preconditions in the inheritance path. Let's assume we've got the following setup:
 

class Root {
   @Requires( { true } )
   def some_method()
}

class Descendant extends Root {
   @Requires( { false } )
   def some_method()
}

class DescendantDescendant extends Descendant {
   @Requires( { false } )
   def some_method()
}

def dd = new DescendantDescendant()
 
Whenever dd.some_method() is invoked, the following assertion statement gets triggered: DescendantDescendant { false } OR Descendant { false } OR Root { true } As you can see from the pseudo-scientific code above, the framework needs to evaluate all boolean expressions until it finds the first one being true. In our case, its the precondition in the root class of the inheritance path. Violation tracking keeps track of assertion errors up the inheritance path for being able to rethrow those errors if the overall boolean expression evaluates to false.

Fully Built with Gradle

GContracts 1.2 has a completely new project structure - it comes with a multi-module Gradle build consisting of the following sub-modules: Expect more sub-modules to be added during the next release cycles.

Bugtracking @ Lighthouse

Bugtracking has been moved from Github to Lighthouse [1]. If you experience any issues, feel free to post a ticket.

Feedback @ Twitter

GContracts now has a its own twitter account [5] - feel free to follow or post any questions/comments there.

Summary

There are multiple ways to get GContracts into your Groovy-based project: Have fun enriching your domain model with contracts! Andre

[0] GContracts Github Project
[1] Lighthouse Bug Tracker
[2] GContracts Binary Downloads
[3] GContracts Central Maven Repository
[4] GContracts Wiki
[5] Follow GContracts at Twitter