diff --git a/constraints/constraint-api/src/main/java/io/toolisticon/aptk/constraints/StringMustMatch.java b/constraints/constraint-api/src/main/java/io/toolisticon/aptk/constraints/StringMustMatch.java new file mode 100644 index 00000000..db28f082 --- /dev/null +++ b/constraints/constraint-api/src/main/java/io/toolisticon/aptk/constraints/StringMustMatch.java @@ -0,0 +1,20 @@ +package io.toolisticon.aptk.constraints; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Constraint +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@On(On.Location.ANNOTATION_ATTRIBUTE) +@OnAnnotationAttributeOfType({OnAnnotationAttributeOfType.AttributeType.STRING, OnAnnotationAttributeOfType.AttributeType.STRING_ARRAY}) +public @interface StringMustMatch { + + String value(); + +} diff --git a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/AnnotationConstraints.java b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/AnnotationConstraints.java index 5fdcc37c..7f9f75aa 100644 --- a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/AnnotationConstraints.java +++ b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/AnnotationConstraints.java @@ -18,13 +18,13 @@ class AnnotationConstraints { } - void addConstraintOnType(AnnotationMirrorWrapper annotationMirror) { + void addConstraintOnAttribute(AnnotationMirrorWrapper annotationMirror) { if (annotationMirror != null) { constraintsOnAnnotationType.add(annotationMirror); } } - void addConstraintOnType(AnnotationAttributeConstraints annotationAttributeConstraints) { + void addConstraintOnAttribute(AnnotationAttributeConstraints annotationAttributeConstraints) { if (annotationAttributeConstraints != null) { constraintsOnAnnotationAttributes.add(annotationAttributeConstraints); } diff --git a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/BasicConstraints.java b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/BasicConstraints.java index 73c0e7a6..57387f53 100644 --- a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/BasicConstraints.java +++ b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/BasicConstraints.java @@ -23,6 +23,7 @@ @DeclareCompilerMessage(code = "BASE_002", enumValueName = "BASE_ERROR_MUST_BE_PLACE_ON_ANNOTATION_TYPE", message = "Constraint must be placed on annotation type", processorClass = BasicConstraints.class) @DeclareCompilerMessage(code = "BASE_003", enumValueName = "BASE_ERROR_MUST_BE_PLACE_ON_ANNOTATION_ATTRIBUTE", message = "Constraint must be placed on annotation attribute", processorClass = BasicConstraints.class) @DeclareCompilerMessage(code = "BASE_004", enumValueName = "BASE_WARNING_CONSTRAINT_SPI_IMPLEMENTATION_NOT_FOUND", message = "Couldn't find and apply annotation constraint implementation for constraint ${0} in annotation ${1}.", processorClass = BasicConstraints.class) +@DeclareCompilerMessage(code = "BASE_005", enumValueName = "BASE_WARNING_INVALID_USAGE_OF_CONSTRAINT", message = "Constraint annotation ${} isn't used correctly and will be ignmored!", processorClass = BasicConstraints.class) public class BasicConstraints { private static BasicConstraints INSTANCE = null; @@ -55,8 +56,6 @@ public boolean checkForConstraints(ElementWrapper elementWrapper) { } - - AnnotationConstraints annotationConstraints = onAnnotationConstraintsLUT.get(annotationFQN); if (annotationConstraints == null) { @@ -117,7 +116,7 @@ private AnnotationConstraints determineConstraints(TypeElementWrapper annotation for (AnnotationMirrorWrapper annotation : annotationTypeElement.getAnnotationMirrors()) { if (hasConstraintAnnotationOnTypeElement(annotation)) { - annotationConstraints.addConstraintOnType(annotation); + annotationConstraints.addConstraintOnAttribute(annotation); } } @@ -141,7 +140,7 @@ private AnnotationConstraints determineConstraints(TypeElementWrapper annotation if (detectedConstraints.size() > 0) { // detected constraint - annotationConstraints.addConstraintOnType(new AnnotationAttributeConstraints(name, detectedConstraints)); + annotationConstraints.addConstraintOnAttribute(new AnnotationAttributeConstraints(name, detectedConstraints)); } } diff --git a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/OnAnnotationAttributeOfTypeConstraintImpl.java b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/OnAnnotationAttributeOfTypeConstraintImpl.java index 9e9d86f4..8778065c 100644 --- a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/OnAnnotationAttributeOfTypeConstraintImpl.java +++ b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/OnAnnotationAttributeOfTypeConstraintImpl.java @@ -1,6 +1,6 @@ package io.toolisticon.aptk.constraints; -import io.toolisticon.aptk.tools.MessagerUtils; +import io.toolisticon.aptk.compilermessage.api.DeclareCompilerMessage; import io.toolisticon.aptk.tools.wrapper.ElementWrapper; import io.toolisticon.aptk.tools.wrapper.ExecutableElementWrapper; import io.toolisticon.spiap.api.SpiService; @@ -9,8 +9,10 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import java.lang.annotation.Annotation; +import java.util.Optional; @SpiService(value = AnnotationConstraintSpi.class) +@DeclareCompilerMessage(code = "ONATTRIBUTETYPE_001", enumValueName = "ONATTRIBUTETYPE_ERROR_WRONG_USAGE", message = "'${0}' Constraint violated: Annotation ${1} must be placed on annotation attribute of kind ${2}", processorClass = BasicConstraints.class) public class OnAnnotationAttributeOfTypeConstraintImpl implements AnnotationConstraintSpi { @Override @@ -22,8 +24,12 @@ public Class getSupportedAnnotation() { @Override public boolean checkConstraints(Element annotatedElement, AnnotationMirror annotationMirrorToCheck, AnnotationMirror constraintAnnotationMirror, String attributeNameToBeCheckedByConstraint) { - if (!ElementWrapper.wrap(annotatedElement).isExecutableElement()) { + ElementWrapper wrappedElement = ElementWrapper.wrap(annotatedElement); + Optional> enclosingElement = wrappedElement.getEnclosingElement(); + + if (!enclosingElement.isPresent() || !enclosingElement.get().isAnnotation() || !wrappedElement.isExecutableElement()) { // TODO: must send warning that constraint is ignored because its not placed correctly + wrappedElement.compilerMessage(annotationMirrorToCheck).asWarning().write(BasicConstraintsCompilerMessages.BASE_WARNING_INVALID_USAGE_OF_CONSTRAINT, constraintAnnotationMirror.getAnnotationType().asElement().getSimpleName()); return true; } @@ -31,14 +37,150 @@ public boolean checkConstraints(Element annotatedElement, AnnotationMirror annot ExecutableElementWrapper attributeElementWrapper = ExecutableElementWrapper.wrap((ExecutableElement) annotatedElement); // Now check if annotation - OnAnnotationAttributeOfTypeWrapper onAnnotation = OnAnnotationAttributeOfTypeWrapper.wrap(constraintAnnotationMirror); + OnAnnotationAttributeOfTypeWrapper onAnnotationOfType = OnAnnotationAttributeOfTypeWrapper.wrap(constraintAnnotationMirror); boolean foundMatchingElementType = false; loop: - for (OnAnnotationAttributeOfType.AttributeType targetAttributeType : onAnnotation.value()) { + for (OnAnnotationAttributeOfType.AttributeType targetAttributeType : onAnnotationOfType.value()) { switch (targetAttributeType) { + case ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + + case SINGLE_VALUE: { + if ( + !attributeElementWrapper.getReturnType().isArray() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case CLASS: { + if ( + attributeElementWrapper.getReturnType().isClass() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case CLASS_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() && attributeElementWrapper.getReturnType().getWrappedComponentType().isClass() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case ENUM: { + if ( + attributeElementWrapper.getReturnType().isEnum() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case ENUM_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() && attributeElementWrapper.getReturnType().getWrappedComponentType().isEnum() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case ANNOTATION: { + if ( + attributeElementWrapper.getReturnType().isAnnotation() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case ANNOTATION_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() && attributeElementWrapper.getReturnType().getWrappedComponentType().isArray() + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case FLOAT: { + if ( + attributeElementWrapper.getReturnType().isPrimitive() + && attributeElementWrapper.getReturnType().getSimpleName().equals(float.class.getSimpleName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case FLOAT_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() + && attributeElementWrapper.getReturnType().getWrappedComponentType().isPrimitive() + && attributeElementWrapper.getReturnType().getWrappedComponentType().getSimpleName().equals(float.class.getSimpleName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case DOUBLE: { + if ( + attributeElementWrapper.getReturnType().isPrimitive() + && attributeElementWrapper.getReturnType().getSimpleName().equals(double.class.getSimpleName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case DOUBLE_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() + && attributeElementWrapper.getReturnType().getWrappedComponentType().isPrimitive() + && attributeElementWrapper.getReturnType().getWrappedComponentType().getSimpleName().equals(double.class.getSimpleName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case INTEGER: { + if ( + attributeElementWrapper.getReturnType().isPrimitive() + && attributeElementWrapper.getReturnType().getSimpleName().equals(int.class.getSimpleName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } + case INTEGER_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() + && attributeElementWrapper.getReturnType().getWrappedComponentType().isPrimitive() + && attributeElementWrapper.getReturnType().getWrappedComponentType().getSimpleName().equals(int.class.getSimpleName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } case LONG: { if ( attributeElementWrapper.getReturnType().isPrimitive() @@ -49,6 +191,17 @@ public boolean checkConstraints(Element annotatedElement, AnnotationMirror annot } break; } + case LONG_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() + && attributeElementWrapper.getReturnType().getWrappedComponentType().isPrimitive() + && attributeElementWrapper.getReturnType().getWrappedComponentType().getSimpleName().equals(long.class.getSimpleName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } case STRING: { if ( attributeElementWrapper.getReturnType().getQualifiedName().equals(String.class.getCanonicalName()) @@ -58,6 +211,17 @@ public boolean checkConstraints(Element annotatedElement, AnnotationMirror annot } break; } + case STRING_ARRAY: { + if ( + attributeElementWrapper.getReturnType().isArray() + && attributeElementWrapper.getReturnType().getWrappedComponentType().isDeclared() + && attributeElementWrapper.getReturnType().getWrappedComponentType().getQualifiedName().equals(String.class.getCanonicalName()) + ) { + foundMatchingElementType = true; + break loop; + } + break; + } } @@ -66,7 +230,7 @@ public boolean checkConstraints(Element annotatedElement, AnnotationMirror annot // trigger error message if matching type hasn't been found if (!foundMatchingElementType) { - MessagerUtils.error(annotatedElement, annotationMirrorToCheck, BasicConstraintsCompilerMessages.ON_ERROR_WRONG_USAGE, UtilityFunctions.getSimpleName(constraintAnnotationMirror), UtilityFunctions.getSimpleName(annotationMirrorToCheck), onAnnotation.value()); + wrappedElement.compilerMessage(annotationMirrorToCheck).asError().write(BasicConstraintsCompilerMessages.ONATTRIBUTETYPE_ERROR_WRONG_USAGE, UtilityFunctions.getSimpleName(constraintAnnotationMirror), UtilityFunctions.getSimpleName(annotationMirrorToCheck), onAnnotationOfType.value()); return false; } diff --git a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/StringMustMatchConstraintImpl.java b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/StringMustMatchConstraintImpl.java new file mode 100644 index 00000000..e837b204 --- /dev/null +++ b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/StringMustMatchConstraintImpl.java @@ -0,0 +1,74 @@ +package io.toolisticon.aptk.constraints; + +import io.toolisticon.aptk.compilermessage.api.DeclareCompilerMessage; +import io.toolisticon.aptk.tools.wrapper.AnnotationMirrorWrapper; +import io.toolisticon.aptk.tools.wrapper.AnnotationValueWrapper; +import io.toolisticon.aptk.tools.wrapper.ElementWrapper; +import io.toolisticon.aptk.tools.wrapper.ExecutableElementWrapper; +import io.toolisticon.spiap.api.SpiService; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +@SpiService(value = AnnotationConstraintSpi.class) +@DeclareCompilerMessage(code = "STRINGMUSTMATCH_001", enumValueName = "STRINGMUSTMATCH_ERROR_WRONG_USAGE", message = "'${0}' Constraint violated: Annotation ${1} attribute ${2} value(s) must match regular expression: ${3}", processorClass = BasicConstraints.class) +public class StringMustMatchConstraintImpl implements AnnotationConstraintSpi { + + @Override + public Class getSupportedAnnotation() { + return StringMustMatch.class; + } + + + @Override + public boolean checkConstraints(Element annotatedElement, AnnotationMirror annotationMirrorToCheck, AnnotationMirror constraintAnnotationMirror, String attributeNameToBeCheckedByConstraint) { + + ElementWrapper wrappedElement = ElementWrapper.wrap(annotatedElement); + Optional> enclosingElement = wrappedElement.getEnclosingElement(); + + if (!enclosingElement.isPresent() || !enclosingElement.get().isAnnotation() || !wrappedElement.isExecutableElement()) { + // TODO: must send warning that constraint is ignored because its not placed correctly + wrappedElement.compilerMessage(annotationMirrorToCheck).asWarning().write(BasicConstraintsCompilerMessages.BASE_WARNING_INVALID_USAGE_OF_CONSTRAINT, constraintAnnotationMirror.getAnnotationType().asElement().getSimpleName()); + return true; + } + + // It's safe to cast now + StringMustMatchWrapper constraintWrapper = StringMustMatchWrapper.wrap(constraintAnnotationMirror); + Optional attribute = AnnotationMirrorWrapper.wrap(annotationMirrorToCheck).getAttribute(attributeNameToBeCheckedByConstraint); + + if (attribute.isPresent()) { + try { + Pattern pattern = Pattern.compile(constraintWrapper.value()); + + if (attribute.get().isArray()) { + for (AnnotationValueWrapper value : attribute.get().getArrayValue()) { + if (!pattern.matcher(value.getStringValue()).matches()) { + wrappedElement.compilerMessage(annotationMirrorToCheck,attribute.get().unwrap()).asError().write(BasicConstraintsCompilerMessages.STRINGMUSTMATCH_ERROR_WRONG_USAGE,UtilityFunctions.getSimpleName(constraintAnnotationMirror), UtilityFunctions.getSimpleName(annotationMirrorToCheck),attributeNameToBeCheckedByConstraint,constraintWrapper.value()); + return false; + } + } + } else { + if (!pattern.matcher(attribute.get().getStringValue()).matches()) { + wrappedElement.compilerMessage(annotationMirrorToCheck,attribute.get().unwrap()).asError().write(BasicConstraintsCompilerMessages.STRINGMUSTMATCH_ERROR_WRONG_USAGE,UtilityFunctions.getSimpleName(constraintAnnotationMirror), UtilityFunctions.getSimpleName(annotationMirrorToCheck),attributeNameToBeCheckedByConstraint,constraintWrapper.value()); + return false; + } + } + + + + } catch (PatternSyntaxException e) { + //TODO: must print warning that constraint is wrong and must be ignored + return true; + } + } + + AnnotationMirrorWrapper annotationMirrorWrapper = AnnotationMirrorWrapper.wrap(annotationMirrorToCheck); + + return true; + } +} \ No newline at end of file diff --git a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/package-info.java b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/package-info.java index 378a5cd0..e78fc469 100644 --- a/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/package-info.java +++ b/constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/package-info.java @@ -4,7 +4,8 @@ @AnnotationWrapper( value = { On.class, - OnAnnotationAttributeOfType.class + OnAnnotationAttributeOfType.class, + StringMustMatch.class }) package io.toolisticon.aptk.constraints; diff --git a/constraints/constraint-impl/src/test/java/io/toolisticon/aptk/constraints/BasicConstraintsTest.java b/constraints/constraint-impl/src/test/java/io/toolisticon/aptk/constraints/BasicConstraintsTest.java index 1d272d02..5d397854 100644 --- a/constraints/constraint-impl/src/test/java/io/toolisticon/aptk/constraints/BasicConstraintsTest.java +++ b/constraints/constraint-impl/src/test/java/io/toolisticon/aptk/constraints/BasicConstraintsTest.java @@ -97,6 +97,54 @@ public void justOnStringAttributeAndIntegers_failure() { ToolingProvider.clearTooling(); } - }).thenExpectThat().compilationFails().andThat().compilerMessage().ofKindError().contains(BasicConstraintsCompilerMessages.ON_ERROR_WRONG_USAGE.getCode()).executeTest(); + }).thenExpectThat().compilationFails().andThat().compilerMessage().ofKindError().contains(BasicConstraintsCompilerMessages.ONATTRIBUTETYPE_ERROR_WRONG_USAGE.getCode()).executeTest(); + } + + + @interface TestStringMustMatch { + + @StringMustMatch("A.+Z") String value(); + + } + + @PassIn + @TestStringMustMatch("ABadasdZ") + static class TestStringAttributeByRegex_Match { + + } + + @PassIn + @TestStringMustMatch("AZ") + static class TestStringAttributeByRegex_NoMatch { + + } + + @Test + public void stringMustMatch_happyPath() { + Cute.unitTest().when().passInElement().fromClass(TestStringAttributeByRegex_Match.class).intoUnitTest((procEnv, e) -> { + + ToolingProvider.setTooling(procEnv); + try { + MatcherAssert.assertThat("Expect true", BasicConstraints.checkConstraints(e)); + } finally { + ToolingProvider.clearTooling(); + } + + }).thenExpectThat().compilationSucceeds().executeTest(); } + + @Test + public void stringMustMatch_failure() { + Cute.unitTest().when().passInElement().fromClass(TestStringAttributeByRegex_NoMatch.class).intoUnitTest((procEnv, e) -> { + + ToolingProvider.setTooling(procEnv); + try { + MatcherAssert.assertThat("Expect false", !BasicConstraints.checkConstraints(e)); + } finally { + ToolingProvider.clearTooling(); + } + + }).thenExpectThat().compilationFails().executeTest(); + } + } diff --git a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ElementWrapper.java b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ElementWrapper.java index 99856592..feb79ead 100644 --- a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ElementWrapper.java +++ b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ElementWrapper.java @@ -710,6 +710,7 @@ public boolean isModuleElement() { return ElementUtils.CastElement.isModuleElement(this.element); } + /** * Converts wrapper to a PackageElementWrapper by casting and re-wrapping wrapped element. * @@ -765,8 +766,6 @@ public static ExecutableElementWrapper toExecutableElementWrapper(ElementWrapper return ExecutableElementWrapper.wrap(ElementUtils.CastElement.castToExecutableElement(wrapper.unwrap())); } - - protected TARGET_TYPE invokeParameterlessMethodOfElement(String interfaceName, String methodName) { return ElementWrapper.invokeParameterlessMethodOfElement(element, interfaceName, methodName); } @@ -793,5 +792,5 @@ protected boolean hasMethod(String interfaceName, String methodName) { return false; } } - + }