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 2024/07/19 23:13:00 UTC

(groovy) branch master updated: GROOVY-11443: Support multiple Requires/Ensures/Invariant annotations in groovy-contracts

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 f8e2f9eb5d GROOVY-11443: Support multiple Requires/Ensures/Invariant annotations in groovy-contracts
f8e2f9eb5d is described below

commit f8e2f9eb5d6278c8a02d43d289dabe244d33f913
Author: Paul King <pa...@asert.com.au>
AuthorDate: Wed Jul 17 20:01:03 2024 +1000

    GROOVY-11443: Support multiple Requires/Ensures/Invariant annotations in groovy-contracts
---
 .../codehaus/groovy/ast/tools/GeneralUtils.java    |  4 ++
 .../src/main/java/groovy/contracts/Ensures.java    |  4 +-
 .../contracts/EnsuresConditions.java}              | 28 ++++----
 .../src/main/java/groovy/contracts/Invariant.java  |  4 +-
 .../contracts/Invariants.java}                     | 28 ++++----
 .../src/main/java/groovy/contracts/Requires.java   |  4 +-
 .../contracts/RequiresConditions.java}             | 28 ++++----
 ...osureExpressionEvaluationASTTransformation.java | 33 +++++-----
 .../ast/visitor/AnnotationClosureVisitor.java      |  4 +-
 .../ast/visitor/AnnotationProcessorVisitor.java    | 74 ++++++++++++++++------
 .../groovy/contracts/ast/visitor/BaseVisitor.java  |  2 +-
 .../classgen/asm/ContractClosureWriter.java        |  3 +-
 .../impl/ClassInvariantAnnotationProcessor.java    | 10 +++
 .../common/impl/EnsuresAnnotationProcessor.java    |  8 +--
 .../common/impl/lc/PostconditionLifecycle.java     | 12 +---
 .../common/spi/ProcessingContextInformation.java   |  8 +--
 .../apache/groovy/contracts/domain/Assertion.java  | 31 ++-------
 .../groovy/contracts/domain/AssertionMap.java      |  2 +-
 .../groovy/contracts/domain/ClassInvariant.java    |  7 +-
 .../generation/AssertStatementCreationUtility.java |  6 +-
 .../groovy/contracts/generation/BaseGenerator.java | 24 ++++---
 .../generation/ClassInvariantGenerator.java        | 16 ++---
 .../groovy/contracts/generation/Configurator.java  |  4 +-
 .../generation/ContractExecutionTracker.java       | 13 ++--
 .../generation/OldVariableGenerationUtility.java   | 12 ++--
 .../generation/PostconditionGenerator.java         | 17 ++---
 .../generation/PreconditionGenerator.java          | 13 ++--
 .../generation/TryCatchBlockGenerator.java         |  3 +-
 .../groovy/contracts/util/AnnotationUtils.java     | 37 ++++++-----
 .../groovy/contracts/util/ExpressionUtils.java     |  7 +-
 .../util/LifecycleImplementationLoader.java        |  2 +-
 .../groovy/contracts/domain/ContractTests.groovy   | 21 +++---
 .../tests/pre/SimplePreconditionTests.groovy       | 49 +++++++++++++-
 33 files changed, 304 insertions(+), 214 deletions(-)

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 bcf2d10e06..f94be91dc7 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
@@ -770,6 +770,10 @@ public class GeneralUtils {
         return new MapEntryExpression(constX(key), valueExpr);
     }
 
+    public static MapExpression mapX() {
+        return new MapExpression();
+    }
+
     public static MapExpression mapX(final List<MapEntryExpression> expressions) {
         return new MapExpression(expressions);
     }
diff --git a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Ensures.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Ensures.java
index 7b905e54d5..930a403097 100644
--- a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Ensures.java
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Ensures.java
@@ -24,6 +24,7 @@ import org.apache.groovy.contracts.common.impl.EnsuresAnnotationProcessor;
 import org.apache.groovy.lang.annotation.Incubating;
 
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -80,7 +81,8 @@ import java.lang.annotation.Target;
 @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
 @Incubating
 @Postcondition
+@Repeatable(EnsuresConditions.class)
 @AnnotationProcessorImplementation(EnsuresAnnotationProcessor.class)
 public @interface Ensures {
     Class value();
-}
\ No newline at end of file
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/EnsuresConditions.java
similarity index 56%
copy from subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
copy to subprojects/groovy-contracts/src/main/java/groovy/contracts/EnsuresConditions.java
index 538ef4b00b..8e5a21210d 100644
--- a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/EnsuresConditions.java
@@ -16,23 +16,21 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.groovy.contracts.domain;
+package groovy.contracts;
 
-import org.codehaus.groovy.ast.expr.BooleanExpression;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
+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>A class-invariant assertion.</p>
+ * Represents multiple postconditions.
  */
-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);
-    }
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
+@Incubating
+public @interface EnsuresConditions {
+    Ensures[] value();
 }
diff --git a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariant.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariant.java
index ca9edf384b..8512cd49e2 100644
--- a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariant.java
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariant.java
@@ -24,6 +24,7 @@ import org.apache.groovy.contracts.common.impl.ClassInvariantAnnotationProcessor
 import org.apache.groovy.lang.annotation.Incubating;
 
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -53,7 +54,8 @@ import java.lang.annotation.Target;
 @Target(ElementType.TYPE)
 @Incubating
 @ClassInvariant
+@Repeatable(Invariants.class)
 @AnnotationProcessorImplementation(ClassInvariantAnnotationProcessor.class)
 public @interface Invariant {
     Class value();
-}
\ No newline at end of file
+}
diff --git a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariants.java
similarity index 56%
copy from subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
copy to subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariants.java
index 538ef4b00b..ac37f83b60 100644
--- a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariants.java
@@ -16,23 +16,21 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.groovy.contracts.domain;
+package groovy.contracts;
 
-import org.codehaus.groovy.ast.expr.BooleanExpression;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
+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>A class-invariant assertion.</p>
+ * Represents multiple invariants
  */
-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);
-    }
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Incubating
+public @interface Invariants {
+    Invariant[] value();
 }
diff --git a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Requires.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Requires.java
index b9635afeca..5e4a276943 100644
--- a/subprojects/groovy-contracts/src/main/java/groovy/contracts/Requires.java
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Requires.java
@@ -24,6 +24,7 @@ import org.apache.groovy.contracts.common.impl.RequiresAnnotationProcessor;
 import org.apache.groovy.lang.annotation.Incubating;
 
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -58,6 +59,7 @@ import java.lang.annotation.Target;
 @Incubating
 @Precondition
 @AnnotationProcessorImplementation(RequiresAnnotationProcessor.class)
+@Repeatable(RequiresConditions.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/domain/ClassInvariant.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/RequiresConditions.java
similarity index 56%
copy from subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
copy to subprojects/groovy-contracts/src/main/java/groovy/contracts/RequiresConditions.java
index 538ef4b00b..0ff81b3332 100644
--- a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/domain/ClassInvariant.java
+++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/RequiresConditions.java
@@ -16,23 +16,21 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.groovy.contracts.domain;
+package groovy.contracts;
 
-import org.codehaus.groovy.ast.expr.BooleanExpression;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
+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>A class-invariant assertion.</p>
+ * Represents multiple preconditions.
  */
-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);
-    }
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
+@Incubating
+public @interface RequiresConditions {
+    Requires[] value();
 }
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
index afbe578338..f63b679df1 100644
--- 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
@@ -37,45 +37,46 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * Evaluates {@link org.codehaus.groovy.ast.expr.ClosureExpression} instances in as actual annotation parameters and
+ * Evaluates {@link org.codehaus.groovy.ast.expr.ClosureExpression} instances in annotation parameters and
  * generates special contract closure classes from them.
  */
 @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
 public class ClosureExpressionEvaluationASTTransformation extends BaseASTTransformation {
 
+    /**
+     * {@link org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[], org.codehaus.groovy.control.SourceUnit)}
+     */
+    @Override
+    public void visit(ASTNode[] nodes, SourceUnit unit) {
+        final ModuleNode moduleNode = unit.getAST();
+
+        ReaderSource source = getReaderSource(unit);
+        final List<ClassNode> classNodes = new ArrayList<>(moduleNode.getClasses());
+
+        generateAnnotationClosureClasses(unit, source, classNodes);
+    }
+
     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;
+            if (!CandidateChecks.isContractsCandidate(classNode) && !CandidateChecks.isInterfaceContractsCandidate(classNode))
+                continue;
 
             final ContractElementVisitor contractElementVisitor = new ContractElementVisitor(unit, source);
             contractElementVisitor.visitClass(classNode);
-
             if (!contractElementVisitor.isFoundContractElement()) continue;
 
             annotationClosureVisitor.visitClass(classNode);
             markClassNodeAsContracted(classNode);
+            if (classNode.isInterface()) continue;
 
             new ConfigurationSetup().init(classNode);
         }
     }
 
-    /**
-     * {@link org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[], org.codehaus.groovy.control.SourceUnit)}
-     */
-    @Override
-    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);
 
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
index f3d2e284c9..a8d1770bcb 100644
--- 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
@@ -107,11 +107,11 @@ public class AnnotationClosureVisitor extends BaseVisitor implements ASTNodeMeta
 
     @Override
     public void visitClass(ClassNode classNode) {
-        if (classNode == null || !(CandidateChecks.isInterfaceContractsCandidate(classNode) || CandidateChecks.isContractsCandidate(classNode))) return;
+        if (classNode == null || !(CandidateChecks.isContractsCandidate(classNode) || CandidateChecks.isInterfaceContractsCandidate(classNode))) return;
 
         this.classNode = classNode;
 
-        if (classNode.getNodeMetaData(PROCESSED) == null && CandidateChecks.isContractsCandidate(classNode)) {
+        if (classNode.getNodeMetaData(PROCESSED) == null) {
             List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(classNode, ContractElement.class.getName());
             for (AnnotationNode annotationNode : annotationNodes) {
                 ClosureExpression closureExpression = getOriginalCondition(annotationNode);
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
index b23e38ab96..f87235ccd0 100644
--- 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
@@ -41,9 +41,13 @@ import org.codehaus.groovy.control.io.ReaderSource;
 
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import static org.apache.groovy.contracts.ast.visitor.AnnotationClosureVisitor.META_DATA_ORIGINAL_TRY_CATCH_BLOCK;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 
@@ -55,7 +59,8 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
  */
 public class AnnotationProcessorVisitor extends BaseVisitor {
 
-    private ProcessingContextInformation pci;
+    private static final String CONTRACT_ELEMENT_CLASSNAME = ContractElement.class.getName();
+    private final ProcessingContextInformation pci;
 
     public AnnotationProcessorVisitor(final SourceUnit sourceUnit, final ReaderSource source, final ProcessingContextInformation pci) {
         super(sourceUnit, source);
@@ -73,11 +78,10 @@ public class AnnotationProcessorVisitor extends BaseVisitor {
 
         for (MethodNode methodNode : methodNodes) {
             if (CandidateChecks.isClassInvariantCandidate(type, methodNode) || CandidateChecks.isPreOrPostconditionCandidate(type, methodNode)) {
-                handleMethodAnnotations(methodNode, AnnotationUtils.hasMetaAnnotations(methodNode, ContractElement.class.getName()));
+                handleMethodAnnotations(methodNode, AnnotationUtils.hasMetaAnnotations(methodNode, CONTRACT_ELEMENT_CLASSNAME));
             }
         }
 
-        // visit all interfaces of this class
         visitInterfaces(type, type.getInterfaces());
         visitAbstractBaseClassesForInterfaceMethodNodes(type, type.getSuperClass());
     }
@@ -87,7 +91,7 @@ public class AnnotationProcessorVisitor extends BaseVisitor {
         for (ClassNode interfaceNode : superClass.getInterfaces()) {
             List<MethodNode> interfaceMethods = new ArrayList<>(interfaceNode.getMethods());
             for (MethodNode interfaceMethod : interfaceMethods) {
-                List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethod, ContractElement.class.getName());
+                List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethod, CONTRACT_ELEMENT_CLASSNAME);
                 if (annotationNodes == null || annotationNodes.isEmpty()) continue;
 
                 MethodNode implementingMethod = superClass.getMethod(interfaceMethod.getName(), interfaceMethod.getParameters());
@@ -104,13 +108,15 @@ public class AnnotationProcessorVisitor extends BaseVisitor {
 
     private void visitInterfaces(final ClassNode classNode, final ClassNode[] interfaces) {
         for (ClassNode interfaceNode : interfaces) {
+            List<AnnotationNode> interfaceNodes = AnnotationUtils.hasMetaAnnotations(interfaceNode, CONTRACT_ELEMENT_CLASSNAME);
+            processAnnotationNodes(classNode, interfaceNodes);
             List<MethodNode> interfaceMethods = new ArrayList<>(interfaceNode.getMethods());
             // @ContractElement annotations are by now only supported on method interfaces
             for (MethodNode interfaceMethod : interfaceMethods) {
                 MethodNode implementingMethod = classNode.getMethod(interfaceMethod.getName(), interfaceMethod.getParameters());
                 if (implementingMethod == null) continue;
 
-                List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethod, ContractElement.class.getName());
+                List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethod, CONTRACT_ELEMENT_CLASSNAME);
                 handleInterfaceMethodNode(classNode, implementingMethod, annotationNodes);
             }
 
@@ -119,13 +125,17 @@ public class AnnotationProcessorVisitor extends BaseVisitor {
     }
 
     private void handleClassNode(final ClassNode classNode) {
-        List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(classNode, ContractElement.class.getName());
+        List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(classNode, CONTRACT_ELEMENT_CLASSNAME);
+        processAnnotationNodes(classNode, annotationNodes);
+    }
+
+    private void processAnnotationNodes(ClassNode classNode, List<AnnotationNode> annotationNodes) {
         for (AnnotationNode annotationNode : annotationNodes) {
-            AnnotationProcessor annotationProcessor = createAnnotationProcessor(annotationNode);
-            if (annotationProcessor != null) {
+            AnnotationProcessor processor = createAnnotationProcessor(annotationNode);
+            if (processor != null) {
                 Expression valueExpression = getReplacedCondition(annotationNode);
                 BlockStatement blockStatement = valueExpression.getNodeMetaData(META_DATA_ORIGINAL_TRY_CATCH_BLOCK);
-                annotationProcessor.process(pci, pci.contract(), classNode, blockStatement, asConditionExecution(annotationNode));
+                processor.process(pci, pci.contract(), classNode, blockStatement, asConditionExecution(annotationNode));
             }
         }
     }
@@ -136,15 +146,30 @@ public class AnnotationProcessorVisitor extends BaseVisitor {
 
     private void handleMethodAnnotations(MethodNode methodNode, List<AnnotationNode> annotationNodes) {
         if (methodNode == null) return;
+        Map<String, List<BooleanExpression>> collectedPreconditions = new HashMap<>();
+        AnnotationProcessor annotationProcessor;
         for (AnnotationNode annotationNode : annotationNodes) {
-            AnnotationProcessor annotationProcessor = createAnnotationProcessor(annotationNode);
+            annotationProcessor = createAnnotationProcessor(annotationNode);
             if (annotationProcessor != null && getReplacedCondition(annotationNode) != null) {
-                handleMethodAnnotation(methodNode, annotationNode, annotationProcessor);
+                handleMethodAnnotation(methodNode, annotationNode, annotationProcessor, collectedPreconditions);
             }
         }
+        // Manually and the expressions if multiple annotations are used
+        for (String processorName : collectedPreconditions.keySet()) {
+            AnnotationProcessor processor = getAnnotationProcessor(processorName);
+            BooleanExpression collectedExpression = null;
+            for (BooleanExpression boolExp : collectedPreconditions.get(processorName)) {
+                if (collectedExpression == null) {
+                    collectedExpression = boolExp;
+                } else {
+                    collectedExpression = boolX(andX(collectedExpression, boolExp));
+                }
+            }
+            processor.process(pci, pci.contract(), methodNode.getDeclaringClass(), methodNode, null, collectedExpression);
+        }
     }
 
-    private void handleMethodAnnotation(MethodNode methodNode, AnnotationNode annotationNode, AnnotationProcessor annotationProcessor) {
+    private void handleMethodAnnotation(MethodNode methodNode, AnnotationNode annotationNode, AnnotationProcessor annotationProcessor, Map<String, List<BooleanExpression>> collected) {
         boolean isPostcondition = AnnotationUtils.hasAnnotationOfType(annotationNode.getClassNode(), Postcondition.class.getName());
 
         ArgumentListExpression argumentList = new ArgumentListExpression();
@@ -162,7 +187,13 @@ public class AnnotationProcessorVisitor extends BaseVisitor {
         BooleanExpression booleanExpression = asConditionExecution(annotationNode);
         ((MethodCallExpression) booleanExpression.getExpression()).setArguments(argumentList);
         BlockStatement blockStatement = valueExpression.getNodeMetaData(META_DATA_ORIGINAL_TRY_CATCH_BLOCK);
-        annotationProcessor.process(pci, pci.contract(), methodNode.getDeclaringClass(), methodNode, blockStatement, booleanExpression);
+        if (isPostcondition) {
+            annotationProcessor.process(pci, pci.contract(), methodNode.getDeclaringClass(), methodNode, blockStatement, booleanExpression);
+        } else {
+            String name = annotationProcessor.getClass().getName();
+            collected.putIfAbsent(name, new ArrayList<>());
+            collected.get(name).add(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())) {
@@ -186,13 +217,20 @@ public class AnnotationProcessorVisitor extends BaseVisitor {
         }
 
         if (annotationProcessor != null) {
-            try {
-                var apt = Class.forName(annotationProcessor.getType().getName());
-                return (AnnotationProcessor) apt.getDeclaredConstructor().newInstance();
-            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException ignore) {
-            }
+            String name = annotationProcessor.getType().getName();
+            AnnotationProcessor apt = getAnnotationProcessor(name);
+            if (apt != null) return apt;
         }
 
         throw new GroovyBugError("Annotation processing class could not be instantiated! This indicates a bug in groovy-contracts, please file an issue!");
     }
+
+    public static AnnotationProcessor getAnnotationProcessor(String name) {
+        try {
+            var apt = Class.forName(name);
+            return (AnnotationProcessor) apt.getDeclaredConstructor().newInstance();
+        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException ignore) {
+        }
+        return null;
+    }
 }
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
index 2337cdd1ef..520698bf0d 100644
--- 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
@@ -58,7 +58,7 @@ public abstract class BaseVisitor extends ClassCodeVisitorSupport {
         this.sourceUnit = sourceUnit;
     }
 
-    public    static BooleanExpression asConditionExecution(final AnnotationNode annotation) {
+    public static BooleanExpression asConditionExecution(final AnnotationNode annotation) {
         var conditionClass = annotation.getMember("value").getType();
         var createInstance = ctorX(conditionClass, args(VariableExpression.THIS_EXPRESSION, VariableExpression.THIS_EXPRESSION));
         final MethodCallExpression doCall = callX(createInstance, "doCall");
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
index c2a02247b4..2a8d3220ba 100644
--- 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
@@ -39,6 +39,7 @@ import java.util.Map;
 
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+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.ctorSuperX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
@@ -112,7 +113,7 @@ public class ContractClosureWriter {
         call.setSynthetic(true);
 
         // let's make the constructor
-        BlockStatement block = new BlockStatement();
+        BlockStatement block = block();
         // 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");
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
index 976a47af91..95a1573ccf 100644
--- 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
@@ -23,9 +23,15 @@ 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.BinaryExpression;
 import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 
+import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+
 /**
  * Internal {@link AnnotationProcessor} implementation for class-invariants.
  */
@@ -36,6 +42,10 @@ public class ClassInvariantAnnotationProcessor extends AnnotationProcessor {
         if (!processingContextInformation.isClassInvariantsEnabled()) return;
         if (booleanExpression == null) return;
 
+        Expression currentInvariant = contract.classInvariant().booleanExpression().getExpression();
+        if (currentInvariant instanceof MethodCallExpression || currentInvariant instanceof BinaryExpression) {
+            booleanExpression = boolX(andX(booleanExpression, currentInvariant));
+        }
         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
index 5a3740cdac..3b1806220a 100644
--- 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
@@ -23,13 +23,10 @@ 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.
  */
@@ -40,8 +37,7 @@ public class EnsuresAnnotationProcessor extends AnnotationProcessor {
         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)));
+        boolean isConstructor = classNode.getDeclaredConstructors().contains(methodNode);
+        contract.postconditions().and(methodNode, new Postcondition(blockStatement, booleanExpression, isConstructor));
     }
 }
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
index f373b2ba8c..a2b08af775 100644
--- 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
@@ -18,15 +18,11 @@
  */
 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;
 
 /**
@@ -36,6 +32,7 @@ public class PostconditionLifecycle extends BaseLifecycle {
 
     @Override
     public void beforeProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) {
+        if (classNode.isInterface()) return;
         final PostconditionGenerator postconditionGenerator = new PostconditionGenerator(processingContextInformation.readerSource());
         postconditionGenerator.addOldVariablesMethod(classNode);
     }
@@ -55,11 +52,6 @@ public class PostconditionLifecycle extends BaseLifecycle {
         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);
-        }
+        postconditionGenerator.generateDefaultPostconditionStatement(classNode, methodNode);
     }
 }
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
index b6d519656e..9b6e632b5a 100644
--- 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
@@ -36,16 +36,16 @@ import java.util.Map;
  */
 public class ProcessingContextInformation {
 
-    private Contract contract;
-    private SourceUnit sourceUnit;
-    private ReaderSource source;
+    private final Contract contract;
+    private final SourceUnit sourceUnit;
+    private final 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>();
+    private final Map<String, Object> extra = new HashMap<>();
 
     public ProcessingContextInformation(ClassNode classNode, SourceUnit sourceUnit, ReaderSource source) {
         Validate.notNull(classNode);
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
index 7751f2960d..f3dc798225 100644
--- 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
@@ -19,12 +19,13 @@
 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;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.orX;
 
 /**
  * <p>Base class for all assertion types.</p>
@@ -37,7 +38,7 @@ public abstract class Assertion<T extends Assertion> {
     private BooleanExpression booleanExpression;
 
     public Assertion() {
-        this.booleanExpression = new BooleanExpression(ConstantExpression.TRUE);
+        this.booleanExpression = boolX(ConstantExpression.TRUE);
     }
 
     public Assertion(final BlockStatement blockStatement, final BooleanExpression booleanExpression) {
@@ -66,33 +67,15 @@ public abstract class Assertion<T extends Assertion> {
 
     public void and(T other) {
         Validate.notNull(other);
-
-        BooleanExpression newBooleanExpression =
-                new BooleanExpression(
-                        new BinaryExpression(
-                                booleanExpression(),
-                                Token.newSymbol(Types.LOGICAL_AND, -1, -1),
-                                other.booleanExpression()
-                        )
-                );
+        BooleanExpression newBooleanExpression = boolX(andX(booleanExpression(), 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()
-                        )
-                );
+        BooleanExpression newBooleanExpression = boolX(orX(booleanExpression(), 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
index aebc273939..0ecfef6cb0 100644
--- 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
@@ -30,7 +30,7 @@ public class AssertionMap<T extends Assertion<T>> implements Iterable<Map.Entry<
     private final Map<MethodNode, T> internalMap;
 
     public AssertionMap() {
-        this.internalMap = new HashMap<MethodNode, T>();
+        this.internalMap = new HashMap<>();
     }
 
     public void and(final MethodNode methodNode, final T assertion) {
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
index 538ef4b00b..474b827f63 100644
--- 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
@@ -19,15 +19,18 @@
 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;
 
+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.constX;
+
 /**
  * <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 static final ClassInvariant DEFAULT = new ClassInvariant(block(), boolX(constX(true)));
 
     public ClassInvariant() {
     }
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
index b2701f9ce9..68069f46e8 100644
--- 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
@@ -218,11 +218,11 @@ public final class AssertStatementCreationUtility {
                 if (statement == returnStatement) {
                     block.getStatements().remove(statement);
 
-                    final VariableExpression $_gc_result = localVarX("$_gc_result", ClassHelper.OBJECT_TYPE);
-                    block.addStatement(declS($_gc_result, returnStatement.getExpression()));
+                    final VariableExpression gcResult = localVarX("$_gc_result", ClassHelper.OBJECT_TYPE);
+                    block.addStatement(declS(gcResult, returnStatement.getExpression()));
                     block.addStatement(assertionCallStatement);
 
-                    final Statement gcResultReturn = returnS($_gc_result);
+                    final Statement gcResultReturn = returnS(gcResult);
                     gcResultReturn.setSourcePosition(returnStatement);
                     block.addStatement(gcResultReturn);
                     return; // we found the return statement under target, let's cancel tree traversal
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
index 718d8d5271..0eb3b3eb80 100644
--- 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
@@ -96,15 +96,15 @@ public abstract class BaseGenerator {
 
     protected BlockStatement wrapAssertionBooleanExpression(ClassNode type, MethodNode methodNode, BooleanExpression classInvariantExpression, String assertionType) {
         ClassNode violationTrackerClassNode = ClassHelper.makeWithoutCaching(ViolationTracker.class);
-        VariableExpression $_gc_result = varX("$_gc_result", ClassHelper.boolean_TYPE);
-        $_gc_result.setAccessedVariable($_gc_result);
+        VariableExpression gcResult = varX("$_gc_result", ClassHelper.boolean_TYPE);
+        gcResult.setAccessedVariable(gcResult);
 
         BlockStatement ifBlockStatement = block(
-                declS($_gc_result, ConstantExpression.FALSE),
+                declS(gcResult, ConstantExpression.FALSE),
                 stmt(callX(classX(violationTrackerClassNode), "init")),
-                assignS($_gc_result, classInvariantExpression),
+                assignS(gcResult, classInvariantExpression),
                 ifS(
-                        boolX(notX(callX($_gc_result, "booleanValue"))),
+                        boolX(notX(callX(gcResult, "booleanValue"))),
                         ifS(
                                 boolX(callX(classX(violationTrackerClassNode), "violationsOccurred")),
                                 tryCatchS(
@@ -115,9 +115,9 @@ public abstract class BaseGenerator {
         );
 
         TryCatchStatement lockTryCatchStatement = tryCatchS(
-                block(ifS(
+                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)),
+                        ifBlockStatement),
                 block(new VariableScope(), stmt(callX(
                         classX(ClassHelper.make(ContractExecutionTracker.class)),
                         "clear",
@@ -133,6 +133,7 @@ public abstract class BaseGenerator {
         if (contractElementAnnotations.isEmpty()) {
             methodNode.putNodeMetaData(META_DATA_USE_INLINE_MODE, Boolean.TRUE);
         } else {
+            BooleanExpression collectedPre = null;
             for (AnnotationNode contractElementAnnotation : contractElementAnnotations) {
                 ArgumentListExpression argumentList = new ArgumentListExpression();
                 for (Parameter parameter : methodNode.getParameters()) {
@@ -151,9 +152,16 @@ public abstract class BaseGenerator {
                 if (isPostcondition) {
                     booleanExpression = boolX(andX(booleanExpression, predicate));
                 } else {
-                    booleanExpression = boolX(orX(booleanExpression, predicate));
+                    if (collectedPre == null) {
+                        collectedPre = predicate;
+                    } else {
+                        collectedPre = boolX(andX(collectedPre, predicate));
+                    }
                 }
             }
+            if (collectedPre != null) {
+                booleanExpression = boolX(orX(booleanExpression, collectedPre));
+            }
         }
         return booleanExpression;
     }
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
index 3bb7ebabb7..57919865fd 100644
--- 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
@@ -35,11 +35,11 @@ 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.ClassHelper.isPrimitiveVoid;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
+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.stmt;
@@ -51,6 +51,8 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
  */
 public class ClassInvariantGenerator extends BaseGenerator {
 
+    private static final ClassNode CLASS_INVARIANT_TYPE = ClassHelper.makeWithoutCaching(ClassInvariant.class);
+
     public ClassInvariantGenerator(final ReaderSource source) {
         super(source);
     }
@@ -65,9 +67,9 @@ public class ClassInvariantGenerator extends BaseGenerator {
      */
     public void generateInvariantAssertionStatement(final ClassNode type, final org.apache.groovy.contracts.domain.ClassInvariant classInvariant) {
 
-        BooleanExpression classInvariantExpression = addCallsToSuperAnnotationClosure(type, ClassInvariant.class, classInvariant.booleanExpression());
+        BooleanExpression classInvariantExpression = addCallsToSuperAnnotationClosure(type, classInvariant.booleanExpression());
 
-        final BlockStatement blockStatement = new BlockStatement();
+        final BlockStatement blockStatement = block();
 
         // 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);
@@ -76,8 +78,8 @@ public class ClassInvariantGenerator extends BaseGenerator {
         blockStatement.addStatements(wrapAssertionBooleanExpression(type, methodNode, classInvariantExpression, "invariant").getStatements());
     }
 
-    private BooleanExpression addCallsToSuperAnnotationClosure(final ClassNode type, final Class<? extends Annotation> annotationType, BooleanExpression booleanExpression) {
-        List<AnnotationNode> contractElementAnnotations = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), ClassHelper.makeWithoutCaching(annotationType));
+    private BooleanExpression addCallsToSuperAnnotationClosure(final ClassNode type, BooleanExpression booleanExpression) {
+        List<AnnotationNode> contractElementAnnotations = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), CLASS_INVARIANT_TYPE);
         for (AnnotationNode contractElementAnnotation : contractElementAnnotations) {
             booleanExpression = boolX(andX(booleanExpression, BaseVisitor.asConditionExecution(contractElementAnnotation)));
         }
@@ -113,9 +115,7 @@ public class ClassInvariantGenerator extends BaseGenerator {
             final BlockStatement blockStatement = (BlockStatement) statement;
             blockStatement.addStatement(invariantMethodCall);
         } else {
-            final BlockStatement assertionBlock = new BlockStatement();
-            assertionBlock.addStatement(statement);
-            assertionBlock.addStatement(invariantMethodCall);
+            final BlockStatement assertionBlock = block(statement, 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
index a3fdabf3d9..f2af5f4328 100644
--- 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
@@ -45,7 +45,7 @@ public final class Configurator {
 
     private static void initAssertionConfiguration() {
 
-        assertionConfiguration = new HashMap<String, Boolean>();
+        assertionConfiguration = new HashMap<>();
         // per default assertion are enabled (Groovy like)
         assertionConfiguration.put(null, Boolean.TRUE);
 
@@ -85,7 +85,7 @@ public final class Configurator {
     }
 
     private static boolean internalMethod(String className) {
-        if (className == null || className.length() == 0) return false;
+        if (className == null || className.isEmpty()) return false;
 
         if (assertionConfiguration.containsKey(className)) return assertionConfiguration.get(className);
         if (className.lastIndexOf('.') < 0) 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
index 9549c14f35..6b20a262e1 100644
--- 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
@@ -20,6 +20,7 @@ package org.apache.groovy.contracts.generation;
 
 import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Keeps track of contract executions to avoid cyclic contract checks.
@@ -71,19 +72,19 @@ public class ContractExecutionTracker {
     }
 
 
-    static class ContractExecutionThreadLocal extends ThreadLocal<HashSet<ContractExecution>> {
+    static class ContractExecutionThreadLocal extends ThreadLocal<Set<ContractExecution>> {
 
         @Override
-        protected HashSet<ContractExecution> initialValue() {
-            return new HashSet<ContractExecution>();
+        protected Set<ContractExecution> initialValue() {
+            return new HashSet<>();
         }
     }
 
-    private static ThreadLocal<HashSet<ContractExecution>> executions = new ContractExecutionThreadLocal();
+    private static final ThreadLocal<Set<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();
+        final Set<ContractExecution> contractExecutions = executions.get();
 
         if (!contractExecutions.contains(ce)) {
             contractExecutions.add(ce);
@@ -94,7 +95,7 @@ public class ContractExecutionTracker {
     }
 
     public static void clear(String className, String methodIdentifier, String assertionType, boolean isStatic) {
-        final HashSet<ContractExecution> contractExecutions = executions.get();
+        final Set<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
index 03b4943944..3a9917fc2e 100644
--- 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
@@ -23,7 +23,6 @@ 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;
@@ -34,12 +33,15 @@ 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.block;
 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.mapEntryX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.mapX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 
 /**
@@ -59,9 +61,9 @@ public class OldVariableGenerationUtility {
     public static void addOldVariableMethodNode(final ClassNode classNode) {
         if (classNode.getDeclaredMethod(OLD_VARIABLES_METHOD, Parameter.EMPTY_ARRAY) != null) return;
 
-        final BlockStatement methodBlockStatement = new BlockStatement();
+        final BlockStatement methodBlockStatement = block();
 
-        final MapExpression oldVariablesMap = new MapExpression();
+        final MapExpression oldVariablesMap = mapX();
 
         // create variable assignments for old variables
         for (final FieldNode fieldNode : classNode.getFields()) {
@@ -87,7 +89,7 @@ public class OldVariableGenerationUtility {
                     Statement oldVariableAssignment = declS(oldVariable, cloneField);
 
                     methodBlockStatement.addStatement(oldVariableAssignment);
-                    oldVariablesMap.addMapEntryExpression(new MapEntryExpression(constX(oldVariable.getName().substring("$old$".length())), oldVariable));
+                    oldVariablesMap.addMapEntryExpression(mapEntryX(constX(oldVariable.getName().substring("$old$".length())), oldVariable));
 
                 } else if (ClassHelper.isPrimitiveType(fieldType)
                         || ClassHelper.isNumberType(fieldType)
@@ -99,7 +101,7 @@ public class OldVariableGenerationUtility {
                     Statement oldVariableAssignment = declS(oldVariable, fieldX(fieldNode));
 
                     methodBlockStatement.addStatement(oldVariableAssignment);
-                    oldVariablesMap.addMapEntryExpression(new MapEntryExpression(constX(oldVariable.getName().substring("$old$".length())), oldVariable));
+                    oldVariablesMap.addMapEntryExpression(mapEntryX(constX(oldVariable.getName().substring("$old$".length())), oldVariable));
                 }
             }
         }
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
index 334a0881fb..e3b5ee4787 100644
--- 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
@@ -53,6 +53,7 @@ import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
  * </p>
  */
 public class PostconditionGenerator extends BaseGenerator {
+    private static final String METHOD_PROCESSED = "org.apache.groovy.contracts.POSTCONDITION_PROCESSED";
 
     public PostconditionGenerator(final ReaderSource source) {
         super(source);
@@ -82,7 +83,7 @@ public class PostconditionGenerator extends BaseGenerator {
 
         BlockStatement blockStatement;
         final BlockStatement originalBlockStatement = postcondition.originalBlockStatement();
-        // if use execution tracker flag is found in the meta-data the annotation closure visitor discovered
+        // if useExecutionTracker 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));
 
@@ -103,14 +104,12 @@ public class PostconditionGenerator extends BaseGenerator {
      * @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;
+        boolean noPostconditionInHierarchy = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), method, ClassHelper.makeWithoutCaching(Postcondition.class)).isEmpty();
+        if (noPostconditionInHierarchy) 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);
+        final BooleanExpression postconditionBooleanExpression = addCallsToSuperMethodNodeAnnotationClosure(method.getDeclaringClass(), method, Postcondition.class, boolX(ConstantExpression.TRUE), true);
         if (postconditionBooleanExpression.getExpression() == ConstantExpression.TRUE) return;
 
         final BlockStatement blockStatement = wrapAssertionBooleanExpression(type, method, postconditionBooleanExpression, "postcondition");
@@ -118,14 +117,15 @@ public class PostconditionGenerator extends BaseGenerator {
     }
 
     private void addPostcondition(MethodNode method, BlockStatement postconditionBlockStatement) {
+        if (Boolean.TRUE.equals(method.getNodeMetaData(METHOD_PROCESSED))) return;
         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) {
+        if (!statements.isEmpty()) {
             Expression contractsEnabled = localVarX(BaseVisitor.GCONTRACTS_ENABLED_VAR, ClassHelper.boolean_TYPE);
 
             if (!isPrimitiveVoid(method.getReturnType())) {
+                // if return type is not void, then a "result" variable is provided in the postcondition expression
                 List<ReturnStatement> returnStatements = AssertStatementCreationUtility.getReturnStatements(method);
 
                 for (ReturnStatement returnStatement : returnStatements) {
@@ -143,6 +143,7 @@ public class PostconditionGenerator extends BaseGenerator {
                 setOldVariablesIfEnabled(block, contractsEnabled);
                 block.addStatements(postconditionBlockStatement.getStatements());
             }
+            method.putNodeMetaData(METHOD_PROCESSED, true);
         }
     }
 
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
index 29b9db961e..82c359069f 100644
--- 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
@@ -34,6 +34,7 @@ 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.block;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
 
 /**
@@ -58,7 +59,7 @@ public class PreconditionGenerator extends BaseGenerator {
         BlockStatement blockStatement;
 
         final BlockStatement originalBlockStatement = precondition.originalBlockStatement();
-        // if use execution tracker flag is found in the meta-data the annotation closure visitor discovered
+        // if useExecutionTracker 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));
 
@@ -79,12 +80,10 @@ public class PreconditionGenerator extends BaseGenerator {
      * @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) {
+        boolean noPreconditionInHierarchy = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), methodNode, ClassHelper.makeWithoutCaching(Precondition.class)).isEmpty();
+        if (noPreconditionInHierarchy) return;
 
-        // 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
+        // if there is another precondition 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);
@@ -98,7 +97,7 @@ public class PreconditionGenerator extends BaseGenerator {
     }
 
     private void addPrecondition(MethodNode method, BlockStatement blockStatement) {
-        final BlockStatement modifiedMethodCode = new BlockStatement();
+        final BlockStatement modifiedMethodCode = block();
         modifiedMethodCode.addStatements(blockStatement.getStatements());
 
         if (method.getCode() instanceof BlockStatement) {
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
index b768557267..84490205de 100644
--- 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
@@ -88,8 +88,7 @@ public class TryCatchBlockGenerator {
         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 overallBlock = block(declS(variableExpression, ConstantExpression.FALSE));
 
         final BlockStatement assertBlockStatement = block(
                 assertStatement,
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
index ef44b7cc81..7743aa33f8 100644
--- 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
@@ -26,8 +26,9 @@ import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 
 /**
  * <p>Helper methods for reading/getting {@link org.codehaus.groovy.ast.AnnotationNode} instances.</p>
@@ -61,9 +62,9 @@ public class AnnotationUtils {
      * @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>();
+        List<AnnotationNode> result = new ArrayList<>();
         for (AnnotationNode annotation : type.getAnnotations()) {
-            if (annotation.getClassNode().getAnnotations(anno).size() > 0) {
+            if (!annotation.getClassNode().getAnnotations(anno).isEmpty()) {
                 result.add(annotation);
             }
         }
@@ -85,18 +86,18 @@ public class AnnotationUtils {
      * @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>();
+        List<AnnotationNode> result = new ArrayList<>();
 
         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) {
+                    if (!annotation.getClassNode().getAnnotations(metaAnnotationClassNode).isEmpty()) {
                         result.add(annotation);
                     }
                 }
 
-                if (result.size() > 0) return result;
+                if (!result.isEmpty()) return result;
             }
 
             type = type.getSuperClass();
@@ -115,7 +116,7 @@ public class AnnotationUtils {
      */
     public static List<AnnotationNode> hasMetaAnnotations(AnnotatedNode annotatedNode, String metaAnnotationClassName) {
         List<AnnotationNode> result = new ArrayList<>();
-        Set<ClassNode> seen = new java.util.HashSet<>();
+        Map<ClassNode, Boolean> seen = new HashMap<>();
         ClassNode type = ClassHelper.makeWithoutCaching(metaAnnotationClassName);
 
         for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
@@ -126,17 +127,23 @@ public class AnnotationUtils {
         return result;
     }
 
-    private static boolean hasMetaAnnotation(ClassNode annotationType, ClassNode metaAnnotationType, Set<ClassNode> cycleCheck) {
-        if (!CandidateChecks.isRuntimeClass(annotationType) && cycleCheck.add(annotationType)) {
+    private static boolean hasMetaAnnotation(ClassNode annotationType, ClassNode metaAnnotationType, Map<ClassNode, Boolean> cache) {
+        if (CandidateChecks.isRuntimeClass(annotationType)) return false;
+        if (!cache.containsKey(annotationType)) {
+            boolean result = false;
             if (!annotationType.getAnnotations(metaAnnotationType).isEmpty()) {
-                return true;
-            }
-            for (AnnotationNode annotationNode : annotationType.getAnnotations()) {
-                if (hasMetaAnnotation(annotationNode.getClassNode(), metaAnnotationType, cycleCheck)) {
-                    return true;
+                result = true;
+            } else {
+                cache.put(annotationType, false); // preliminary value to avoid cycles
+                for (AnnotationNode annotationNode : annotationType.getAnnotations()) {
+                    if (hasMetaAnnotation(annotationNode.getClassNode(), metaAnnotationType, cache)) {
+                        result = true;
+                        break;
+                    }
                 }
             }
+            cache.put(annotationType, result);
         }
-        return false;
+        return cache.get(annotationType);
     }
 }
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
index fafe349c4c..749524e72a 100644
--- 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
@@ -33,8 +33,7 @@ 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.andX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
 
 /**
@@ -43,7 +42,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
  * @see ClosureExpression
  * @see BooleanExpression
  */
-public class ExpressionUtils {
+public final class ExpressionUtils {
     private ExpressionUtils() { }
 
     /**
@@ -115,7 +114,7 @@ public class ExpressionUtils {
             if (result == null) {
                 result = booleanExpression;
             } else {
-                result = boolX(binX(result, AND, booleanExpression));
+                result = boolX(andX(result, booleanExpression));
             }
         }
 
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
index 8f2ca57288..66ec5e7d6e 100644
--- 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
@@ -214,6 +214,6 @@ public final class LifecycleImplementationLoader<S> implements Iterable<S> {
      * loader.
      */
     public static <S> LifecycleImplementationLoader<S> load(Class<S> service, ClassLoader loader) {
-        return new LifecycleImplementationLoader<S>(service, loader);
+        return new LifecycleImplementationLoader<>(service, loader);
     }
 }
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
index 534ca2dd79..8bf8c08eec 100644
--- 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
@@ -22,14 +22,14 @@ 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.codehaus.groovy.ast.tools.GeneralUtils.block
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
 import static org.junit.Assert.assertEquals
 import static org.junit.Assert.assertNotNull
 import static org.junit.Assert.assertTrue
@@ -43,7 +43,6 @@ class ContractTests {
     public void setUp() {
         def source = '''
         class Tester {
-
            void some_method()  {}
         }
 '''
@@ -60,7 +59,7 @@ class ContractTests {
     void create_simple_contract() {
         Contract contract = new Contract(classNode)
 
-        Precondition precondition = new Precondition(new BlockStatement(), new BooleanExpression(new ConstantExpression(true)))
+        Precondition precondition = new Precondition(block(), boolX(constX(true)))
         contract.preconditions().or(classNode.getMethod("some_method", [] as Parameter[]), precondition)
 
         assertEquals(1, contract.preconditions().size())
@@ -71,8 +70,8 @@ class ContractTests {
 
         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)))
+        Precondition precondition1 = new Precondition(block(), boolX(constX(true)))
+        Precondition precondition2 = new Precondition(block(), boolX(constX(true)))
 
         contract.preconditions().or(methodNode, precondition1)
         contract.preconditions().or(methodNode, precondition2)
@@ -86,8 +85,8 @@ class ContractTests {
 
         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)
+        Postcondition postcondition = new Postcondition(block(), boolX(constX(true)), false)
+        Postcondition postcondition1 = new Postcondition(block(), boolX(constX(true)), false)
 
         contract.postconditions().and(methodNode, postcondition)
         contract.postconditions().and(methodNode, postcondition1)
@@ -101,8 +100,8 @@ class ContractTests {
 
         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)))
+        Precondition precondition1 = new Precondition(block(), boolX(constX(true)))
+        Precondition precondition2 = new Precondition(block(), boolX(constX(true)))
 
         contract.preconditions().join(methodNode, precondition1)
         contract.preconditions().join(methodNode, precondition2)
diff --git a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/pre/SimplePreconditionTests.groovy b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/pre/SimplePreconditionTests.groovy
index ca4adeed3a..7dd1c155c2 100644
--- a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/pre/SimplePreconditionTests.groovy
+++ b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/pre/SimplePreconditionTests.groovy
@@ -18,6 +18,7 @@
  */
 package org.apache.groovy.contracts.tests.pre
 
+import org.apache.groovy.contracts.ClassInvariantViolation
 import org.apache.groovy.contracts.PreconditionViolation
 import org.apache.groovy.contracts.tests.basic.BaseTestClass
 import org.junit.Test
@@ -433,6 +434,52 @@ class Account
         create_instance_of(source, ['test', 'test'])
     }
 
+    @Test
+    void requires_on_constructor_with_params_instance_vars_same_name_and_this_expression_multiple_annotations() {
+
+        def source = """
+            import groovy.contracts.*
+
+            @Invariant({ a == b })
+            @Invariant({ a != 'foo' })
+            class A {
+                private final String a
+                private final String b
+
+                @Requires({ a != null })
+                @Requires({ b != null })
+                @Requires({ this.a == null })
+                @Requires({ this.b == null })
+                @Ensures({ this.a == a })
+                @Ensures({ this.b == b })
+                A(String a, String b) {
+                    this.a = a
+                    this.b = b
+                }
+            }
+        """
+
+        create_instance_of(source, ['test', 'test'])
+
+        shouldFail PreconditionViolation, {
+            create_instance_of(source, [null, null])
+        }
+        shouldFail PreconditionViolation, {
+            create_instance_of(source, ['a', null])
+        }
+        shouldFail PreconditionViolation, {
+            create_instance_of(source, [null, 'b'])
+        }
+
+        shouldFail ClassInvariantViolation, {
+            create_instance_of(source, ['foo', 'foo'])
+        }
+        shouldFail ClassInvariantViolation, {
+            create_instance_of(source, ['a', 'b'])
+        }
+
+    }
+
     @Test
     void requires_with_default_parameters() {
 
@@ -466,4 +513,4 @@ class Account
         def a = create_instance_of(source)
         a.m(null)
     }
-}
\ No newline at end of file
+}