You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2020/08/06 06:21:11 UTC
[groovy] branch master updated: GROOVY-9671: Absorb GContracts
project into Groovy project - initial code checkin (closes #1337)
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 2c8fc42 GROOVY-9671: Absorb GContracts project into Groovy project - initial code checkin (closes #1337)
2c8fc42 is described below
commit 2c8fc42710a5527911c21ca37aea7b6c83d954f9
Author: Andre Steingress <me...@andresteingress.com>
AuthorDate: Sun Jul 19 21:24:24 2020 +1000
GROOVY-9671: Absorb GContracts project into Groovy project - initial code checkin (closes #1337)
Co-authored-by: Paul King <pa...@asert.com.au>
---
gradle/upload.gradle | 1 +
settings.gradle | 1 +
.../codehaus/groovy/ast/tools/GeneralUtils.java | 2 +-
src/spec/doc/index.adoc | 2 +
subprojects/groovy-contracts/build.gradle | 34 ++
.../src/main/java/groovy/contracts/Contracted.java | 41 ++
.../src/main/java/groovy/contracts/Ensures.java | 86 ++++
.../src/main/java/groovy/contracts/Invariant.java | 59 +++
.../src/main/java/groovy/contracts/Requires.java | 63 +++
.../groovy/contracts/AssertionViolation.java | 64 +++
.../contracts/CircularAssertionCallException.java | 42 ++
.../groovy/contracts/ClassInvariantViolation.java | 58 +++
.../groovy/contracts/PostconditionViolation.java | 58 +++
.../groovy/contracts/PreconditionViolation.java | 58 +++
.../apache/groovy/contracts/ViolationTracker.java | 75 +++
.../meta/AnnotationProcessorImplementation.java | 37 ++
.../contracts/annotations/meta/ClassInvariant.java | 33 ++
.../annotations/meta/ContractElement.java | 32 ++
.../contracts/annotations/meta/Postcondition.java | 33 ++
.../contracts/annotations/meta/Precondition.java | 33 ++
.../contracts/ast/BaseASTTransformation.java | 58 +++
...osureExpressionEvaluationASTTransformation.java | 84 ++++
.../contracts/ast/GContractsASTTransformation.java | 78 +++
.../contracts/ast/visitor/ASTNodeMetaData.java | 27 ++
.../ast/visitor/AnnotationClosureVisitor.java | 534 +++++++++++++++++++++
.../AnnotationContractParameterVisitor.java | 69 +++
.../ast/visitor/AnnotationProcessorVisitor.java | 243 ++++++++++
.../groovy/contracts/ast/visitor/BaseVisitor.java | 51 ++
.../contracts/ast/visitor/ConfigurationSetup.java | 54 +++
.../ast/visitor/ContractElementVisitor.java | 84 ++++
.../ast/visitor/DomainModelInjectionVisitor.java | 99 ++++
.../ast/visitor/DynamicSetterInjectionVisitor.java | 105 ++++
.../LifecycleAfterTransformationVisitor.java | 69 +++
.../LifecycleBeforeTransformationVisitor.java | 69 +++
.../classgen/asm/ContractClosureWriter.java | 193 ++++++++
.../contracts/common/base/BaseLifecycle.java | 52 ++
.../impl/ClassInvariantAnnotationProcessor.java | 41 ++
.../common/impl/EnsuresAnnotationProcessor.java | 47 ++
.../common/impl/RequiresAnnotationProcessor.java | 42 ++
.../common/impl/lc/ClassInvariantLifecycle.java | 51 ++
.../common/impl/lc/PostconditionLifecycle.java | 65 +++
.../common/impl/lc/PreconditionLifecycle.java | 51 ++
.../contracts/common/spi/AnnotationProcessor.java | 40 ++
.../groovy/contracts/common/spi/Lifecycle.java | 77 +++
.../common/spi/ProcessingContextInformation.java | 110 +++++
.../apache/groovy/contracts/domain/Assertion.java | 98 ++++
.../groovy/contracts/domain/AssertionMap.java | 77 +++
.../groovy/contracts/domain/ClassInvariant.java | 38 ++
.../apache/groovy/contracts/domain/Contract.java | 67 +++
.../groovy/contracts/domain/Postcondition.java | 42 ++
.../groovy/contracts/domain/Precondition.java | 35 ++
.../generation/AssertStatementCreationUtility.java | 235 +++++++++
.../groovy/contracts/generation/BaseGenerator.java | 194 ++++++++
.../contracts/generation/CandidateChecks.java | 150 ++++++
.../generation/ClassInvariantGenerator.java | 143 ++++++
.../groovy/contracts/generation/Configurator.java | 106 ++++
.../generation/ContractExecutionTracker.java | 101 ++++
.../generation/OldVariableGenerationUtility.java | 124 +++++
.../generation/PostconditionGenerator.java | 155 ++++++
.../generation/PreconditionGenerator.java | 120 +++++
.../generation/TryCatchBlockGenerator.java | 132 +++++
.../groovy/contracts/util/AnnotationUtils.java | 132 +++++
.../groovy/contracts/util/ExpressionUtils.java | 137 ++++++
.../apache/groovy/contracts/util/FieldValues.java | 60 +++
.../util/LifecycleImplementationLoader.java | 211 ++++++++
.../org/apache/groovy/contracts/util/Validate.java | 34 ++
...rg.apache.groovy.contracts.common.spi.Lifecycle | 17 +
...org.codehaus.groovy.transform.ASTTransformation | 18 +
.../src/main/resources/dsld/org.gcontracts.dsld | 53 ++
.../src/main/resources/org.gcontracts.gdsl | 40 ++
.../src/spec/doc/contracts-userguide.adoc | 70 +++
.../src/spec/test/ContractsTest.groovy | 119 +++++
.../compability/CompileStaticTests.groovy | 96 ++++
.../compability/EqualsAndHashCodeTests.groovy | 45 ++
.../contracts/compability/ImmutableTests.groovy | 40 ++
.../groovy/contracts/compability/LockTests.groovy | 55 +++
.../contracts/compability/SynchronizedTests.groovy | 44 ++
.../contracts/compability/ToStringTests.groovy | 46 ++
.../compability/TupleConstructorTests.groovy | 39 ++
.../contracts/compability/TypeCheckedTests.groovy | 99 ++++
.../groovy/contracts/domain/ContractTests.groovy | 114 +++++
.../ContractExecutionTrackerTests.groovy | 64 +++
.../contracts/spock/SpockIntegrationTests.groovy | 48 ++
.../contracts/tests/basic/BaseTestClass.groovy | 174 +++++++
.../tests/doc/DocumentationExampleTests.groovy | 151 ++++++
.../tests/doc/RootClassExampleTests.groovy | 264 ++++++++++
.../contracts/tests/doc/StackExampleTests.groovy | 181 +++++++
.../AbstractClassInheritanceTests.groovy | 118 +++++
.../tests/interfaces/AbstractClassTests.groovy | 117 +++++
.../InterfaceAbstractClassMixturesTests.groovy | 109 +++++
.../SimpleInterfaceInheritanceTests.groovy | 132 +++++
.../tests/interfaces/StackExampleTests.groovy | 98 ++++
.../contracts/tests/inv/InheritanceTests.groovy | 416 ++++++++++++++++
.../tests/inv/POGOClassInvariantTests.groovy | 81 ++++
.../tests/inv/SimpleClassInvariantTests.groovy | 305 ++++++++++++
.../tests/other/AbstractClassTests.groovy | 87 ++++
.../tests/other/CandidateChecksTests.groovy | 73 +++
.../tests/other/CircularAssertionCallTests.groovy | 83 ++++
.../other/ClosureExpressionValidationTests.groovy | 202 ++++++++
.../tests/other/ContractLabelTests.groovy | 132 +++++
.../contracts/tests/other/ContractedTests.groovy | 89 ++++
.../contracts/tests/other/GenericTypeTests.groovy | 79 +++
.../tests/other/MissingLineNumberTests.groovy | 117 +++++
.../tests/other/NotContractedTests.groovy | 87 ++++
.../ImplicitVariableNamesPostconditionTests.groovy | 47 ++
.../contracts/tests/post/InheritanceTests.groovy | 287 +++++++++++
.../post/OldVariablePostconditionTests.groovy | 169 +++++++
.../ResultAndOldVariablePostconditionTests.groovy | 66 +++
.../post/ResultVariablePostconditionTests.groovy | 59 +++
.../tests/post/SimplePostconditionTests.groovy | 296 ++++++++++++
.../contracts/tests/pre/InheritanceTests.groovy | 182 +++++++
.../tests/pre/SimplePreconditionTests.groovy | 469 ++++++++++++++++++
.../contracts/util/AnnotationUtilsTests.groovy | 58 +++
.../groovy/contracts/util/FieldValuesTests.groovy | 39 ++
114 files changed, 11369 insertions(+), 1 deletion(-)
diff --git a/gradle/upload.gradle b/gradle/upload.gradle
index 1055b12..04d3eb9 100644
--- a/gradle/upload.gradle
+++ b/gradle/upload.gradle
@@ -189,6 +189,7 @@ def optionalModules = [
'groovy-bsf',
'groovy-cli-commons',
'groovy-dateutil',
+ 'groovy-gcontracts',
'groovy-jaxb',
'groovy-testng'
]
diff --git a/settings.gradle b/settings.gradle
index f11e894..058ed1f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -35,6 +35,7 @@ def subprojects = ['groovy-ant',
'groovy-cli-commons',
'groovy-cli-picocli',
'groovy-console',
+ 'groovy-contracts',
'groovy-datetime',
'groovy-dateutil',
'groovy-docgenerator',
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
index 833e40c..b3fa2b3 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -94,7 +94,7 @@ public class GeneralUtils {
public static final Token OR = Token.newSymbol(Types.LOGICAL_OR, -1, -1);
public static final Token CMP = Token.newSymbol(Types.COMPARE_TO, -1, -1);
public static final Token INSTANCEOF = Token.newSymbol(Types.KEYWORD_INSTANCEOF, -1, -1);
- private static final Token PLUS = Token.newSymbol(Types.PLUS, -1, -1);
+ public static final Token PLUS = Token.newSymbol(Types.PLUS, -1, -1);
private static final Token INDEX = Token.newSymbol("[", -1, -1);
public static BinaryExpression andX(final Expression lhv, final Expression rhv) {
diff --git a/src/spec/doc/index.adoc b/src/spec/doc/index.adoc
index 7946e0b..95f2f08 100644
--- a/src/spec/doc/index.adoc
+++ b/src/spec/doc/index.adoc
@@ -75,6 +75,8 @@ include::{projectdir}/subprojects/groovy-xml/{specfolder}/xml-userguide.adoc[lev
include::{projectdir}/subprojects/groovy-yaml/{specfolder}/yaml-userguide.adoc[leveloffset=+2]
+include::{projectdir}/subprojects/groovy-contracts/{specfolder}/contracts-userguide.adoc[leveloffset=+2]
+
=== Scripting Ant tasks
Groovy integrates very well with http://ant.apache.org[Apache Ant] thanks to <<_antbuilder,AntBuilder>>.
diff --git a/subprojects/groovy-contracts/build.gradle b/subprojects/groovy-contracts/build.gradle
new file mode 100644
index 0000000..278e2ed
--- /dev/null
+++ b/subprojects/groovy-contracts/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+dependencies {
+ api rootProject // ASTMatcher use ASTNode...
+ testImplementation rootProject.sourceSets.test.runtimeClasspath
+ testImplementation project(':groovy-test')
+ testImplementation project(':groovy-templates')
+ testImplementation ("org.spockframework:spock-core:$spockVersion") {
+ exclude group: 'org.codehaus.groovy'
+ }
+ testImplementation ("org.spockframework:spock-junit4:$spockVersion")
+ testRuntime("org.junit.vintage:junit-vintage-engine:$junit5Version")
+}
+
+test {
+ useJUnitPlatform()
+ systemProperty "spock.iKnowWhatImDoing.disableGroovyVersionCheck", "true"
+}
diff --git a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Contracted.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Contracted.java
new file mode 100644
index 0000000..050a99e
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Contracted.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.contracts;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Package-level and class-level annotation indicating that the package is enabled for
+ * class-invariants, pre- and post-conditions.</p>
+ * <p>
+ * For example:
+ * <pre>
+ * @Contracted
+ * package my.package
+ *
+ * import groovy.contracts.*
+ * </pre>
+ */
+@Target({ElementType.PACKAGE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Contracted {
+}
diff --git a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Ensures.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Ensures.java
new file mode 100644
index 0000000..49f5e05
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Ensures.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.contracts;
+
+import org.apache.groovy.contracts.annotations.meta.AnnotationProcessorImplementation;
+import org.apache.groovy.contracts.annotations.meta.Postcondition;
+import org.apache.groovy.contracts.common.impl.EnsuresAnnotationProcessor;
+import org.apache.groovy.lang.annotation.Incubating;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Represents a <b>method postcondition</b>.
+ * </p>
+ * <p>
+ * A postcondition is a condition that is guaranteed to be fulfilled by suppliers.
+ * </p>
+ * <p>
+ * A method's postcondition is executed <i>after</i> a method call
+ * has finished. A successor's postcondition strengthens the postcondition of its parent class, e.g. if A.someMethod
+ * declares a postcondition and B.someMethod overrides the method the postconditions are combined with a boolean AND.
+ * </p>
+ * <p>
+ * Compared to pre-conditions, postcondition annotation closures are optionally called with two additional
+ * closure arguments: <tt>result</tt> and <tt>old</tt>.
+ * </p>
+ * <p>
+ * <tt>result</tt> is available if the corresponding method has a non-void return-type and holds the
+ * result of the method call. Be aware that modifying the internal state of a reference type can lead
+ * to side-effects. Groovy-contracts does not keep track of any sort of modifications, neither any conversion to
+ * immutability.
+ * </p>
+ * <p>
+ * <tt>old</tt> is available in every postcondition. It is a {@link java.util.Map} which holds the values
+ * of value types and {@link Cloneable} types before the method has been executed.
+ * </p>
+ * <p>
+ * Examples:
+ * <p>
+ * Accessing the <tt>result</tt> closure parameter:
+ *
+ * <pre>
+ * @Ensures({ result -> result != argument1 })
+ * def T someOperation(def argument1, def argument2) {
+ * ...
+ * }
+ * </pre>
+ * <p>
+ * Accessing the <tt>old</tt> closure parameter:
+ *
+ * <pre>
+ * @Ensures({ old -> old.counter + 1 == counter })
+ * def T someOperation(def argument1, def argument2) {
+ * ...
+ * }
+ * </pre>
+ * </p>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
+@Incubating
+@Postcondition
+@AnnotationProcessorImplementation(EnsuresAnnotationProcessor.class)
+public @interface Ensures {
+ Class value();
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariant.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariant.java
new file mode 100644
index 0000000..ca9edf3
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariant.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.contracts;
+
+import org.apache.groovy.contracts.annotations.meta.AnnotationProcessorImplementation;
+import org.apache.groovy.contracts.annotations.meta.ClassInvariant;
+import org.apache.groovy.contracts.common.impl.ClassInvariantAnnotationProcessor;
+import org.apache.groovy.lang.annotation.Incubating;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Represents a <b>class-invariant</b>.
+ * </p>
+ *
+ * <p>
+ * The class-invariant defines assertions holding during the entire objects life-time.
+ * </p>
+ * <p>
+ * Class-invariants are verified at runtime at the following pointcuts:
+ * <ul>
+ * <li>after a constructor call</li>
+ * <li>before a method call</li>
+ * <li>after a method call</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Whenever a class has a parent which itself specifies a class-invariant, that class-invariant expression is combined
+ * with the actual class's invariant (by using a logical AND).
+ * </p>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Incubating
+@ClassInvariant
+@AnnotationProcessorImplementation(ClassInvariantAnnotationProcessor.class)
+public @interface Invariant {
+ Class value();
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Requires.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Requires.java
new file mode 100644
index 0000000..26d4c4c
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Requires.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.contracts;
+
+import org.apache.groovy.contracts.annotations.meta.AnnotationProcessorImplementation;
+import org.apache.groovy.contracts.annotations.meta.Precondition;
+import org.apache.groovy.contracts.common.impl.RequiresAnnotationProcessor;
+import org.apache.groovy.lang.annotation.Incubating;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Represents a <b>method precondition</b>.
+ * </p>
+ * <p>
+ * A precondition is a condition that must be met by clients of this class. Whenever the
+ * precondition can be satisfied, it is guaranteed that the supplier will fulfil the method's
+ * postcondition.
+ * </p>
+ * <p>
+ * A method's precondition is executed <i>as the first statement</i> within a method call. A
+ * successor's precondition weakens the precondition of its parent class, e.g. if A.someMethod
+ * declares a precondition and B.someMethod overrides the method the preconditions are combined with a boolean OR.
+ * </p>
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * @Requires({ argument1 != argument2 && argument2 >= 0 })
+ * void someOperation(def argument1, def argument2) {
+ * ...
+ * }
+ * </pre>
+ * </p>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
+@Incubating
+@Precondition
+@AnnotationProcessorImplementation(RequiresAnnotationProcessor.class)
+public @interface Requires {
+ Class value();
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/AssertionViolation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/AssertionViolation.java
new file mode 100644
index 0000000..7d51049
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/AssertionViolation.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts;
+
+/**
+ * <p>Abstract base class for all assertion violations.</p>
+ */
+public abstract class AssertionViolation extends AssertionError {
+
+ protected AssertionViolation() {
+ ViolationTracker.INSTANCE.get().track(this);
+ }
+
+ protected AssertionViolation(Object o) {
+ super(o);
+ if (ViolationTracker.INSTANCE.get() != null) ViolationTracker.INSTANCE.get().track(this);
+ }
+
+ protected AssertionViolation(boolean b) {
+ super(b);
+ if (ViolationTracker.INSTANCE.get() != null) ViolationTracker.INSTANCE.get().track(this);
+ }
+
+ protected AssertionViolation(char c) {
+ super(c);
+ if (ViolationTracker.INSTANCE.get() != null) ViolationTracker.INSTANCE.get().track(this);
+ }
+
+ protected AssertionViolation(int i) {
+ super(i);
+ if (ViolationTracker.INSTANCE.get() != null) ViolationTracker.INSTANCE.get().track(this);
+ }
+
+ protected AssertionViolation(long l) {
+ super(l);
+ if (ViolationTracker.INSTANCE.get() != null) ViolationTracker.INSTANCE.get().track(this);
+ }
+
+ protected AssertionViolation(float v) {
+ super(v);
+ if (ViolationTracker.INSTANCE.get() != null) ViolationTracker.INSTANCE.get().track(this);
+ }
+
+ protected AssertionViolation(double v) {
+ super(v);
+ if (ViolationTracker.INSTANCE.get() != null) ViolationTracker.INSTANCE.get().track(this);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/CircularAssertionCallException.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/CircularAssertionCallException.java
new file mode 100644
index 0000000..b7ee8d7
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/CircularAssertionCallException.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts;
+
+/**
+ * <p>Thrown whenever pre- or post-conditions are called in a cyclic way.</p>
+ *
+ * @see AssertionViolation
+ */
+public class CircularAssertionCallException extends RuntimeException {
+
+ public CircularAssertionCallException() {
+ }
+
+ public CircularAssertionCallException(String s) {
+ super(s);
+ }
+
+ public CircularAssertionCallException(String s, Throwable throwable) {
+ super(s, throwable);
+ }
+
+ public CircularAssertionCallException(Throwable throwable) {
+ super(throwable);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ClassInvariantViolation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ClassInvariantViolation.java
new file mode 100644
index 0000000..befa807
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ClassInvariantViolation.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts;
+
+/**
+ * <p>Thrown whenever a class invariant violation occurs.</p>
+ *
+ * @see AssertionViolation
+ */
+public class ClassInvariantViolation extends AssertionViolation {
+
+ public ClassInvariantViolation() {
+ }
+
+ public ClassInvariantViolation(Object o) {
+ super(o);
+ }
+
+ public ClassInvariantViolation(boolean b) {
+ super(b);
+ }
+
+ public ClassInvariantViolation(char c) {
+ super(c);
+ }
+
+ public ClassInvariantViolation(int i) {
+ super(i);
+ }
+
+ public ClassInvariantViolation(long l) {
+ super(l);
+ }
+
+ public ClassInvariantViolation(float v) {
+ super(v);
+ }
+
+ public ClassInvariantViolation(double v) {
+ super(v);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/PostconditionViolation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/PostconditionViolation.java
new file mode 100644
index 0000000..4e448e6
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/PostconditionViolation.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts;
+
+/**
+ * <p>Thrown whenever a postcondition violation occurs.</p>
+ *
+ * @see AssertionViolation
+ */
+public class PostconditionViolation extends AssertionViolation {
+
+ public PostconditionViolation() {
+ }
+
+ public PostconditionViolation(Object o) {
+ super(o);
+ }
+
+ public PostconditionViolation(boolean b) {
+ super(b);
+ }
+
+ public PostconditionViolation(char c) {
+ super(c);
+ }
+
+ public PostconditionViolation(int i) {
+ super(i);
+ }
+
+ public PostconditionViolation(long l) {
+ super(l);
+ }
+
+ public PostconditionViolation(float v) {
+ super(v);
+ }
+
+ public PostconditionViolation(double v) {
+ super(v);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/PreconditionViolation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/PreconditionViolation.java
new file mode 100644
index 0000000..195389b
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/PreconditionViolation.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts;
+
+/**
+ * <p>Thrown whenever a precondition violation occurs.</p>
+ *
+ * @see AssertionViolation
+ */
+public class PreconditionViolation extends AssertionViolation {
+
+ public PreconditionViolation() {
+ }
+
+ public PreconditionViolation(Object o) {
+ super(o);
+ }
+
+ public PreconditionViolation(boolean b) {
+ super(b);
+ }
+
+ public PreconditionViolation(char c) {
+ super(c);
+ }
+
+ public PreconditionViolation(int i) {
+ super(i);
+ }
+
+ public PreconditionViolation(long l) {
+ super(l);
+ }
+
+ public PreconditionViolation(float v) {
+ super(v);
+ }
+
+ public PreconditionViolation(double v) {
+ super(v);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ViolationTracker.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ViolationTracker.java
new file mode 100644
index 0000000..85230f9
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ViolationTracker.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts;
+
+import org.apache.groovy.contracts.util.Validate;
+
+import java.util.TreeMap;
+
+/**
+ * <p>
+ * A violation tracker is used to keep a list of pre-, post-condition or class-invariant
+ * violations in chronological order. This is necessary to evaluate all parts of a pre- or postcondition, and still
+ * being able to rethrow assertion errors.
+ * </p>
+ */
+public class ViolationTracker {
+
+ public static ThreadLocal<ViolationTracker> INSTANCE = new ThreadLocal<ViolationTracker>();
+
+ public static void init() {
+ INSTANCE.set(new ViolationTracker());
+ }
+
+ public static void deinit() {
+ INSTANCE.remove();
+ }
+
+ public static boolean violationsOccurred() {
+ return INSTANCE.get().hasViolations();
+ }
+
+ public static void rethrowFirst() {
+ throw INSTANCE.get().first();
+ }
+
+ public static void rethrowLast() {
+ throw INSTANCE.get().last();
+ }
+
+ private TreeMap<Long, AssertionViolation> violations = new TreeMap<Long, AssertionViolation>();
+
+ public void track(final AssertionViolation assertionViolation) {
+ Validate.notNull(assertionViolation);
+
+ violations.put(System.nanoTime(), assertionViolation);
+ }
+
+ public boolean hasViolations() {
+ return violations.size() > 0;
+ }
+
+ public AssertionViolation first() {
+ return violations.firstEntry().getValue();
+ }
+
+ public AssertionViolation last() {
+ return violations.lastEntry().getValue();
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/AnnotationProcessorImplementation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/AnnotationProcessorImplementation.java
new file mode 100644
index 0000000..9d7cfdf
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/AnnotationProcessorImplementation.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.annotations.meta;
+
+import org.apache.groovy.contracts.common.spi.AnnotationProcessor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <p>Indicates what {@link AnnotationProcessor} implementation should be used to process
+ * the correlating annotation.</p>
+ *
+ * @see org.apache.groovy.contracts.common.spi.AnnotationProcessor
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnnotationProcessorImplementation {
+ Class<? extends AnnotationProcessor> value();
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/ClassInvariant.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/ClassInvariant.java
new file mode 100644
index 0000000..7dfbd62
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/ClassInvariant.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.annotations.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that annotations being marked as {@code @ClassInvariant} are to be treated
+ * as class invariant modifying annotations.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ContractElement
+public @interface ClassInvariant {
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/ContractElement.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/ContractElement.java
new file mode 100644
index 0000000..bd4eaa8
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/ContractElement.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.annotations.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that annotations being marked as {@code @ContractElement} are to be used
+ * by some contract element being either a class-invariant, pre- or post-condition.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ContractElement {
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/Postcondition.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/Postcondition.java
new file mode 100644
index 0000000..519c1e6
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/Postcondition.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.annotations.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that annotations being marked as {@code @Postcondition} are to be treated
+ * as post-condition modifying annotations.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ContractElement
+public @interface Postcondition {
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/Precondition.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/Precondition.java
new file mode 100644
index 0000000..aa6da4f
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/annotations/meta/Precondition.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.annotations.meta;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that annotations being marked as {@code @Precondition} are to be treated
+ * as pre-condition modifying annotations.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@ContractElement
+public @interface Precondition {
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/BaseASTTransformation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/BaseASTTransformation.java
new file mode 100644
index 0000000..22df11c
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/BaseASTTransformation.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast;
+
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.codehaus.groovy.transform.ASTTransformation;
+
+import java.lang.reflect.Field;
+
+/**
+ * Base AST transformation encapsulating all common helper methods and implementing {@link org.codehaus.groovy.transform.ASTTransformation}.
+ *
+ * @see org.codehaus.groovy.transform.ASTTransformation
+ */
+public abstract class BaseASTTransformation implements ASTTransformation {
+
+ /**
+ * Reads the protected <tt>source1</tt> instance variable of {@link org.codehaus.groovy.control.SourceUnit}.
+ *
+ * @param unit the {@link org.codehaus.groovy.control.SourceUnit} to retrieve the {@link org.codehaus.groovy.control.io.ReaderSource} from
+ * @return the {@link org.codehaus.groovy.control.io.ReaderSource} of the given <tt>unit</tt>.
+ */
+ protected ReaderSource getReaderSource(SourceUnit unit) {
+
+ try {
+ Class sourceUnitClass = unit.getClass();
+
+ while (sourceUnitClass != SourceUnit.class) {
+ sourceUnitClass = sourceUnitClass.getSuperclass();
+ }
+
+ Field field = sourceUnitClass.getDeclaredField("source1");
+ field.setAccessible(true);
+
+ return (ReaderSource) field.get(unit);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/ClosureExpressionEvaluationASTTransformation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/ClosureExpressionEvaluationASTTransformation.java
new file mode 100644
index 0000000..97e699d
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/ClosureExpressionEvaluationASTTransformation.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast;
+
+import groovy.contracts.Contracted;
+import org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor;
+import org.apache.groovy.contracts.ast.visitor.ConfigurationSetup;
+import org.apache.groovy.contracts.ast.visitor.ContractElementVisitor;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Evaluates {@link org.codehaus.groovy.ast.expr.ClosureExpression} instances in as actual annotation parameters and
+ * generates special contract closure classes from them.
+ */
+@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
+public class ClosureExpressionEvaluationASTTransformation extends BaseASTTransformation {
+
+ private void generateAnnotationClosureClasses(SourceUnit unit, ReaderSource source, List<ClassNode> classNodes) {
+ final AnnotationClosureVisitor annotationClosureVisitor = new AnnotationClosureVisitor(unit, source);
+
+ for (final ClassNode classNode : classNodes) {
+ annotationClosureVisitor.visitClass(classNode);
+
+ if (!CandidateChecks.isContractsCandidate(classNode)) continue;
+
+ final ContractElementVisitor contractElementVisitor = new ContractElementVisitor(unit, source);
+ contractElementVisitor.visitClass(classNode);
+
+ if (!contractElementVisitor.isFoundContractElement()) continue;
+
+ annotationClosureVisitor.visitClass(classNode);
+ markClassNodeAsContracted(classNode);
+
+ new ConfigurationSetup().init(classNode);
+ }
+ }
+
+ /**
+ * {@link org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[], org.codehaus.groovy.control.SourceUnit)}
+ */
+ public void visit(ASTNode[] nodes, SourceUnit unit) {
+ final ModuleNode moduleNode = unit.getAST();
+
+ ReaderSource source = getReaderSource(unit);
+ final List<ClassNode> classNodes = new ArrayList<ClassNode>(moduleNode.getClasses());
+
+ generateAnnotationClosureClasses(unit, source, classNodes);
+ }
+
+ private void markClassNodeAsContracted(final ClassNode classNode) {
+ final ClassNode contractedAnnotationClassNode = ClassHelper.makeWithoutCaching(Contracted.class);
+
+ if (classNode.getAnnotations(contractedAnnotationClassNode).isEmpty())
+ classNode.addAnnotation(new AnnotationNode(contractedAnnotationClassNode));
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/GContractsASTTransformation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/GContractsASTTransformation.java
new file mode 100644
index 0000000..23826bf
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/GContractsASTTransformation.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast;
+
+import groovy.contracts.Contracted;
+import groovy.contracts.Ensures;
+import groovy.contracts.Invariant;
+import groovy.contracts.Requires;
+import org.apache.groovy.contracts.ast.visitor.AnnotationProcessorVisitor;
+import org.apache.groovy.contracts.ast.visitor.DomainModelInjectionVisitor;
+import org.apache.groovy.contracts.ast.visitor.DynamicSetterInjectionVisitor;
+import org.apache.groovy.contracts.ast.visitor.LifecycleAfterTransformationVisitor;
+import org.apache.groovy.contracts.ast.visitor.LifecycleBeforeTransformationVisitor;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+/**
+ * <p>
+ * Custom AST transformation that removes closure annotations of {@link Invariant},
+ * {@link Requires} and {@link Ensures} and adds Java
+ * assertions executing the closure-code.
+ * </p>
+ * <p>
+ * Whenever an assertion is broken an {@link org.apache.groovy.contracts.AssertionViolation} descendant class will be thrown.
+ * </p>
+ *
+ * @see org.apache.groovy.contracts.PreconditionViolation
+ * @see org.apache.groovy.contracts.PostconditionViolation
+ * @see org.apache.groovy.contracts.ClassInvariantViolation
+ */
+@GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION)
+public class GContractsASTTransformation extends BaseASTTransformation {
+
+ /**
+ * {@link org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[], org.codehaus.groovy.control.SourceUnit)}
+ */
+ public void visit(ASTNode[] nodes, SourceUnit unit) {
+ final ModuleNode moduleNode = unit.getAST();
+
+ ReaderSource source = getReaderSource(unit);
+ final ClassNode contractedAnnotationClassNode = ClassHelper.makeWithoutCaching(Contracted.class);
+
+ for (final ClassNode classNode : moduleNode.getClasses()) {
+ if (classNode.getAnnotations(contractedAnnotationClassNode).isEmpty()) continue;
+
+ final ProcessingContextInformation pci = new ProcessingContextInformation(classNode, unit, source);
+ new LifecycleBeforeTransformationVisitor(unit, source, pci).visitClass(classNode);
+ new AnnotationProcessorVisitor(unit, source, pci).visitClass(classNode);
+ new DomainModelInjectionVisitor(unit, source, pci).visitClass(classNode);
+ new LifecycleAfterTransformationVisitor(unit, source, pci).visitClass(classNode);
+ new DynamicSetterInjectionVisitor(unit, source).visitClass(classNode);
+ }
+ }
+}
+
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ASTNodeMetaData.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ASTNodeMetaData.java
new file mode 100644
index 0000000..d276ecc
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ASTNodeMetaData.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+/**
+ * Holds all constants to be used as AST node meta data keys.
+ */
+public interface ASTNodeMetaData {
+ String PROCESSED = "org.apache.groovy.contracts.CLOSURE_REPLACED";
+ String CLOSURE_REPLACED = "org.apache.groovy.contracts.CLOSURE_REPLACED";
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationClosureVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationClosureVisitor.java
new file mode 100644
index 0000000..5d8ece5
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationClosureVisitor.java
@@ -0,0 +1,534 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import groovy.contracts.Ensures;
+import groovy.contracts.Requires;
+import org.apache.groovy.contracts.ClassInvariantViolation;
+import org.apache.groovy.contracts.PostconditionViolation;
+import org.apache.groovy.contracts.PreconditionViolation;
+import org.apache.groovy.contracts.annotations.meta.ContractElement;
+import org.apache.groovy.contracts.annotations.meta.Postcondition;
+import org.apache.groovy.contracts.classgen.asm.ContractClosureWriter;
+import org.apache.groovy.contracts.generation.AssertStatementCreationUtility;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.generation.TryCatchBlockGenerator;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.apache.groovy.contracts.util.ExpressionUtils;
+import org.apache.groovy.contracts.util.FieldValues;
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.TransformingCodeVisitor;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.CastExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PostfixExpression;
+import org.codehaus.groovy.ast.expr.PrefixExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.codehaus.groovy.syntax.Token;
+import org.codehaus.groovy.syntax.Types;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+
+/**
+ * Visits interfaces & classes and looks for <tt>@Requires</tt> or <tt>@Ensures</tt> and creates {@link groovy.lang.Closure}
+ * classes for the annotation closures.<p/>
+ * <p>
+ * The annotation closure classes are used later on to check interface contract pre- and post-conditions in
+ * implementation classes.
+ *
+ * @see Requires
+ * @see Ensures
+ * @see org.apache.groovy.contracts.ast.visitor.BaseVisitor
+ */
+public class AnnotationClosureVisitor extends BaseVisitor implements ASTNodeMetaData {
+
+ public static final String META_DATA_USE_EXECUTION_TRACKER = "org.apache.groovy.contracts.META_DATA.USE_EXECUTION_TRACKER";
+ public static final String META_DATA_ORIGINAL_TRY_CATCH_BLOCK = "org.apache.groovy.contracts.META_DATA.ORIGINAL_TRY_CATCH_BLOCK";
+
+ private static final String POSTCONDITION_TYPE_NAME = Postcondition.class.getName();
+ private static final ClassNode FIELD_VALUES = ClassHelper.makeCached(FieldValues.class);
+
+ private ClassNode classNode;
+ private final ContractClosureWriter contractClosureWriter = new ContractClosureWriter();
+
+ public AnnotationClosureVisitor(final SourceUnit sourceUnit, final ReaderSource source) {
+ super(sourceUnit, source);
+ }
+
+ @Override
+ public void visitClass(ClassNode node) {
+ if (node == null) return;
+ if (!(CandidateChecks.isInterfaceContractsCandidate(node) || CandidateChecks.isContractsCandidate(node)))
+ return;
+
+ classNode = node;
+
+ if (classNode.getNodeMetaData(PROCESSED) == null && CandidateChecks.isContractsCandidate(node)) {
+ final List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(node, ContractElement.class.getName());
+ for (AnnotationNode annotationNode : annotationNodes) {
+ Expression expression = annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME);
+ if (expression == null || expression instanceof ClassExpression) continue;
+
+ ClosureExpression closureExpression = (ClosureExpression) expression;
+
+ ClosureExpressionValidator validator = new ClosureExpressionValidator(classNode, null, annotationNode, sourceUnit);
+ validator.visitClosureExpression(closureExpression);
+ validator.secondPass(closureExpression);
+
+ List<Parameter> parameters = new ArrayList<>(Arrays.asList(closureExpression.getParameters()));
+
+ final List<BooleanExpression> booleanExpressions = ExpressionUtils.getBooleanExpression(closureExpression);
+ if (booleanExpressions == null || booleanExpressions.isEmpty()) continue;
+
+ BlockStatement closureBlockStatement = (BlockStatement) closureExpression.getCode();
+
+ BlockStatement newClosureBlockStatement = TryCatchBlockGenerator.generateTryCatchBlock(
+ ClassHelper.makeWithoutCaching(ClassInvariantViolation.class),
+ "<" + annotationNode.getClassNode().getName() + "> " + classNode.getName() + " \n\n",
+ AssertStatementCreationUtility.getAssertionStatements(booleanExpressions)
+ );
+
+ newClosureBlockStatement.setSourcePosition(closureBlockStatement);
+
+ ClosureExpression rewrittenClosureExpression = new ClosureExpression(parameters.toArray(Parameter.EMPTY_ARRAY), newClosureBlockStatement);
+ rewrittenClosureExpression.setSourcePosition(closureExpression);
+ rewrittenClosureExpression.setDeclaringClass(closureExpression.getDeclaringClass());
+ rewrittenClosureExpression.setSynthetic(true);
+ rewrittenClosureExpression.setVariableScope(closureExpression.getVariableScope());
+ rewrittenClosureExpression.setType(closureExpression.getType());
+
+ ClassNode closureClassNode = contractClosureWriter.createClosureClass(classNode, null, rewrittenClosureExpression, false, false, Opcodes.ACC_PUBLIC);
+ classNode.getModule().addClass(closureClassNode);
+
+ final ClassExpression value = new ClassExpression(closureClassNode);
+ value.setSourcePosition(annotationNode);
+
+ BlockStatement value1 = TryCatchBlockGenerator.generateTryCatchBlockForInlineMode(
+ ClassHelper.makeWithoutCaching(ClassInvariantViolation.class),
+ "<" + annotationNode.getClassNode().getName() + "> " + classNode.getName() + " \n\n",
+ AssertStatementCreationUtility.getAssertionStatements(booleanExpressions)
+ );
+ value1.setNodeMetaData(META_DATA_USE_EXECUTION_TRACKER, validator.isMethodCalls());
+
+ value.setNodeMetaData(META_DATA_ORIGINAL_TRY_CATCH_BLOCK, value1);
+
+ annotationNode.setMember(CLOSURE_ATTRIBUTE_NAME, value);
+
+ markClosureReplaced(classNode);
+ }
+ }
+
+ super.visitClass(node);
+
+ // generate closure classes for the super class and all implemented interfaces
+ visitClass(node.getSuperClass());
+ for (ClassNode i : node.getInterfaces()) {
+ visitClass(i);
+ }
+
+ markProcessed(classNode);
+ }
+
+ @Override
+ public void visitConstructorOrMethod(MethodNode methodNode, boolean isConstructor) {
+ if (!CandidateChecks.couldBeContractElementMethodNode(classNode, methodNode) && !(CandidateChecks.isPreconditionCandidate(classNode, methodNode)))
+ return;
+ if (methodNode.getNodeMetaData(PROCESSED) != null) return;
+
+ final List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(methodNode, ContractElement.class.getName());
+ for (AnnotationNode annotationNode : annotationNodes) {
+ replaceWithClosureClassReference(annotationNode, methodNode);
+ }
+
+ markProcessed(methodNode);
+
+ super.visitConstructorOrMethod(methodNode, isConstructor);
+ }
+
+ private void replaceWithClosureClassReference(AnnotationNode annotationNode, MethodNode methodNode) {
+ Validate.notNull(annotationNode);
+ Validate.notNull(methodNode);
+
+ // check whether this is a pre- or postcondition
+ boolean isPostcondition = AnnotationUtils.hasAnnotationOfType(annotationNode.getClassNode(), org.apache.groovy.contracts.annotations.meta.Postcondition.class.getName());
+
+ Expression expression = annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME);
+ if (expression == null || expression instanceof ClassExpression) return;
+
+ ClosureExpression closureExpression = (ClosureExpression) expression;
+ ClassCodeExpressionTransformer transformer = new OldPropertyExpressionTransformer(methodNode);
+ TransformingCodeVisitor visitor = new TransformingCodeVisitor(transformer);
+ visitor.visitClosureExpression(closureExpression);
+
+ ClosureExpressionValidator validator = new ClosureExpressionValidator(classNode, methodNode, annotationNode, sourceUnit);
+ validator.visitClosureExpression(closureExpression);
+ validator.secondPass(closureExpression);
+
+ List<Parameter> parameters = new ArrayList<>(Arrays.asList(closureExpression.getParameters()));
+
+ parameters.addAll(new ArrayList<>(Arrays.asList(methodNode.getParameters())));
+
+ final List<BooleanExpression> booleanExpressions = ExpressionUtils.getBooleanExpression(closureExpression);
+ if (booleanExpressions == null || booleanExpressions.isEmpty()) return;
+
+ BlockStatement closureBlockStatement = (BlockStatement) closureExpression.getCode();
+
+ BlockStatement newClosureBlockStatement = TryCatchBlockGenerator.generateTryCatchBlock(
+ isPostcondition ? ClassHelper.makeWithoutCaching(PostconditionViolation.class) : ClassHelper.makeWithoutCaching(PreconditionViolation.class),
+ "<" + annotationNode.getClassNode().getName() + "> " + classNode.getName() + "." + methodNode.getTypeDescriptor() + " \n\n",
+ AssertStatementCreationUtility.getAssertionStatements(booleanExpressions)
+ );
+
+ newClosureBlockStatement.setSourcePosition(closureBlockStatement);
+
+ ClosureExpression rewrittenClosureExpression = new ClosureExpression(parameters.toArray(Parameter.EMPTY_ARRAY), newClosureBlockStatement);
+ rewrittenClosureExpression.setSourcePosition(closureExpression);
+ rewrittenClosureExpression.setDeclaringClass(closureExpression.getDeclaringClass());
+ rewrittenClosureExpression.setSynthetic(true);
+ rewrittenClosureExpression.setVariableScope(correctVariableScope(closureExpression.getVariableScope(), methodNode));
+ rewrittenClosureExpression.setType(closureExpression.getType());
+
+ boolean isConstructor = methodNode instanceof ConstructorNode;
+ ClassNode closureClassNode = contractClosureWriter.createClosureClass(classNode, methodNode, rewrittenClosureExpression, isPostcondition && !isConstructor, isPostcondition && !isConstructor, Opcodes.ACC_PUBLIC);
+ classNode.getModule().addClass(closureClassNode);
+
+ final ClassExpression value = new ClassExpression(closureClassNode);
+ value.setSourcePosition(annotationNode);
+
+ BlockStatement value1 = TryCatchBlockGenerator.generateTryCatchBlockForInlineMode(
+ isPostcondition ? ClassHelper.makeWithoutCaching(PostconditionViolation.class) : ClassHelper.makeWithoutCaching(PreconditionViolation.class),
+ "<" + annotationNode.getClassNode().getName() + "> " + classNode.getName() + "." + methodNode.getTypeDescriptor() + " \n\n",
+ AssertStatementCreationUtility.getAssertionStatements(booleanExpressions)
+ );
+ value1.setNodeMetaData(META_DATA_USE_EXECUTION_TRACKER, validator.isMethodCalls());
+
+ value.setNodeMetaData(META_DATA_ORIGINAL_TRY_CATCH_BLOCK, value1);
+ annotationNode.setMember(CLOSURE_ATTRIBUTE_NAME, value);
+
+ markClosureReplaced(methodNode);
+ }
+
+ private VariableScope correctVariableScope(VariableScope variableScope, MethodNode methodNode) {
+ if (variableScope == null) return null;
+ if (methodNode == null || methodNode.getParameters() == null || methodNode.getParameters().length == 0)
+ return variableScope;
+
+ VariableScope copy = copy(variableScope);
+
+ for (Iterator<Variable> iterator = variableScope.getReferencedClassVariablesIterator(); iterator.hasNext(); ) {
+ Variable variable = iterator.next();
+ String name = variable.getName();
+
+ for (Parameter parameter : methodNode.getParameters()) {
+ if (parameter.getName().equals(name)) {
+ copy.putReferencedLocalVariable(parameter);
+ break;
+ }
+ }
+
+ if (!copy.isReferencedLocalVariable(name)) {
+ copy.putReferencedClassVariable(variable);
+ }
+ }
+
+ return copy;
+ }
+
+ private VariableScope copy(VariableScope original) {
+ VariableScope copy = new VariableScope(original.getParent());
+ copy.setClassScope(original.getClassScope());
+ copy.setInStaticContext(original.isInStaticContext());
+ return copy;
+ }
+
+ private void markProcessed(ASTNode someNode) {
+ if (someNode.getNodeMetaData(PROCESSED) == null)
+ someNode.setNodeMetaData(PROCESSED, Boolean.TRUE);
+ }
+
+ private void markClosureReplaced(ASTNode someNode) {
+ if (someNode.getNodeMetaData(CLOSURE_REPLACED) == null)
+ someNode.setNodeMetaData(CLOSURE_REPLACED, Boolean.TRUE);
+ }
+
+ static class ClosureExpressionValidator extends ClassCodeVisitorSupport implements Opcodes {
+
+ private final ClassNode classNode;
+ private final MethodNode methodNode;
+ private final AnnotationNode annotationNode;
+ private final SourceUnit sourceUnit;
+
+ private final Map<VariableExpression, StaticMethodCallExpression> variableExpressions;
+
+ private boolean secondPass = false;
+ private boolean methodCalls = false;
+
+ public ClosureExpressionValidator(ClassNode classNode, MethodNode methodNode, AnnotationNode annotationNode, SourceUnit sourceUnit) {
+ this.classNode = classNode;
+ this.methodNode = methodNode;
+ this.annotationNode = annotationNode;
+ this.sourceUnit = sourceUnit;
+ this.variableExpressions = new HashMap<>();
+ }
+
+ @Override
+ public void visitClosureExpression(ClosureExpression expression) {
+ secondPass = false;
+
+ if (expression.getCode() == null || expression.getCode() instanceof EmptyStatement) {
+ addError("[groovy-contracts] Annotation does not contain any expressions (e.g. use '@Requires({ argument1 })').", expression);
+ }
+
+ if (expression.getCode() instanceof BlockStatement &&
+ ((BlockStatement) expression.getCode()).getStatements().isEmpty()) {
+ addError("[groovy-contracts] Annotation does not contain any expressions (e.g. use '@Requires({ argument1 })').", expression);
+ }
+
+ if (expression.isParameterSpecified() && !AnnotationUtils.hasAnnotationOfType(annotationNode.getClassNode(), POSTCONDITION_TYPE_NAME)) {
+ addError("[groovy-contracts] Annotation does not support parameters (the only exception are postconditions).", expression);
+ }
+
+ if (expression.isParameterSpecified()) {
+ for (Parameter param : expression.getParameters()) {
+ if (!("result".equals(param.getName()) || "old".equals(param.getName()))) {
+ addError("[groovy-contracts] Postconditions only allow 'old' and 'result' closure parameters.", expression);
+ }
+
+ if (!param.isDynamicTyped()) {
+ addError("[groovy-contracts] Postconditions do not support explicit types.", expression);
+ }
+ }
+ }
+
+ super.visitClosureExpression(expression);
+ }
+
+ @Override
+ public void visitVariableExpression(VariableExpression expression) {
+
+ // in case of a FieldNode, checks whether the FieldNode can be replaced with a Parameter
+ Variable accessedVariable = getParameterCandidate(expression.getAccessedVariable());
+ if (accessedVariable instanceof FieldNode) {
+ FieldNode fieldNode = (FieldNode) accessedVariable;
+
+ if (fieldNode.isPrivate() && !classNode.hasProperty(fieldNode.getName())) {
+ // if this is a class invariant we'll change the field node access
+ StaticMethodCallExpression field = callX(FIELD_VALUES, "fieldValue", args(VariableExpression.THIS_EXPRESSION, constX(fieldNode.getName()), classX(fieldNode.getType())));
+ variableExpressions.put(expression, field);
+ }
+ }
+
+ if (accessedVariable instanceof Parameter) {
+ Parameter parameter = (Parameter) accessedVariable;
+ if ("it".equals(parameter.getName())) {
+ addError("[groovy-contracts] Access to 'it' is not supported.", expression);
+ }
+ }
+
+ expression.setAccessedVariable(accessedVariable);
+
+ super.visitVariableExpression(expression);
+ }
+
+ @Override
+ public void visitPostfixExpression(PostfixExpression expression) {
+ checkOperation(expression, expression.getOperation());
+
+ if (secondPass) {
+ if (expression.getExpression() instanceof VariableExpression) {
+ VariableExpression variableExpression = (VariableExpression) expression.getExpression();
+ if (variableExpressions.containsKey(variableExpression)) {
+ expression.setExpression(variableExpressions.get(variableExpression));
+ }
+ }
+ }
+
+ super.visitPostfixExpression(expression);
+ }
+
+ @Override
+ public void visitPrefixExpression(PrefixExpression expression) {
+ checkOperation(expression, expression.getOperation());
+
+ if (secondPass) {
+ if (expression.getExpression() instanceof VariableExpression) {
+ VariableExpression variableExpression = (VariableExpression) expression.getExpression();
+ if (variableExpressions.containsKey(variableExpression)) {
+ expression.setExpression(variableExpressions.get(variableExpression));
+ }
+ }
+ }
+
+ super.visitPrefixExpression(expression);
+ }
+
+ @Override
+ public void visitBinaryExpression(BinaryExpression expression) {
+ checkOperation(expression, expression.getOperation());
+
+ if (secondPass) {
+ if (expression.getLeftExpression() instanceof VariableExpression) {
+ VariableExpression variableExpression = (VariableExpression) expression.getLeftExpression();
+ if (variableExpressions.containsKey(variableExpression)) {
+ expression.setLeftExpression(variableExpressions.get(variableExpression));
+ }
+ }
+ if (expression.getRightExpression() instanceof VariableExpression) {
+ VariableExpression variableExpression = (VariableExpression) expression.getRightExpression();
+ if (variableExpressions.containsKey(variableExpression)) {
+ expression.setRightExpression(variableExpressions.get(variableExpression));
+ }
+ }
+ }
+
+ super.visitBinaryExpression(expression);
+ }
+
+ @Override
+ public void visitStaticMethodCallExpression(StaticMethodCallExpression call) {
+ methodCalls = true;
+ super.visitStaticMethodCallExpression(call);
+ }
+
+ @Override
+ public void visitMethodCallExpression(MethodCallExpression call) {
+ methodCalls = true;
+ super.visitMethodCallExpression(call);
+ }
+
+ @Override
+ public void visitConstructorCallExpression(ConstructorCallExpression call) {
+ methodCalls = true;
+ super.visitConstructorCallExpression(call);
+ }
+
+ private void checkOperation(Expression expression, Token operation) {
+ if (Types.ofType(operation.getType(), Types.ASSIGNMENT_OPERATOR)) {
+ addError("[groovy-contracts] Assignment operators are not supported.", expression);
+ }
+ if (Types.ofType(operation.getType(), Types.POSTFIX_OPERATOR)) {
+ addError("[groovy-contracts] State changing postfix & prefix operators are not supported.", expression);
+ }
+ }
+
+ private Variable getParameterCandidate(Variable variable) {
+ if (variable == null || methodNode == null) return variable;
+ if (variable instanceof Parameter) return variable;
+
+ String name = variable.getName();
+ for (Parameter param : methodNode.getParameters()) {
+ if (name.equals(param.getName())) return param;
+ }
+
+ return variable;
+ }
+
+ public void secondPass(ClosureExpression closureExpression) {
+ secondPass = true;
+ super.visitClosureExpression(closureExpression);
+ }
+
+ public boolean isMethodCalls() {
+ return methodCalls;
+ }
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return sourceUnit;
+ }
+ }
+
+ private static class OldPropertyExpressionTransformer extends ClassCodeExpressionTransformer {
+ private final MethodNode methodNode;
+ private CastExpression currentCast = null;
+
+ public OldPropertyExpressionTransformer(MethodNode methodNode) {
+ this.methodNode = methodNode;
+ }
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+
+ @Override
+ public Expression transform(Expression expr) {
+ if (expr instanceof CastExpression) {
+ CastExpression saved = currentCast;
+ currentCast = (CastExpression) expr;
+ Expression result = expr.transformExpression(this);
+ currentCast = saved;
+ return result;
+ }
+ if (expr instanceof PropertyExpression && expr.getNodeMetaData(PROCESSED) == null && (currentCast == null || expr != currentCast.getExpression())) {
+ // add a cast but only if an explicit cast is not already there and we haven't been here before
+ PropertyExpression propExpr = (PropertyExpression) super.transform(expr);
+ Expression objExpr = propExpr.getObjectExpression();
+ if (objExpr instanceof VariableExpression) {
+ VariableExpression varExpr = (VariableExpression) objExpr;
+ if ("old".equals(varExpr.getName())) {
+ String propName = propExpr.getPropertyAsString();
+ ClassNode declaringClass = methodNode.getDeclaringClass();
+ if (declaringClass != null && declaringClass.getField(propName) != null) {
+ CastExpression adjusted = new CastExpression(declaringClass.getField(propName).getType(), expr);
+ adjusted.setSourcePosition(expr);
+ expr.setNodeMetaData(PROCESSED, Boolean.TRUE);
+ return adjusted;
+ }
+ }
+ }
+ }
+ return expr.transformExpression(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationContractParameterVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationContractParameterVisitor.java
new file mode 100644
index 0000000..9f09d55
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationContractParameterVisitor.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+import java.util.List;
+
+/**
+ * This {@link BaseVisitor} walks up the class hierarchy for the given {@link org.codehaus.groovy.ast.ClassNode}
+ * and adds {@link org.apache.groovy.contracts.annotations.meta.AnnotationContract} annotations to method parameters.
+ */
+public class AnnotationContractParameterVisitor extends BaseVisitor {
+
+ private MethodNode currentMethodNode;
+
+ public AnnotationContractParameterVisitor(final SourceUnit sourceUnit, final ReaderSource source) {
+ super(sourceUnit, source);
+ }
+
+ @Override
+ public void visitClass(ClassNode node) {
+ if (node == null) return;
+
+ // walk up the class hierarchy
+ super.visitClass(node.getSuperClass());
+
+ // walk through all interfaces
+ for (ClassNode i : node.getAllInterfaces()) {
+ super.visitClass(i);
+ }
+ }
+
+ @Override
+ public void visitMethod(MethodNode node) {
+ currentMethodNode = node;
+ super.visitMethod(node);
+ currentMethodNode = null;
+ }
+
+ @Override
+ public void visitAnnotations(AnnotatedNode node) {
+ if (!(node instanceof Parameter) || currentMethodNode == null) return;
+ List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(node, "org.apache.groovy.contracts.annotations.meta.AnnotationContract");
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationProcessorVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationProcessorVisitor.java
new file mode 100644
index 0000000..eccdae9
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationProcessorVisitor.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.apache.groovy.contracts.annotations.meta.ContractElement;
+import org.apache.groovy.contracts.common.spi.AnnotationProcessor;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * Visits annotations of meta-type {@link ContractElement} and applies the AST transformations of the underlying
+ * {@link org.apache.groovy.contracts.common.spi.AnnotationProcessor} implementation.
+ *
+ * @see org.apache.groovy.contracts.common.spi.AnnotationProcessor
+ */
+public class AnnotationProcessorVisitor extends BaseVisitor {
+
+ private ProcessingContextInformation pci;
+
+ public AnnotationProcessorVisitor(final SourceUnit sourceUnit, final ReaderSource source, final ProcessingContextInformation pci) {
+ super(sourceUnit, source);
+ Validate.notNull(pci);
+
+ this.pci = pci;
+ }
+
+ @Override
+ public void visitClass(ClassNode type) {
+ handleClassNode(type);
+
+ List<MethodNode> methodNodes = new ArrayList<MethodNode>();
+ methodNodes.addAll(type.getMethods());
+ methodNodes.addAll(type.getDeclaredConstructors());
+
+ for (MethodNode methodNode : methodNodes) {
+ if (!CandidateChecks.isClassInvariantCandidate(type, methodNode) && !CandidateChecks.isPreOrPostconditionCandidate(type, methodNode))
+ continue;
+
+ handleMethodNode(methodNode, AnnotationUtils.hasMetaAnnotations(methodNode, ContractElement.class.getName()));
+ }
+
+ // visit all interfaces of this class
+ visitInterfaces(type, type.getInterfaces());
+ visitAbstractBaseClassesForInterfaceMethodNodes(type, type.getSuperClass());
+ }
+
+ private void visitAbstractBaseClassesForInterfaceMethodNodes(ClassNode origin, ClassNode superClass) {
+ if (superClass == null) return;
+ if (!Modifier.isAbstract(superClass.getModifiers())) return;
+
+ for (ClassNode interfaceClassNode : superClass.getInterfaces()) {
+ List<MethodNode> methodNodes = new ArrayList<MethodNode>();
+ methodNodes.addAll(interfaceClassNode.getMethods());
+
+ for (MethodNode interfaceMethodNode : methodNodes) {
+ final List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethodNode, ContractElement.class.getName());
+ if (annotationNodes == null || annotationNodes.isEmpty()) continue;
+
+ MethodNode implementingMethodNode = superClass.getMethod(interfaceMethodNode.getName(), interfaceMethodNode.getParameters());
+
+ // if implementingMethodNode == null, then superClass is abstract and does not implement
+ // the current interface methodNode
+ if (implementingMethodNode != null) continue;
+
+ MethodNode implementationInOriginClassNode = origin.getMethod(interfaceMethodNode.getName(), interfaceMethodNode.getParameters());
+ if (implementationInOriginClassNode == null) continue;
+
+ handleMethodNode(implementationInOriginClassNode, annotationNodes);
+ }
+ }
+ }
+
+ private void visitInterfaces(final ClassNode classNode, final ClassNode[] interfaces) {
+ for (ClassNode interfaceClassNode : interfaces) {
+ List<MethodNode> methodNodes = new ArrayList<MethodNode>();
+ methodNodes.addAll(interfaceClassNode.getMethods());
+
+ // @ContractElement annotations are by now only supported on method interfaces
+ for (MethodNode interfaceMethodNode : methodNodes) {
+ MethodNode implementingMethodNode = classNode.getMethod(interfaceMethodNode.getName(), interfaceMethodNode.getParameters());
+ if (implementingMethodNode == null) continue;
+
+ final List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethodNode, ContractElement.class.getName());
+ handleInterfaceMethodNode(classNode, implementingMethodNode, annotationNodes);
+ }
+
+ visitInterfaces(classNode, interfaceClassNode.getInterfaces());
+ }
+ }
+
+ private void handleClassNode(final ClassNode classNode) {
+ final List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(classNode, ContractElement.class.getName());
+
+ for (AnnotationNode annotationNode : annotationNodes) {
+ final AnnotationProcessor annotationProcessor = createAnnotationProcessor(annotationNode);
+
+ if (annotationProcessor != null && annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME) instanceof ClassExpression) {
+ final ClassExpression closureClassExpression = (ClassExpression) annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME);
+
+ MethodCallExpression doCall = callX(
+ ctorX(closureClassExpression.getType(), args(VariableExpression.THIS_EXPRESSION, VariableExpression.THIS_EXPRESSION)),
+ "doCall"
+ );
+ doCall.setMethodTarget(closureClassExpression.getType().getMethods("doCall").get(0));
+
+ final BooleanExpression booleanExpression = boolX(doCall);
+ booleanExpression.setSourcePosition(annotationNode);
+
+ annotationProcessor.process(pci, pci.contract(), classNode, closureClassExpression.getNodeMetaData(AnnotationClosureVisitor.META_DATA_ORIGINAL_TRY_CATCH_BLOCK), booleanExpression);
+ }
+ }
+ }
+
+ private void handleInterfaceMethodNode(ClassNode type, MethodNode methodNode, List<AnnotationNode> annotationNodes) {
+ handleMethodNode(type.getMethod(methodNode.getName(), methodNode.getParameters()), annotationNodes);
+ }
+
+ private void handleMethodNode(MethodNode methodNode, List<AnnotationNode> annotationNodes) {
+ if (methodNode == null) return;
+
+ for (AnnotationNode annotationNode : annotationNodes) {
+ final AnnotationProcessor annotationProcessor = createAnnotationProcessor(annotationNode);
+
+ if (annotationProcessor != null && annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME) instanceof ClassExpression) {
+ boolean isPostcondition = AnnotationUtils.hasAnnotationOfType(annotationNode.getClassNode(), org.apache.groovy.contracts.annotations.meta.Postcondition.class.getName());
+
+ ClassExpression closureClassExpression = (ClassExpression) annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME);
+
+ ArgumentListExpression closureArgumentList = new ArgumentListExpression();
+
+ for (Parameter parameter : methodNode.getParameters()) {
+ closureArgumentList.addExpression(varX(parameter));
+ }
+
+ if (methodNode.getReturnType() != ClassHelper.VOID_TYPE && isPostcondition && !(methodNode instanceof ConstructorNode)) {
+ closureArgumentList.addExpression(localVarX("result", methodNode.getReturnType()));
+ }
+
+ if (isPostcondition && !(methodNode instanceof ConstructorNode)) {
+ closureArgumentList.addExpression(localVarX("old", new ClassNode(Map.class)));
+ }
+
+ MethodCallExpression doCall = callX(
+ ctorX(annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME).getType(), args(VariableExpression.THIS_EXPRESSION, VariableExpression.THIS_EXPRESSION)),
+ "doCall",
+ closureArgumentList
+ );
+ ClassNode type = annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME).getType();
+ doCall.setMethodTarget(type.getMethods("doCall").get(0));
+
+ final BooleanExpression booleanExpression = boolX(doCall);
+ booleanExpression.setSourcePosition(annotationNode);
+
+ annotationProcessor.process(pci, pci.contract(), methodNode.getDeclaringClass(), methodNode, closureClassExpression.getNodeMetaData(AnnotationClosureVisitor.META_DATA_ORIGINAL_TRY_CATCH_BLOCK), booleanExpression);
+
+ // if the implementation method has no annotation, we need to set a dummy marker in order to find parent pre/postconditions
+ if (!AnnotationUtils.hasAnnotationOfType(methodNode, annotationNode.getClassNode().getName())) {
+ AnnotationNode annotationMarker = new AnnotationNode(annotationNode.getClassNode());
+ annotationMarker.setMember(CLOSURE_ATTRIBUTE_NAME, annotationNode.getMember(CLOSURE_ATTRIBUTE_NAME));
+ annotationMarker.setRuntimeRetention(true);
+ annotationMarker.setSourceRetention(false);
+
+ methodNode.addAnnotation(annotationMarker);
+ }
+ }
+ }
+ }
+
+ private AnnotationProcessor createAnnotationProcessor(AnnotationNode annotationNode) {
+ ClassExpression annotationProcessingAnno = null;
+
+ List<AnnotationNode> annotations = annotationNode.getClassNode().redirect().getAnnotations();
+ for (AnnotationNode anno : annotations) {
+ Class typeClass = anno.getClassNode().getTypeClass();
+
+ if (typeClass.getName().equals("org.apache.groovy.contracts.annotations.meta.AnnotationProcessorImplementation")) {
+ annotationProcessingAnno = (ClassExpression) anno.getMember("value");
+ break;
+ }
+ }
+
+ if (annotationProcessingAnno == null)
+ throw new GroovyBugError("Annotation processing class could not be found! This indicates a bug in groovy-contracts, please file an issue!");
+
+ try {
+ final Class clz = Class.forName(annotationProcessingAnno.getType().getTypeClass().getName());
+ return (AnnotationProcessor) clz.getDeclaredConstructor().newInstance();
+ } catch (InstantiationException e) {
+ } catch (IllegalAccessException e) {
+ } catch (ClassNotFoundException e) {
+ } catch (NoSuchMethodException e) {
+ } catch (InvocationTargetException e) {
+ }
+
+ throw new GroovyBugError("Annotation processing class could not be instantiated! This indicates a bug in groovy-contracts, please file an issue!");
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/BaseVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/BaseVisitor.java
new file mode 100644
index 0000000..f620378
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/BaseVisitor.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+/**
+ * <p>
+ * Base class for {@link org.codehaus.groovy.ast.ClassCodeVisitorSupport} descendants. This class is used in groovy-contracts
+ * as root class for all code visitors directly used by global AST transformations.
+ * </p>
+ *
+ * @see org.codehaus.groovy.ast.ClassCodeVisitorSupport
+ */
+public abstract class BaseVisitor extends ClassCodeVisitorSupport {
+
+ public static final String GCONTRACTS_ENABLED_VAR = "$GCONTRACTS_ENABLED";
+
+ public static final String CLOSURE_ATTRIBUTE_NAME = "value";
+
+ protected SourceUnit sourceUnit;
+ protected ReaderSource source;
+
+ public BaseVisitor(final SourceUnit sourceUnit, final ReaderSource source) {
+ this.sourceUnit = sourceUnit;
+ this.source = source;
+ }
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ConfigurationSetup.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ConfigurationSetup.java
new file mode 100644
index 0000000..2fe85c7
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ConfigurationSetup.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.apache.groovy.contracts.generation.Configurator;
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
+import org.objectweb.asm.Opcodes;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+
+/**
+ * Makes some initialization in order to use the {@link Configurator} for determining
+ * which assertions in what packages will be executed.
+ *
+ * @see Configurator
+ */
+public class ConfigurationSetup {
+
+ /**
+ * Adds an instance field which allows to control whether GContract assertions
+ * are enabled or not. Before assertions are evaluated this field will be checked.
+ *
+ * @param type the current {@link ClassNode}
+ * @see Configurator
+ */
+ public void init(final ClassNode type) {
+ Validate.notNull(type);
+ StaticMethodCallExpression checkAssertionsEnabledMethodCall = callX(ClassHelper.makeWithoutCaching(Configurator.class), "checkAssertionsEnabled", args(constX(type.getName())));
+ final FieldNode fieldNode = type.addField(BaseVisitor.GCONTRACTS_ENABLED_VAR, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, ClassHelper.boolean_TYPE, checkAssertionsEnabledMethodCall);
+ fieldNode.setSynthetic(true);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ContractElementVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ContractElementVisitor.java
new file mode 100644
index 0000000..4d3b38d
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/ContractElementVisitor.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import groovy.contracts.Contracted;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+/**
+ * Checks whether the given {@link org.codehaus.groovy.ast.ClassNode} is relevant for
+ * further processing.
+ */
+public class ContractElementVisitor extends BaseVisitor implements ASTNodeMetaData {
+
+ private ClassNode classNode;
+ private boolean foundContractElement = false;
+
+ public ContractElementVisitor(final SourceUnit sourceUnit, final ReaderSource source) {
+ super(sourceUnit, source);
+ }
+
+ @Override
+ public void visitClass(ClassNode node) {
+ if (!CandidateChecks.isContractsCandidate(node) && !CandidateChecks.isInterfaceContractsCandidate(node)) return;
+
+ classNode = node;
+
+ // check for the @Contracted shortcut
+ if (AnnotationUtils.hasAnnotationOfType(node, Contracted.class.getName())) {
+ foundContractElement = true;
+ return;
+ }
+
+ foundContractElement |= classNode.getNodeMetaData(CLOSURE_REPLACED) != null;
+
+ if (!foundContractElement) {
+ super.visitClass(node);
+ }
+
+ // check base classes
+ if (!foundContractElement && node.getSuperClass() != null) {
+ visitClass(node.getSuperClass());
+ }
+
+ // check interfaces
+ if (!foundContractElement) {
+ for (ClassNode interfaceNode : node.getInterfaces()) {
+ visitClass(interfaceNode);
+ if (foundContractElement) return;
+ }
+ }
+ }
+
+ @Override
+ protected void visitConstructorOrMethod(MethodNode methodNode, boolean isConstructor) {
+ if (!CandidateChecks.couldBeContractElementMethodNode(classNode, methodNode) && !(CandidateChecks.isPreconditionCandidate(classNode, methodNode)))
+ return;
+ foundContractElement |= methodNode.getNodeMetaData(CLOSURE_REPLACED) != null;
+ }
+
+ public boolean isFoundContractElement() {
+ return foundContractElement;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/DomainModelInjectionVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/DomainModelInjectionVisitor.java
new file mode 100644
index 0000000..300d0b1
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/DomainModelInjectionVisitor.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.domain.ClassInvariant;
+import org.apache.groovy.contracts.domain.Contract;
+import org.apache.groovy.contracts.domain.Postcondition;
+import org.apache.groovy.contracts.domain.Precondition;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.generation.ClassInvariantGenerator;
+import org.apache.groovy.contracts.generation.PostconditionGenerator;
+import org.apache.groovy.contracts.generation.PreconditionGenerator;
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+import java.util.Map;
+
+/**
+ * Visits the given {@link ClassNode} and injects the current {@link org.apache.groovy.contracts.domain.Contract} into the given AST
+ * nodes.
+ *
+ * @see org.apache.groovy.contracts.domain.Contract
+ */
+public class DomainModelInjectionVisitor extends BaseVisitor {
+
+ private final ProcessingContextInformation pci;
+ private final Contract contract;
+
+ public DomainModelInjectionVisitor(final SourceUnit sourceUnit, final ReaderSource source, final ProcessingContextInformation pci) {
+ super(sourceUnit, source);
+ Validate.notNull(pci);
+ Validate.notNull(pci.contract());
+
+ this.pci = pci;
+ this.contract = pci.contract();
+ }
+
+ @Override
+ public void visitClass(ClassNode type) {
+ injectClassInvariant(type, contract.classInvariant());
+
+ for (Map.Entry<MethodNode, Precondition> entry : contract.preconditions()) {
+ injectPrecondition(entry.getKey(), entry.getValue());
+ }
+
+ for (Map.Entry<MethodNode, Postcondition> entry : contract.postconditions()) {
+ injectPostcondition(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public void injectClassInvariant(final ClassNode type, final ClassInvariant classInvariant) {
+ if (!pci.isClassInvariantsEnabled() || !CandidateChecks.isContractsCandidate(type)) return;
+
+ final ReaderSource source = pci.readerSource();
+ final ClassInvariantGenerator classInvariantGenerator = new ClassInvariantGenerator(source);
+
+ classInvariantGenerator.generateInvariantAssertionStatement(type, classInvariant);
+ }
+
+ public void injectPrecondition(final MethodNode method, final Precondition precondition) {
+ if (!pci.isPreconditionsEnabled() || !CandidateChecks.isPreconditionCandidate(method.getDeclaringClass(), method))
+ return;
+
+ final ReaderSource source = pci.readerSource();
+ final PreconditionGenerator preconditionGenerator = new PreconditionGenerator(source);
+
+ preconditionGenerator.generatePreconditionAssertionStatement(method, precondition);
+ }
+
+ public void injectPostcondition(final MethodNode method, final Postcondition postcondition) {
+ if (!pci.isPostconditionsEnabled() || !CandidateChecks.isPostconditionCandidate(method.getDeclaringClass(), method))
+ return;
+
+ final ReaderSource source = pci.readerSource();
+ final PostconditionGenerator postconditionGenerator = new PostconditionGenerator(source);
+
+ postconditionGenerator.generatePostconditionAssertionStatement(method, postcondition);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/DynamicSetterInjectionVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/DynamicSetterInjectionVisitor.java
new file mode 100644
index 0000000..acfefbb
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/DynamicSetterInjectionVisitor.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.apache.groovy.contracts.generation.BaseGenerator;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.apache.groovy.util.BeanUtils;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * <p>
+ * Implements contract support for setter methods and default constructors of POGOs.
+ * </p>
+ *
+ * @see BaseVisitor
+ */
+public class DynamicSetterInjectionVisitor extends BaseVisitor {
+
+ private static final String SPRING_STEREOTYPE_PACKAGE = "org.springframework.stereotype";
+
+ private BlockStatement invariantAssertionBlockStatement;
+
+ public DynamicSetterInjectionVisitor(final SourceUnit sourceUnit, final ReaderSource source) {
+ super(sourceUnit, source);
+ }
+
+ protected Statement createSetterBlock(final ClassNode classNode, final FieldNode field, final Parameter parameter) {
+ return block(
+ invariantAssertionBlockStatement, // check invariant before assignment
+ assignS(fieldX(field), varX(parameter)), // do assignment
+ invariantAssertionBlockStatement // check invariant after assignment
+ );
+ }
+
+ @Override
+ public void visitProperty(PropertyNode node) {
+ final ClassNode classNode = node.getDeclaringClass();
+ final String setterName = "set" + BeanUtils.capitalize(node.getName());
+
+ final Statement setterBlock = node.getSetterBlock();
+ final Parameter parameter = param(node.getType(), "value");
+
+ if (CandidateChecks.isClassInvariantCandidate(node) && (setterBlock == null && classNode.getMethod(setterName, new Parameter[]{parameter}) == null)) {
+ final Statement setterBlockStatement = createSetterBlock(classNode, node.getField(), parameter);
+ node.setSetterBlock(setterBlockStatement);
+ }
+ }
+
+ @Override
+ public void visitClass(ClassNode classNode) {
+ // if a class invariant is available visit all property nodes else skip this class
+ final MethodNode invariantMethodNode = BaseGenerator.getInvariantMethodNode(classNode);
+ if (invariantMethodNode == null || AnnotationUtils.hasAnnotationOfType(classNode, SPRING_STEREOTYPE_PACKAGE))
+ return;
+
+ invariantAssertionBlockStatement = block(stmt(callThisX(invariantMethodNode.getName())));
+
+ List<ConstructorNode> declaredConstructors = classNode.getDeclaredConstructors();
+ if (declaredConstructors == null || declaredConstructors.isEmpty()) {
+ // create default constructor with class invariant check
+ ConstructorNode constructor = new ConstructorNode(Opcodes.ACC_PUBLIC, invariantAssertionBlockStatement);
+ constructor.setSynthetic(true);
+ classNode.addConstructor(constructor);
+ }
+
+ super.visitClass(classNode);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/LifecycleAfterTransformationVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/LifecycleAfterTransformationVisitor.java
new file mode 100644
index 0000000..ec7c3c9
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/LifecycleAfterTransformationVisitor.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.apache.groovy.contracts.common.spi.Lifecycle;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.util.LifecycleImplementationLoader;
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>AST transformation visitor which is triggered after applying {@link org.apache.groovy.contracts.common.spi.AnnotationProcessor}
+ * related transformations.</p>
+ *
+ * @see AnnotationProcessorVisitor
+ */
+public class LifecycleAfterTransformationVisitor extends BaseVisitor {
+
+ private final ProcessingContextInformation pci;
+
+ public LifecycleAfterTransformationVisitor(SourceUnit sourceUnit, ReaderSource source, final ProcessingContextInformation pci) {
+ super(sourceUnit, source);
+
+ Validate.notNull(pci);
+ this.pci = pci;
+ }
+
+ @Override
+ public void visitClass(ClassNode node) {
+ super.visitClass(node);
+
+ List<MethodNode> methods = new ArrayList<>(node.getMethods());
+ List<MethodNode> constructors = new ArrayList<>(node.getDeclaredConstructors());
+
+ for (Lifecycle lifecyle : LifecycleImplementationLoader.load(Lifecycle.class, getClass().getClassLoader())) {
+ lifecyle.afterProcessingClassNode(pci, node);
+
+ for (MethodNode constructor : constructors) {
+ lifecyle.afterProcessingConstructorNode(pci, node, constructor);
+ }
+
+ for (MethodNode method : methods) {
+ lifecyle.afterProcessingMethodNode(pci, node, method);
+ }
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/LifecycleBeforeTransformationVisitor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/LifecycleBeforeTransformationVisitor.java
new file mode 100644
index 0000000..b4db706
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/LifecycleBeforeTransformationVisitor.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.ast.visitor;
+
+import org.apache.groovy.contracts.common.spi.Lifecycle;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.util.LifecycleImplementationLoader;
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>AST transformation visitor which is triggered before applying {@link org.apache.groovy.contracts.common.spi.AnnotationProcessor}
+ * related transformations.</p>
+ *
+ * @see AnnotationProcessorVisitor
+ */
+public class LifecycleBeforeTransformationVisitor extends BaseVisitor {
+
+ private final ProcessingContextInformation pci;
+
+ public LifecycleBeforeTransformationVisitor(SourceUnit sourceUnit, ReaderSource source, final ProcessingContextInformation pci) {
+ super(sourceUnit, source);
+
+ Validate.notNull(pci);
+ this.pci = pci;
+ }
+
+ @Override
+ public void visitClass(ClassNode node) {
+ super.visitClass(node);
+
+ List<MethodNode> methods = new ArrayList<>(node.getMethods());
+ List<MethodNode> constructors = new ArrayList<>(node.getDeclaredConstructors());
+
+ for (Lifecycle lifecyle : LifecycleImplementationLoader.load(Lifecycle.class, getClass().getClassLoader())) {
+ lifecyle.beforeProcessingClassNode(pci, node);
+
+ for (MethodNode constructor : constructors) {
+ lifecyle.beforeProcessingConstructorNode(pci, node, constructor);
+ }
+
+ for (MethodNode method : methods) {
+ lifecyle.beforeProcessingMethodNode(pci, node, method);
+ }
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/classgen/asm/ContractClosureWriter.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/classgen/asm/ContractClosureWriter.java
new file mode 100644
index 0000000..cd195c0
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/classgen/asm/ContractClosureWriter.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.classgen.asm;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.DynamicVariable;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorSuperX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+
+/**
+ * <p>Replaces annotation closures with closure implementation classes.</p>
+ *
+ * <p>Attention: large parts of this class have been backported from Groovy 1.8 and customized
+ * for usage in groovy-contracts.</p>
+ */
+public class ContractClosureWriter {
+
+ private int closureCount = 1;
+
+ public ClassNode createClosureClass(ClassNode classNode, MethodNode methodNode, ClosureExpression expression, boolean addOldVariable, boolean addResultVariable, int mods) {
+ ClassNode outerClass = getOutermostClass(classNode);
+ String name = outerClass.getName() + "$" + getClosureInnerName(outerClass, classNode);
+
+ // fetch all method parameters, and possibly add 'old' and 'result'
+ ArrayList<Parameter> parametersTemp = new ArrayList<Parameter>(Arrays.asList(expression.getParameters()));
+ removeParameter("old", parametersTemp);
+ removeParameter("result", parametersTemp);
+
+ if (methodNode != null && addResultVariable && methodNode.getReturnType() != ClassHelper.VOID_TYPE) {
+ parametersTemp.add(new Parameter(methodNode.getReturnType(), "result"));
+ }
+
+ if (addOldVariable) {
+ parametersTemp.add(new Parameter(new ClassNode(Map.class), "old"));
+ }
+
+ // contains all params of the original method
+ ArrayList<Parameter> closureParameters = new ArrayList<Parameter>();
+ for (Parameter param : parametersTemp) {
+ Parameter closureParameter = new Parameter(param.getType().getPlainNodeReference(), param.getName());
+ closureParameters.add(closureParameter);
+ }
+
+ ClassNode answer = new ClassNode(name, mods | ACC_FINAL, ClassHelper.CLOSURE_TYPE.getPlainNodeReference());
+ answer.setSynthetic(true);
+ answer.setSourcePosition(expression);
+
+ MethodNode method =
+ answer.addMethod("doCall", ACC_PUBLIC, ClassHelper.Boolean_TYPE, closureParameters.toArray(new Parameter[closureParameters.size()]), ClassNode.EMPTY_ARRAY, expression.getCode());
+ method.setSourcePosition(expression);
+
+ VariableScope varScope = expression.getVariableScope();
+ if (varScope == null) {
+ throw new RuntimeException(
+ "Must have a VariableScope by now! for expression: " + expression + " class: " + name);
+ } else {
+ method.setVariableScope(varScope.copy());
+ }
+
+ // let's add a typesafe call method
+ ArgumentListExpression arguments = new ArgumentListExpression();
+ for (Parameter parameter : closureParameters) {
+ arguments.addExpression(varX(parameter));
+ }
+
+ MethodNode call = answer.addMethod(
+ "call",
+ ACC_PUBLIC,
+ ClassHelper.Boolean_TYPE,
+ closureParameters.toArray(new Parameter[closureParameters.size()]),
+ ClassNode.EMPTY_ARRAY,
+ returnS(callThisX("doCall", arguments)));
+
+ call.setSourcePosition(expression);
+ call.setSynthetic(true);
+
+ // let's make the constructor
+ BlockStatement block = new BlockStatement();
+ // this block does not get a source position, because we don't
+ // want this synthetic constructor to show up in corbertura reports
+ VariableExpression outer = varX("_outerInstance");
+ outer.setSourcePosition(expression);
+ block.getVariableScope().putReferencedLocalVariable(outer);
+ VariableExpression thisObject = varX("_thisObject");
+ thisObject.setSourcePosition(expression);
+ block.getVariableScope().putReferencedLocalVariable(thisObject);
+ TupleExpression conArgs = new TupleExpression(outer, thisObject);
+ block.addStatement(stmt(ctorSuperX(conArgs)));
+
+ Parameter[] consParams = params(
+ param(ClassHelper.OBJECT_TYPE, "_outerInstance"),
+ param(ClassHelper.OBJECT_TYPE, "_thisObject"));
+
+ ASTNode sn = answer.addConstructor(ACC_PUBLIC, consParams, ClassNode.EMPTY_ARRAY, block);
+ sn.setSourcePosition(expression);
+ correctAccessedVariable(method, expression);
+ return answer;
+ }
+
+ private void removeParameter(String name, List<Parameter> parameters) {
+ parameters.removeIf(parameter -> parameter.getName().equals(name));
+ }
+
+ private ClassNode getOutermostClass(ClassNode outermostClass) {
+ while (outermostClass instanceof InnerClassNode) {
+ outermostClass = outermostClass.getOuterClass();
+ }
+ return outermostClass;
+ }
+
+ private void correctAccessedVariable(final MethodNode methodNode, ClosureExpression ce) {
+ CodeVisitorSupport visitor = new CodeVisitorSupport() {
+ @Override
+ public void visitVariableExpression(VariableExpression expression) {
+ Variable v = expression.getAccessedVariable();
+ if (v == null) return;
+ String name = expression.getName();
+ if (v instanceof DynamicVariable) {
+ for (Parameter param : methodNode.getParameters()) {
+ if (name.equals(param.getName())) {
+ expression.setAccessedVariable(param);
+ }
+ }
+
+ }
+ }
+ };
+ visitor.visitClosureExpression(ce);
+ }
+
+ private String getClosureInnerName(ClassNode owner, ClassNode enclosingClass) {
+ String ownerShortName = owner.getNameWithoutPackage();
+ String classShortName = enclosingClass.getNameWithoutPackage();
+ if (classShortName.equals(ownerShortName)) {
+ classShortName = "";
+ } else {
+ classShortName += "_";
+ }
+ // remove $
+ int dp = classShortName.lastIndexOf("$");
+ if (dp >= 0) {
+ classShortName = classShortName.substring(++dp);
+ }
+ // remove leading _
+ if (classShortName.startsWith("_")) {
+ classShortName = classShortName.substring(1);
+ }
+
+ return "_gc_" + classShortName + "closure" + closureCount++;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/base/BaseLifecycle.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/base/BaseLifecycle.java
new file mode 100644
index 0000000..4c85458
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/base/BaseLifecycle.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.base;
+
+import org.apache.groovy.contracts.common.spi.Lifecycle;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+
+/**
+ * Base implementation class for interface {@link Lifecycle}. This class is supposed
+ * tp be extended by {@link Lifecycle} implementation classes and provides empty
+ * method bodies for all interface methods.
+ *
+ * @see Lifecycle
+ */
+public abstract class BaseLifecycle implements Lifecycle {
+
+ public void beforeProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) {
+ }
+
+ public void afterProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) {
+ }
+
+ public void beforeProcessingMethodNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode methodNode) {
+ }
+
+ public void afterProcessingMethodNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode methodNode) {
+ }
+
+ public void beforeProcessingConstructorNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode constructorNode) {
+ }
+
+ public void afterProcessingConstructorNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode constructorNode) {
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/ClassInvariantAnnotationProcessor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/ClassInvariantAnnotationProcessor.java
new file mode 100644
index 0000000..976a47a
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/ClassInvariantAnnotationProcessor.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.impl;
+
+import org.apache.groovy.contracts.common.spi.AnnotationProcessor;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.domain.ClassInvariant;
+import org.apache.groovy.contracts.domain.Contract;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+/**
+ * Internal {@link AnnotationProcessor} implementation for class-invariants.
+ */
+public class ClassInvariantAnnotationProcessor extends AnnotationProcessor {
+
+ @Override
+ public void process(ProcessingContextInformation processingContextInformation, Contract contract, ClassNode classNode, BlockStatement blockStatement, BooleanExpression booleanExpression) {
+ if (!processingContextInformation.isClassInvariantsEnabled()) return;
+ if (booleanExpression == null) return;
+
+ contract.setClassInvariant(new ClassInvariant(blockStatement, booleanExpression));
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/EnsuresAnnotationProcessor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/EnsuresAnnotationProcessor.java
new file mode 100644
index 0000000..5a3740c
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/EnsuresAnnotationProcessor.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.impl;
+
+import org.apache.groovy.contracts.common.spi.AnnotationProcessor;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.domain.Contract;
+import org.apache.groovy.contracts.domain.Postcondition;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+import java.util.List;
+
+/**
+ * Internal {@link AnnotationProcessor} implementation for post-conditions.
+ */
+public class EnsuresAnnotationProcessor extends AnnotationProcessor {
+
+ @Override
+ public void process(ProcessingContextInformation processingContextInformation, Contract contract, ClassNode classNode, MethodNode methodNode, BlockStatement blockStatement, BooleanExpression booleanExpression) {
+ if (!processingContextInformation.isPostconditionsEnabled()) return;
+ if (booleanExpression == null) return;
+
+ final List<ConstructorNode> declaredConstructors = classNode.getDeclaredConstructors();
+
+ contract.postconditions().and(methodNode, new Postcondition(blockStatement, booleanExpression, declaredConstructors.contains(methodNode)));
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/RequiresAnnotationProcessor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/RequiresAnnotationProcessor.java
new file mode 100644
index 0000000..4fc2b10
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/RequiresAnnotationProcessor.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.impl;
+
+import org.apache.groovy.contracts.common.spi.AnnotationProcessor;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.domain.Contract;
+import org.apache.groovy.contracts.domain.Precondition;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+/**
+ * Internal {@link AnnotationProcessor} implementation for pre-conditions.
+ */
+public class RequiresAnnotationProcessor extends AnnotationProcessor {
+
+ @Override
+ public void process(ProcessingContextInformation processingContextInformation, Contract contract, ClassNode classNode, MethodNode methodNode, BlockStatement blockStatement, BooleanExpression booleanExpression) {
+ if (!processingContextInformation.isPreconditionsEnabled()) return;
+ if (booleanExpression == null) return;
+
+ contract.preconditions().or(methodNode, new Precondition(blockStatement, booleanExpression));
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/ClassInvariantLifecycle.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/ClassInvariantLifecycle.java
new file mode 100644
index 0000000..c9f2654
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/ClassInvariantLifecycle.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.impl.lc;
+
+import org.apache.groovy.contracts.common.base.BaseLifecycle;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.generation.ClassInvariantGenerator;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+
+/**
+ * Internal {@link org.apache.groovy.contracts.common.spi.Lifecycle} implementation for class-invariants.
+ */
+public class ClassInvariantLifecycle extends BaseLifecycle {
+
+ @Override
+ public void afterProcessingMethodNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode methodNode) {
+ if (!CandidateChecks.isClassInvariantCandidate(classNode, methodNode)) return;
+ if (processingContextInformation.contract().hasDefaultClassInvariant()) return;
+
+ final ClassInvariantGenerator classInvariantGenerator = new ClassInvariantGenerator(processingContextInformation.readerSource());
+ classInvariantGenerator.addInvariantAssertionStatement(classNode, methodNode);
+ }
+
+ @Override
+ public void afterProcessingConstructorNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode constructorNode) {
+ if (!CandidateChecks.isClassInvariantCandidate(classNode, constructorNode)) return;
+ if (!processingContextInformation.isConstructorAssertionsEnabled()) return;
+ if (processingContextInformation.contract().hasDefaultClassInvariant()) return;
+
+ final ClassInvariantGenerator classInvariantGenerator = new ClassInvariantGenerator(processingContextInformation.readerSource());
+ classInvariantGenerator.addInvariantAssertionStatement(classNode, constructorNode);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/PostconditionLifecycle.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/PostconditionLifecycle.java
new file mode 100644
index 0000000..f373b2b
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/PostconditionLifecycle.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.impl.lc;
+
+import org.apache.groovy.contracts.annotations.meta.Postcondition;
+import org.apache.groovy.contracts.common.base.BaseLifecycle;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.generation.PostconditionGenerator;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.MethodNode;
+
+/**
+ * Internal {@link org.apache.groovy.contracts.common.spi.Lifecycle} implementation for post-conditions.
+ */
+public class PostconditionLifecycle extends BaseLifecycle {
+
+ @Override
+ public void beforeProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) {
+ final PostconditionGenerator postconditionGenerator = new PostconditionGenerator(processingContextInformation.readerSource());
+ postconditionGenerator.addOldVariablesMethod(classNode);
+ }
+
+ @Override
+ public void afterProcessingConstructorNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode constructorNode) {
+ generatePostcondition(processingContextInformation, classNode, constructorNode);
+ }
+
+ @Override
+ public void afterProcessingMethodNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode methodNode) {
+ generatePostcondition(processingContextInformation, classNode, methodNode);
+ }
+
+ private void generatePostcondition(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode methodNode) {
+ if (!processingContextInformation.isPostconditionsEnabled()) return;
+ if (!CandidateChecks.isPostconditionCandidate(classNode, methodNode)) return;
+
+ final PostconditionGenerator postconditionGenerator = new PostconditionGenerator(processingContextInformation.readerSource());
+
+ if (!(methodNode instanceof ConstructorNode) && AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(classNode, methodNode, ClassHelper.makeWithoutCaching(Postcondition.class)).size() > 0) {
+ postconditionGenerator.generateDefaultPostconditionStatement(classNode, methodNode);
+ } else {
+ postconditionGenerator.generateDefaultPostconditionStatement(classNode, methodNode);
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/PreconditionLifecycle.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/PreconditionLifecycle.java
new file mode 100644
index 0000000..8179ff1
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/impl/lc/PreconditionLifecycle.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.impl.lc;
+
+import org.apache.groovy.contracts.common.base.BaseLifecycle;
+import org.apache.groovy.contracts.common.spi.ProcessingContextInformation;
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.apache.groovy.contracts.generation.PreconditionGenerator;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+
+/**
+ * Internal {@link org.apache.groovy.contracts.common.spi.Lifecycle} implementation for pre-conditions.
+ */
+public class PreconditionLifecycle extends BaseLifecycle {
+
+ @Override
+ public void beforeProcessingConstructorNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode constructorNode) {
+ generatePrecondition(processingContextInformation, classNode, constructorNode);
+ }
+
+ @Override
+ public void afterProcessingMethodNode(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode methodNode) {
+ generatePrecondition(processingContextInformation, classNode, methodNode);
+ }
+
+ private void generatePrecondition(ProcessingContextInformation processingContextInformation, ClassNode classNode, MethodNode methodNode) {
+ if (!processingContextInformation.isPreconditionsEnabled()) return;
+ if (!CandidateChecks.isPreconditionCandidate(classNode, methodNode)) return;
+ if (processingContextInformation.contract().preconditions().contains(methodNode)) return;
+
+ final PreconditionGenerator preconditionGenerator = new PreconditionGenerator(processingContextInformation.readerSource());
+ preconditionGenerator.generateDefaultPreconditionStatement(classNode, methodNode);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/AnnotationProcessor.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/AnnotationProcessor.java
new file mode 100644
index 0000000..87cad54
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/AnnotationProcessor.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.spi;
+
+import org.apache.groovy.contracts.domain.Contract;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+/**
+ * <p>Base class for modifying the internal domain model, starting at {@link Contract}, and adding parts to it.</p>
+ *
+ * @see org.apache.groovy.contracts.annotations.meta.AnnotationProcessorImplementation
+ */
+public abstract class AnnotationProcessor {
+
+ public void process(final ProcessingContextInformation processingContextInformation, final Contract contract, final ClassNode classNode, final BlockStatement blockStatement, final BooleanExpression booleanExpression) {
+ }
+
+ public void process(final ProcessingContextInformation processingContextInformation, final Contract contract, final ClassNode classNode, final MethodNode methodNode, final BlockStatement blockStatement, final BooleanExpression booleanExpression) {
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/Lifecycle.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/Lifecycle.java
new file mode 100644
index 0000000..04ec888
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/Lifecycle.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.spi;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+
+/**
+ * <p>Specifies life-cycle hook-ins for applying AST transformation logic before and
+ * after the annotation processors have been run.</p>
+ *
+ * <p>During excution of GContracts AST transformations, the following process is applied on each {@link ClassNode}
+ * instance which qualifies for contract annotations:</P>
+ *
+ * <ol>
+ * <li>Generation of closure classes.</li>
+ * <li>Handling of {@link AnnotationProcessor} implementation classes</li>
+ * <li>Domain Model Conversion and Injection</li>
+ * </ol>
+ *
+ * <h3>Generation of closure classes</h3>
+ *
+ * <p>In order to support Groovy 1.7.x GContracts backported Groovy 1.8 handling of annotation closures. This is done
+ * by extracting {@link org.codehaus.groovy.ast.expr.ClosureExpression} from annotations and creating {@link groovy.lang.Closure}
+ * implementation classes.</p>
+ *
+ * <h3>Handling of AnnotationProcessor implementation classes</h3>
+ *
+ * <p>{@link AnnotationProcessor} implementatios are used to modify domain classes found in <tt>org.apache.groovy.contracts.domain</tt>. For that
+ * reason, concrete annotation processor often don't modify AST nodes directly, but simply work with domain classes like
+ * {@link org.apache.groovy.contracts.domain.Contract}. Whenever an annotation processor is done, it has finished its work on the
+ * underlying domain model. </p>
+ *
+ * <p>{@link #beforeProcessingClassNode(ProcessingContextInformation, org.codehaus.groovy.ast.ClassNode)},
+ * {@link #beforeProcessingMethodNode(ProcessingContextInformation, org.codehaus.groovy.ast.ClassNode, org.codehaus.groovy.ast.MethodNode)},
+ * {@link #beforeProcessingConstructorNode(ProcessingContextInformation, org.codehaus.groovy.ast.ClassNode, org.codehaus.groovy.ast.MethodNode)} are fired
+ * before annotation processors are executed.</p>
+ *
+ * <h3>Domain Model Conversion and Injection</h3>
+ *
+ * <p>Takes a look at the domain model instances and generates the corresponding AST transformation code.</p>
+ *
+ * <p>{@link #afterProcessingClassNode(ProcessingContextInformation, org.codehaus.groovy.ast.ClassNode)},
+ * {@link #afterProcessingMethodNode(ProcessingContextInformation, org.codehaus.groovy.ast.ClassNode, org.codehaus.groovy.ast.MethodNode)},
+ * {@link #afterProcessingConstructorNode(ProcessingContextInformation, org.codehaus.groovy.ast.ClassNode, org.codehaus.groovy.ast.MethodNode)} are fired
+ * after domain model conversion and injection is done.</p>
+ */
+public interface Lifecycle {
+
+ public void beforeProcessingClassNode(final ProcessingContextInformation processingContextInformation, final ClassNode classNode);
+
+ public void afterProcessingClassNode(final ProcessingContextInformation processingContextInformation, final ClassNode classNode);
+
+ public void beforeProcessingMethodNode(final ProcessingContextInformation processingContextInformation, final ClassNode classNode, final MethodNode methodNode);
+
+ public void afterProcessingMethodNode(final ProcessingContextInformation processingContextInformation, final ClassNode classNode, final MethodNode methodNode);
+
+ public void beforeProcessingConstructorNode(final ProcessingContextInformation processingContextInformation, final ClassNode classNode, final MethodNode constructorNode);
+
+ public void afterProcessingConstructorNode(final ProcessingContextInformation processingContextInformation, final ClassNode classNode, final MethodNode constructorNode);
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/ProcessingContextInformation.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/ProcessingContextInformation.java
new file mode 100644
index 0000000..b6d5196
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/common/spi/ProcessingContextInformation.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.common.spi;
+
+import org.apache.groovy.contracts.domain.Contract;
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>Holds all context-specific information which is needed during the transformation
+ * phase of a single {@link Contract} and its related {@link ClassNode}.</p>
+ */
+public class ProcessingContextInformation {
+
+ private Contract contract;
+ private SourceUnit sourceUnit;
+ private ReaderSource source;
+
+ private boolean constructorAssertionsEnabled = true;
+ private boolean preconditionsEnabled = true;
+ private boolean postconditionsEnabled = true;
+ private boolean classInvariantsEnabled = true;
+
+ private Map<String, Object> extra = new HashMap<String, Object>();
+
+ public ProcessingContextInformation(ClassNode classNode, SourceUnit sourceUnit, ReaderSource source) {
+ Validate.notNull(classNode);
+
+ this.contract = new Contract(classNode);
+ this.sourceUnit = sourceUnit;
+ this.source = source;
+ }
+
+ public void setConstructorAssertionsEnabled(boolean other) {
+ constructorAssertionsEnabled = other;
+ }
+
+ public boolean isConstructorAssertionsEnabled() {
+ return constructorAssertionsEnabled;
+ }
+
+ public boolean isPreconditionsEnabled() {
+ return preconditionsEnabled;
+ }
+
+ public boolean isPostconditionsEnabled() {
+ return postconditionsEnabled;
+ }
+
+ public boolean isClassInvariantsEnabled() {
+ return classInvariantsEnabled;
+ }
+
+ public Contract contract() {
+ return contract;
+ }
+
+ public ReaderSource readerSource() {
+ return source;
+ }
+
+ public SourceUnit sourceUnit() {
+ return sourceUnit;
+ }
+
+ public void put(String key, Object value) {
+ Validate.notNull(key);
+
+ extra.put(key, value);
+ }
+
+ public Object get(String key) {
+ Validate.notNull(key);
+
+ return extra.get(key);
+ }
+
+ public void addError(String msg, ASTNode expr) {
+ int line = expr.getLineNumber();
+ int col = expr.getColumnNumber();
+ SourceUnit source = sourceUnit();
+ source.getErrorCollector().addErrorAndContinue(
+ new SyntaxErrorMessage(new SyntaxException(msg + '\n', line, col), source)
+ );
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Assertion.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Assertion.java
new file mode 100644
index 0000000..7751f29
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Assertion.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.domain;
+
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.syntax.Token;
+import org.codehaus.groovy.syntax.Types;
+
+/**
+ * <p>Base class for all assertion types.</p>
+ *
+ * @param <T>
+ */
+public abstract class Assertion<T extends Assertion> {
+
+ private BlockStatement originalBlockStatement;
+ private BooleanExpression booleanExpression;
+
+ public Assertion() {
+ this.booleanExpression = new BooleanExpression(ConstantExpression.TRUE);
+ }
+
+ public Assertion(final BlockStatement blockStatement, final BooleanExpression booleanExpression) {
+ Validate.notNull(booleanExpression);
+
+ this.originalBlockStatement = blockStatement; // the BlockStatement might be null! we do not always have the original expression available
+ this.booleanExpression = booleanExpression;
+ }
+
+ public BooleanExpression booleanExpression() {
+ return booleanExpression;
+ }
+
+ public BlockStatement originalBlockStatement() {
+ return originalBlockStatement;
+ }
+
+ public void renew(BooleanExpression booleanExpression) {
+ Validate.notNull(booleanExpression);
+
+ // don't renew the source position to keep the new assertion expression without source code replacement
+ // booleanExpression.setSourcePosition(this.booleanExpression);
+
+ this.booleanExpression = booleanExpression;
+ }
+
+ public void and(T other) {
+ Validate.notNull(other);
+
+ BooleanExpression newBooleanExpression =
+ new BooleanExpression(
+ new BinaryExpression(
+ booleanExpression(),
+ Token.newSymbol(Types.LOGICAL_AND, -1, -1),
+ other.booleanExpression()
+ )
+ );
+ newBooleanExpression.setSourcePosition(booleanExpression());
+
+ renew(newBooleanExpression);
+ }
+
+ public void or(T other) {
+ Validate.notNull(other);
+
+ BooleanExpression newBooleanExpression =
+ new BooleanExpression(
+ new BinaryExpression(
+ booleanExpression(),
+ Token.newSymbol(Types.LOGICAL_OR, -1, -1),
+ other.booleanExpression()
+ )
+ );
+ newBooleanExpression.setSourcePosition(booleanExpression());
+
+ renew(newBooleanExpression);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/AssertionMap.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/AssertionMap.java
new file mode 100644
index 0000000..e61f1e2
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/AssertionMap.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.domain;
+
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.MethodNode;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class AssertionMap<T extends Assertion<T>> implements Iterable<Map.Entry<MethodNode, T>> {
+
+ private final Map<MethodNode, T> internalMap;
+
+ public AssertionMap() {
+ this.internalMap = new HashMap<MethodNode, T>();
+ }
+
+ public void and(final MethodNode methodNode, final T assertion) {
+ Validate.notNull(methodNode);
+ Validate.notNull(assertion);
+
+ if (!internalMap.containsKey(methodNode)) {
+ internalMap.put(methodNode, assertion);
+ } else {
+ internalMap.get(methodNode).and(assertion);
+ }
+ }
+
+ public void or(final MethodNode methodNode, final T assertion) {
+ Validate.notNull(methodNode);
+ Validate.notNull(assertion);
+
+ if (!internalMap.containsKey(methodNode)) {
+ internalMap.put(methodNode, assertion);
+ } else {
+ internalMap.get(methodNode).or(assertion);
+ }
+ }
+
+ public void join(final MethodNode methodNode, final T assertion) {
+ and(methodNode, assertion);
+ }
+
+ public boolean contains(final MethodNode methodNode) {
+ return internalMap.containsKey(methodNode);
+ }
+
+ public Iterator<Map.Entry<MethodNode, T>> iterator() {
+ return internalMap.entrySet().iterator();
+ }
+
+ public int size() {
+ return internalMap.size();
+ }
+
+ public T get(final MethodNode methodNode) {
+ return internalMap.get(methodNode);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
new file mode 100644
index 0000000..538ef4b
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.domain;
+
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+/**
+ * <p>A class-invariant assertion.</p>
+ */
+public class ClassInvariant extends Assertion<ClassInvariant> {
+
+ public static final ClassInvariant DEFAULT = new ClassInvariant(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)));
+
+ public ClassInvariant() {
+ }
+
+ public ClassInvariant(BlockStatement blockStatement, BooleanExpression booleanExpression) {
+ super(blockStatement, booleanExpression);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Contract.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Contract.java
new file mode 100644
index 0000000..66c17b6
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Contract.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.domain;
+
+import org.apache.groovy.contracts.util.Validate;
+import org.codehaus.groovy.ast.ClassNode;
+
+/**
+ * <p>Represents a contract between a supplier and a customer of a class.</p>
+ */
+public class Contract {
+
+ private final ClassNode classNode;
+
+ private ClassInvariant classInvariant = ClassInvariant.DEFAULT;
+ private final AssertionMap<Precondition> preconditions;
+ private final AssertionMap<Postcondition> postconditions;
+
+ public Contract(final ClassNode classNode) {
+ Validate.notNull(classNode);
+
+ this.classNode = classNode;
+ this.preconditions = new AssertionMap<>();
+ this.postconditions = new AssertionMap<>();
+ }
+
+ public ClassNode classNode() {
+ return classNode;
+ }
+
+ public void setClassInvariant(final ClassInvariant classInvariant) {
+ Validate.notNull(classInvariant);
+ this.classInvariant = classInvariant;
+ }
+
+ public AssertionMap<Precondition> preconditions() {
+ return preconditions;
+ }
+
+ public AssertionMap<Postcondition> postconditions() {
+ return postconditions;
+ }
+
+ public boolean hasDefaultClassInvariant() {
+ return classInvariant == ClassInvariant.DEFAULT;
+ }
+
+ public ClassInvariant classInvariant() {
+ return classInvariant;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Postcondition.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Postcondition.java
new file mode 100644
index 0000000..fb2210e
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Postcondition.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.domain;
+
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+/**
+ * <p>A post-condition assertion.</p>
+ */
+public class Postcondition extends Assertion<Postcondition> {
+
+ private boolean isPartOfConstructor = false;
+
+ public Postcondition() {
+ }
+
+ public Postcondition(BlockStatement blockStatement, BooleanExpression booleanExpression, boolean isPartOfConstructor) {
+ super(blockStatement, booleanExpression);
+ this.isPartOfConstructor = isPartOfConstructor;
+ }
+
+ public boolean isPartOfConstructor() {
+ return isPartOfConstructor;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Precondition.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Precondition.java
new file mode 100644
index 0000000..5bd193c
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/Precondition.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.domain;
+
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+
+/**
+ * <p>A pre-condition assertion.</p>
+ */
+public class Precondition extends Assertion<Precondition> {
+
+ public Precondition() {
+ }
+
+ public Precondition(BlockStatement blockStatement, BooleanExpression booleanExpression) {
+ super(blockStatement, booleanExpression);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/AssertStatementCreationUtility.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/AssertStatementCreationUtility.java
new file mode 100644
index 0000000..0fbbe61
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/AssertStatementCreationUtility.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.AssertStatement;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+
+/**
+ * Central place to create {@link org.codehaus.groovy.ast.stmt.AssertStatement} instances in groovy-contracts.
+ * Utilized to centralize {@link AssertionError} message generation.
+ *
+ * @see org.codehaus.groovy.ast.stmt.AssertStatement
+ * @see AssertionError
+ */
+public final class AssertStatementCreationUtility {
+
+ /**
+ * Reusable method for creating assert statements for the given <tt>booleanExpression</tt>.
+ *
+ * @param booleanExpressions the assertion's {@link org.codehaus.groovy.ast.expr.BooleanExpression} instances
+ * @return a newly created {@link org.codehaus.groovy.ast.stmt.AssertStatement}
+ */
+ public static BlockStatement getAssertionStatements(final List<BooleanExpression> booleanExpressions) {
+
+ List<Statement> assertStatements = new ArrayList<>();
+ for (BooleanExpression booleanExpression : booleanExpressions) {
+ assertStatements.add(getAssertionStatement(booleanExpression));
+ }
+
+ final BlockStatement blockStatement = block();
+ blockStatement.getStatements().addAll(assertStatements);
+
+ return blockStatement;
+ }
+
+ /**
+ * Reusable method for creating assert statements for the given <tt>booleanExpression</tt>.
+ *
+ * @param booleanExpression the assertion's {@link org.codehaus.groovy.ast.expr.BooleanExpression}
+ * @return a newly created {@link org.codehaus.groovy.ast.stmt.AssertStatement}
+ */
+ public static AssertStatement getAssertionStatement(final BooleanExpression booleanExpression) {
+
+ final AssertStatement assertStatement = new AssertStatement(booleanExpression);
+ assertStatement.setStatementLabel(booleanExpression.getNodeMetaData("statementLabel"));
+ assertStatement.setSourcePosition(booleanExpression);
+
+ return assertStatement;
+ }
+
+ /**
+ * Gets a list of {@link org.codehaus.groovy.ast.stmt.ReturnStatement} instances from the given {@link MethodNode}.
+ *
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} that holds the given <tt>lastStatement</tt>
+ * @return a {@link org.codehaus.groovy.ast.stmt.ReturnStatement} or <tt>null</tt>
+ */
+ public static List<ReturnStatement> getReturnStatements(MethodNode method) {
+
+ final ReturnStatementVisitor returnStatementVisitor = new ReturnStatementVisitor();
+ returnStatementVisitor.visitMethod(method);
+
+ final List<ReturnStatement> returnStatements = returnStatementVisitor.getReturnStatements();
+ final BlockStatement blockStatement = (BlockStatement) method.getCode();
+
+ if (returnStatements.isEmpty()) {
+ final int statementCount = blockStatement.getStatements().size();
+ if (statementCount > 0) {
+ final Statement lastStatement = blockStatement.getStatements().get(statementCount - 1);
+ if (lastStatement instanceof ExpressionStatement) {
+ final ReturnStatement returnStatement = new ReturnStatement((ExpressionStatement) lastStatement);
+ returnStatement.setSourcePosition(lastStatement);
+ blockStatement.getStatements().remove(lastStatement);
+ blockStatement.addStatement(returnStatement);
+ returnStatements.add(returnStatement);
+ }
+ }
+ }
+
+ return returnStatements;
+ }
+
+ public static void injectResultVariableReturnStatementAndAssertionCallStatement(BlockStatement statement, ClassNode returnType, ReturnStatement returnStatement, BlockStatement assertionCallStatement) {
+ final AddResultReturnStatementVisitor addResultReturnStatementVisitor = new AddResultReturnStatementVisitor(returnStatement, returnType, assertionCallStatement);
+ addResultReturnStatementVisitor.visitBlockStatement(statement);
+ }
+
+ public static void addAssertionCallStatementToReturnStatement(BlockStatement statement, ReturnStatement returnStatement, Statement assertionCallStatement) {
+ final AddAssertionCallStatementToReturnStatementVisitor addAssertionCallStatementToReturnStatementVisitor = new AddAssertionCallStatementToReturnStatementVisitor(returnStatement, assertionCallStatement);
+ addAssertionCallStatementToReturnStatementVisitor.visitBlockStatement(statement);
+ }
+
+ /**
+ * Collects all {@link ReturnStatement} instances from a given code block.
+ */
+ public static class ReturnStatementVisitor extends ClassCodeVisitorSupport {
+
+ private final List<ReturnStatement> returnStatements = new ArrayList<>();
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+
+ @Override
+ public void visitReturnStatement(ReturnStatement statement) {
+ returnStatements.add(statement);
+ }
+
+ @Override
+ public void visitClosureExpression(ClosureExpression expression) {
+ // do nothing to prevent getting return statements from closures
+ }
+
+ public List<ReturnStatement> getReturnStatements() {
+ return returnStatements;
+ }
+ }
+
+ /**
+ * Replaces a given {@link ReturnStatement} with the appropriate assertion call statement and returns a result variable expression.
+ */
+ public static class AddResultReturnStatementVisitor extends ClassCodeVisitorSupport {
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+
+ private final ReturnStatement returnStatement;
+ private final ClassNode returnType;
+ private final BlockStatement assertionCallBlock;
+
+ public AddResultReturnStatementVisitor(ReturnStatement returnStatement, ClassNode returnType, BlockStatement assertionCallBlock) {
+ this.returnStatement = returnStatement;
+ this.returnType = returnType;
+ this.assertionCallBlock = assertionCallBlock;
+ }
+
+ @Override
+ public void visitBlockStatement(BlockStatement block) {
+
+ List<Statement> blockStatementsCopy = new ArrayList<>(block.getStatements());
+
+ for (Statement statement : blockStatementsCopy) {
+ if (statement == returnStatement) {
+ block.getStatements().remove(statement);
+ block.addStatements(assertionCallBlock.getStatements());
+
+ VariableExpression variableExpression = localVarX("result", returnType);
+
+ block.addStatement(returnS(variableExpression));
+ return; // we found the return statement under target, let's cancel tree traversal
+ }
+ }
+
+ super.visitBlockStatement(block);
+ }
+ }
+
+ /**
+ * Replaces a given {@link ReturnStatement} with the appropriate assertion call statement and returns a result variable expression.
+ */
+ public static class AddAssertionCallStatementToReturnStatementVisitor extends ClassCodeVisitorSupport {
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+
+ private final ReturnStatement returnStatement;
+ private final Statement assertionCallStatement;
+
+ public AddAssertionCallStatementToReturnStatementVisitor(ReturnStatement returnStatement, Statement assertionCallStatement) {
+ this.returnStatement = returnStatement;
+ this.assertionCallStatement = assertionCallStatement;
+ }
+
+ @Override
+ public void visitBlockStatement(BlockStatement block) {
+ List<Statement> blockStatementsCopy = new ArrayList<>(block.getStatements());
+
+ for (Statement statement : blockStatementsCopy) {
+ if (statement == returnStatement) {
+ block.getStatements().remove(statement);
+
+ final VariableExpression $_gc_result = localVarX("$_gc_result", ClassHelper.DYNAMIC_TYPE);
+ block.addStatement(declS($_gc_result, returnStatement.getExpression()));
+ block.addStatement(assertionCallStatement);
+
+ final Statement gcResultReturn = returnS($_gc_result);
+ gcResultReturn.setSourcePosition(returnStatement);
+ block.addStatement(gcResultReturn);
+ return; // we found the return statement under target, let's cancel tree traversal
+ }
+ }
+
+ super.visitBlockStatement(block);
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/BaseGenerator.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/BaseGenerator.java
new file mode 100644
index 0000000..6e2f989
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/BaseGenerator.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import org.apache.groovy.contracts.ViolationTracker;
+import org.apache.groovy.contracts.ast.visitor.BaseVisitor;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.apache.groovy.contracts.util.ExpressionUtils;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ArrayExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.TryCatchStatement;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.codehaus.groovy.syntax.Token;
+import org.codehaus.groovy.syntax.Types;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.notX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.tryCatchS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * <pe
+ * Base class for groovy-contracts code generators.
+ * </p>
+ */
+public abstract class BaseGenerator {
+
+ public static final String INVARIANT_CLOSURE_PREFIX = "invariant";
+ public static final String META_DATA_USE_INLINE_MODE = "org.apache.groovy.contracts.USE_INLINE_MODE";
+
+ protected final ReaderSource source;
+
+ public BaseGenerator(final ReaderSource source) {
+ this.source = source;
+ }
+
+ /**
+ * @param classNode the {@link org.codehaus.groovy.ast.ClassNode} used to look up the invariant closure field
+ * @return the field name of the invariant closure field of the given <tt>classNode</tt>
+ */
+ public static String getInvariantMethodName(final ClassNode classNode) {
+ return INVARIANT_CLOSURE_PREFIX + "_" + classNode.getName().replaceAll("\\.", "_");
+ }
+
+ /**
+ * @param classNode the {@link org.codehaus.groovy.ast.ClassNode} used to look up the invariant closure field
+ * @return the {@link org.codehaus.groovy.ast.MethodNode} which contains the invariant of the given <tt>classNode</tt>
+ */
+ public static MethodNode getInvariantMethodNode(final ClassNode classNode) {
+ return classNode.getDeclaredMethod(getInvariantMethodName(classNode), Parameter.EMPTY_ARRAY);
+ }
+
+ protected BlockStatement getInlineModeBlockStatement(BlockStatement blockStatement) {
+ final BooleanExpression combinedBooleanExpression = ExpressionUtils.getBooleanExpression(ExpressionUtils.getBooleanExpressionsFromAssertionStatements(blockStatement));
+ return block(ifS(
+ boolX(varX(BaseVisitor.GCONTRACTS_ENABLED_VAR, ClassHelper.boolean_TYPE)),
+ block(ifS(notX(combinedBooleanExpression), blockStatement))));
+ }
+
+ protected BlockStatement wrapAssertionBooleanExpression(ClassNode type, MethodNode methodNode, BooleanExpression classInvariantExpression, String assertionType) {
+
+ final ClassNode violationTrackerClassNode = ClassHelper.makeWithoutCaching(ViolationTracker.class);
+ final VariableExpression $_gc_result = varX("$_gc_result", ClassHelper.boolean_TYPE);
+ $_gc_result.setAccessedVariable($_gc_result);
+
+ final BlockStatement ifBlockStatement = block(
+ declS($_gc_result, ConstantExpression.FALSE),
+ stmt(callX(classX(violationTrackerClassNode), "init")),
+ assignS($_gc_result, classInvariantExpression),
+ ifS(
+ boolX(notX(callX($_gc_result, "booleanValue"))),
+ ifS(
+ boolX(callX(classX(violationTrackerClassNode), "violationsOccurred")),
+ tryCatchS(
+ stmt(callX(classX(violationTrackerClassNode), "rethrowFirst")),
+ block(stmt(callX(classX(violationTrackerClassNode), "deinit"))))
+ )
+ )
+ );
+
+ final TryCatchStatement lockTryCatchStatement = tryCatchS(
+ block(ifS(
+ boolX(callX(classX(ClassHelper.make(ContractExecutionTracker.class)), "track", args(constX(type.getName()), constX(methodNode.getTypeDescriptor()), constX(assertionType), methodNode.isStatic() ? ConstantExpression.TRUE : ConstantExpression.FALSE))),
+ ifBlockStatement)),
+ block(new VariableScope(), stmt(callX(
+ classX(ClassHelper.make(ContractExecutionTracker.class)),
+ "clear",
+ args(constX(type.getName()), constX(methodNode.getTypeDescriptor()), constX(assertionType), methodNode.isStatic() ? ConstantExpression.TRUE : ConstantExpression.FALSE)
+ ))));
+
+ return block(ifS(boolX(varX(BaseVisitor.GCONTRACTS_ENABLED_VAR, ClassHelper.boolean_TYPE)), lockTryCatchStatement));
+ }
+
+ // TODO: what about constructor method nodes - does it find a constructor node in the super class?
+ protected BooleanExpression addCallsToSuperMethodNodeAnnotationClosure(final ClassNode type, final MethodNode methodNode, final Class<? extends Annotation> annotationType, BooleanExpression booleanExpression, boolean isPostcondition) {
+
+ final List<AnnotationNode> nextContractElementAnnotations = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), methodNode, ClassHelper.makeWithoutCaching(annotationType));
+ if (nextContractElementAnnotations.isEmpty()) {
+ if (methodNode.getNodeMetaData(META_DATA_USE_INLINE_MODE) == null)
+ methodNode.setNodeMetaData(META_DATA_USE_INLINE_MODE, Boolean.TRUE);
+ return booleanExpression;
+ }
+
+ for (AnnotationNode nextContractElementAnnotation : nextContractElementAnnotations) {
+ ClassExpression classExpression = (ClassExpression) nextContractElementAnnotation.getMember(BaseVisitor.CLOSURE_ATTRIBUTE_NAME);
+ if (classExpression == null) continue;
+
+ ArgumentListExpression callArgumentList = new ArgumentListExpression();
+ for (Parameter parameter : methodNode.getParameters()) {
+ callArgumentList.addExpression(varX(parameter));
+ }
+
+ if (isPostcondition && methodNode.getReturnType() != ClassHelper.VOID_TYPE && !(methodNode instanceof ConstructorNode)) {
+ callArgumentList.addExpression(localVarX("result", methodNode.getReturnType()));
+ }
+
+ if (isPostcondition && !(methodNode instanceof ConstructorNode)) {
+ callArgumentList.addExpression(localVarX("old", new ClassNode(Map.class)));
+ }
+
+ ArgumentListExpression newInstanceArguments = args(
+ classExpression,
+ new ArrayExpression(
+ ClassHelper.DYNAMIC_TYPE,
+ Arrays.<Expression>asList(VariableExpression.THIS_EXPRESSION, VariableExpression.THIS_EXPRESSION)
+ )
+ );
+
+ MethodCallExpression doCall = callX(
+ callX(ClassHelper.makeWithoutCaching(InvokerHelper.class), "invokeConstructorOf", newInstanceArguments),
+ "doCall",
+ callArgumentList);
+ doCall.setMethodTarget(classExpression.getType().getMethods("doCall").get(0));
+
+ booleanExpression.setSourcePosition(nextContractElementAnnotation);
+
+ booleanExpression = boolX(
+ binX(
+ booleanExpression,
+ isPostcondition ? Token.newSymbol(Types.LOGICAL_AND, -1, -1) : Token.newSymbol(Types.LOGICAL_OR, -1, -1),
+ boolX(doCall))
+ );
+ }
+
+ return booleanExpression;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/CandidateChecks.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/CandidateChecks.java
new file mode 100644
index 0000000..7c2b541
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/CandidateChecks.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.PropertyNode;
+
+/**
+ * <p>
+ * Functions in this class are used to determine whether a certain AST node fulfills certain assertion
+ * requirements. E.g. whether a class node is a class invariant candidate or not.
+ * </p>
+ */
+public class CandidateChecks {
+
+ /**
+ * Checks whether the given {@link org.codehaus.groovy.ast.ClassNode} is a candidate
+ * for applying contracts. <p/>
+ * <p>
+ * If the given class node has already been processed in this compilation run, this
+ * method will return <tt>false</tt>.
+ *
+ * @param type the {@link org.codehaus.groovy.ast.ClassNode} to be checked
+ * @return whether the given <tt>type</tt> is a candidate for applying contract assertions
+ */
+ public static boolean isContractsCandidate(final ClassNode type) {
+ return type != null && !type.isSynthetic() && !type.isInterface() && !type.isEnum() && !type.isGenericsPlaceHolder() && !type.isScript() && !type.isScriptBody() && !isRuntimeClass(type);
+ }
+
+ /**
+ * Checks whether the given {@link org.codehaus.groovy.ast.ClassNode} is a candidate
+ * for applying interface contracts.
+ *
+ * @param type the {@link org.codehaus.groovy.ast.ClassNode} to be checked
+ * @return whether the given <tt>type</tt> is a candidate for applying interface contract assertions
+ */
+ public static boolean isInterfaceContractsCandidate(final ClassNode type) {
+ return type != null && type.isInterface() && !type.isSynthetic() && !type.isEnum() && !type.isGenericsPlaceHolder() && !type.isScript() && !type.isScriptBody() && !isRuntimeClass(type);
+ }
+
+ /**
+ * Decides whether the given <tt>propertyNode</tt> is a candidate for class invariant injection.
+ *
+ * @param propertyNode the {@link org.codehaus.groovy.ast.PropertyNode} to check
+ * @return whether the <tt>propertyNode</tt> is a candidate for injecting the class invariant or not
+ */
+ public static boolean isClassInvariantCandidate(final PropertyNode propertyNode) {
+ return propertyNode != null &&
+ propertyNode.isPublic() && !propertyNode.isStatic() && !propertyNode.isInStaticContext() && !propertyNode.isClosureSharedVariable();
+ }
+
+ /**
+ * Decides whether the given <tt>method</tt> is a candidate for a pre- or postcondition.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} to check for pre- or postcondition compliance
+ * @return whether the given {@link org.codehaus.groovy.ast.MethodNode} is a candidate for pre- or postconditions
+ */
+ public static boolean isPreOrPostconditionCandidate(final ClassNode type, final MethodNode method) {
+ if (!isPreconditionCandidate(type, method) && !isPostconditionCandidate(type, method)) return false;
+
+ return true;
+ }
+
+ /**
+ * Decides whether the given <tt>method</tt> is a candidate for class invariants.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} to check for class invariant compliance
+ * @return whether the given {@link org.codehaus.groovy.ast.MethodNode} is a candidate for class invariants
+ */
+ public static boolean isClassInvariantCandidate(final ClassNode type, final MethodNode method) {
+ if (method.isSynthetic() || method.isAbstract() || method.isStatic() || !method.isPublic()) return false;
+ if (method.getDeclaringClass() != type) return false;
+
+ return true;
+ }
+
+ /**
+ * Decides whether the given <tt>method</tt> is a candidate for a pre-condition.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} to check for pre-condition compliance
+ * @return whether the given {@link org.codehaus.groovy.ast.MethodNode} is a candidate for pre-conditions
+ */
+ public static boolean isPreconditionCandidate(final ClassNode type, final MethodNode method) {
+ if (method.isSynthetic() || method.isAbstract()) return false;
+ if (method.getDeclaringClass() != type) return false;
+
+ return true;
+ }
+
+ /**
+ * Decides whether the given <tt>method</tt> is a candidate for a post-condition.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} to check for post-condition compliance
+ * @return whether the given {@link org.codehaus.groovy.ast.MethodNode} is a candidate for post-conditions
+ */
+ public static boolean isPostconditionCandidate(final ClassNode type, final MethodNode method) {
+ if (!isPreconditionCandidate(type, method)) return false;
+ if (method.isStatic()) return false;
+
+ return true;
+ }
+
+ /**
+ * Checks whether the given {@link MethodNode} could be a candidate for an arbitrary {@link org.apache.groovy.contracts.annotations.meta.ContractElement}
+ * annotation.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} to check for {@link org.apache.groovy.contracts.annotations.meta.ContractElement} compliance
+ * @return whether the given method node could be a candidate or not
+ */
+ public static boolean couldBeContractElementMethodNode(final ClassNode type, final MethodNode method) {
+ if (method.isSynthetic() || !method.isPublic()) return false;
+ if (method.getDeclaringClass() != null && !method.getDeclaringClass().getName().equals(type.getName()))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Checks whether the given {@link ClassNode} is part of the Groovy/Java runtime.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @return <tt>true</tt> whether the current {@link org.codehaus.groovy.ast.ClassNode} is a Groovy/Java system class
+ */
+ public static boolean isRuntimeClass(final ClassNode type) {
+ String name = type.getName();
+ return name.startsWith("java.") || (name.startsWith("groovy.") && !name.startsWith("groovy.contracts."));
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/ClassInvariantGenerator.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/ClassInvariantGenerator.java
new file mode 100644
index 0000000..5a6ccf5
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/ClassInvariantGenerator.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import groovy.contracts.Invariant;
+import org.apache.groovy.contracts.annotations.meta.ClassInvariant;
+import org.apache.groovy.contracts.ast.visitor.BaseVisitor;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.io.ReaderSource;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.AND;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+
+/**
+ * <p>
+ * Code generator for class invariants.
+ * </p>
+ */
+public class ClassInvariantGenerator extends BaseGenerator {
+
+ public ClassInvariantGenerator(final ReaderSource source) {
+ super(source);
+ }
+
+ /**
+ * Reads the {@link Invariant} boolean expression and generates a synthetic
+ * method holding this class invariant. This is used for heir calls to find out about inherited class
+ * invariants.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @param classInvariant the {@link org.apache.groovy.contracts.domain.ClassInvariant} the assertion statement should be generated from
+ */
+ public void generateInvariantAssertionStatement(final ClassNode type, final org.apache.groovy.contracts.domain.ClassInvariant classInvariant) {
+
+ BooleanExpression classInvariantExpression = addCallsToSuperAnnotationClosure(type, ClassInvariant.class, classInvariant.booleanExpression());
+
+ final BlockStatement blockStatement = new BlockStatement();
+
+ // add a local protected method with the invariant closure - this is needed for invariant checks in inheritance lines
+ MethodNode methodNode = type.addMethod(getInvariantMethodName(type), Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, blockStatement);
+ methodNode.setSynthetic(true);
+
+ blockStatement.addStatements(wrapAssertionBooleanExpression(type, methodNode, classInvariantExpression, "invariant").getStatements());
+ }
+
+ private BooleanExpression addCallsToSuperAnnotationClosure(final ClassNode type, final Class<? extends Annotation> annotationType, BooleanExpression booleanExpression) {
+
+ final List<AnnotationNode> nextContractElementAnnotations = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), ClassHelper.makeWithoutCaching(annotationType));
+ if (nextContractElementAnnotations.isEmpty()) return booleanExpression;
+
+ for (AnnotationNode nextContractElementAnnotation : nextContractElementAnnotations) {
+ ClassExpression classExpression = (ClassExpression) nextContractElementAnnotation.getMember(BaseVisitor.CLOSURE_ATTRIBUTE_NAME);
+ if (classExpression == null) continue;
+
+ MethodCallExpression doCall = callX(
+ ctorX(classExpression.getType(), args(VariableExpression.THIS_EXPRESSION, VariableExpression.THIS_EXPRESSION)),
+ "doCall"
+ );
+ doCall.setMethodTarget(classExpression.getType().getMethods("doCall").get(0));
+
+ final BooleanExpression rightExpression = boolX(doCall);
+ booleanExpression.setSourcePosition(nextContractElementAnnotation);
+ booleanExpression = boolX(binX(booleanExpression, AND, rightExpression));
+ }
+
+ return booleanExpression;
+ }
+
+ /**
+ * Adds the current class-invariant to the given <tt>method</tt>.
+ *
+ * @param type the {@link org.codehaus.groovy.ast.ClassNode} which declared the given {@link org.codehaus.groovy.ast.MethodNode}
+ * @param method the current {@link org.codehaus.groovy.ast.MethodNode}
+ */
+ public void addInvariantAssertionStatement(final ClassNode type, final MethodNode method) {
+
+ final String invariantMethodName = getInvariantMethodName(type);
+ final MethodNode invariantMethod = type.getDeclaredMethod(invariantMethodName, Parameter.EMPTY_ARRAY);
+ if (invariantMethod == null) return;
+
+ Statement invariantMethodCall = stmt(callThisX(invariantMethod.getName()));
+
+ final Statement statement = method.getCode();
+ if (statement instanceof BlockStatement && method.getReturnType() != ClassHelper.VOID_TYPE && !(method instanceof ConstructorNode)) {
+ final BlockStatement blockStatement = (BlockStatement) statement;
+
+ final List<ReturnStatement> returnStatements = AssertStatementCreationUtility.getReturnStatements(method);
+ for (ReturnStatement returnStatement : returnStatements) {
+ AssertStatementCreationUtility.addAssertionCallStatementToReturnStatement(blockStatement, returnStatement, invariantMethodCall);
+ }
+
+ if (returnStatements.isEmpty()) blockStatement.addStatement(invariantMethodCall);
+
+ } else if (statement instanceof BlockStatement) {
+ final BlockStatement blockStatement = (BlockStatement) statement;
+ blockStatement.addStatement(invariantMethodCall);
+ } else {
+ final BlockStatement assertionBlock = new BlockStatement();
+ assertionBlock.addStatement(statement);
+ assertionBlock.addStatement(invariantMethodCall);
+ method.setCode(assertionBlock);
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/Configurator.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/Configurator.java
new file mode 100644
index 0000000..eb78396
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/Configurator.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>Handles {@code -ea} and {@code -da} runtime input arguments for enabling and
+ * disabling contract elements.</p>
+ */
+public final class Configurator {
+
+ public static final String DISABLED_ASSERTIONS = "-da";
+ public static final String ENABLED_ASSERTIONS = "-ea";
+
+ public static final String PACKAGE_PREFIX = ":";
+ public static final String ENABLE_PACKAGE_ASSERTIONS = ENABLED_ASSERTIONS + PACKAGE_PREFIX;
+ public static final String DISABLE_PACKAGE_ASSERTIONS = DISABLED_ASSERTIONS + PACKAGE_PREFIX;
+ public static final String PACKAGE_POSTFIX = "...";
+
+ private static Map<String, Boolean> assertionConfiguration;
+
+ static {
+ initAssertionConfiguration();
+ }
+
+ private static void initAssertionConfiguration() {
+
+ assertionConfiguration = new HashMap<String, Boolean>();
+ // per default assertion are enabled (Groovy like)
+ assertionConfiguration.put(null, Boolean.TRUE);
+
+ RuntimeMXBean runtimemxBean = ManagementFactory.getRuntimeMXBean();
+ for (String arg : runtimemxBean.getInputArguments()) {
+ if (DISABLED_ASSERTIONS.equals(arg)) {
+ assertionConfiguration.put(null, Boolean.FALSE);
+
+ } else if (arg.startsWith(ENABLE_PACKAGE_ASSERTIONS) && arg.endsWith(PACKAGE_POSTFIX)) {
+ final String packageName = arg.substring(ENABLE_PACKAGE_ASSERTIONS.length(), arg.length() - PACKAGE_POSTFIX.length());
+ assertionConfiguration.put(packageName, Boolean.TRUE);
+
+ } else if (arg.startsWith(DISABLE_PACKAGE_ASSERTIONS) && arg.endsWith(PACKAGE_POSTFIX)) {
+ final String packageName = arg.substring(DISABLE_PACKAGE_ASSERTIONS.length(), arg.length() - PACKAGE_POSTFIX.length());
+
+ assertionConfiguration.put(packageName, Boolean.FALSE);
+ } else if (arg.startsWith(ENABLE_PACKAGE_ASSERTIONS)) {
+ final String className = arg.substring(ENABLE_PACKAGE_ASSERTIONS.length(), arg.length());
+ assertionConfiguration.put(className, Boolean.TRUE);
+
+ } else if (arg.startsWith(DISABLE_PACKAGE_ASSERTIONS)) {
+ final String className = arg.substring(DISABLE_PACKAGE_ASSERTIONS.length(), arg.length());
+
+ assertionConfiguration.put(className, Boolean.FALSE);
+ }
+ }
+ }
+
+ /**
+ * This static method is used within generated code to check whether assertions have been disabled for the current class or not.
+ *
+ * @param className the class name to look up in the assertion configuration
+ * @return whether assertion checking is enabled or not
+ */
+ public static boolean checkAssertionsEnabled(final String className) {
+ return internalMethod(className);
+ }
+
+ private static boolean internalMethod(String className) {
+ if (className == null || className.length() == 0) return false;
+
+ if (assertionConfiguration.containsKey(className)) return assertionConfiguration.get(className);
+ if (className.lastIndexOf('.') < 0) return assertionConfiguration.get(null);
+
+ String packageName = className.substring(0, className.lastIndexOf('.'));
+
+ while (!assertionConfiguration.containsKey(packageName)) {
+ int dotIndex = packageName.lastIndexOf('.');
+ if (dotIndex < 0) return assertionConfiguration.get(null);
+
+ packageName = packageName.substring(0, dotIndex);
+ }
+
+ if (assertionConfiguration.containsKey(packageName)) return assertionConfiguration.get(packageName);
+
+ return assertionConfiguration.get(null);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/ContractExecutionTracker.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/ContractExecutionTracker.java
new file mode 100644
index 0000000..9549c14
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/ContractExecutionTracker.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import java.util.HashSet;
+import java.util.Objects;
+
+/**
+ * Keeps track of contract executions to avoid cyclic contract checks.
+ */
+public class ContractExecutionTracker {
+
+ public static final class ContractExecution {
+ final String className;
+ final String methodIdentifier;
+ final String assertionType;
+ final boolean isStatic;
+
+ public ContractExecution(String className, String methodIdentifier, String assertionType, boolean isStatic) {
+ this.className = className;
+ this.methodIdentifier = methodIdentifier;
+ this.assertionType = assertionType;
+ this.isStatic = isStatic;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ ContractExecution that = (ContractExecution) o;
+
+ if (isStatic != that.isStatic)
+ return false;
+ if (!Objects.equals(assertionType, that.assertionType))
+ return false;
+ if (!Objects.equals(className, that.className))
+ return false;
+ if (!Objects.equals(methodIdentifier, that.methodIdentifier))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = className != null ? className.hashCode() : 0;
+ result = 31 * result + (methodIdentifier != null ? methodIdentifier.hashCode() : 0);
+ result = 31 * result + (assertionType != null ? assertionType.hashCode() : 0);
+ result = 31 * result + (isStatic ? 1 : 0);
+ return result;
+ }
+ }
+
+
+ static class ContractExecutionThreadLocal extends ThreadLocal<HashSet<ContractExecution>> {
+
+ @Override
+ protected HashSet<ContractExecution> initialValue() {
+ return new HashSet<ContractExecution>();
+ }
+ }
+
+ private static ThreadLocal<HashSet<ContractExecution>> executions = new ContractExecutionThreadLocal();
+
+ public static boolean track(String className, String methodIdentifier, String assertionType, boolean isStatic) {
+ final ContractExecution ce = new ContractExecution(className, methodIdentifier, assertionType, isStatic);
+ final HashSet<ContractExecution> contractExecutions = executions.get();
+
+ if (!contractExecutions.contains(ce)) {
+ contractExecutions.add(ce);
+ return true;
+ }
+
+ return false;
+ }
+
+ public static void clear(String className, String methodIdentifier, String assertionType, boolean isStatic) {
+ final HashSet<ContractExecution> contractExecutions = executions.get();
+
+ contractExecutions.remove(new ContractExecution(className, methodIdentifier, assertionType, isStatic));
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/OldVariableGenerationUtility.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/OldVariableGenerationUtility.java
new file mode 100644
index 0000000..03b4943
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/OldVariableGenerationUtility.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callSuperX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+
+/**
+ * <p>Central place where code generation for the <tt>old</tt> closure variable
+ * takes place.</p>
+ */
+public class OldVariableGenerationUtility {
+
+ public static final String OLD_VARIABLES_METHOD = "$_gc_computeOldVariables";
+
+ /**
+ * Creates a synthetic method handling generation of the <tt>old</tt> variable map. If a super class declares
+ * the same synthetic method it will be called and the results will be merged.
+ *
+ * @param classNode which contains postconditions, so an old variable generating method makes sense here.
+ */
+ public static void addOldVariableMethodNode(final ClassNode classNode) {
+ if (classNode.getDeclaredMethod(OLD_VARIABLES_METHOD, Parameter.EMPTY_ARRAY) != null) return;
+
+ final BlockStatement methodBlockStatement = new BlockStatement();
+
+ final MapExpression oldVariablesMap = new MapExpression();
+
+ // create variable assignments for old variables
+ for (final FieldNode fieldNode : classNode.getFields()) {
+ if (fieldNode.getName().startsWith("$")) continue;
+
+ final ClassNode fieldType = ClassHelper.getWrapper(fieldNode.getType());
+
+ if (fieldType.getName().startsWith("java.lang") || ClassHelper.isPrimitiveType(fieldType) || fieldType.getName().startsWith("java.math") ||
+ fieldType.getName().startsWith("java.util") ||
+ fieldType.getName().startsWith("java.sql") ||
+ fieldType.getName().equals("groovy.lang.GString") ||
+ fieldType.getName().equals("java.lang.String")) {
+
+ MethodNode cloneMethod = fieldType.getMethod("clone", Parameter.EMPTY_ARRAY);
+ // if a clone classNode is available, the value is cloned
+ if (cloneMethod != null && fieldType.implementsInterface(ClassHelper.make("java.lang.Cloneable"))) {
+
+ final MethodCallExpression cloneField = callX(fieldX(fieldNode), "clone");
+ // return null if field is null
+ cloneField.setSafe(true);
+
+ VariableExpression oldVariable = localVarX("$old$" + fieldNode.getName(), fieldNode.getType());
+ Statement oldVariableAssignment = declS(oldVariable, cloneField);
+
+ methodBlockStatement.addStatement(oldVariableAssignment);
+ oldVariablesMap.addMapEntryExpression(new MapEntryExpression(constX(oldVariable.getName().substring("$old$".length())), oldVariable));
+
+ } else if (ClassHelper.isPrimitiveType(fieldType)
+ || ClassHelper.isNumberType(fieldType)
+ || fieldType.getName().startsWith("java.math")
+ || fieldType.getName().equals("groovy.lang.GString")
+ || fieldType.getName().equals("java.lang.String")) {
+
+ VariableExpression oldVariable = localVarX("$old$" + fieldNode.getName(), fieldNode.getType());
+ Statement oldVariableAssignment = declS(oldVariable, fieldX(fieldNode));
+
+ methodBlockStatement.addStatement(oldVariableAssignment);
+ oldVariablesMap.addMapEntryExpression(new MapEntryExpression(constX(oldVariable.getName().substring("$old$".length())), oldVariable));
+ }
+ }
+ }
+
+ VariableExpression oldVariable = localVarX("old", new ClassNode(Map.class));
+ methodBlockStatement.addStatement(declS(oldVariable, oldVariablesMap));
+ VariableExpression mergedOldVariables = null;
+
+ // let's ask the super class for old variables...
+ if (classNode.getSuperClass() != null && classNode.getSuperClass().getMethod(OLD_VARIABLES_METHOD, Parameter.EMPTY_ARRAY) != null) {
+ mergedOldVariables = localVarX("mergedOldVariables", new ClassNode(Map.class));
+ methodBlockStatement.addStatement(declS(mergedOldVariables,
+ callX(oldVariable, "plus", args(callSuperX(OLD_VARIABLES_METHOD)))));
+ }
+
+ methodBlockStatement.addStatement(returnS(mergedOldVariables != null ? mergedOldVariables : oldVariable));
+
+ final MethodNode preconditionMethodNode = classNode.addMethod(OLD_VARIABLES_METHOD, Opcodes.ACC_PROTECTED, new ClassNode(Map.class), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, methodBlockStatement);
+ preconditionMethodNode.setSynthetic(true);
+
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java
new file mode 100644
index 0000000..0b39d82
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import org.apache.groovy.contracts.annotations.meta.Postcondition;
+import org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor;
+import org.apache.groovy.contracts.ast.visitor.BaseVisitor;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+
+/**
+ * <p>
+ * Code generator for postconditions.
+ * </p>
+ */
+public class PostconditionGenerator extends BaseGenerator {
+
+ public PostconditionGenerator(final ReaderSource source) {
+ super(source);
+ }
+
+ /**
+ * Adds a synthetic method to the given <tt>classNode</tt> which can be used
+ * to create a map of most instance variables found in this class. Used for the <tt>old</tt> variable
+ * mechanism.
+ *
+ * @param classNode the {@link org.codehaus.groovy.ast.ClassNode} to add the synthetic method to
+ */
+ public void addOldVariablesMethod(final ClassNode classNode) {
+ OldVariableGenerationUtility.addOldVariableMethodNode(classNode);
+ }
+
+ /**
+ * Injects a postcondition assertion statement in the given <tt>method</tt>, based on the <tt>booleanExpression</tt>.
+ *
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} for assertion injection
+ * @param postcondition the {@link org.apache.groovy.contracts.domain.Postcondition} the assertion statement should be generated from
+ */
+ public void generatePostconditionAssertionStatement(MethodNode method, org.apache.groovy.contracts.domain.Postcondition postcondition) {
+
+ final BooleanExpression postconditionBooleanExpression = addCallsToSuperMethodNodeAnnotationClosure(method.getDeclaringClass(), method, Postcondition.class, postcondition.booleanExpression(), true);
+
+
+ BlockStatement blockStatement;
+ final BlockStatement originalBlockStatement = postcondition.originalBlockStatement();
+ // if use execution tracker flag is found in the meta-data the annotation closure visitor discovered
+ // method calls which might be subject to cycling boolean expressions -> no inline mode possible
+ final boolean useExecutionTracker = originalBlockStatement == null || Boolean.TRUE.equals(originalBlockStatement.getNodeMetaData(AnnotationClosureVisitor.META_DATA_USE_EXECUTION_TRACKER));
+
+ if (!useExecutionTracker && Boolean.TRUE.equals(method.getNodeMetaData(META_DATA_USE_INLINE_MODE))) {
+ blockStatement = getInlineModeBlockStatement(originalBlockStatement);
+ } else {
+ blockStatement = wrapAssertionBooleanExpression(method.getDeclaringClass(), method, postconditionBooleanExpression, "postcondition");
+ }
+
+ addPostcondition(method, blockStatement);
+ }
+
+ /**
+ * Adds a default postcondition if a postcondition has already been defined for this {@link org.codehaus.groovy.ast.MethodNode}
+ * in a super-class.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode} of the given <tt>methodNode</tt>
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} to create the default postcondition for
+ */
+ public void generateDefaultPostconditionStatement(final ClassNode type, final MethodNode method) {
+
+ // if another precondition is available we'll evaluate to false
+ boolean isAnotherPostconditionAvailable = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), method, ClassHelper.makeWithoutCaching(Postcondition.class)).size() > 0;
+ if (!isAnotherPostconditionAvailable) return;
+
+ // if another post-condition is available we need to add a default expression of TRUE
+ // since post-conditions are usually connected with a logical AND
+ final BooleanExpression postconditionBooleanExpression = addCallsToSuperMethodNodeAnnotationClosure(method.getDeclaringClass(), method, Postcondition.class, new BooleanExpression(ConstantExpression.TRUE), true);
+ if (postconditionBooleanExpression.getExpression() == ConstantExpression.TRUE) return;
+
+ final BlockStatement blockStatement = wrapAssertionBooleanExpression(type, method, postconditionBooleanExpression, "postcondition");
+ addPostcondition(method, blockStatement);
+ }
+
+ private void addPostcondition(MethodNode method, BlockStatement postconditionBlockStatement) {
+ final BlockStatement block = (BlockStatement) method.getCode();
+
+ // if return type is not void, than a "result" variable is provided in the postcondition expression
+ final List<Statement> statements = block.getStatements();
+ if (statements.size() > 0) {
+ Expression contractsEnabled = localVarX(BaseVisitor.GCONTRACTS_ENABLED_VAR, ClassHelper.boolean_TYPE);
+
+ if (method.getReturnType() != ClassHelper.VOID_TYPE) {
+ List<ReturnStatement> returnStatements = AssertStatementCreationUtility.getReturnStatements(method);
+
+ for (ReturnStatement returnStatement : returnStatements) {
+ BlockStatement localPostconditionBlockStatement = block(new VariableScope(), postconditionBlockStatement.getStatements());
+
+ Expression result = localVarX("result", method.getReturnType());
+ localPostconditionBlockStatement.getStatements().add(0, declS(result, returnStatement.getExpression()));
+ AssertStatementCreationUtility.injectResultVariableReturnStatementAndAssertionCallStatement(block, method.getReturnType().redirect(), returnStatement, localPostconditionBlockStatement);
+ }
+ setOldVariablesIfEnabled(block, contractsEnabled);
+
+ } else if (method instanceof ConstructorNode) {
+ block.addStatements(postconditionBlockStatement.getStatements());
+ } else {
+ setOldVariablesIfEnabled(block, contractsEnabled);
+ block.addStatements(postconditionBlockStatement.getStatements());
+ }
+ }
+ }
+
+ private void setOldVariablesIfEnabled(BlockStatement block, Expression contractsEnabled) {
+ // Assign the return statement expression to a local variable: Map old
+ final Expression oldVariableExpression = localVarX("old", new ClassNode(Map.class));
+ Statement oldVariableStatement = assignS(oldVariableExpression, callThisX(OldVariableGenerationUtility.OLD_VARIABLES_METHOD));
+ block.getStatements().add(0, declS(oldVariableExpression, ConstantExpression.NULL));
+ block.getStatements().add(1, ifS(boolX(contractsEnabled), block(oldVariableStatement)));
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PreconditionGenerator.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PreconditionGenerator.java
new file mode 100644
index 0000000..29b9db9
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PreconditionGenerator.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import groovy.contracts.Requires;
+import org.apache.groovy.contracts.annotations.meta.Precondition;
+import org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor;
+import org.apache.groovy.contracts.util.AnnotationUtils;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.io.ReaderSource;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+
+/**
+ * Code generator for preconditions.
+ */
+public class PreconditionGenerator extends BaseGenerator {
+
+ public PreconditionGenerator(final ReaderSource source) {
+ super(source);
+ }
+
+ /**
+ * Injects a precondition assertion statement in the given <tt>method</tt>, based on the given <tt>annotation</tt> of
+ * type {@link Requires}.
+ *
+ * @param method the {@link org.codehaus.groovy.ast.MethodNode} for assertion injection
+ * @param precondition the {@link org.apache.groovy.contracts.domain.Precondition} the assertion statement should be generated from
+ */
+ public void generatePreconditionAssertionStatement(final MethodNode method, final org.apache.groovy.contracts.domain.Precondition precondition) {
+ final BooleanExpression preconditionBooleanExpression = addCallsToSuperMethodNodeAnnotationClosure(method.getDeclaringClass(), method, Precondition.class, precondition.booleanExpression(), false);
+
+ BlockStatement blockStatement;
+
+ final BlockStatement originalBlockStatement = precondition.originalBlockStatement();
+ // if use execution tracker flag is found in the meta-data the annotation closure visitor discovered
+ // method calls which might be subject to cycling boolean expressions -> no inline mode possible
+ final boolean useExecutionTracker = originalBlockStatement == null || Boolean.TRUE.equals(originalBlockStatement.getNodeMetaData(AnnotationClosureVisitor.META_DATA_USE_EXECUTION_TRACKER));
+
+ if (!useExecutionTracker && Boolean.TRUE.equals(method.getNodeMetaData(META_DATA_USE_INLINE_MODE))) {
+ blockStatement = getInlineModeBlockStatement(precondition.originalBlockStatement());
+ } else {
+ blockStatement = wrapAssertionBooleanExpression(method.getDeclaringClass(), method, preconditionBooleanExpression, "precondition");
+ }
+
+ addPrecondition(method, blockStatement);
+ }
+
+ /**
+ * Generates the default precondition statement for {@link org.codehaus.groovy.ast.MethodNode} instances with
+ * the {@link org.apache.groovy.contracts.annotations.meta.Precondition} annotation.
+ *
+ * @param type the current {@link org.codehaus.groovy.ast.ClassNode}
+ * @param methodNode the {@link org.codehaus.groovy.ast.MethodNode} with a {@link org.apache.groovy.contracts.annotations.meta.Precondition} annotation
+ */
+ public void generateDefaultPreconditionStatement(final ClassNode type, final MethodNode methodNode) {
+
+ // if another precondition is available we'll evaluate to false
+ boolean isAnotherPreconditionAvailable = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), methodNode, ClassHelper.makeWithoutCaching(Precondition.class)).size() > 0;
+ if (!isAnotherPreconditionAvailable) return;
+
+ // if there is another preconditio up the inheritance path, we need a default precondition with FALSE
+ // e.g. C1 <no precondition> : C2 <item != null> == false || item != null
+ BooleanExpression preconditionBooleanExpression = boolX(ConstantExpression.FALSE);
+ preconditionBooleanExpression = addCallsToSuperMethodNodeAnnotationClosure(type, methodNode, Precondition.class, preconditionBooleanExpression, false);
+ // if precondition could not be found in parent class, let's return
+ if (preconditionBooleanExpression.getExpression() == ConstantExpression.FALSE)
+ return;
+
+ final BlockStatement blockStatement = wrapAssertionBooleanExpression(type, methodNode, preconditionBooleanExpression, "precondition");
+
+ addPrecondition(methodNode, blockStatement);
+ }
+
+ private void addPrecondition(MethodNode method, BlockStatement blockStatement) {
+ final BlockStatement modifiedMethodCode = new BlockStatement();
+ modifiedMethodCode.addStatements(blockStatement.getStatements());
+
+ if (method.getCode() instanceof BlockStatement) {
+
+ BlockStatement methodBlock = (BlockStatement) method.getCode();
+ for (Statement statement : methodBlock.getStatements()) {
+ if (method instanceof ConstructorNode && statement instanceof ExpressionStatement && ((ExpressionStatement) statement).getExpression() instanceof ConstructorCallExpression) {
+ modifiedMethodCode.getStatements().add(0, statement);
+ } else {
+ modifiedMethodCode.getStatements().add(statement);
+ }
+ }
+ } else {
+ modifiedMethodCode.addStatement(method.getCode());
+ }
+
+ method.setCode(modifiedMethodCode);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/TryCatchBlockGenerator.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/TryCatchBlockGenerator.java
new file mode 100644
index 0000000..ef148fc
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/TryCatchBlockGenerator.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.stmt.TryCatchStatement;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.PLUS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.catchS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.tryCatchS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+
+/**
+ * Creates a try-catch block around a given {@link org.codehaus.groovy.ast.stmt.AssertStatement} and catches
+ * a PowerAssertionError to reuse the generated visual output.
+ */
+public class TryCatchBlockGenerator {
+
+ public static BlockStatement generateTryCatchBlockForInlineMode(final ClassNode assertionErrorClass, final String message, final Statement assertStatement) {
+
+ final Class powerAssertionErrorClass = loadPowerAssertionErrorClass();
+
+ if (powerAssertionErrorClass == null)
+ throw new GroovyBugError("groovy-contracts needs Groovy 1.7 or above!");
+
+ VariableExpression newErrorVariableExpression = localVarX("newError", assertionErrorClass);
+
+ Statement expr = declS(newErrorVariableExpression,
+ ctorX(assertionErrorClass,
+ args(binX(constX(message), PLUS, callX(varX(param(ClassHelper.makeWithoutCaching(powerAssertionErrorClass), "error")), "getMessage")))));
+
+ Statement exp2 = stmt(callX(newErrorVariableExpression, "setStackTrace", args(
+ callX(varX(param(ClassHelper.makeWithoutCaching(powerAssertionErrorClass), "error")), "getStackTrace")
+ )));
+
+ final TryCatchStatement tryCatchStatement = tryCatchS(assertStatement);
+ tryCatchStatement.addCatch(catchS(
+ param(ClassHelper.makeWithoutCaching(powerAssertionErrorClass), "error"),
+ block(expr, exp2, throwS(newErrorVariableExpression))));
+
+ return block(tryCatchStatement);
+ }
+
+ public static BlockStatement generateTryCatchBlock(final ClassNode assertionErrorClass, final String message, final Statement assertStatement) {
+
+ final String $_gc_closure_result = "$_gc_closure_result";
+
+ final VariableExpression variableExpression = localVarX($_gc_closure_result, ClassHelper.Boolean_TYPE);
+
+ // if the assert statement is successful the return variable will be true else false
+ final BlockStatement overallBlock = new BlockStatement();
+ overallBlock.addStatement(declS(variableExpression, ConstantExpression.FALSE));
+
+ final BlockStatement assertBlockStatement = block(
+ assertStatement,
+ assignS(variableExpression, ConstantExpression.TRUE)
+ );
+
+ final Class powerAssertionErrorClass = loadPowerAssertionErrorClass();
+
+ if (powerAssertionErrorClass == null)
+ throw new GroovyBugError("groovy-contracts needs Groovy 1.7 or above!");
+
+ VariableExpression newErrorVariableExpression = localVarX("newError", assertionErrorClass);
+
+ Statement expr = declS(newErrorVariableExpression, ctorX(assertionErrorClass,
+ args(binX(constX(message), PLUS, callX(varX(param(ClassHelper.makeWithoutCaching(powerAssertionErrorClass), "error")), "getMessage")))));
+
+ Statement exp2 = stmt(callX(newErrorVariableExpression, "setStackTrace", args(
+ callX(varX(param(ClassHelper.makeWithoutCaching(powerAssertionErrorClass), "error")), "getStackTrace")
+ )));
+
+ final TryCatchStatement tryCatchStatement = tryCatchS(assertBlockStatement);
+ tryCatchStatement.addCatch(catchS(param(ClassHelper.makeWithoutCaching(powerAssertionErrorClass), "error"), block(expr, exp2)));
+
+ overallBlock.addStatement(tryCatchStatement);
+ overallBlock.addStatement(returnS(variableExpression));
+
+ return overallBlock;
+ }
+
+ private static Class loadPowerAssertionErrorClass() {
+
+ Class result = null;
+
+ try {
+ result = TryCatchBlockGenerator.class.getClassLoader().loadClass("org.codehaus.groovy.transform.powerassert.PowerAssertionError");
+ } catch (ClassNotFoundException e) {
+ try {
+ result = TryCatchBlockGenerator.class.getClassLoader().loadClass("org.codehaus.groovy.runtime.powerassert.PowerAssertionError");
+ } catch (ClassNotFoundException ignore) {
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/AnnotationUtils.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/AnnotationUtils.java
new file mode 100644
index 0000000..1cd89eb
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/AnnotationUtils.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.util;
+
+import org.apache.groovy.contracts.generation.CandidateChecks;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>Helper methods for reading/getting {@link org.codehaus.groovy.ast.AnnotationNode} instances.</p>
+ */
+public class AnnotationUtils {
+
+ /**
+ * Checks whether the given {@link org.codehaus.groovy.ast.ClassNode} is annotated
+ * with an annotations of the given package or full annotatedNode name.
+ *
+ * @param annotatedNode the {@link org.codehaus.groovy.ast.AnnotatedNode} to search for the given annotation
+ * @param typeOrPackageName can either be a part of the package or the complete annotation class name
+ * @return <tt>true</tt> if an annotation was found, <tt>false</tt> otherwise
+ */
+ public static boolean hasAnnotationOfType(AnnotatedNode annotatedNode, String typeOrPackageName) {
+ for (AnnotationNode annotation : annotatedNode.getAnnotations()) {
+ if (annotation.getClassNode().getName().startsWith(typeOrPackageName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the next {@link org.codehaus.groovy.ast.AnnotationNode} instance in the inheritance line which is annotated
+ * with the given Annotation class <tt>anno</tt>.
+ *
+ * @param type the {@link org.codehaus.groovy.ast.ClassNode} to check for the annotation
+ * @param anno the annotation to watch out for
+ * @return the next {@link org.codehaus.groovy.ast.AnnotationNode} in the inheritance line, or <tt>null</tt>
+ */
+ public static List<AnnotationNode> getAnnotationNodeInHierarchyWithMetaAnnotation(ClassNode type, ClassNode anno) {
+ List<AnnotationNode> result = new ArrayList<AnnotationNode>();
+ for (AnnotationNode annotation : type.getAnnotations()) {
+ if (annotation.getClassNode().getAnnotations(anno).size() > 0) {
+ result.add(annotation);
+ }
+ }
+
+ if (result.isEmpty() && type.getSuperClass() != null) {
+ return getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), anno);
+ } else {
+ return result;
+ }
+ }
+
+ /**
+ * <p>Checks whether there exists a {@link MethodNode} up the inheritance tree where exists an annotation which is annotated
+ * with <tt>metaAnnotationClassNode</tt>.</p>
+ *
+ * @param type the origin {@link ClassNode}
+ * @param originMethodNode the origin {@link MethodNode}
+ * @param metaAnnotationClassNode the {@link ClassNode} of the meta-annotation
+ * @return a list of {@link AnnotationNode} all annotated with <tt>metaAnnotationClassNode</tt>
+ */
+ public static List<AnnotationNode> getAnnotationNodeInHierarchyWithMetaAnnotation(ClassNode type, MethodNode originMethodNode, ClassNode metaAnnotationClassNode) {
+ List<AnnotationNode> result = new ArrayList<AnnotationNode>();
+
+ while (type != null) {
+ MethodNode methodNode = type.getMethod(originMethodNode.getName(), originMethodNode.getParameters());
+ if (methodNode != null) {
+ for (AnnotationNode annotation : methodNode.getAnnotations()) {
+ if (annotation.getClassNode().getAnnotations(metaAnnotationClassNode).size() > 0) {
+ result.add(annotation);
+ }
+ }
+
+ if (result.size() > 0) return result;
+ }
+
+ type = type.getSuperClass();
+ }
+
+ return result;
+ }
+
+ /**
+ * Loads all annotation nodes of the given {@link org.codehaus.groovy.ast.AnnotatedNode} instance which are marked
+ * with the annotation <tt>metaAnnotationClassName</tt>.
+ *
+ * @param annotatedNode an {@link org.codehaus.groovy.ast.AnnotatedNode} from which the annotations are checked
+ * @param metaAnnotationClassName the name of the meta annotation
+ * @return a list of {@link AnnotationNode} instances which implement the given <tt>metaAnnotationClass</tt>
+ */
+ public static List<AnnotationNode> hasMetaAnnotations(AnnotatedNode annotatedNode, String metaAnnotationClassName) {
+
+ ArrayList<AnnotationNode> result = new ArrayList<AnnotationNode>();
+
+ for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
+ if (CandidateChecks.isRuntimeClass(annotationNode.getClassNode())) continue;
+
+ // is the annotation marked with the given meta annotation?
+ List<AnnotationNode> metaAnnotations = annotationNode.getClassNode().getAnnotations(ClassHelper.makeWithoutCaching(metaAnnotationClassName));
+ if (metaAnnotations.isEmpty()) {
+ metaAnnotations = hasMetaAnnotations(annotationNode.getClassNode(), metaAnnotationClassName);
+ }
+
+ if (metaAnnotations.size() > 0) result.add(annotationNode);
+ }
+ return result;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/ExpressionUtils.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/ExpressionUtils.java
new file mode 100644
index 0000000..54a1a81
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/ExpressionUtils.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.util;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.AssertStatement;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.syntax.Token;
+import org.codehaus.groovy.syntax.Types;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.AND;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+
+/**
+ * <p>Internal utility class for extracting a boolean expression from the given expression or statement.</p>
+ *
+ * @see ClosureExpression
+ * @see BooleanExpression
+ */
+public class ExpressionUtils {
+
+ /**
+ * Returns all {@link BooleanExpression} instances found in the given {@link ClosureExpression}.
+ */
+ public static List<BooleanExpression> getBooleanExpression(ClosureExpression closureExpression) {
+ if (closureExpression == null) return null;
+
+ final BlockStatement closureBlockStatement = (BlockStatement) closureExpression.getCode();
+ return getBooleanExpressions(closureBlockStatement);
+ }
+
+ /**
+ * Returns all {@link BooleanExpression} instances found in the given {@link BlockStatement}.
+ */
+ private static List<BooleanExpression> getBooleanExpressions(BlockStatement closureBlockStatement) {
+ final List<Statement> statementList = closureBlockStatement.getStatements();
+
+ List<BooleanExpression> booleanExpressions = new ArrayList<BooleanExpression>();
+
+ for (Statement stmt : statementList) {
+ BooleanExpression tmp = null;
+
+ if (stmt instanceof ExpressionStatement && ((ExpressionStatement) stmt).getExpression() instanceof BooleanExpression) {
+ tmp = (BooleanExpression) ((ExpressionStatement) stmt).getExpression();
+ tmp.setNodeMetaData("statementLabel", stmt.getStatementLabel());
+ } else if (stmt instanceof ExpressionStatement) {
+ Expression expression = ((ExpressionStatement) stmt).getExpression();
+ tmp = boolX(expression);
+ tmp.setSourcePosition(expression);
+ tmp.setNodeMetaData("statementLabel", stmt.getStatementLabel());
+ }
+
+ booleanExpressions.add(tmp);
+ }
+
+ return booleanExpressions;
+ }
+
+ /**
+ * Returns all {@link BooleanExpression} instances found in the given {@link BlockStatement}.
+ */
+ public static List<BooleanExpression> getBooleanExpressionsFromAssertionStatements(BlockStatement blockStatement) {
+ AssertStatementCollector collector = new AssertStatementCollector();
+ collector.visitBlockStatement(blockStatement);
+
+ List<AssertStatement> assertStatements = collector.assertStatements;
+ if (assertStatements.isEmpty()) return Collections.emptyList();
+
+ List<BooleanExpression> booleanExpressions = new ArrayList<BooleanExpression>();
+ for (AssertStatement assertStatement : assertStatements) {
+ booleanExpressions.add(assertStatement.getBooleanExpression());
+ }
+
+ return booleanExpressions;
+ }
+
+ public static BooleanExpression getBooleanExpression(List<BooleanExpression> booleanExpressions) {
+ if (booleanExpressions == null || booleanExpressions.isEmpty())
+ return boolX(ConstantExpression.TRUE);
+
+ BooleanExpression result = null;
+ for (BooleanExpression booleanExpression : booleanExpressions) {
+ if (result == null) {
+ result = booleanExpression;
+ } else {
+ result = boolX(binX(result, AND, booleanExpression));
+ }
+ }
+
+ return result;
+ }
+
+ static class AssertStatementCollector extends ClassCodeVisitorSupport implements Opcodes {
+
+ public List<AssertStatement> assertStatements = new ArrayList<AssertStatement>();
+
+ @Override
+ public void visitAssertStatement(AssertStatement statement) {
+ assertStatements.add(statement);
+ }
+
+ @Override
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/FieldValues.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/FieldValues.java
new file mode 100644
index 0000000..fc3d895
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/FieldValues.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.util;
+
+import java.lang.reflect.Field;
+
+/**
+ * This utility is meant to be used to replace direct calls to private
+ * field variables in class invariants.
+ */
+public class FieldValues {
+
+ @SuppressWarnings("unchecked")
+ public static <T> T fieldValue(Object obj, String fieldName, Class<T> type) throws IllegalAccessException {
+ Validate.notNull(obj);
+ Validate.notNull(fieldName);
+
+ Field f = findField(obj.getClass(), "thisObject");
+ if (f == null) throw new IllegalArgumentException("Field thisObject could not be found!");
+ f.setAccessible(true);
+
+ Object target = f.get(obj);
+
+ f = findField(target.getClass(), fieldName);
+ if (f == null) throw new IllegalArgumentException("Field " + fieldName + " could not be found!");
+ f.setAccessible(true);
+
+ return (T) f.get(target);
+ }
+
+ private static Field findField(Class<?> clazz, String name) {
+ Class<?> next = clazz;
+ while (!Object.class.equals(next) && next != null) {
+ Field[] fields = next.getDeclaredFields();
+ for (Field field : fields) {
+ if ((name.equals(field.getName()))) {
+ return field;
+ }
+ }
+ next = next.getSuperclass();
+ }
+ return null;
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/LifecycleImplementationLoader.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/LifecycleImplementationLoader.java
new file mode 100644
index 0000000..bd4e6b5
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/LifecycleImplementationLoader.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.ServiceConfigurationError;
+
+/**
+ * <p>Finds and loads implementation classes of interface {@link org.apache.groovy.contracts.common.spi.Lifecycle}.</p>
+ **/
+public final class LifecycleImplementationLoader<S> implements Iterable<S> {
+
+ private static final String PREFIX = "META-INF/services/";
+
+ private Class<S> service;
+ private ClassLoader loader;
+ private LinkedHashMap<String, S> providers = new LinkedHashMap<String, S>();
+ private LazyIterator lookupIterator;
+
+ public void reload() {
+ providers.clear();
+ lookupIterator = new LazyIterator(service, loader);
+ }
+
+ private LifecycleImplementationLoader(Class<S> svc, ClassLoader cl) {
+ service = svc;
+ loader = cl;
+ reload();
+ }
+
+ private static void fail(Class service, String msg, Throwable cause) throws ServiceConfigurationError {
+ throw new ServiceConfigurationError(service.getName() + ": " + msg, cause);
+ }
+
+ private static void fail(Class service, String msg) throws ServiceConfigurationError {
+ throw new ServiceConfigurationError(service.getName() + ": " + msg);
+ }
+
+ private static void fail(Class service, URL u, int line, String msg) throws ServiceConfigurationError {
+ fail(service, u + ":" + line + ": " + msg);
+ }
+
+ private int parseLine(Class service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError {
+ String ln = r.readLine();
+ if (ln == null) {
+ return -1;
+ }
+ int ci = ln.indexOf('#');
+ if (ci >= 0) ln = ln.substring(0, ci);
+ ln = ln.trim();
+ int n = ln.length();
+ if (n != 0) {
+ if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
+ fail(service, u, lc, "Illegal configuration-file syntax");
+ int cp = ln.codePointAt(0);
+ if (!Character.isJavaIdentifierStart(cp))
+ fail(service, u, lc, "Illegal provider-class name: " + ln);
+ for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
+ cp = ln.codePointAt(i);
+ if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
+ fail(service, u, lc, "Illegal provider-class name: " + ln);
+ }
+ if (!providers.containsKey(ln) && !names.contains(ln))
+ names.add(ln);
+ }
+ return lc + 1;
+ }
+
+ private Iterator<String> parse(Class service, URL u) throws ServiceConfigurationError {
+ InputStream in = null;
+ BufferedReader r = null;
+ ArrayList<String> names = new ArrayList<String>();
+ try {
+ in = u.openStream();
+ r = new BufferedReader(new InputStreamReader(in, "utf-8"));
+ int lc = 1;
+ while ((lc = parseLine(service, u, r, lc, names)) >= 0) ;
+ } catch (IOException x) {
+ fail(service, "Error reading configuration file", x);
+ } finally {
+ try {
+ if (r != null) r.close();
+ if (in != null) in.close();
+ } catch (IOException y) {
+ fail(service, "Error closing configuration file", y);
+ }
+ }
+ return names.iterator();
+ }
+
+ private class LazyIterator implements Iterator<S> {
+
+ Class<S> service;
+ ClassLoader loader;
+ Enumeration<URL> configs = null;
+ Iterator<String> pending = null;
+ String nextName = null;
+
+ private LazyIterator(Class<S> service, ClassLoader loader) {
+ this.service = service;
+ this.loader = loader;
+ }
+
+ public boolean hasNext() {
+ if (nextName != null) {
+ return true;
+ }
+ if (configs == null) {
+ try {
+ String fullName = PREFIX + service.getName();
+ if (loader == null)
+ configs = ClassLoader.getSystemResources(fullName);
+ else
+ configs = loader.getResources(fullName);
+ } catch (IOException x) {
+ fail(service, "Error locating configuration files", x);
+ }
+ }
+ while ((pending == null) || !pending.hasNext()) {
+ if (!configs.hasMoreElements()) {
+ return false;
+ }
+ pending = parse(service, configs.nextElement());
+ }
+ nextName = pending.next();
+ return true;
+ }
+
+ public S next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ String cn = nextName;
+ nextName = null;
+ try {
+ S p = service.cast(Class.forName(cn, true, loader).newInstance());
+ providers.put(cn, p);
+ return p;
+ } catch (ClassNotFoundException x) {
+ fail(service, "Provider " + cn + " not found");
+ } catch (Throwable x) {
+ fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
+ }
+ throw new Error(); // This cannot happen
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ public Iterator<S> iterator() {
+ return new Iterator<S>() {
+
+ Iterator<Map.Entry<String, S>> knownProviders = providers.entrySet().iterator();
+
+ public boolean hasNext() {
+ if (knownProviders.hasNext())
+ return true;
+ return lookupIterator.hasNext();
+ }
+
+ public S next() {
+ if (knownProviders.hasNext())
+ return knownProviders.next().getValue();
+ return lookupIterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ };
+ }
+
+ /**
+ * Creates a new {@link org.apache.groovy.contracts.common.spi.Lifecycle} for the given type and class
+ * loader.
+ */
+ public static <S> LifecycleImplementationLoader<S> load(Class<S> service, ClassLoader loader) {
+ return new LifecycleImplementationLoader<S>(service, loader);
+ }
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/Validate.java b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/Validate.java
new file mode 100644
index 0000000..807d47b
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/util/Validate.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.util;
+
+/**
+ * <p>Internal utility class for in-code assertion checks.</p>
+ */
+public class Validate {
+
+ public static void notNull(Object obj) {
+ if (obj == null) throw new AssertionError("obj must not be null");
+ }
+
+ public static void isTrue(boolean expression) {
+ if (!expression) throw new AssertionError("expression must be true");
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/main/resources/META-INF/services/org.apache.groovy.contracts.common.spi.Lifecycle b/subprojects/groovy-contracts/src/main/resources/META-INF/services/org.apache.groovy.contracts.common.spi.Lifecycle
new file mode 100644
index 0000000..60a0afa
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/resources/META-INF/services/org.apache.groovy.contracts.common.spi.Lifecycle
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+org.apache.groovy.contracts.common.impl.lc.PreconditionLifecycle
+org.apache.groovy.contracts.common.impl.lc.PostconditionLifecycle
+org.apache.groovy.contracts.common.impl.lc.ClassInvariantLifecycle
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation b/subprojects/groovy-contracts/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
new file mode 100644
index 0000000..a5c12e0
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# global xforms for groovy-contracts
+org.apache.groovy.contracts.ast.GContractsASTTransformation
+org.apache.groovy.contracts.ast.ClosureExpressionEvaluationASTTransformation
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/resources/dsld/org.gcontracts.dsld b/subprojects/groovy-contracts/src/main/resources/dsld/org.gcontracts.dsld
new file mode 100644
index 0000000..97f0821
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/resources/dsld/org.gcontracts.dsld
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package dsld
+
+contribute(bind(closure: enclosingClosure()) & bind(method: enclosingMethod(annotatedBy('groovy.contracts.Ensures')))) {
+ property name : 'old', doc: 'java.util.Map of all property values before the method call.', provider: 'groovy-contracts', type : java.util.Map
+
+ if (method.isEmpty()) return
+
+ methodNode.parameters.each {
+ property name : it.name, provider: 'groovy-contracts', type : it.type
+ }
+
+ def methodNode = method.first()
+ def returnType = methodNode.returnType
+ if (returnType.name == 'void') return
+
+ property name : 'result', doc: 'The return value of this method.', provider: 'groovy-contracts', type : returnType
+}
+
+contribute(bind(closure: enclosingClosure()) & bind(method: enclosingMethod(annotatedBy('groovy.contracts.Requires')))) {
+ if (method.isEmpty()) return
+
+ def methodNode = method.first()
+ methodNode.parameters.each {
+ property name : it.name, provider: 'groovy-contracts', type : it.type
+ }
+}
+
+contribute(bind(closure: enclosingClosure()) & bind(clazz: enclosingClass(annotatedBy('groovy.contracts.Invariant')))) {
+ if (clazz.isEmpty()) return
+
+ def classNode = clazz.first()
+ classNode.properties.each {
+ property name : it.name, provider: 'groovy-contracts', type : it.type
+ }
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/main/resources/org.gcontracts.gdsl b/subprojects/groovy-contracts/src/main/resources/org.gcontracts.gdsl
new file mode 100644
index 0000000..231c089
--- /dev/null
+++ b/subprojects/groovy-contracts/src/main/resources/org.gcontracts.gdsl
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiType
+
+PsiMethod findParentMethod(PsiElement start) {
+ def psiElement = start
+ while (!(psiElement instanceof PsiMethod) && psiElement != null) psiElement = psiElement.parent
+ psiElement
+}
+
+contributor(scope: closureScope(annotationName: 'groovy.contracts.Ensures')) {
+ variable(name: 'old', type: 'Map')
+
+ PsiMethod method = findParentMethod(place)
+ if (method == null) return
+
+ // if we have a void return type then we're done
+ if (method.returnType == PsiType.VOID) return
+
+ def type = method.returnType.canonicalText
+ if (type) variable(name: 'result', type: type)
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/spec/doc/contracts-userguide.adoc b/subprojects/groovy-contracts/src/spec/doc/contracts-userguide.adoc
new file mode 100644
index 0000000..a11244d
--- /dev/null
+++ b/subprojects/groovy-contracts/src/spec/doc/contracts-userguide.adoc
@@ -0,0 +1,70 @@
+//////////////////////////////////////////
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+//////////////////////////////////////////
+
+= Groovy Contracts – design by contract support for Groovy
+
+This module provides contract annotations that support the specification of class-invariants,
+pre- and post-conditions on Groovy classes and interfaces.
+Special support is provided so that post-conditions may refer to the old value of variables
+or to the result value associated with calling a method.
+
+== Applying @Invariant, @Requires and @Ensures
+
+With GContracts in your class-path, contracts can be applied on a Groovy class or interface by using one of the assertions found in package org.gcontracts.annotations.
+
+[source,groovy]
+----
+include::{rootProjectDir}/subprojects/groovy-contracts/src/spec/test/ContractsTest.groovy[tags=basic_example,indent=0]
+----
+
+== More Features
+
+GContracts supports the following feature set:
+
+* definition of class invariants, pre- and post-conditions via @Invariant, @Requires and @Ensures
+* inheritance of class invariants, pre- and post-conditions of concrete predecessor classes
+* inheritance of class invariants, pre- and post-conditions in implemented interfaces
+* usage of old and result variable in post-condition assertions
+* assertion injection in Plain Old Groovy Objects (POGOs)
+* human-readable assertion messages, based on Groovy power asserts
+* enabling contracts at package- or class-level with @AssertionsEnabled
+* enable or disable contract checking with Java's -ea and -da VM parameters
+* annotation contracts: a way to reuse reappearing contract elements in a project domain model
+* detection of circular assertion method calls
+
+== The Stack Example
+
+Currently, Groovy contracts supports 3 annotations: @Invariant, @Requires and @Ensures – all of them work
+as annotations with closures, where closures allow you to specify arbitrary code pieces as annotation parameters:
+
+[source,groovy]
+----
+include::{rootProjectDir}/subprojects/groovy-contracts/src/spec/test/ContractsTest.groovy[tags=stack_prelim,indent=0]
+include::{rootProjectDir}/subprojects/groovy-contracts/src/spec/test/ContractsTest.groovy[tags=stack_example,indent=0]
+----
+
+The example above specifies a class-invariant and methods with pre- and post-conditions.
+Note, that preconditions may reference method arguments and post-conditions have access
+to the method’s result with the result variable and old instance variables values with old.
+
+Indeed, Groovy AST transformations change these assertion annotations into Java assertion
+statements (can be turned on and off with a JVM param) and inject them at appropriate places,
+e.g. class-invariants are used to check an object's state before and after each method call.
diff --git a/subprojects/groovy-contracts/src/spec/test/ContractsTest.groovy b/subprojects/groovy-contracts/src/spec/test/ContractsTest.groovy
new file mode 100644
index 0000000..f32c456
--- /dev/null
+++ b/subprojects/groovy-contracts/src/spec/test/ContractsTest.groovy
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import groovy.test.GroovyTestCase
+
+class ContractsTest extends GroovyTestCase {
+
+ void testBasicExample() {
+ assertScript '''
+ // tag::basic_example[]
+ package acme
+
+ import groovy.contracts.*
+
+ @Invariant({ speed() >= 0 })
+ class Rocket {
+ int speed = 0
+ boolean started = true
+
+ @Requires({ isStarted() })
+ @Ensures({ old.speed < speed })
+ def accelerate(inc) { speed += inc }
+
+ def isStarted() { started }
+
+ def speed() { speed }
+ }
+
+ def r = new Rocket()
+ r.accelerate(5)
+ // end::basic_example[]
+ '''
+ }
+
+ void testStackExample() {
+ assertScript '''
+ /*
+ // tag::stack_prelim[]
+ @Grab(group='org.apache.groovy', module='groovy-contracts', version='4.0.0')
+ // end::stack_prelim[]
+ */
+ // tag::stack_example[]
+ import groovy.contracts.*
+
+ @Invariant({ elements != null })
+ class Stack<T> {
+
+ List<T> elements
+
+ @Ensures({ is_empty() })
+ def Stack() {
+ elements = []
+ }
+
+ @Requires({ preElements?.size() > 0 })
+ @Ensures({ !is_empty() })
+ def Stack(List<T> preElements) {
+ elements = preElements
+ }
+
+ boolean is_empty() {
+ elements.isEmpty()
+ }
+
+ @Requires({ !is_empty() })
+ T last_item() {
+ elements.get(count() - 1)
+ }
+
+ def count() {
+ elements.size()
+ }
+
+ @Ensures({ result == true ? count() > 0 : count() >= 0 })
+ boolean has(T item) {
+ elements.contains(item)
+ }
+
+ @Ensures({ last_item() == item })
+ def push(T item) {
+ elements.add(item)
+ }
+
+ @Requires({ !is_empty() })
+ @Ensures({ last_item() == item })
+ def replace(T item) {
+ remove()
+ elements.add(item)
+ }
+
+ @Requires({ !is_empty() })
+ @Ensures({ result != null })
+ T remove() {
+ elements.remove(count() - 1)
+ }
+
+ String toString() { elements.toString() }
+ }
+
+ def stack = new Stack<Integer>()
+ // end::stack_example[]
+ '''
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/CompileStaticTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/CompileStaticTests.groovy
new file mode 100644
index 0000000..6535484
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/CompileStaticTests.groovy
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+import org.apache.groovy.contracts.PostconditionViolation
+import org.apache.groovy.contracts.PreconditionViolation
+
+class CompileStaticTests extends GroovyShellTestCase {
+
+ void testPrecondition() {
+ evaluate """
+ import groovy.contracts.*
+
+ @groovy.transform.CompileStatic
+ class A {
+ @Requires({ param.size() > 0 })
+ void someOperation(String param) { }
+ }
+ new A()
+ """
+ }
+
+ void testPreconditionViolation() {
+ shouldFail PreconditionViolation, {
+ evaluate """
+ import groovy.contracts.*
+
+ @groovy.transform.CompileStatic
+ class A {
+ @Requires({ param.length() > 0 })
+ void someOperation(String param) { }
+ }
+ new A().someOperation('')
+ """
+ }
+ }
+
+ void testPostcondition() {
+ evaluate """
+ import groovy.contracts.*
+
+ @groovy.transform.CompileStatic
+ class A {
+ @Ensures({ result == 3 })
+ Integer add() { return 1 + 1 }
+ }
+ new A()
+ """
+ }
+
+ void testPostconditionViolation() {
+ shouldFail PostconditionViolation, {
+ evaluate """
+ import groovy.contracts.*
+
+ @groovy.transform.CompileStatic
+ class A {
+ @Ensures({ result == 3 })
+ Integer add() { return 1 + 1 }
+ }
+ new A().add()
+ """
+ }
+ }
+
+ void testClassInvariant() {
+ evaluate """
+ import groovy.contracts.*
+
+ @groovy.transform.CompileStatic
+ @Invariant({ speed >= 0 })
+ class A {
+ Integer speed = 1
+ }
+ new A()
+ """
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/EqualsAndHashCodeTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/EqualsAndHashCodeTests.groovy
new file mode 100644
index 0000000..caea455
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/EqualsAndHashCodeTests.groovy
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+
+class EqualsAndHashCodeTests extends GroovyShellTestCase {
+
+ void testEqualsAndHashCode() {
+
+ def result = evaluate """
+ import groovy.contracts.*
+
+ @Invariant({ name && lastName })
+ @groovy.transform.EqualsAndHashCode class Person {
+ String name
+ String lastName
+
+ def Person(name, lastName) {
+ this.name = name
+ this.lastName = lastName
+ }
+ }
+ new Person('Max', 'Mustermann').equals(new Person('Max', 'Mustermann'))
+ """
+
+ assertTrue result as boolean
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/ImmutableTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/ImmutableTests.groovy
new file mode 100644
index 0000000..a7668db
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/ImmutableTests.groovy
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+
+class ImmutableTests extends GroovyShellTestCase {
+
+ void testSimpleImmutableClass() {
+
+ evaluate """
+ import groovy.contracts.*
+
+ @groovy.transform.Immutable
+ @Invariant({ name })
+ class Person {
+ String name
+ }
+
+ def p = new Person(name: 'John Doe')
+ """
+
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/LockTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/LockTests.groovy
new file mode 100644
index 0000000..bc00478
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/LockTests.groovy
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+
+class LockTests extends GroovyShellTestCase {
+
+ void test_withReadAndWriteLock() {
+
+ def result = evaluate """
+ import groovy.transform.*;
+ import groovy.contracts.*
+
+ public class ResourceProvider {
+
+ private final Map<String, String> data = new HashMap<String, String>();
+
+ @Requires({ key })
+ @WithReadLock
+ public String getResource(String key) throws Exception {
+ return data.get(key)
+ }
+
+ @WithWriteLock
+ public void refresh() throws Exception {
+ data['test'] = 'test'
+ }
+ }
+
+ def resourceProvider = new ResourceProvider()
+ resourceProvider.refresh()
+
+ resourceProvider.getResource('test')
+ """
+
+ assert result == 'test'
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/SynchronizedTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/SynchronizedTests.groovy
new file mode 100644
index 0000000..6982cef
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/SynchronizedTests.groovy
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+
+class SynchronizedTests extends GroovyShellTestCase {
+
+ void test_Synchronized_on_methods() {
+
+ def source = """
+ import groovy.contracts.*
+
+ class A {
+
+ @groovy.transform.Synchronized
+ @Requires({ a >= 0 })
+ def m(int a) { return a}
+
+ }
+
+ def a = new A()
+ a.m(12)
+ """
+
+ evaluate source
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/ToStringTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/ToStringTests.groovy
new file mode 100644
index 0000000..bd606cc
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/ToStringTests.groovy
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+
+class ToStringTests extends GroovyShellTestCase {
+
+ void testToString() {
+
+ def result = evaluate """
+ import groovy.contracts.*
+
+ @Invariant({ name })
+ @groovy.transform.ToString
+ class Person {
+ String name
+
+ def Person(String name) {
+ this.name = name
+ }
+ }
+
+ new Person('Max Mustermann').toString()
+ """
+
+ assertEquals result, 'Person(Max Mustermann)'
+
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/TupleConstructorTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/TupleConstructorTests.groovy
new file mode 100644
index 0000000..45e6cc2
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/TupleConstructorTests.groovy
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+
+class TupleConstructorTests extends GroovyShellTestCase {
+
+ void testTupleConstructor() {
+ evaluate """
+ import groovy.contracts.*
+
+ @Invariant({ firstName && lastName })
+ @groovy.transform.TupleConstructor class Person {
+ String firstName
+ String lastName
+ }
+
+ new Person('Max', 'Mustermann')
+ """
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/TypeCheckedTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/TypeCheckedTests.groovy
new file mode 100644
index 0000000..bc6d275
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/compability/TypeCheckedTests.groovy
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.compability
+
+import groovy.test.GroovyShellTestCase
+
+class TypeCheckedTests extends GroovyShellTestCase {
+
+ void testPrecondition() {
+ evaluate '''
+ import groovy.contracts.*
+
+ @groovy.transform.TypeChecked
+ class A {
+ @Requires({ some?.size() > 0 })
+ def op(String some) {
+ }
+ }
+
+ def a = new A()
+ '''
+
+ evaluate '''
+ import groovy.contracts.*
+
+ @groovy.transform.TypeChecked
+ class A {
+ @Requires({ some?.size() > 0 })
+ def op(def some) {
+ }
+ }
+
+ def a = new A()
+ '''
+ }
+
+ void testPostcondition() {
+ evaluate '''
+ import groovy.contracts.*
+
+ @groovy.transform.TypeChecked
+ class A {
+
+ @Ensures({ result.size() > 0 })
+ String op(String some) {
+ some
+ }
+ }
+
+ def a = new A()
+ '''
+
+ evaluate '''
+ import groovy.contracts.*
+
+ @groovy.transform.TypeChecked
+ class A {
+ private int i = 12
+
+ @Ensures({ old.i + 2 == 12 })
+ def op(String some) {
+ some
+ }
+ }
+
+ def a = new A()
+ '''
+ }
+
+ void testClassInvariant() {
+ evaluate '''
+ import groovy.contracts.*
+
+ @groovy.transform.TypeChecked
+ @Invariant({ i >= 0 })
+ class A {
+ private int i = 12
+ }
+
+ def a = new A()
+ '''
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/domain/ContractTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/domain/ContractTests.groovy
new file mode 100644
index 0000000..534ca2d
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/domain/ContractTests.groovy
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.domain
+
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.Parameter
+import org.codehaus.groovy.ast.builder.AstBuilder
+import org.codehaus.groovy.ast.expr.BooleanExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.syntax.Types
+import org.junit.Before
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.assertTrue
+
+class ContractTests {
+
+ ClassNode classNode
+ MethodNode methodNode
+
+ @Before
+ public void setUp() {
+ def source = '''
+ class Tester {
+
+ void some_method() {}
+ }
+'''
+
+ def astNodes = new AstBuilder().buildFromString(CompilePhase.SEMANTIC_ANALYSIS, false, source)
+ classNode = astNodes[1]
+ assertNotNull(classNode)
+
+ methodNode = classNode.getMethod("some_method", Parameter.EMPTY_ARRAY)
+ assertNotNull(methodNode)
+ }
+
+ @Test
+ void create_simple_contract() {
+ Contract contract = new Contract(classNode)
+
+ Precondition precondition = new Precondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)))
+ contract.preconditions().or(classNode.getMethod("some_method", [] as Parameter[]), precondition)
+
+ assertEquals(1, contract.preconditions().size())
+ }
+
+ @Test
+ void anding_precondition_causes_logical_or() {
+
+ Contract contract = new Contract(classNode)
+
+ Precondition precondition1 = new Precondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)))
+ Precondition precondition2 = new Precondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)))
+
+ contract.preconditions().or(methodNode, precondition1)
+ contract.preconditions().or(methodNode, precondition2)
+
+ assertEquals(1, contract.preconditions().size())
+ assertTrue(contract.preconditions().get(methodNode).booleanExpression().expression.operation.type == Types.LOGICAL_OR)
+ }
+
+ @Test
+ void anding_postcondition_causes_logical_and() {
+
+ Contract contract = new Contract(classNode)
+
+ Postcondition postcondition = new Postcondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)), false)
+ Postcondition postcondition1 = new Postcondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)), false)
+
+ contract.postconditions().and(methodNode, postcondition)
+ contract.postconditions().and(methodNode, postcondition1)
+
+ assertEquals(1, contract.postconditions().size())
+ assertTrue(contract.postconditions().get(methodNode).booleanExpression().expression.operation.type == Types.LOGICAL_AND)
+ }
+
+ @Test
+ void joining_preconditions() {
+
+ Contract contract = new Contract(classNode)
+
+ Precondition precondition1 = new Precondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)))
+ Precondition precondition2 = new Precondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)))
+
+ contract.preconditions().join(methodNode, precondition1)
+ contract.preconditions().join(methodNode, precondition2)
+
+ assertEquals(1, contract.preconditions().size())
+ assertTrue(contract.preconditions().get(methodNode).booleanExpression().expression.operation.type == Types.LOGICAL_AND)
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/generation/ContractExecutionTrackerTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/generation/ContractExecutionTrackerTests.groovy
new file mode 100644
index 0000000..e789e26
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/generation/ContractExecutionTrackerTests.groovy
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.generation
+
+import org.junit.Test
+
+class ContractExecutionTrackerTests {
+
+ @Test
+ void track_double_execution() {
+
+ ContractExecutionTracker.clear('Dummy', 'method 1', 'pre', false)
+
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', false)
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', false) == false
+
+ ContractExecutionTracker.clear('Dummy', 'method 1', 'pre', false)
+ }
+
+ @Test
+ void clear_only_for_first_stack_element() {
+
+ ContractExecutionTracker.clear('Dummy', 'method 1', 'pre', false)
+ ContractExecutionTracker.clear('Dummy', 'method 2', 'pre', false)
+
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', false)
+ assert ContractExecutionTracker.track('Dummy', 'method 2', 'pre', false)
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', false) == false
+
+ ContractExecutionTracker.clear('Dummy', 'method 2', 'pre', false)
+
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', false) == false
+ ContractExecutionTracker.clear('Dummy', 'method 1', 'pre', false)
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', false)
+ }
+
+ @Test
+ void track_static_method() {
+
+ ContractExecutionTracker.clear('Dummy', 'method 1', 'pre', false)
+
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', true)
+ assert ContractExecutionTracker.track('Dummy', 'method 1', 'pre', true) == false
+
+ ContractExecutionTracker.clear('Dummy', 'method 1', 'pre', true)
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/spock/SpockIntegrationTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/spock/SpockIntegrationTests.groovy
new file mode 100644
index 0000000..372b21e
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/spock/SpockIntegrationTests.groovy
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.spock
+
+import groovy.contracts.Requires
+import spock.lang.Specification
+import org.apache.groovy.contracts.PreconditionViolation
+
+class ContractsSpec extends Specification {
+ def "contracted method with precondition violation"(String dir, String file, String path) {
+ when:
+ contractedMethod(dir, file, path)
+ then:
+ thrown(PreconditionViolation)
+ where:
+ dir | file | path
+ 42 | '' | null
+ }
+
+ @Requires({ dir && file && path })
+ private contractedMethod(String dir, String file, String path) { }
+
+ @spock.lang.Requires({ count < max })
+ def "spock Requires annotation still works with groovy-contracts"(Integer count, Integer max) {
+ expect:
+ count < max
+ where:
+ count | max
+ 10 | 20
+ 20 | 10 // should be aborted/ignored and not throw a groovy-contracts related exception
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/basic/BaseTestClass.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/basic/BaseTestClass.groovy
new file mode 100644
index 0000000..49d3225
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/basic/BaseTestClass.groovy
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.basic
+
+import groovy.text.GStringTemplateEngine
+import groovy.text.TemplateEngine
+import org.junit.Before
+import org.codehaus.groovy.runtime.ScriptBytecodeAdapter
+
+import static org.junit.Assert.assertTrue
+import static org.junit.Assert.fail
+
+class BaseTestClass {
+
+ private static final int MAX_NESTED_EXCEPTIONS = 10;
+
+ private TemplateEngine templateEngine
+ private GroovyClassLoader loader;
+
+ @Before
+ void setUp() {
+ templateEngine = new GStringTemplateEngine()
+ loader = new GroovyClassLoader(getClass().getClassLoader())
+ }
+
+ String createSourceCodeForTemplate(final String template, final Map binding) {
+ templateEngine.createTemplate(template).make(binding).toString()
+ }
+
+ def create_instance_of(final String sourceCode) {
+ return create_instance_of(sourceCode, new Object[0])
+ }
+
+ def create_instance_of(final String sourceCode, def constructor_args) {
+
+ def clazz = add_class_to_classpath(sourceCode)
+
+ return clazz.newInstance(constructor_args as Object[])
+ }
+
+ def add_class_to_classpath(final String sourceCode) {
+ loader.parseClass(sourceCode)
+ }
+
+ /**
+ * Asserts that the given code closure fails when it is evaluated
+ *
+ * @param code
+ * @return the message of the thrown Throwable
+ */
+ protected String shouldFail(Closure code) {
+ boolean failed = false;
+ String result = null;
+ try {
+ code.call();
+ }
+ catch (GroovyRuntimeException gre) {
+ failed = true;
+ result = ScriptBytecodeAdapter.unwrap(gre).getMessage();
+ }
+ catch (Throwable e) {
+ failed = true;
+ result = e.getMessage();
+ }
+ assertTrue("Closure " + code + " should have failed", failed);
+ return result;
+ }
+
+ /**
+ * Asserts that the given code closure fails when it is evaluated
+ * and that a particular exception is thrown.
+ *
+ * @param clazz the class of the expected exception
+ * @param code the closure that should fail
+ * @return the message of the expected Throwable
+ */
+ protected String shouldFail(Class clazz, Closure code) {
+ Throwable th = null;
+ try {
+ code.call();
+ } catch (GroovyRuntimeException gre) {
+ th = ScriptBytecodeAdapter.unwrap(gre);
+ } catch (Throwable e) {
+ th = e;
+ }
+
+ if (th == null) {
+ fail("Closure " + code + " should have failed with an exception of type " + clazz.getName());
+ } else if (!clazz.isInstance(th)) {
+ fail("Closure " + code + " should have failed with an exception of type " + clazz.getName() + ", instead got Exception " + th);
+ }
+ return th.getMessage();
+ }
+
+ /**
+ * Asserts that the given code closure fails when it is evaluated
+ * and that a particular exception can be attributed to the cause.
+ * The expected exception class is compared recursively with any nested
+ * exceptions using getCause() until either a match is found or no more
+ * nested exceptions exist.
+ * <p/>
+ * If a match is found the error message associated with the matching
+ * exception is returned. If no match was found the method will fail.
+ *
+ * @param clazz the class of the expected exception
+ * @param code the closure that should fail
+ * @return the message of the expected Throwable
+ */
+ protected String shouldFailWithCause(Class clazz, Closure code) {
+ Throwable th = null;
+ Throwable orig = null;
+ int level = 0;
+ try {
+ code.call();
+ } catch (GroovyRuntimeException gre) {
+ orig = ScriptBytecodeAdapter.unwrap(gre);
+ th = orig.getCause();
+ } catch (Throwable e) {
+ orig = e;
+ th = orig.getCause();
+ }
+
+ while (th != null && !clazz.isInstance(th) && th != th.getCause() && level < MAX_NESTED_EXCEPTIONS) {
+ th = th.getCause();
+ level++;
+ }
+
+ if (orig == null) {
+ fail("Closure " + code + " should have failed with an exception caused by type " + clazz.getName());
+ } else if (th == null || !clazz.isInstance(th)) {
+ fail("Closure " + code + " should have failed with an exception caused by type " + clazz.getName() + ", instead found these Exceptions:\n" + buildExceptionList(orig));
+ }
+ return th.getMessage();
+ }
+
+ private String buildExceptionList(Throwable th) {
+ StringBuilder sb = new StringBuilder();
+ int level = 0;
+ while (th != null) {
+ if (level > 1) {
+ for (int i = 0; i < level - 1; i++) sb.append(" ");
+ }
+ if (level > 0) sb.append("-> ");
+ if (level > MAX_NESTED_EXCEPTIONS) {
+ sb.append("...");
+ break;
+ }
+ sb.append(th.getClass().getName()).append(": ").append(th.getMessage()).append("\n");
+ if (th == th.getCause()) {
+ break;
+ }
+ th = th.getCause();
+ level++;
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/DocumentationExampleTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/DocumentationExampleTests.groovy
new file mode 100644
index 0000000..2bc1109
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/DocumentationExampleTests.groovy
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.doc
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+import static org.junit.Assert.assertTrue
+
+class DocumentationExampleTests extends BaseTestClass {
+
+ def example_person = '''
+ @Contracted
+ package tests
+
+ import groovy.contracts.*
+
+ @Invariant({ firstName != null && lastName != null })
+ class Person {
+ String firstName
+ String lastName
+
+ @Requires ({ delimiter in ['.', ',', ' '] })
+ @Ensures({ result -> result == (firstName + delimiter + lastName) })
+ def String getName(String delimiter) {
+ return delimiter
+ }
+ }
+ '''
+
+ def example_eiffel_stack = '''
+ @Contracted
+ package tests
+
+ import groovy.contracts.*
+
+ @Invariant({ elements != null })
+ class EiffelStack {
+
+ private List elements
+
+ @Ensures({ is_empty() })
+ public EiffelStack() {
+ elements = []
+ }
+
+ @Requires({ preElements?.size() > 0 })
+ @Ensures({ !is_empty() })
+ public EiffelStack(List preElements) {
+ elements = preElements
+ }
+
+ def boolean is_empty() {
+ elements.isEmpty()
+ }
+
+ @Requires({ !is_empty() })
+ def last_item() {
+ elements.last()
+ }
+
+ def count() {
+ elements.size()
+ }
+
+ @Ensures({ result == true ? count() > 0 : count() >= 0 })
+ def boolean has(def item) {
+ elements.contains(item)
+ }
+
+ @Ensures({ last_item() == item })
+ def put(def item) {
+ elements.push(item)
+ }
+
+ @Requires({ !is_empty() })
+ @Ensures({ last_item() == item })
+ def replace(def item) {
+ remove()
+ elements.push(item)
+ }
+
+ @Requires({ !is_empty() })
+ @Ensures({ result != null })
+ def remove() {
+ elements.pop()
+ }
+ }
+ '''
+
+
+ @Test
+ void test_stack_creation() {
+ create_instance_of(example_eiffel_stack)
+ }
+
+ @Test
+ void test_stack_creation_with_list() {
+ create_instance_of(example_eiffel_stack, [[1, 2, 3, 4]])
+ }
+
+ @Test
+ void test_stack_put() {
+ def stack = create_instance_of(example_eiffel_stack)
+ stack.put("hello world")
+
+ assertTrue stack.last_item() == 'hello world'
+ }
+
+ @Test
+ void test_stack_replace() {
+ def stack = create_instance_of(example_eiffel_stack)
+ stack.put("hello world")
+ stack.replace("hallo welt")
+
+ assertTrue stack.last_item() == 'hallo welt'
+ assertTrue stack.count() == 1
+ }
+
+ @Test
+ void test_stack_remove() {
+ def stack = create_instance_of(example_eiffel_stack)
+ stack.put("hello world")
+ stack.remove()
+
+ assertTrue stack.count() == 0
+ }
+
+ @Test
+ void test_person_creation() {
+ shouldFail AssertionError, {
+ create_instance_of(example_person)
+ }
+ }
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/RootClassExampleTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/RootClassExampleTests.groovy
new file mode 100644
index 0000000..4ec00fd
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/RootClassExampleTests.groovy
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.doc
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+class RootClassExampleTests extends BaseTestClass {
+
+ def source = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ field1 > 0 })
+class RootClass {
+
+ // made field protected due to groovy compilation bug
+ protected Integer field1
+ private Integer field2
+ private Integer field3
+
+ private Date dateField1
+
+ Integer property1
+
+ RootClass(final Integer attribute) {
+ field1 = attribute
+ }
+
+ @Requires({ paramAttribute1 > 1 && paramAttribute2 > 1 })
+ void some_operation(final Integer paramAttribute1, final Integer paramAttribute2) {
+ this.field1 = paramAttribute1
+ this.field2 = paramAttribute2
+ }
+
+ @Ensures({ field1 == paramAttribute1 })
+ void some_operation2(final Integer paramAttribute1) {
+ field1 = paramAttribute1
+ }
+
+ @Ensures({ old -> old.field1 != paramAttribute1 })
+ void some_operation3(final Integer paramAttribute1) {
+ field1 = paramAttribute1
+ }
+
+ @Ensures({ result -> result == paramAttribute1 + paramAttribute2 })
+ def int some_operation4(final Integer paramAttribute1, final Integer paramAttribute2) {
+ return paramAttribute1 + paramAttribute2
+ }
+
+ @Ensures({ result -> result == field3 })
+ def int some_operation5(final Integer paramAttribute1, final Integer paramAttribute2) {
+ field3 = paramAttribute1 + paramAttribute2
+ return field3
+ }
+
+ @Ensures({ old, result -> old.field1 != field1 && old.field2 != field2 && field3 == result })
+ def some_operation6(def param1, def param2) {
+ field1 = param1
+ field2 = param2
+ field3 = param1 + param2
+ return field3
+ }
+
+ @Ensures({ result, old -> old.field1 != field1 && old.field2 != field2 && field3 == result })
+ def some_operation7(def param1, def param2) {
+ field1 = param1
+ field2 = param2
+ field3 = param1 + param2
+ return field3
+ }
+
+ @Ensures({ result -> result == param1 })
+ def some_operation8(def param1) {
+ param1
+ }
+
+ @Ensures({ result -> result == param1 + param2})
+ def some_operation9(def param1, def param2) {
+ param1 + param2
+ }
+
+ @Ensures({ old -> old.dateField1 != param1 && dateField1 == param1 })
+ void some_operation10(def param1) {
+ dateField1 = param1
+ }
+
+ @Requires({ param1 > 10 })
+ void some_operation11(def param1) {
+ field1 = param1
+ }
+
+ @Ensures({ old -> old.dateField1 != param1 && dateField1 == param1 })
+ void some_operation12(def param1) {
+ dateField1 = param1
+ }
+}
+'''
+
+ @Test
+ void test_class_invariant() {
+ create_instance_of(source, [1])
+ }
+
+ @Test
+ void test_class_invariant_fail() {
+
+ shouldFail AssertionError, {
+ create_instance_of(source, [0])
+ }
+ }
+
+ @Test
+ void test_class_invariant_with_default_constructor() {
+ shouldFail AssertionError, { create_instance_of(source) }
+ }
+
+ @Test
+ void test_precondition_with_multiple_arguments() {
+
+ def root = create_instance_of(source, [1])
+
+ root.some_operation 2, 2
+ }
+
+ @Test
+ void test_precond_with_first_argument_fail() {
+
+ def root = create_instance_of(source, [1])
+
+ shouldFail AssertionError, {
+ root.some_operation(1, 2)
+ }
+ }
+
+ @Test
+ void test_precond_with_second_argument_fail() {
+
+ def root = create_instance_of(source, [1])
+
+ shouldFail AssertionError, {
+ root.some_operation 2, 1
+ }
+ }
+
+ @Test
+ void test_postcond_with_single_argument() {
+ def root = create_instance_of(source, [1])
+
+ root.some_operation2 2
+ }
+
+ @Test
+ void test_postcond_with_single_argument_and_old_var() {
+ def root = create_instance_of(source, [1])
+
+ root.some_operation3 2
+ }
+
+ @Test
+ void test_postcond_with_single_argument_and_old_var_fail() {
+ def root = create_instance_of(source, [1])
+
+ shouldFail AssertionError, {
+ root.some_operation3 1
+ }
+ }
+
+ @Test
+ void test_postcond_with_result_variable() {
+ def root = create_instance_of(source, [1])
+
+ def result = root.some_operation4(1, 1)
+
+ assertEquals 2, result
+ }
+
+ @Test
+ void test_postcond_with_result_variable_and_field() {
+ def root = create_instance_of(source, [1])
+
+ def result = root.some_operation5(1, 1)
+
+ assertEquals 2, result
+ }
+
+ @Test
+ void test_postcond_with_result_and_old_variables() {
+ def root = create_instance_of(source, [1])
+
+ def result = root.some_operation6(2, 2)
+
+ assertEquals 4, result
+ }
+
+ void test_postcond_with_result_and_old_variables_switched() {
+ def root = create_instance_of(source, [1])
+
+ def result = root.some_operation7(2, 2)
+
+ assertEquals 4, result
+ }
+
+ @Test
+ void test_postcond_with_implicit_return_statement() {
+ def root = create_instance_of(source, [1])
+
+ def result = root.some_operation8(2)
+
+ assertEquals 2, result
+ }
+
+ @Test
+ void test_postcond_with_complex_return_statement() {
+ def root = create_instance_of(source, [1])
+
+ def result = root.some_operation9(2, 2)
+
+ assertEquals 4, result
+ }
+
+
+ @Test
+ void test_multiple_preconditions() {
+ def root = create_instance_of(source, [1])
+
+ root.some_operation11 12
+ }
+
+ @Test
+ void test_multiple_precondition_fail() {
+ def root = create_instance_of(source, [1])
+
+ shouldFail AssertionError, { root.some_operation11 10 }
+ }
+
+ @Test
+ void test_multiple_postconditions() {
+ def root = create_instance_of(source, [1])
+
+ root.some_operation10(new Date())
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/StackExampleTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/StackExampleTests.groovy
new file mode 100644
index 0000000..339754d
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/doc/StackExampleTests.groovy
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.doc
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class StackExampleTests extends BaseTestClass {
+
+ def source_stack = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ list != null && anotherName != null })
+class Stack {
+
+ protected def list
+ def anotherName = ""
+ def protected name = ""
+
+ public Stack() {
+ this.list = []
+ }
+
+ public Stack(def list) {
+ this.list = list
+ }
+
+ @Requires({ item != null })
+ @Ensures({ list[-1] == item })
+ void push(def item) {
+ list.add item
+ }
+
+ @Requires({ item1 != null && item2 != null })
+ void multi_push(def item1, def item2) {
+ push item1
+ push item2
+ }
+
+// @Requires({ list.size() > 0 })
+// @Ensures({ result != null })
+// def Object pop() {
+// list[-1]
+// }
+
+ @Ensures({ result -> result == list.size() })
+ def int size() {
+ return list.size()
+ }
+
+ @Ensures({ result -> comp1 != null && comp2 != null && result > 0 })
+ def int size(def comp1, comp2) {
+ return comp1 + comp2
+ }
+
+ @Ensures({ result -> result == 'tostring'})
+ @Override
+ def String toString() {
+ return 'tostring'
+ }
+
+ void modifyClassInvariant() {
+ anotherName = null
+ }
+}
+'''
+
+ def source_stack_descendant = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ count >= 0 })
+class StackDescendant extends Stack implements Serializable {
+
+ def private int count = 0
+
+ def StackDescendant() {
+ super()
+ }
+
+ def StackDescendant(list) {
+ super(list)
+ }
+
+ @Override
+ void push(def item) {
+ count++
+ super.push item
+ }
+
+ void push_fail(def item) {
+ count++
+ list = null
+ }
+
+ @Ensures({ old -> old.count < count })
+ void test_count() {
+ count++
+ }
+
+ @Ensures({ result, old -> true })
+ def int test_count_with_result_variable() {
+ count++
+ return count
+ }
+}
+'''
+
+ @Test
+ void creation() {
+ create_instance_of(source_stack)
+ create_instance_of(source_stack_descendant)
+ }
+
+ @Test
+ void inherited_invariant() {
+ create_instance_of(source_stack)
+ def stack = create_instance_of(source_stack_descendant)
+
+ stack.push 'item 1'
+ }
+
+
+ @Test
+ void inherited_invariant_failure() {
+ create_instance_of(source_stack)
+
+ shouldFail AssertionError, {
+ create_instance_of(source_stack_descendant, [null])
+ }
+ }
+
+ @Test
+ void inherited_invariant_fail_on_method_call() {
+ create_instance_of(source_stack)
+ def stack = create_instance_of(source_stack_descendant)
+
+ shouldFail AssertionError, {
+ stack.push_fail 'item 1'
+ }
+ }
+
+ @Test
+ void old_variable() {
+ create_instance_of(source_stack)
+ def stack = create_instance_of(source_stack_descendant)
+
+ stack.test_count()
+ }
+
+
+ @Test
+ void old_and_result_variable() {
+ create_instance_of(source_stack)
+ def stack = create_instance_of(source_stack_descendant)
+
+ stack.test_count_with_result_variable()
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/AbstractClassInheritanceTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/AbstractClassInheritanceTests.groovy
new file mode 100644
index 0000000..80962d0
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/AbstractClassInheritanceTests.groovy
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.interfaces
+
+import org.apache.groovy.contracts.PreconditionViolation
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class AbstractClassInheritanceTests extends BaseTestClass {
+
+ def source_stackable = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+abstract class Stackable {
+
+ @Requires({ item != null })
+ abstract void push(def item)
+
+ @Requires({ item1 != null && item2 != null })
+ abstract void multi_push(def item1, def item2)
+}
+'''
+
+ def source_stack = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ list != null && anotherName != null })
+class Stack extends Stackable {
+
+ protected def list
+ def anotherName = ""
+ def protected name = ""
+
+ public Stack() {
+ this.list = []
+ }
+
+ public Stack(def list) {
+ this.list = list
+ }
+
+ @Ensures({ list[-1] == item })
+ void push(def item) {
+ list.add item
+ }
+
+ void multi_push(def item1, def item2) {
+ push item1
+ push item2
+ }
+
+// @Requires({ list.size() > 0 })
+// @Ensures({ result != null })
+// def Object pop() {
+// list[-1]
+// }
+
+ @Ensures({ result -> result == list.size() })
+ def int size() {
+ return list.size()
+ }
+
+ @Ensures({ result -> comp1 != null && comp2 != null && result > 0 })
+ def int size(def comp1, comp2) {
+ return comp1 + comp2
+ }
+
+ @Ensures({ result -> result == 'tostring'})
+ @Override
+ def String toString() {
+ return 'tostring'
+ }
+
+ void modifyClassInvariant() {
+ anotherName = null
+ }
+}
+'''
+
+ @Test
+ void creation() {
+ add_class_to_classpath(source_stackable)
+ create_instance_of(source_stack)
+ }
+
+ @Test
+ void push_precondition() {
+ add_class_to_classpath(source_stackable)
+
+ def stack = create_instance_of(source_stack)
+
+ shouldFail PreconditionViolation.class, {
+ stack.push null
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/AbstractClassTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/AbstractClassTests.groovy
new file mode 100644
index 0000000..1756151
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/AbstractClassTests.groovy
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.interfaces
+
+import org.apache.groovy.contracts.PreconditionViolation
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class AbstractClassTests extends BaseTestClass {
+
+ def source_stackable = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+abstract class Stackable {
+
+ @Requires({ item != null })
+ abstract void push(def item)
+
+ @Requires({ item1 != null && item2 != null })
+ abstract void multi_push(def item1, def item2)
+}
+'''
+
+ def source_stack = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ list != null && anotherName != null })
+class Stack extends Stackable {
+
+ protected def list
+ def anotherName = ""
+ def protected name = ""
+
+ public Stack() {
+ this.list = []
+ }
+
+ public Stack(def list) {
+ this.list = list
+ }
+
+ @Ensures({ list[-1] == item })
+ void push(def item) {
+ list.add item
+ }
+
+ void multi_push(def item1, def item2) {
+ push item1
+ push item2
+ }
+
+// @Requires({ list.size() > 0 })
+// @Ensures({ result != null })
+// def Object pop() {
+// list[-1]
+// }
+
+ @Ensures({ result -> result == list.size() })
+ def int size() {
+ return list.size()
+ }
+
+ @Ensures({ result -> comp1 != null && comp2 != null && result > 0 })
+ def int size(def comp1, comp2) {
+ return comp1 + comp2
+ }
+
+ @Ensures({ result -> result == 'tostring'})
+ @Override
+ def String toString() {
+ return 'tostring'
+ }
+
+ void modifyClassInvariant() {
+ anotherName = null
+ }
+}
+'''
+
+ @Test
+ void creation() {
+ add_class_to_classpath(source_stackable)
+ create_instance_of(source_stack)
+ }
+
+ @Test
+ void push_precondition() {
+ add_class_to_classpath(source_stackable)
+ def stack = create_instance_of(source_stack)
+
+ shouldFail PreconditionViolation.class, {
+ stack.push null
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/InterfaceAbstractClassMixturesTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/InterfaceAbstractClassMixturesTests.groovy
new file mode 100644
index 0000000..b1f81f8
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/InterfaceAbstractClassMixturesTests.groovy
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.interfaces
+
+import org.apache.groovy.contracts.PostconditionViolation
+import org.apache.groovy.contracts.PreconditionViolation
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class InterfaceAbstractClassMixturesTests extends BaseTestClass {
+
+ @Test
+ void class_with_abstract_class_and_interface() {
+
+ def s1 = '''
+ @Contracted
+ package tests
+
+ import groovy.contracts.*
+
+ interface Stackable {
+ @Ensures({ result != null })
+ def pop()
+ }
+
+ abstract class StackableAbstract implements Stackable {
+ abstract def pop()
+ }
+ '''
+
+ def s2 = '''
+ @Contracted
+ package tests
+
+ import groovy.contracts.*
+
+ class Stack extends StackableAbstract {
+ def pop() { return null }
+ }
+ '''
+
+ add_class_to_classpath(s1)
+ def stack = create_instance_of(s2)
+
+ shouldFail PostconditionViolation, {
+ stack.pop()
+ }
+ }
+
+ @Test
+ void interface_and_abstract_class_both_contain_abstract_methods() {
+
+ def s1 = '''
+ @Contracted
+ package tests
+
+ import groovy.contracts.*
+
+ interface Stackable {
+ @Ensures({ result != null })
+ def pop()
+ }
+
+ abstract class StackableAbstract implements Stackable {
+ @Requires({ item != null })
+ abstract def push(def item)
+ }
+ '''
+
+ def s2 = '''
+ @Contracted
+ package tests
+
+ import groovy.contracts.*
+
+ class Stack extends StackableAbstract {
+ def pop() { return null }
+ def push(def item) {}
+ }
+ '''
+
+ add_class_to_classpath(s1)
+ def stack = create_instance_of(s2)
+
+ shouldFail PostconditionViolation.class, {
+ stack.pop()
+ }
+
+ shouldFail PreconditionViolation.class, {
+ stack.push(null)
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/SimpleInterfaceInheritanceTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/SimpleInterfaceInheritanceTests.groovy
new file mode 100644
index 0000000..92966ba
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/SimpleInterfaceInheritanceTests.groovy
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.interfaces
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class SimpleInterfaceInheritanceTests extends BaseTestClass {
+
+ def source_stackable = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+abstract class Stackable {
+
+ @Requires({ item != null })
+ abstract void push(def item)
+}
+
+'''
+
+ def source_stack = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ list != null && anotherName != null })
+class Stack extends Stackable {
+
+ protected def list
+ def anotherName = ""
+ def protected name = ""
+
+ public Stack() {
+ this.list = []
+ }
+
+ public Stack(def list) {
+ this.list = list
+ }
+
+ @Requires({ item > 2 })
+ @Ensures({ list[-1] == item })
+ void push(def item) {
+ list.add item
+ }
+}
+'''
+
+ def source_implicit_interface = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+interface A {
+ @Ensures({ old != null && result != null })
+ def some_method()
+}
+
+class B implements A {
+
+ def some_method() { return null }
+
+}
+
+class C extends B {
+ def some_method() { return null }
+}
+'''
+
+ def source_implicit_interface2 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+class C extends B {
+ def some_method() { return null }
+}
+'''
+
+ @Test
+ void creation() {
+ add_class_to_classpath(source_stackable)
+ create_instance_of(source_stack)
+ }
+
+ @Test
+ void push_precondition() {
+ add_class_to_classpath(source_stackable)
+
+ def stack = create_instance_of(source_stack)
+
+ shouldFail AssertionError, {
+ stack.push null
+ }
+
+ stack.push 1
+ stack.push 2
+ }
+
+ @Test
+ void postcondition_in_indirect_parent_interface() {
+ add_class_to_classpath(source_implicit_interface)
+ def c = create_instance_of(source_implicit_interface2)
+
+ shouldFail AssertionError, {
+ c.some_method()
+ }
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/StackExampleTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/StackExampleTests.groovy
new file mode 100644
index 0000000..7a16d21
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/interfaces/StackExampleTests.groovy
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.interfaces
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+import static org.junit.Assert.assertTrue
+
+class StackExampleTests extends BaseTestClass {
+
+ def source_stackable = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+interface Stackable {
+
+ @Requires({ item != null })
+ void push(def item)
+
+ @Ensures({ result != null && old != null })
+ def isEmpty()
+}
+'''
+
+ def source_stack = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+class Stack implements Stackable {
+
+ protected def list
+
+ public Stack() {
+ this.list = []
+ }
+
+ public Stack(def list) {
+ this.list = list
+ }
+
+ @Ensures({ list.last() == item })
+ void push(def item) {
+ list.add item
+ }
+
+ def isEmpty() {
+ return list.size() == 0
+ }
+}
+'''
+
+ @Test
+ void creation() {
+ add_class_to_classpath(source_stackable)
+ create_instance_of(source_stack)
+ }
+
+ @Test
+ void push_precondition() {
+ add_class_to_classpath(source_stackable)
+ def stack = create_instance_of(source_stack)
+
+ stack.push 1
+ shouldFail AssertionError, {
+ stack.push null
+ }
+ }
+
+ @Test
+ void old_variable_in_postcondition() {
+ add_class_to_classpath(source_stackable)
+ def stack = create_instance_of(source_stack)
+
+ assertTrue(stack.isEmpty())
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/InheritanceTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/InheritanceTests.groovy
new file mode 100644
index 0000000..d142d74
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/InheritanceTests.groovy
@@ -0,0 +1,416 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.inv
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+import org.apache.groovy.contracts.ClassInvariantViolation
+
+class InheritanceTests extends BaseTestClass {
+
+ def source1 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property != null })
+class A {
+ def property
+
+ def A(def value) { property = value }
+}
+'''
+
+ def source2 = '''
+@Contracted
+package tests
+
+
+import groovy.contracts.*
+
+@Invariant({ property2 != null })
+class B extends A {
+ def property2
+
+ def B(def value, def value2) { super(value); property2 = value2 }
+
+ def set_values(def value, def value2) {
+ property = value
+ property2 = value2
+ }
+}
+
+'''
+
+ def source3 = '''
+@Contracted
+package tests
+
+
+import groovy.contracts.*
+
+@Invariant({ property2 != null })
+class C extends B {
+ def property3
+
+ def C(def value, def value2, def value3) { super(value, value2); property3 = value3 }
+}
+
+'''
+
+ def source11 = '''
+@Contracted
+package tests
+
+
+import groovy.contracts.*
+
+@Invariant({ property?.size() > 0 })
+class A {
+ private String property
+
+ def A(String value) { property = value }
+}
+
+'''
+
+ def source12 = '''
+@Contracted
+package tests
+
+
+import groovy.contracts.*
+
+class B extends A {
+ def B(String value) { super(value) }
+}
+
+'''
+
+ def source21 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ prop1 != null && prop2 != null })
+class PrivateConstructor {
+
+ def prop1
+ def prop2
+
+ def PrivateConstructor(def arg1, def arg2) {
+ prop1 = arg1
+ prop2 = arg2
+ }
+
+ private PrivateConstructor() {}
+}
+'''
+
+ def source31 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ isAvailable() == true })
+abstract class A {
+
+ abstract boolean isAvailable()
+}
+
+'''
+
+ def source32 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+class B extends A {
+ boolean isAvailable() { return true }
+}
+
+'''
+
+ def source41 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ isAvailable() == true })
+class A {
+ def boolean isAvailable() { return true }
+}
+'''
+
+ def source51 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property1 != null })
+class A {
+
+ def property1 = "test"
+
+ def A() {}
+
+ def set_values(def prop) { property1 = prop }
+}
+'''
+
+ def source52 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property2 != null })
+class B extends A {
+
+ def property2 = "test"
+
+ def B() {}
+
+ def set_values(def prop) { property2 = prop }
+}
+'''
+
+ def source61 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property1 != null })
+class A {
+
+ private def property1 = "test"
+
+ def A() {}
+
+ def set_values(def prop) { property1 = prop }
+}
+'''
+
+ def source62 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property2 != null })
+class B extends A {
+
+ def property2 = "test"
+
+ def B() {}
+
+ def set_values(def prop) { property2 = prop }
+}
+'''
+
+ def source71 = '''
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ getBalance() >= 0.0 })
+class Account {
+
+ protected BigDecimal balance = 0.0
+
+ def Account( def amount = 0.0 )
+ {
+ balance = amount
+ }
+
+ @Requires({ amount >= 0.0 })
+ @Ensures({ balance == old.balance + amount })
+ void deposit( def amount )
+ {
+ balance += amount
+ }
+
+ @Requires({ amount >= 0.0 && getBalance() >= amount })
+ @Ensures({ balance == old.balance - amount })
+ def withdraw( def amount )
+ {
+ balance -= amount
+ return amount
+ }
+
+ def getBalance()
+ {
+ balance
+ }
+}
+'''
+
+
+ @Test
+ void two_way_inheritance_path() {
+ create_instance_of(source1, ['test'])
+ create_instance_of(source2, ['test', 'test2'])
+
+ shouldFail AssertionError, {
+ create_instance_of(source2, [null, 'test2'])
+ }
+ }
+
+ @Test
+ void three_way_inheritance_path() {
+ create_instance_of(source1, ['test'])
+ create_instance_of(source2, ['test', 'test2'])
+ create_instance_of(source3, ['test', 'test2', 'test3'])
+
+ shouldFail AssertionError, {
+ create_instance_of(source3, [null, 'test2', 'test3'])
+ }
+
+ shouldFail AssertionError, {
+ create_instance_of(source3, [null, null, 'test3'])
+ }
+
+ shouldFail AssertionError, {
+ create_instance_of(source3, ['test', null, 'test3'])
+ }
+ }
+
+ /*
+ see: http://gcontracts.lighthouseapp.com/projects/71511/tickets/3-accessing-private-variables-from-invariant
+ @Test void with_private_instance_variable_in_super_class() {
+ create_instance_of(source11, ['test'])
+ create_instance_of(source12, ['test'])
+
+ shouldFail AssertionError, {
+ create_instance_of(source12, [''])
+ }
+ }*/
+
+ @Test
+ void invariant_check_on_method_call() {
+ create_instance_of(source1, ['test'])
+ def b = create_instance_of(source2, ['test', 'test2'])
+
+ shouldFail AssertionError, {
+ b.set_values(null, null)
+ }
+
+ shouldFail AssertionError, {
+ b.set_values(null, '')
+ }
+
+ shouldFail AssertionError, {
+ b.set_values('', null)
+ }
+ }
+
+ @Test
+ void private_constructor_creation() {
+ create_instance_of(source21)
+ }
+
+ @Test
+ void public_constructor_creation() {
+ shouldFail AssertionError, {
+ create_instance_of(source21, ['test1', null])
+ }
+ }
+
+ @Test
+ void inherited_class_invariant() {
+ add_class_to_classpath(source51)
+ def b = create_instance_of(source52, [])
+
+ shouldFail AssertionError, {
+ b.set_values(null)
+ }
+ }
+
+ @Test
+ void inherited_class_invariant_with_private_instance_variable() {
+ add_class_to_classpath(source61)
+ def b = create_instance_of(source62, [])
+
+ shouldFail AssertionError, {
+ b.set_values(null)
+ }
+
+ }
+
+ @Test
+ void recursive_class_invariant() {
+
+ def b = create_instance_of(source71)
+ assert b != null
+ }
+
+ @Test
+ void abstract_method_with_postcondition() {
+
+ add_class_to_classpath """
+ package tests
+
+ import groovy.contracts.*
+
+ abstract class Base {
+ @Ensures({ result })
+ abstract List<String> sources()
+ }
+ """
+
+ def c = add_class_to_classpath """
+ package tests
+
+ class DirectImpl extends Base {
+
+ List<String> sources() { ['a','b','c'] }
+
+ }
+ """
+
+ c.newInstance().sources()
+ }
+
+ @Test(expected = ClassInvariantViolation)
+ void separate_class_invariant() {
+ def c = add_class_to_classpath """
+ package tests
+
+ import groovy.contracts.*
+
+ @Invariant({
+ i_never_null: i != null
+ j_never_null: j != null
+ })
+ class Test {
+ private def i
+ private def j
+ }
+ """
+
+ c.newInstance()
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/POGOClassInvariantTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/POGOClassInvariantTests.groovy
new file mode 100644
index 0000000..e1950b8
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/POGOClassInvariantTests.groovy
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.inv
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+/**
+ * POGO class invariant tests.
+ */
+class POGOClassInvariantTests extends BaseTestClass {
+
+ def dynamic_constructor_class_code = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property != null })
+class DynamicConstructor {
+
+ def property
+}
+'''
+
+ def dynamic_setter_class_code = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ string1 != null && string2 != null && string3 != null })
+class DynamicSetter {
+
+ String string1 = ''
+ def String string2 = ''
+ final String string3 = ''
+}
+
+'''
+
+ @Test
+ void dynamic_constructor_class_invariant() {
+ shouldFail AssertionError, {
+ create_instance_of dynamic_constructor_class_code;
+ }
+ }
+
+ @Test
+ void dynamic_setter_methods() {
+ def instance = create_instance_of(dynamic_setter_class_code)
+
+ shouldFail AssertionError, {
+ instance.string1 = null
+ }
+
+ shouldFail AssertionError, {
+ instance.string2 = null
+ }
+
+ shouldFail AssertionError, {
+ instance.string3 = null
+ }
+ }
+}
\ No newline at end of file
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/SimpleClassInvariantTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/SimpleClassInvariantTests.groovy
new file mode 100644
index 0000000..3f319e5
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/inv/SimpleClassInvariantTests.groovy
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.inv
+
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+import org.apache.groovy.contracts.ClassInvariantViolation
+
+class SimpleClassInvariantTests extends BaseTestClass {
+
+ def source1 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property != null })
+class A {
+
+ def property
+
+ def A(def someValue) {
+ property = someValue
+ }
+}
+'''
+
+ def source2 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property != null })
+class A {
+
+ private property
+
+ def A(def someValue) {
+ property = someValue
+ }
+}
+'''
+
+ def source3 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property != null })
+class A {
+
+ private property
+
+ def A(def someValue) {
+ property = someValue
+ }
+
+ static me = "me"
+}
+'''
+
+ @Test
+ void class_invariant() {
+ create_instance_of(source1, ['test'])
+
+ shouldFail AssertionError, {
+ create_instance_of(source1, [null])
+ }
+ }
+
+ @Test
+ void class_invariant_with_private_instance_variable() {
+ create_instance_of(source2, ['test'])
+
+ shouldFail AssertionError, {
+ create_instance_of(source2, [null])
+ }
+ }
+
+ @Test
+ void class_with_constant() {
+ create_instance_of(source3, ['test'])
+ }
+
+
+ @Test
+ void multiple_return_statements() {
+
+ def source = """
+ import groovy.contracts.*
+
+@Invariant({ property != 0 })
+class Account {
+
+ def property = 1
+
+ def some_method() {
+ if (true) {
+ property = 0
+ return;
+ }
+
+ return;
+ }
+}
+ """
+
+ def source2 = """
+ import groovy.contracts.*
+
+@Invariant({ property != 0 })
+class Account {
+
+ def property = 1
+
+ def some_method() {
+ if (false) {
+ property = 1
+ return;
+ }
+
+ property = 0
+ return;
+ }
+}
+ """
+
+ def a = create_instance_of(source2)
+ shouldFail ClassInvariantViolation, {
+ a.some_method()
+ }
+ }
+
+ @Test
+ void duplicate_return_statements() {
+
+ def source = '''
+ import groovy.contracts.*
+
+ @Invariant({ elements != null })
+ class Stack {
+ def elements = []
+
+ def push(def item) {
+ elements.push(item)
+ }
+
+ def pop() {
+ elements.pop()
+ }
+ }
+ '''
+
+ def stack = create_instance_of(source)
+
+ stack.push(1)
+ stack.push(2)
+
+ assert stack.pop() == 2
+ assert stack.pop() == 1
+ }
+
+ @Test
+ void avoid_invariant_on_read_only_methods() {
+
+ def source = """
+import groovy.contracts.*
+
+@Invariant({ speed() >= 0.0 })
+class Rocket {
+
+ def speed() { 1.0 }
+}
+
+ """
+
+ create_instance_of(source)
+ }
+
+
+ @Test
+ void recursive_invariant_with_getter_method() {
+
+ def source = """
+ import groovy.contracts.*
+
+ @Invariant({ speed >= 0.0 })
+ class Rocket {
+
+ @Requires({ true })
+ def getSpeed() { 1.0 }
+ }
+
+ """
+
+ create_instance_of(source)
+ }
+
+ @Test
+ void direct_field_access() {
+
+ def source = """
+ import groovy.contracts.*
+
+ @Invariant({ speed >= 0.0 })
+ class Rocket {
+ def speed = 0.0
+
+ def increase() {
+ this.speed -= 1
+ }
+ }
+
+ """
+
+ def rocket = create_instance_of(source)
+
+ shouldFail(ClassInvariantViolation) {
+ rocket.increase()
+ }
+ }
+
+ @Test
+ void direct_field_access_in_class_invariant() {
+
+ add_class_to_classpath """
+ import groovy.contracts.*
+
+ @Invariant({ this.speed >= 0.0 })
+ class Rocket {
+ def speed = 0.0
+
+ def increase() {
+ this.speed -= 1
+ }
+ }"""
+ }
+
+ @Test
+ void private_field_access_in_direct_class() {
+
+ def c = add_class_to_classpath """
+ import groovy.contracts.*
+
+ @Invariant({ speed >= 0.0 })
+ class Rocket {
+ private double speed = 0.0
+
+ def increase() {
+ this.speed -= 1
+ }
+ }"""
+
+ def rocket = c.newInstance()
+
+ shouldFail ClassInvariantViolation, {
+ rocket.increase()
+ }
+ }
+
+ @Test
+ void private_field_access_in_descendant_class() {
+
+ def c = add_class_to_classpath """
+ import groovy.contracts.*
+
+ @Invariant({ speed >= 0.0 })
+ class Rocket {
+ private double speed = 0.0
+
+ def increase() {
+ this.speed -= 1
+ }
+ }
+ """
+
+ def c2 = add_class_to_classpath """
+ class BetterRocket extends Rocket {}
+ """
+
+ def betterRocket = c2.newInstance()
+
+ shouldFail ClassInvariantViolation, {
+ betterRocket.increase()
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/AbstractClassTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/AbstractClassTests.groovy
new file mode 100644
index 0000000..935f2f7
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/AbstractClassTests.groovy
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.other
+
+import org.apache.groovy.contracts.PreconditionViolation
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class AbstractClassTests extends BaseTestClass {
+
+ def source1 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+@Invariant({ property != null })
+abstract class A {
+
+ def property
+
+ def A(def someValue) {
+ property = someValue
+ }
+
+ @Requires({ param != null })
+ def some_operation(def param) {
+ // noop
+ }
+}
+'''
+
+ def source2 = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+class B extends A {
+
+ def B(def someValue) {
+ super(someValue)
+ }
+
+ def some_operation(def param) {
+ // noop
+ }
+}
+'''
+
+ @Test
+ void inherited_class_invariant() {
+ add_class_to_classpath source1
+
+ shouldFail AssertionError, {
+ create_instance_of(source2, [null])
+ }
+ }
+
+ @Test
+ void inherited_precondition() {
+ add_class_to_classpath source1
+
+ def bInstance = create_instance_of(source2, ["test"])
+
+ shouldFail PreconditionViolation, {
+ bInstance.some_operation null
+ }
+ }
+
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/CandidateChecksTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/CandidateChecksTests.groovy
new file mode 100644
index 0000000..24cdab7
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/CandidateChecksTests.groovy
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.other
+
+import org.codehaus.groovy.ast.ClassHelper
+import org.apache.groovy.contracts.generation.CandidateChecks
+import org.junit.Test
+import org.codehaus.groovy.ast.Parameter
+
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertTrue
+
+class A {
+
+}
+
+interface B {
+
+}
+
+enum C {
+
+}
+
+class D {
+ private D() {}
+
+ private def method() {}
+}
+
+/**
+ * all test cases for {@link CandidateChecks}.
+ *
+ * @see CandidateChecks
+ */
+class CandidateChecksTests {
+
+ @Test
+ void testContractsCandidateChecks() {
+ assert !CandidateChecks.isContractsCandidate(ClassHelper.make(B.class))
+ assert !CandidateChecks.isContractsCandidate(ClassHelper.make(C.class))
+ assert CandidateChecks.isContractsCandidate(ClassHelper.make(A.class))
+ }
+
+ // refs #22
+ @Test
+ void testPrivateConstructors() {
+ def classNode = ClassHelper.make(D.class)
+ assertTrue "private constructors should support preconditions",
+ CandidateChecks.isPreconditionCandidate(classNode, classNode.getDeclaredConstructors().first())
+ assertTrue "private methods should support preconditions",
+ CandidateChecks.isPreconditionCandidate(classNode, classNode.getMethod("method", [] as Parameter[]))
+
+ assertFalse "private constructors should by now NOT support class invariants",
+ CandidateChecks.isClassInvariantCandidate(classNode, classNode.getDeclaredConstructors().first())
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/CircularAssertionCallTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/CircularAssertionCallTests.groovy
new file mode 100644
index 0000000..3c069e1
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/CircularAssertionCallTests.groovy
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.other
+
+import org.apache.groovy.contracts.PreconditionViolation
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class CircularAssertionCallTests extends BaseTestClass {
+
+ @Test
+ void detectCircularAssertionCalls() {
+
+ def source = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+class A {
+
+ @Requires({ isConditionB() })
+ def isConditionA() { return false }
+
+ @Requires({ isConditionA() })
+ def isConditionB() { return true }
+}
+'''
+
+ def a = create_instance_of(source)
+
+ shouldFail PreconditionViolation, {
+ a.isConditionB()
+ }
+ }
+
+ @Test
+ void detect_diamon_assertion_calls() {
+
+ def source = '''
+@Contracted
+package tests
+
+import groovy.contracts.*
+
+class A {
+
+ @Requires({ isConditionC() })
+ def the_method_to_call() {}
+
+ @Requires({ isConditionA() && isConditionB() })
+ def isConditionC() {}
+
+ @Requires({ isConditionC() })
+ def isConditionA() {}
+
+ @Requires({ isConditionC() })
+ def isConditionB() {}
+}
+'''
+
+ def a = create_instance_of(source)
+ shouldFail PreconditionViolation, {
+ a.isConditionB()
+ }
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ClosureExpressionValidationTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ClosureExpressionValidationTests.groovy
new file mode 100644
index 0000000..5a42686
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ClosureExpressionValidationTests.groovy
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.other
+
+import groovy.test.GroovyShellTestCase
+import org.codehaus.groovy.control.CompilationFailedException
+
+class ClosureExpressionValidationTests extends GroovyShellTestCase {
+
+ void testCheckMissingExpressionsClassInvariant() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ @Invariant({})
+ class A {}
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("Annotation does not contain any expressions")
+ }
+
+ void testCheckMissingExpressionsPrecondition() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ class A {
+ @Requires({})
+ def op() {}
+ }
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("Annotation does not contain any expressions")
+ }
+
+ void testCheckMissingExpressionsPostcondition() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ class A {
+ @Ensures({})
+ def op() {}
+ }
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("Annotation does not contain any expressions")
+ }
+
+ void testParameterSpecifiedClassInvariant() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ @Invariant({ some -> 1 == 1 })
+ class A {}
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("Annotation does not support parameters")
+ }
+
+ void testParameterSpecifiedPostcondition() {
+
+ evaluate """
+ import groovy.contracts.*
+
+ class A {
+
+ @Ensures({ result })
+ def op() {}
+ }
+
+ def a = new A()
+ """
+ }
+
+ void testParameterNamesPostcondition() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ class A {
+
+ @Ensures({ test -> 1 == 1 })
+ def op() {}
+ }
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("Postconditions only allow 'old' and 'result' closure parameters")
+ }
+
+ void testParameterWithExplicitTypePostcondition() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ class A {
+
+ @Ensures({ java.util.Map<String, Object> result -> 1 == 1 })
+ def op() {}
+ }
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("Postconditions do not support explicit types")
+ }
+
+ void testClosureItAccess() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ @Invariant({ it })
+ class A {
+
+ }
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("Access to 'it' is not supported.")
+ }
+
+ void testPrefixOperatorUsage() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ class A {
+
+ @Requires({ ++arg })
+ def op(def arg) {}
+ }
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("State changing postfix & prefix operators are not supported.")
+ }
+
+ void testPostfixOperatorUsage() {
+
+ def msg = shouldFail CompilationFailedException, {
+ evaluate """
+ import groovy.contracts.*
+
+ class A {
+
+ @Requires({ arg++ })
+ def op(def arg) {}
+ }
+
+ def a = new A()
+ """
+ }
+
+ assertTrue msg.contains("State changing postfix & prefix operators are not supported.")
+ }
+}
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ContractLabelTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ContractLabelTests.groovy
new file mode 100644
index 0000000..40e858d
--- /dev/null
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ContractLabelTests.groovy
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.contracts.tests.other
+
+import org.apache.groovy.contracts.ClassInvariantViolation
+import org.apache.groovy.contracts.PostconditionViolation
+import org.apache.groovy.contracts.PreconditionViolation
+import org.apache.groovy.contracts.tests.basic.BaseTestClass
+import org.junit.Test
+
+class ContractLabelTests extends BaseTestClass {
+
+ @Test
+ void class_invariant() {
+ def source1 = '''
+package tests
+
+import groovy.contracts.*
+
+@Invariant({
+ not_null_property: property != null
+})
+class A {
+
+ def property
+}
+'''
+
+ shouldFail ClassInvariantViolation, {
... 2217 lines suppressed ...