CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-apache-groovy--groovy-contracts

Design by contract support for Groovy with @Invariant, @Requires, and @Ensures annotations

Pending
Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

Groovy Contracts provides specialized exception classes for different types of contract violations and utilities for tracking multiple violations during contract evaluation.

Exception Hierarchy

AssertionViolation (Abstract Base)

abstract class AssertionViolation extends AssertionError {
    protected AssertionViolation()
    protected AssertionViolation(Object o)
    protected AssertionViolation(boolean b)
    protected AssertionViolation(char c)
    protected AssertionViolation(int i)
    protected AssertionViolation(long l)
    protected AssertionViolation(float v)
    protected AssertionViolation(double v)
}

Abstract base class for all contract assertion violations. Automatically registers with ViolationTracker upon instantiation to support chronological violation tracking.

Features:

  • Extends AssertionError for integration with Java assertion system
  • Automatic registration with violation tracker
  • Protected constructors for all primitive types and Object

Specific Violation Types

PreconditionViolation

class PreconditionViolation extends AssertionViolation {
    PreconditionViolation()
    PreconditionViolation(Object o)
    PreconditionViolation(boolean b)
    PreconditionViolation(char c)
    PreconditionViolation(int i)
    PreconditionViolation(long l)
    PreconditionViolation(float v)
    PreconditionViolation(double v)
}

Thrown when a @Requires annotation condition fails. Indicates that a method was called with invalid arguments or in an invalid state.

Usage Example:

@Requires({ amount > 0 })
void withdraw(BigDecimal amount) {
    balance -= amount
}

// Calling withdraw(-100) will throw PreconditionViolation

Catching Precondition Violations:

try {
    account.withdraw(new BigDecimal("-100"))
} catch (PreconditionViolation e) {
    println "Invalid withdrawal amount: ${e.message}"
}

PostconditionViolation

class PostconditionViolation extends AssertionViolation {
    PostconditionViolation()
    PostconditionViolation(Object o)
    PostconditionViolation(boolean b)
    PostconditionViolation(char c)
    PostconditionViolation(int i)
    PostconditionViolation(long l)
    PostconditionViolation(float v)
    PostconditionViolation(double v)
}

Thrown when an @Ensures annotation condition fails. Indicates that a method did not fulfill its guaranteed postcondition.

Usage Example:

@Ensures({ result > 0 })
int calculateAge(Date birthDate) {
    // Bug: might return negative value for future dates
    return (new Date().time - birthDate.time) / (365 * 24 * 60 * 60 * 1000)
}

// Method will throw PostconditionViolation if it returns negative age

Catching Postcondition Violations:

try {
    int age = person.calculateAge(futureDate)
} catch (PostconditionViolation e) {
    println "Method failed to satisfy postcondition: ${e.message}"
}

ClassInvariantViolation

class ClassInvariantViolation extends AssertionViolation {
    ClassInvariantViolation()
    ClassInvariantViolation(Object o)
    ClassInvariantViolation(boolean b)
    ClassInvariantViolation(char c)
    ClassInvariantViolation(int i)
    ClassInvariantViolation(long l)
    ClassInvariantViolation(float v)
    ClassInvariantViolation(double v)
}

Thrown when an @Invariant annotation condition fails. Indicates that an object's state became invalid during construction or method execution.

Usage Example:

@Invariant({ balance >= 0 })
class BankAccount {
    private BigDecimal balance = BigDecimal.ZERO
    
    void withdraw(BigDecimal amount) {
        balance -= amount  // Might violate invariant
    }
}

// Creating account or calling withdraw might throw ClassInvariantViolation

Catching Invariant Violations:

try {
    BankAccount account = new BankAccount()
    account.withdraw(new BigDecimal("1000"))
} catch (ClassInvariantViolation e) {
    println "Object state became invalid: ${e.message}"
}

CircularAssertionCallException

class CircularAssertionCallException extends RuntimeException {
    CircularAssertionCallException()
    CircularAssertionCallException(String s)
    CircularAssertionCallException(String s, Throwable throwable)
    CircularAssertionCallException(Throwable throwable)
}

Thrown when contract evaluation results in circular method calls, preventing infinite recursion. Unlike other contract violations, this extends RuntimeException rather than AssertionViolation.

Usage Example:

@Invariant({ isValid() })
class CircularExample {
    boolean isValid() {
        return someCondition()  // If this triggers invariant check, causes circular call
    }
    
    void doSomething() {
        // Method call will trigger invariant check
    }
}

Preventing Circular Calls:

@Invariant({ balance >= 0 })  // Simple field check, no method calls
class SafeAccount {
    private BigDecimal balance = BigDecimal.ZERO
    
    boolean isValid() {
        return true  // Don't call methods in contracts
    }
}

Violation Tracking

ViolationTracker

class ViolationTracker {
    static final ThreadLocal<ViolationTracker> INSTANCE
    
    static void init()
    static void deinit()
    static boolean violationsOccurred()
    static void rethrowFirst()
    static void rethrowLast()
    
    void track(AssertionViolation assertionViolation)
    boolean hasViolations()
    AssertionViolation first()
    AssertionViolation last()
}

Thread-local utility for tracking multiple contract violations in chronological order. Used internally by the contract system to manage violation reporting.

Important: The INSTANCE.get() method can return null if no tracker has been initialized for the current thread. Most static methods include null checking, but direct access requires manual null checking.

Static Methods:

init(): Initialize violation tracker for current thread

ViolationTracker.init()

deinit(): Remove violation tracker from current thread

ViolationTracker.deinit()

violationsOccurred(): Check if any violations occurred

// Safe to call - includes null checking
if (ViolationTracker.violationsOccurred()) {
    // Handle violations
}

rethrowFirst(): Rethrow the first violation that occurred

ViolationTracker.rethrowFirst()

rethrowLast(): Rethrow the most recent violation

ViolationTracker.rethrowLast()

Instance Methods:

track(AssertionViolation): Track a violation (called automatically) hasViolations(): Check if tracker has recorded violations first(): Get the first violation last(): Get the most recent violation

Exception Handling Patterns

Comprehensive Exception Handling

try {
    performContractedOperation()
} catch (PreconditionViolation e) {
    // Handle invalid input or state
    log.error("Invalid precondition: ${e.message}")
    return errorResponse("Invalid parameters provided")
} catch (PostconditionViolation e) {
    // Handle implementation bugs
    log.error("Method failed postcondition: ${e.message}")
    return errorResponse("Internal processing error")
} catch (ClassInvariantViolation e) {
    // Handle object state corruption
    log.error("Object invariant violated: ${e.message}")
    return errorResponse("Object state became invalid")
} catch (CircularAssertionCallException e) {
    // Handle contract design issues
    log.error("Circular contract evaluation: ${e.message}")
    return errorResponse("Contract evaluation error")
}

Assertion-Based Exception Handling

// Contracts work with Java assertion system
try {
    contractedMethod()
} catch (AssertionError e) {
    if (e instanceof AssertionViolation) {
        // Handle contract violations specifically
        handleContractViolation((AssertionViolation) e)
    } else {
        // Handle other assertion failures
        handleGeneralAssertion(e)
    }
}

Production Error Handling

// Disable assertions in production to avoid contract exceptions
// Use JVM flags: -da (disable assertions)
// Or package-specific: -da:com.example.contracts.*

class ProductionService {
    @Requires({ input != null })
    @Ensures({ result != null })
    String processInput(String input) {
        // In production with -da, contracts won't throw exceptions
        // But defensive programming is still recommended
        if (input == null) {
            throw new IllegalArgumentException("Input cannot be null")
        }
        
        String result = doProcessing(input)
        
        if (result == null) {
            throw new IllegalStateException("Processing failed")
        }
        
        return result
    }
}

Best Practices

Exception Handling Guidelines

  1. Catch specific exceptions: Handle each violation type appropriately
  2. Log contract violations: Include detailed information for debugging
  3. Use assertions for development: Enable during testing and development
  4. Control production behavior: Use JVM assertion flags to control contract checking
  5. Combine with defensive programming: Don't rely solely on contracts for validation

Debugging Contract Violations

  1. Enable assertion messages: Use descriptive expressions in contracts
  2. Use violation tracker: Access chronological violation information
  3. Test contract scenarios: Verify both success and failure cases
  4. Review inheritance hierarchies: Check combined contract behavior

Install with Tessl CLI

npx tessl i tessl/maven-org-apache-groovy--groovy-contracts

docs

annotations.md

exceptions.md

index.md

tile.json