Written on
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
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:

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:@Precondition
or@Postcondition
: indicates that the annotation contract condition will be part of the precondition and/or postcondition@AnnotationContract
: specifies the boolean condition which will be part of either the pre- or postcondition, or both. The boolean condition is declared using an annotation closure. Although annotation closures are officially supported since Groovy 1.8, GContracts uses a customized version of classClosureWriter
to enable annotation closures with 1.7.x code.
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) {
// ...
}
}
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 theAnnotationProcessor
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)
Lifecycle Hooks
In addition to annotation contracts and annotation processor implementations, there exists another way to hook into GContracts AST transformation process: usingLifecycle
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 intoStackOverflowError
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()
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()
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:- gcontracts-core: the core framework
- gcontracts-doc: contains the
ContractGroovyDoc
Ant task - gcontracts-grails: contains a special
Lifecycle
implementation for applying GContracts in Grails projects
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:- Download GContracts binaries at Github [2]
- Add a Maven Dependency to your
build.gradle
,pom.xml
,BuildConfig.grovvy
or whatever [3] - Copy And Paste the Gihub wiki examples [4]
[4] GContracts Wiki