From 86dfa0a732dc3d1122bdcb2e32c0363abb3c6345 Mon Sep 17 00:00:00 2001 From: Tobias Stamann Date: Tue, 9 Apr 2024 16:15:22 +0200 Subject: [PATCH] [#148] support records and modules via reflection --- .../RecordComponentElementWrapper.java | 72 ------------------- .../aptk/tools/wrapper/Java16Tests.java | 31 ++++---- .../tools/wrapper/ModuleElementWrapper.java | 47 ------------ .../wrapper/ModuleElementWrapperTest.java | 46 ++++++------ .../aptk/tools/wrapper/ElementWrapper.java | 9 +++ .../tools/wrapper/ModuleElementWrapper.java | 71 ++++++++++++++++++ .../RecordComponentElementWrapper.java | 49 +++++++++++++ .../tools/wrapper/TypeElementWrapper.java | 17 +++++ 8 files changed, 185 insertions(+), 157 deletions(-) delete mode 100644 extensions/java16/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java delete mode 100644 extensions/java9/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java create mode 100644 tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java create mode 100644 tools/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java diff --git a/extensions/java16/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java b/extensions/java16/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java deleted file mode 100644 index 30cb7233..00000000 --- a/extensions/java16/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.toolisticon.aptk.tools.wrapper; - -import javax.lang.model.element.Element; -import javax.lang.model.element.RecordComponentElement; -import javax.lang.model.element.TypeElement; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Wrapper class for RecordComponentElementWrapper. - */ -public class RecordComponentElementWrapper extends ElementWrapper { - private RecordComponentElementWrapper(RecordComponentElement recordComponentElement) { - super(recordComponentElement); - } - - /** - * Gets the enclosing records TypeElement - * @return the wrapped enclosing records TypeElement - */ - public TypeElementWrapper getEnclosingRecordTypeElement() { - return TypeElementWrapper.wrap((TypeElement) element.getEnclosingElement()); - } - - /** - * Wraps the getAccessor method, but returns a ExecutableElementWrapper - * @return the accessors wrapped ExecutableElement - */ - public ExecutableElementWrapper getAccessor() { - return ExecutableElementWrapper.wrap(element.getAccessor()); - } - - /** - * Gets the record components for a TypeElementWrapper. - * @param typeElement the type element wrapper to get the record components for - * @return A list containing the wrapped RecordComponentElement if they exist, otherwise an empty list. - */ - public static List getRecordComponents(TypeElementWrapper typeElement) { - if (typeElement == null) { - return Collections.EMPTY_LIST; - } - return getRecordComponents(typeElement.unwrap()); - } - - /** - * Gets the record components for a TypeElement. - * @param typeElement the type element wrapper to get the record components for - * @return A list containing the wrapped RecordComponentElement if they exist, otherwise an empty list. - */ - public static List getRecordComponents(TypeElement typeElement) { - if (typeElement == null) { - return Collections.EMPTY_LIST; - } - - return typeElement.getRecordComponents().stream().map(RecordComponentElementWrapper::wrap).toList(); - - } - - public static RecordComponentElementWrapper toRecordComponentElement(Element element) { - return RecordComponentElementWrapper.wrap((RecordComponentElement) element); - } - - public static RecordComponentElementWrapper wrap(RecordComponentElement moduleElement) { - return new RecordComponentElementWrapper(moduleElement); - } - - public static List wrapList(List moduleElements) { - return moduleElements.stream().map(RecordComponentElementWrapper::new).collect(Collectors.toList()); - } - -} diff --git a/extensions/java16/src/test/java/io/toolisticon/aptk/tools/wrapper/Java16Tests.java b/extensions/java16/src/test/java/io/toolisticon/aptk/tools/wrapper/Java16Tests.java index dc2d1fee..74574d6a 100644 --- a/extensions/java16/src/test/java/io/toolisticon/aptk/tools/wrapper/Java16Tests.java +++ b/extensions/java16/src/test/java/io/toolisticon/aptk/tools/wrapper/Java16Tests.java @@ -15,11 +15,12 @@ public class Java16Tests { @PassIn - record MyRecord(String name, String surname){} + record MyRecord(String name, String surname) { + } @Test public void test_records_isRecord() { - Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> { + Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest((processingEnvironment, element) -> { try { ToolingProvider.setTooling(processingEnvironment); @@ -38,7 +39,7 @@ public void test_records_isRecord() { @Test public void test_records_isTypeElement() { - Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> { + Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest((processingEnvironment, element) -> { try { ToolingProvider.setTooling(processingEnvironment); @@ -61,7 +62,7 @@ public void test_records_isTypeElement() { @Test public void test_recordComponent_isRecordComponent() { - Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> { + Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest((processingEnvironment, element) -> { try { ToolingProvider.setTooling(processingEnvironment); @@ -82,7 +83,7 @@ public void test_recordComponent_isRecordComponent() { @Test public void test_recordComponent_isTypeElement() { - Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> { + Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest((processingEnvironment, element) -> { try { ToolingProvider.setTooling(processingEnvironment); @@ -107,13 +108,13 @@ public void test_recordComponent_isTypeElement() { public void test_recordComponent_simpleName() { - Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> { + Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest((processingEnvironment, element) -> { try { ToolingProvider.setTooling(processingEnvironment); - TypeElement typeElement = (TypeElement) element; + TypeElementWrapper typeElement = TypeElementWrapper.wrap(element); - RecordComponentElementWrapper elementWrapper = RecordComponentElementWrapper.wrap(typeElement.getRecordComponents().get(0)); - MatcherAssert.assertThat( elementWrapper.getSimpleName(), Matchers.is("name")); + RecordComponentElementWrapper elementWrapper = typeElement.getRecordComponents().get(0); + MatcherAssert.assertThat(elementWrapper.getSimpleName(), Matchers.is("name")); } finally { ToolingProvider.clearTooling(); @@ -125,14 +126,14 @@ public void test_recordComponent_simpleName() { @Test public void test_recordComponent_filtering_byTypeElement() { - Cute.unitTest().when( (processingEnvironment) -> { + Cute.unitTest().when((processingEnvironment) -> { try { ToolingProvider.setTooling(processingEnvironment); TypeElementWrapper typeElement = TypeElementWrapper.getByClass(Java16Tests.class).get(); - Set enclosedTypeElements = typeElement.filterFlattenedEnclosedElementTree().applyFilter(AptkCoreMatchers.IS_TYPE_ELEMENT).getResult().stream().map(e -> e.getQualifiedName().toString()).collect(Collectors.toSet()); + Set enclosedTypeElements = typeElement.filterFlattenedEnclosedElementTree().applyFilter(AptkCoreMatchers.IS_TYPE_ELEMENT).getResult().stream().map(e -> e.getQualifiedName().toString()).collect(Collectors.toSet()); - MatcherAssert.assertThat( enclosedTypeElements, Matchers.contains(MyRecord.class.getCanonicalName())); + MatcherAssert.assertThat(enclosedTypeElements, Matchers.contains(MyRecord.class.getCanonicalName())); } finally { ToolingProvider.clearTooling(); @@ -144,15 +145,15 @@ public void test_recordComponent_filtering_byTypeElement() { @Test public void test_recordComponent_filtering_byRecord() { - Cute.unitTest().when( (processingEnvironment) -> { + Cute.unitTest().when((processingEnvironment) -> { try { ToolingProvider.setTooling(processingEnvironment); TypeElementWrapper typeElement = TypeElementWrapper.getByClass(Java16Tests.class).get(); - Set enclosedTypeElements = typeElement.filterFlattenedEnclosedElementTree().applyFilter(AptkCoreMatchers.IS_RECORD).getResult().stream().map(e -> e.getQualifiedName().toString()).collect(Collectors.toSet()); + Set enclosedTypeElements = typeElement.filterFlattenedEnclosedElementTree().applyFilter(AptkCoreMatchers.IS_RECORD).getResult().stream().map(e -> e.getQualifiedName().toString()).collect(Collectors.toSet()); MatcherAssert.assertThat(enclosedTypeElements, Matchers.hasSize(1)); - MatcherAssert.assertThat( enclosedTypeElements, Matchers.contains(MyRecord.class.getCanonicalName())); + MatcherAssert.assertThat(enclosedTypeElements, Matchers.contains(MyRecord.class.getCanonicalName())); } finally { ToolingProvider.clearTooling(); diff --git a/extensions/java9/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java b/extensions/java9/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java deleted file mode 100644 index aeea5432..00000000 --- a/extensions/java9/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.toolisticon.aptk.tools.wrapper; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ModuleElement; -import java.util.List; -import java.util.stream.Collectors; - -public class ModuleElementWrapper extends ElementWrapper { - - private ModuleElementWrapper(ModuleElement moduleElement) { - super(moduleElement); - } - - public String getQualifiedName() { - return this.element.getQualifiedName().toString(); - } - - public boolean hasQualifiedName(String name) { - return name != null && this.getQualifiedName().equals(name); - } - - public boolean isOpen() { - return this.element.isOpen(); - } - - public boolean isUnnamed() { - return this.element.isUnnamed(); - } - - public List getDirectives() { - return this.element.getDirectives(); - } - - public static ModuleElementWrapper toModuleElement(Element element) { - return ModuleElementWrapper.wrap((ModuleElement) element); - } - - public static ModuleElementWrapper wrap(ModuleElement moduleElement) { - return new ModuleElementWrapper(moduleElement); - } - - public static List wrapList(List moduleElements) { - return moduleElements.stream().map(ModuleElementWrapper::new).collect(Collectors.toList()); - } - -} diff --git a/extensions/java9/src/test/java/io/toolisticon/aptk/wrapper/ModuleElementWrapperTest.java b/extensions/java9/src/test/java/io/toolisticon/aptk/wrapper/ModuleElementWrapperTest.java index 406c28b2..d2d80d18 100644 --- a/extensions/java9/src/test/java/io/toolisticon/aptk/wrapper/ModuleElementWrapperTest.java +++ b/extensions/java9/src/test/java/io/toolisticon/aptk/wrapper/ModuleElementWrapperTest.java @@ -7,6 +7,7 @@ import org.junit.Test; import org.mockito.Mockito; +import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ModuleElement; import javax.lang.model.element.Name; @@ -18,10 +19,14 @@ public class ModuleElementWrapperTest { + + @Test public void testCreationOfWrapperAndUnwrap() { ModuleElement moduleElement = Mockito.mock(ModuleElement.class); - ModuleElementWrapper unit = ModuleElementWrapper.wrap(moduleElement); + Mockito.when(moduleElement.getKind()).thenReturn(ElementKind.MODULE); + + ModuleElementWrapper unit = ModuleElementWrapper.wrap((Element)moduleElement); MatcherAssert.assertThat(unit, Matchers.notNullValue()); MatcherAssert.assertThat(unit.unwrap(), Matchers.is(moduleElement)); } @@ -29,20 +34,24 @@ public void testCreationOfWrapperAndUnwrap() { @Test public void test_getQualifiedName() { ModuleElement moduleElement = Mockito.mock(ModuleElement.class); + Mockito.when(moduleElement.getKind()).thenReturn(ElementKind.MODULE); + Name name = Mockito.mock(Name.class); Mockito.when(name.toString()).thenReturn("JUPP"); Mockito.when(moduleElement.getQualifiedName()).thenReturn(name); - ModuleElementWrapper unit = ModuleElementWrapper.wrap(moduleElement); + ModuleElementWrapper unit = ModuleElementWrapper.wrap((Element)moduleElement); MatcherAssert.assertThat(unit.getQualifiedName(), Matchers.is("JUPP")); } @Test public void test_hasQualifiedName() { ModuleElement moduleElement = Mockito.mock(ModuleElement.class); + Mockito.when(moduleElement.getKind()).thenReturn(ElementKind.MODULE); + Name name = Mockito.mock(Name.class); Mockito.when(name.toString()).thenReturn("YES"); Mockito.when(moduleElement.getQualifiedName()).thenReturn(name); - ModuleElementWrapper unit = ModuleElementWrapper.wrap(moduleElement); + ModuleElementWrapper unit = ModuleElementWrapper.wrap((Element)moduleElement); MatcherAssert.assertThat(unit.hasQualifiedName("YES"), Matchers.is(true)); MatcherAssert.assertThat(unit.hasQualifiedName("NO"), Matchers.is(false)); MatcherAssert.assertThat(unit.hasQualifiedName(null), Matchers.is(false)); @@ -51,7 +60,9 @@ public void test_hasQualifiedName() { @Test public void proxyTests_isOpen() { ModuleElement moduleElement = Mockito.spy(ModuleElement.class); - ModuleElementWrapper unit = ModuleElementWrapper.wrap(moduleElement); + Mockito.when(moduleElement.getKind()).thenReturn(ElementKind.MODULE); + + ModuleElementWrapper unit = ModuleElementWrapper.wrap((Element)moduleElement); unit.isOpen(); Mockito.verify(moduleElement, Mockito.times(1)).isOpen(); @@ -61,28 +72,33 @@ public void proxyTests_isOpen() { @Test public void proxyTests_isUnnamed() { ModuleElement moduleElement = Mockito.spy(ModuleElement.class); - ModuleElementWrapper unit = ModuleElementWrapper.wrap(moduleElement); + Mockito.when(moduleElement.getKind()).thenReturn(ElementKind.MODULE); + + ModuleElementWrapper unit = ModuleElementWrapper.wrap((Element)moduleElement); unit.isUnnamed(); Mockito.verify(moduleElement, Mockito.times(1)).isUnnamed(); } + /*- + // TODO: MUST IMPLEMENT DIRECTIVES VIA REFLECTION @Test public void proxyTests_getDirectives() { ModuleElement moduleElement = Mockito.spy(ModuleElement.class); - ModuleElementWrapper unit = ModuleElementWrapper.wrap(moduleElement); + ModuleElementWrapper unit = ModuleElementWrapper.wrap((Element)moduleElement); unit.getDirectives(); Mockito.verify(moduleElement, Mockito.times(1)).getDirectives(); } + */ @Test public void test_isModuleElement() { ModuleElement moduleElement = Mockito.mock(ModuleElement.class); - Mockito.when(moduleElement.getKind()).thenReturn(ElementKind.MODULE); + MatcherAssert.assertThat(ElementWrapper.wrap(moduleElement).isModule(), Matchers.is(true)); @@ -91,21 +107,5 @@ public void test_isModuleElement() { MatcherAssert.assertThat(ElementWrapper.wrap(typeElement).isModuleElement(), Matchers.is(false)); } -/*- - - public static boolean isModuleElement(Element element) { - return element != null & element.getKind().equals(ElementKind.MODULE); - } - - public static ModuleElementWrapperTest toModuleElement(Element element) { - return ModuleElementWrapperTest.wrap((ModuleElement) element); - } - - public static ModuleElementWrapperTest wrap(ModuleElement moduleElement) { - return new ModuleElementWrapperTest(moduleElement); - } - - - */ } 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 0de9deb0..dd6b39ea 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 @@ -16,6 +16,7 @@ import javax.lang.model.element.TypeElement; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -746,4 +747,12 @@ public static ExecutableElementWrapper toExecutableElementWrapper(ElementWrapper return ExecutableElementWrapper.wrap(ElementUtils.CastElement.castToExecutableElement(wrapper.unwrap())); } + protected TARGET_TYPE invokeParameterlessMethodOfElement(String methodName, TARGET_TYPE defaultReturnValue) { + try { + return (TARGET_TYPE) this.element.getClass().getMethod(methodName).invoke(element); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + return defaultReturnValue; + } + } + } diff --git a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java new file mode 100644 index 00000000..77b01c59 --- /dev/null +++ b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/ModuleElementWrapper.java @@ -0,0 +1,71 @@ +package io.toolisticon.aptk.tools.wrapper; + +import javax.lang.model.element.Element; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ModuleElementWrapper extends ElementWrapper { + + private ModuleElementWrapper(Element moduleElement) { + super(moduleElement); + } + + public String getQualifiedName() { + // default value not needed + return invokeParameterlessMethodOfElement("getQualifiedName", null).toString(); + } + + public boolean hasQualifiedName(String name) { + return name != null && this.getQualifiedName().equals(name); + } + + public boolean isOpen() { + // default value not needed + return this.invokeParameterlessMethodOfElement("isOpen", false); + } + + public boolean isUnnamed() { + // default value not needed + return this.invokeParameterlessMethodOfElement("isUnnamed", false); + } + + /*- + public List getDirectives() { + return this.element.getDirectives(); + } + */ + + /** + * Converts an ElementWrapper to a ModuleElementWrapper id the wrapped element is a ModuleElement + * + * @param element the element to convert + * @return a ModuleElementWrapper wrapping passed ElementWrapper wraps a ModuleElement, otherwise null + */ + public static ModuleElementWrapper toModuleElement(ElementWrapper element) { + return ModuleElementWrapper.wrap(element.unwrap()); + } + + /** + * Wraps the passed element in ModuleElementWrapper if it is a ModuleElement. + * + * @param element the element to wrap + * @return a ModuleElementWrapper wrapping passed element if it is a ModuleElement, otherwise null + */ + public static ModuleElementWrapper wrap(Element element) { + return (element == null || !"MODULE".equals(element.getKind().name())) ? null : new ModuleElementWrapper(element); + } + + /** + * Wraps a List of ModuleElements. + * Unfortunately because of backward compatibility it is not possible to limit lists component type to ModuleElement. + * So this method will try to wrap all Elements of the passed list. It will drop those not being ModuleElements. + * + * @param moduleElements A List of ModuleElements. + * @return A list of ModuleElementWrapper of elements representing modules that could successfully wrapped, or an empty list. + */ + public static List wrapList(List moduleElements) { + return moduleElements.stream().map(ModuleElementWrapper::new).filter(Objects::nonNull).collect(Collectors.toList()); + } + +} diff --git a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java new file mode 100644 index 00000000..a08bd743 --- /dev/null +++ b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/RecordComponentElementWrapper.java @@ -0,0 +1,49 @@ +package io.toolisticon.aptk.tools.wrapper; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +/** + * Wrapper class for RecordComponentElementWrapper. + */ +public class RecordComponentElementWrapper extends ElementWrapper { + private RecordComponentElementWrapper(Element recordComponentElement) { + super(recordComponentElement); + } + + /** + * Gets the enclosing records TypeElement + * + * @return the wrapped enclosing records TypeElement + */ + public TypeElementWrapper getEnclosingRecordTypeElement() { + return TypeElementWrapper.wrap((TypeElement) element.getEnclosingElement()); + } + + /** + * Wraps the getAccessor method, but returns a ExecutableElementWrapper + * + * @return the accessors wrapped ExecutableElement + */ + public ExecutableElementWrapper getAccessor() { + // doesn't use default value + return ExecutableElementWrapper.wrap(this.invokeParameterlessMethodOfElement("getAccessor", null)); + } + + public static RecordComponentElementWrapper toRecordComponentElement(ElementWrapper element) { + if (element == null) { + return null; + } + return RecordComponentElementWrapper.wrap(element.unwrap()); + } + + public static RecordComponentElementWrapper wrap(Element element) { + if (element == null || !"RECORD_COMPONENT".equals(element.getKind().name())) { + return null; + } + return new RecordComponentElementWrapper(element); + } + + +} diff --git a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/TypeElementWrapper.java b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/TypeElementWrapper.java index 830676e0..498ce141 100644 --- a/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/TypeElementWrapper.java +++ b/tools/src/main/java/io/toolisticon/aptk/tools/wrapper/TypeElementWrapper.java @@ -5,6 +5,7 @@ import io.toolisticon.aptk.tools.corematcher.AptkCoreMatchers; import io.toolisticon.aptk.tools.fluentfilter.FluentElementFilter; +import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; @@ -12,6 +13,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -261,6 +263,21 @@ public List getEnumValues() { : null; } + /** + * Returns the record components of this class or interface element in declaration order. + * @return the record components, or an empty list if there are none + */ + public List getRecordComponents() { + + if (!isRecord()) { + return Collections.EMPTY_LIST; + } + List recordComponentElements = this.invokeParameterlessMethodOfElement("getRecordComponents", Collections.EMPTY_LIST); + + return recordComponentElements.stream().map(RecordComponentElementWrapper::wrap).collect(Collectors.toList()); + + } + /** * Wraps a TypeElement. *