From 07054960bc15c602aba64f6b331650ee2c14a8e8 Mon Sep 17 00:00:00 2001 From: Tobias Stamann Date: Fri, 5 Apr 2024 16:36:53 +0200 Subject: [PATCH] Added StringMustMatch constraint --- .../aptk/constraints/StringMustMatch.java | 20 ++ .../constraints/AnnotationConstraints.java | 4 +- .../aptk/constraints/BasicConstraints.java | 7 +- ...notationAttributeOfTypeConstraintImpl.java | 174 +++++++++++++++++- .../StringMustMatchConstraintImpl.java | 74 ++++++++ .../aptk/constraints/package-info.java | 3 +- .../constraints/BasicConstraintsTest.java | 50 ++++- .../aptk/tools/wrapper/ElementWrapper.java | 5 +- .../tools/wrapper/PackageElementWrapper.java | 15 ++ .../wrapper/PackageElementWrapperTest.java | 42 +++++ 10 files changed, 380 insertions(+), 14 deletions(-) create mode 100644 constraints/constraint-api/src/main/java/io/toolisticon/aptk/constraints/StringMustMatch.java create mode 100644 constraints/constraint-impl/src/main/java/io/toolisticon/aptk/constraints/StringMustMatchConstraintImpl.java 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 b49671a7..501a8e64 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 @@ -311,7 +311,7 @@ public List> getFlattenedEnclosedElementTree(boolean inc */ public List> getFlattenedEnclosedElementTree(boolean includeSelf, int maxDepth) { return ElementUtils.AccessEnclosingElements.getFlattenedEnclosingElementsTree(this.unwrap(), includeSelf, maxDepth) - .stream().map(ExecutableElementWrapper::wrap).collect(Collectors.toList()); + .stream().map(ElementWrapper::wrap).collect(Collectors.toList()); } /** @@ -664,6 +664,7 @@ public boolean isModuleElement() { return ElementUtils.CastElement.isModuleElement(this.element); } + /** * Converts wrapper to a PackageElementWrapper by casting and re-wrapping wrapped element. * @@ -719,4 +720,6 @@ public static ExecutableElementWrapper toExecutableElementWrapper(ElementWrapper return ExecutableElementWrapper.wrap(ElementUtils.CastElement.castToExecutableElement(wrapper.unwrap())); } + + } diff --git a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapper.java b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapper.java index bb1fc229..d1f42f57 100644 --- a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapper.java +++ b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapper.java @@ -1,6 +1,9 @@ package io.toolisticon.aptk.tools.wrapper; +import io.toolisticon.aptk.common.ToolingProvider; + import javax.lang.model.element.PackageElement; +import java.util.Optional; /** * Wrapper for PackageElement. @@ -45,4 +48,16 @@ public static PackageElementWrapper wrap(PackageElement element) { return new PackageElementWrapper(element); } + /** + * Gets the PackageElementWrapper by using a fully qualified package name. + * @param fqn the fully qualified name of the package + * @return an Optional containing the PackageElementWrapper or an empty Optional if the package element can't be found + */ + public static Optional getByFqn(String fqn) { + + PackageElement packageElement = ToolingProvider.getTooling().getElements().getPackageElement(fqn); + return packageElement != null ? Optional.of(PackageElementWrapper.wrap(packageElement)) : Optional.empty(); + + } + } diff --git a/tools/src/test/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapperTest.java b/tools/src/test/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapperTest.java index 758f77c7..15eec98f 100644 --- a/tools/src/test/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapperTest.java +++ b/tools/src/test/java/io/toolisticon/aptk/tools/wrapper/PackageElementWrapperTest.java @@ -1,5 +1,6 @@ package io.toolisticon.aptk.tools.wrapper; +import io.toolisticon.aptk.common.ToolingProvider; import io.toolisticon.cute.CompileTestBuilder; import io.toolisticon.cute.PassIn; import org.hamcrest.MatcherAssert; @@ -7,6 +8,7 @@ import org.junit.Test; import org.mockito.Mockito; +import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; @@ -26,6 +28,46 @@ public void test_getQualifiedName() { } + @Test + public void test_getByFqn() { + + CompileTestBuilder.unitTest().defineTest((processingEnvironment, element) -> { + + try { + + ToolingProvider.setTooling(processingEnvironment); + + String fqn = PackageElementWrapperTest.class.getPackage().getName(); + MatcherAssert.assertThat(PackageElementWrapper.getByFqn(fqn).get().getQualifiedName(), Matchers.is(fqn)); + + } finally { + ToolingProvider.clearTooling(); + } + + }).executeTest(); + + } + + @Test + public void test_getByFqn_nonExistingPackage() { + + CompileTestBuilder.unitTest().defineTest((processingEnvironment, element) -> { + + try { + + ToolingProvider.setTooling(processingEnvironment); + + String fqn = "xyz.xyz.xyz"; + MatcherAssert.assertThat("Package must not be found", !PackageElementWrapper.getByFqn(fqn).isPresent()); + + } finally { + ToolingProvider.clearTooling(); + } + + }).executeTest(); + + } + @Test public void test_isUnnamed() { PackageElement packageElement = Mockito.mock(PackageElement.class);