Skip to content

Commit

Permalink
Added StringMustMatch constraint
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasstamann committed Apr 25, 2024
1 parent 5e21396 commit 183be1b
Show file tree
Hide file tree
Showing 8 changed files with 321 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,8 +56,6 @@ public boolean checkForConstraints(ElementWrapper<?> elementWrapper) {
}




AnnotationConstraints annotationConstraints = onAnnotationConstraintsLUT.get(annotationFQN);

if (annotationConstraints == null) {
Expand Down Expand Up @@ -117,7 +116,7 @@ private AnnotationConstraints determineConstraints(TypeElementWrapper annotation
for (AnnotationMirrorWrapper annotation : annotationTypeElement.getAnnotationMirrors()) {

if (hasConstraintAnnotationOnTypeElement(annotation)) {
annotationConstraints.addConstraintOnType(annotation);
annotationConstraints.addConstraintOnAttribute(annotation);
}

}
Expand All @@ -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));
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand All @@ -22,23 +24,163 @@ public Class<? extends Annotation> getSupportedAnnotation() {
@Override
public boolean checkConstraints(Element annotatedElement, AnnotationMirror annotationMirrorToCheck, AnnotationMirror constraintAnnotationMirror, String attributeNameToBeCheckedByConstraint) {

if (!ElementWrapper.wrap(annotatedElement).isExecutableElement()) {
ElementWrapper<Element> wrappedElement = ElementWrapper.wrap(annotatedElement);
Optional<ElementWrapper<Element>> 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
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()
Expand All @@ -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())
Expand All @@ -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;
}

}

Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<? extends Annotation> getSupportedAnnotation() {
return StringMustMatch.class;
}


@Override
public boolean checkConstraints(Element annotatedElement, AnnotationMirror annotationMirrorToCheck, AnnotationMirror constraintAnnotationMirror, String attributeNameToBeCheckedByConstraint) {

ElementWrapper<Element> wrappedElement = ElementWrapper.wrap(annotatedElement);
Optional<ElementWrapper<Element>> 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<AnnotationValueWrapper> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
@AnnotationWrapper(
value = {
On.class,
OnAnnotationAttributeOfType.class
OnAnnotationAttributeOfType.class,
StringMustMatch.class
})
package io.toolisticon.aptk.constraints;

Expand Down
Loading

0 comments on commit 183be1b

Please sign in to comment.