From 6725458207ba1dd89dbb45824f7310de8b461ef1 Mon Sep 17 00:00:00 2001 From: Paul King Date: Wed, 17 Jul 2024 20:01:03 +1000 Subject: [PATCH] GROOVY-11443: Support multiple Requires/Ensures/Invariant annotations in groovy-contracts --- .../groovy/ast/tools/GeneralUtils.java | 4 + .../main/java/groovy/contracts/Ensures.java | 4 +- .../groovy/contracts/EnsuresConditions.java | 36 +++++++++ .../main/java/groovy/contracts/Invariant.java | 4 +- .../java/groovy/contracts/Invariants.java | 36 +++++++++ .../main/java/groovy/contracts/Requires.java | 4 +- .../groovy/contracts/RequiresConditions.java | 36 +++++++++ ...ExpressionEvaluationASTTransformation.java | 33 +++++---- .../ast/visitor/AnnotationClosureVisitor.java | 4 +- .../visitor/AnnotationProcessorVisitor.java | 74 ++++++++++++++----- .../contracts/ast/visitor/BaseVisitor.java | 2 +- .../classgen/asm/ContractClosureWriter.java | 3 +- .../ClassInvariantAnnotationProcessor.java | 10 +++ .../impl/EnsuresAnnotationProcessor.java | 8 +- .../impl/lc/PostconditionLifecycle.java | 12 +-- .../spi/ProcessingContextInformation.java | 8 +- .../groovy/contracts/domain/Assertion.java | 31 ++------ .../groovy/contracts/domain/AssertionMap.java | 2 +- .../contracts/domain/ClassInvariant.java | 7 +- .../AssertStatementCreationUtility.java | 6 +- .../contracts/generation/BaseGenerator.java | 24 ++++-- .../generation/ClassInvariantGenerator.java | 16 ++-- .../contracts/generation/Configurator.java | 4 +- .../generation/ContractExecutionTracker.java | 13 ++-- .../OldVariableGenerationUtility.java | 12 +-- .../generation/PostconditionGenerator.java | 17 +++-- .../generation/PreconditionGenerator.java | 13 ++-- .../generation/TryCatchBlockGenerator.java | 3 +- .../contracts/util/AnnotationUtils.java | 37 ++++++---- .../contracts/util/ExpressionUtils.java | 7 +- .../util/LifecycleImplementationLoader.java | 2 +- .../contracts/domain/ContractTests.groovy | 21 +++--- .../tests/pre/SimplePreconditionTests.groovy | 49 +++++++++++- 33 files changed, 373 insertions(+), 169 deletions(-) create mode 100644 subprojects/groovy-contracts/src/main/java/groovy/contracts/EnsuresConditions.java create mode 100644 subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariants.java create mode 100644 subprojects/groovy-contracts/src/main/java/groovy/contracts/RequiresConditions.java 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 bcf2d10e060..f94be91dc7c 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 static MapEntryExpression mapEntryX(final String key, final Expression va return new MapEntryExpression(constX(key), valueExpr); } + public static MapExpression mapX() { + return new MapExpression(); + } + public static MapExpression mapX(final List 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 7b905e54d5b..930a403097d 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.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 @@ @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/groovy/contracts/EnsuresConditions.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/EnsuresConditions.java new file mode 100644 index 00000000000..8e5a21210dc --- /dev/null +++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/EnsuresConditions.java @@ -0,0 +1,36 @@ +/* + * 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.lang.annotation.Incubating; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents multiple postconditions. + */ +@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 ca9edf384b9..8512cd49e2b 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.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 @@ @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/groovy/contracts/Invariants.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariants.java new file mode 100644 index 00000000000..ac37f83b607 --- /dev/null +++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/Invariants.java @@ -0,0 +1,36 @@ +/* + * 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.lang.annotation.Incubating; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents multiple invariants + */ +@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 b9635afeca5..5e4a276943e 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.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 @@ @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/groovy/contracts/RequiresConditions.java b/subprojects/groovy-contracts/src/main/java/groovy/contracts/RequiresConditions.java new file mode 100644 index 00000000000..0ff81b3332d --- /dev/null +++ b/subprojects/groovy-contracts/src/main/java/groovy/contracts/RequiresConditions.java @@ -0,0 +1,36 @@ +/* + * 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.lang.annotation.Incubating; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents multiple preconditions. + */ +@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 afbe5783380..f63b679df14 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.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 classNodes = new ArrayList<>(moduleNode.getClasses()); + + generateAnnotationClosureClasses(unit, source, classNodes); + } + private void generateAnnotationClosureClasses(SourceUnit unit, ReaderSource source, List 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 classNodes = new ArrayList(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 f3d2e284c90..a8d1770bcb3 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 AnnotationClosureVisitor(final SourceUnit sourceUnit, final ReaderSource @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 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 b23e38ab968..f87235ccd05 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 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 @@ */ 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 void visitClass(ClassNode type) { 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 @@ private void visitAbstractBaseClassesForInterfaceMethodNodes(ClassNode origin, C for (ClassNode interfaceNode : superClass.getInterfaces()) { List interfaceMethods = new ArrayList<>(interfaceNode.getMethods()); for (MethodNode interfaceMethod : interfaceMethods) { - List annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethod, ContractElement.class.getName()); + List 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 @@ private void visitAbstractBaseClassesForInterfaceMethodNodes(ClassNode origin, C private void visitInterfaces(final ClassNode classNode, final ClassNode[] interfaces) { for (ClassNode interfaceNode : interfaces) { + List interfaceNodes = AnnotationUtils.hasMetaAnnotations(interfaceNode, CONTRACT_ELEMENT_CLASSNAME); + processAnnotationNodes(classNode, interfaceNodes); List 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 annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethod, ContractElement.class.getName()); + List annotationNodes = AnnotationUtils.hasMetaAnnotations(interfaceMethod, CONTRACT_ELEMENT_CLASSNAME); handleInterfaceMethodNode(classNode, implementingMethod, annotationNodes); } @@ -119,13 +125,17 @@ private void visitInterfaces(final ClassNode classNode, final ClassNode[] interf } private void handleClassNode(final ClassNode classNode) { - List annotationNodes = AnnotationUtils.hasMetaAnnotations(classNode, ContractElement.class.getName()); + List annotationNodes = AnnotationUtils.hasMetaAnnotations(classNode, CONTRACT_ELEMENT_CLASSNAME); + processAnnotationNodes(classNode, annotationNodes); + } + + private void processAnnotationNodes(ClassNode classNode, List 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 @@ private void handleInterfaceMethodNode(ClassNode type, MethodNode methodNode, Li private void handleMethodAnnotations(MethodNode methodNode, List annotationNodes) { if (methodNode == null) return; + Map> 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> collected) { boolean isPostcondition = AnnotationUtils.hasAnnotationOfType(annotationNode.getClassNode(), Postcondition.class.getName()); ArgumentListExpression argumentList = new ArgumentListExpression(); @@ -162,7 +187,13 @@ private void handleMethodAnnotation(MethodNode methodNode, AnnotationNode annota 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 @@ private AnnotationProcessor createAnnotationProcessor(AnnotationNode annotationN } 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 2337cdd1ef5..520698bf0de 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 BaseVisitor(final SourceUnit sourceUnit, final ReaderSource source) { 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 c2a02247b41..2a8d3220bac 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 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 ClassNode createClosureClass(ClassNode classNode, MethodNode methodNode, 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 976a47af913..95a1573ccf1 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.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 void process(ProcessingContextInformation processingContextInformation, C 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 5a3740cdac7..3b1806220a9 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.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 void process(ProcessingContextInformation processingContextInformation, C if (!processingContextInformation.isPostconditionsEnabled()) return; if (booleanExpression == null) return; - final List 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 f373b2ba8c1..a2b08af7753 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 @@ private void generatePostcondition(ProcessingContextInformation processingContex 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 b6d519656ed..9b6e632b5ae 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 @@ */ 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 extra = new HashMap(); + private final Map 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 7751f2960df..f3dc7982256 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; /** *

Base class for all assertion types.

@@ -37,7 +38,7 @@ public abstract class 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 void renew(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() - ) - ); + 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 aebc2739393..0ecfef6cb0a 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> implements Iterable internalMap; public AssertionMap() { - this.internalMap = new HashMap(); + 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 538ef4b00bf..474b827f63a 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; + /** *

A class-invariant assertion.

*/ public class ClassInvariant extends Assertion { - 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 b2701f9ce9e..68069f46e80 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 void visitBlockStatement(BlockStatement block) { 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 718d8d52717..0eb3b3eb80d 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 @@ protected BlockStatement getInlineModeBlockStatement(BlockStatement blockStateme 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 @@ protected BlockStatement wrapAssertionBooleanExpression(ClassNode type, MethodNo ); 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 @@ protected BooleanExpression addCallsToSuperMethodNodeAnnotationClosure(final Cla 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 @@ protected BooleanExpression addCallsToSuperMethodNodeAnnotationClosure(final Cla 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 3bb7ebabb73..57919865fd8 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.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 @@ */ 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 ClassInvariantGenerator(final ReaderSource source) { */ 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 void generateInvariantAssertionStatement(final ClassNode type, final org. blockStatement.addStatements(wrapAssertionBooleanExpression(type, methodNode, classInvariantExpression, "invariant").getStatements()); } - private BooleanExpression addCallsToSuperAnnotationClosure(final ClassNode type, final Class annotationType, BooleanExpression booleanExpression) { - List contractElementAnnotations = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), ClassHelper.makeWithoutCaching(annotationType)); + private BooleanExpression addCallsToSuperAnnotationClosure(final ClassNode type, BooleanExpression booleanExpression) { + List contractElementAnnotations = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), CLASS_INVARIANT_TYPE); for (AnnotationNode contractElementAnnotation : contractElementAnnotations) { booleanExpression = boolX(andX(booleanExpression, BaseVisitor.asConditionExecution(contractElementAnnotation))); } @@ -113,9 +115,7 @@ public void addInvariantAssertionStatement(final ClassNode type, final MethodNod 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 a3fdabf3d9c..f2af5f4328a 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(); + assertionConfiguration = new HashMap<>(); // per default assertion are enabled (Groovy like) assertionConfiguration.put(null, Boolean.TRUE); @@ -85,7 +85,7 @@ public static boolean checkAssertionsEnabled(final String className) { } 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 9549c14f351..6b20a262e16 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 @@ 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 int hashCode() { } - static class ContractExecutionThreadLocal extends ThreadLocal> { + static class ContractExecutionThreadLocal extends ThreadLocal> { @Override - protected HashSet initialValue() { - return new HashSet(); + protected Set initialValue() { + return new HashSet<>(); } } - private static ThreadLocal> executions = new ContractExecutionThreadLocal(); + private static final ThreadLocal> 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 contractExecutions = executions.get(); + final Set contractExecutions = executions.get(); if (!contractExecutions.contains(ce)) { contractExecutions.add(ce); @@ -94,7 +95,7 @@ public static boolean track(String className, String methodIdentifier, String as } public static void clear(String className, String methodIdentifier, String assertionType, boolean isStatic) { - final HashSet contractExecutions = executions.get(); + final Set 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 03b49439444..3a9917fc2ec 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.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 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 static void addOldVariableMethodNode(final ClassNode classNode) { 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 static void addOldVariableMethodNode(final ClassNode classNode) { 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 334a0881fb6..e3b5ee4787f 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 @@ *

*/ 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 void generatePostconditionAssertionStatement(MethodNode method, org.apach 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 void generatePostconditionAssertionStatement(MethodNode method, org.apach * @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 void generateDefaultPostconditionStatement(final ClassNode type, final Me } 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 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 returnStatements = AssertStatementCreationUtility.getReturnStatements(method); for (ReturnStatement returnStatement : returnStatements) { @@ -143,6 +143,7 @@ private void addPostcondition(MethodNode method, BlockStatement postconditionBlo 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 29b9db961eb..82c359069fe 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.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 void generatePreconditionAssertionStatement(final MethodNode method, fina 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 void generatePreconditionAssertionStatement(final MethodNode method, fina * @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 : C2 == false || item != null BooleanExpression preconditionBooleanExpression = boolX(ConstantExpression.FALSE); preconditionBooleanExpression = addCallsToSuperMethodNodeAnnotationClosure(type, methodNode, Precondition.class, preconditionBooleanExpression, false); @@ -98,7 +97,7 @@ public void generateDefaultPreconditionStatement(final ClassNode type, final Met } 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 b7685572674..84490205deb 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 static BlockStatement generateTryCatchBlock(final ClassNode assertionErro 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 ef44b7cc81c..7743aa33f84 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.MethodNode; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.Set; +import java.util.Map; /** *

Helper methods for reading/getting {@link org.codehaus.groovy.ast.AnnotationNode} instances.

@@ -61,9 +62,9 @@ public static boolean hasAnnotationOfType(AnnotatedNode annotatedNode, String ty * @return the next {@link org.codehaus.groovy.ast.AnnotationNode} in the inheritance line, or null */ public static List getAnnotationNodeInHierarchyWithMetaAnnotation(ClassNode type, ClassNode anno) { - List result = new ArrayList(); + List 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 static List getAnnotationNodeInHierarchyWithMetaAnnotatio * @return a list of {@link AnnotationNode} all annotated with metaAnnotationClassNode */ public static List getAnnotationNodeInHierarchyWithMetaAnnotation(ClassNode type, MethodNode originMethodNode, ClassNode metaAnnotationClassNode) { - List result = new ArrayList(); + List 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 static List getAnnotationNodeInHierarchyWithMetaAnnotatio */ public static List hasMetaAnnotations(AnnotatedNode annotatedNode, String metaAnnotationClassName) { List result = new ArrayList<>(); - Set seen = new java.util.HashSet<>(); + Map seen = new HashMap<>(); ClassNode type = ClassHelper.makeWithoutCaching(metaAnnotationClassName); for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) { @@ -126,17 +127,23 @@ public static List hasMetaAnnotations(AnnotatedNode annotatedNod return result; } - private static boolean hasMetaAnnotation(ClassNode annotationType, ClassNode metaAnnotationType, Set cycleCheck) { - if (!CandidateChecks.isRuntimeClass(annotationType) && cycleCheck.add(annotationType)) { + private static boolean hasMetaAnnotation(ClassNode annotationType, ClassNode metaAnnotationType, Map 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 fafe349c4ce..749524e72a4 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.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 @@ * @see ClosureExpression * @see BooleanExpression */ -public class ExpressionUtils { +public final class ExpressionUtils { private ExpressionUtils() { } /** @@ -115,7 +114,7 @@ public static BooleanExpression getBooleanExpression(List boo 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 8f2ca57288b..66ec5e7d6e1 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 void remove() { * loader. */ public static LifecycleImplementationLoader load(Class service, ClassLoader loader) { - return new LifecycleImplementationLoader(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 534ca2dd796..8bf8c08eecc 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 ca4adeed3a1..7dd1c155c21 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 +}