Skip to content

Commit

Permalink
[#148] Add proper support for records introduced with Java 16
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasstamann committed Apr 9, 2024
1 parent 6425ee9 commit 2beea95
Show file tree
Hide file tree
Showing 15 changed files with 477 additions and 41 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ jobs:
run: echo ${{ secrets.GPG_SECRET_KEYS }} | base64 --decode | gpg --import --no-tty --batch --yes

# Setup JDK and Maven
- name: Set up JDK 9
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 9
java-version: 17
server-id: sonatype-nexus-staging
server-username: OSS_CENTRAL_USERNAME # env variable for Maven Central
server-password: OSS_CENTRAL_PASSWORD # env variable for Maven Central
Expand Down
93 changes: 93 additions & 0 deletions extensions/java16/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>aptk-tools-java16</artifactId>
<packaging>jar</packaging>

<parent>
<groupId>io.toolisticon.aptk</groupId>
<artifactId>extension-parent</artifactId>
<version>0.24.1-148_records-SNAPSHOT</version>
</parent>

<name>aptk-tools-java16</name>


<dependencies>

<!-- Should transitively bind all extensions for lesser java versions -->
<dependency>
<groupId>io.toolisticon.aptk</groupId>
<artifactId>aptk-tools-java9</artifactId>
</dependency>

<dependency>
<groupId>io.toolisticon.aptk</groupId>
<artifactId>aptk-tools</artifactId>
</dependency>

</dependencies>


<build>

<plugins>

<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.0.4,)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>9</version>
</requireJavaVersion>
<bannedDependencies>
<searchTransitive>false</searchTransitive>
<excludes>
<exclude>*</exclude>
</excludes>
<includes>
<include>io.toolisticon.aptk:aptk-tools:*</include>
<include>io.toolisticon.aptk:aptk-tools-java9:*</include>
<include>*:*:*:*:test:*</include>
<include>*:*:*:*:provided:*</include>
</includes>
</bannedDependencies>
</rules>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>16</source>
<target>16</target>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>


</plugins>


</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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<RecordComponentElement> {
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<RecordComponentElementWrapper> 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<RecordComponentElementWrapper> 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<RecordComponentElementWrapper> wrapList(List<RecordComponentElement> moduleElements) {
return moduleElements.stream().map(RecordComponentElementWrapper::new).collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package io.toolisticon.aptk.tools.wrapper;

import io.toolisticon.aptk.common.ToolingProvider;
import io.toolisticon.aptk.tools.corematcher.AptkCoreMatchers;
import io.toolisticon.cute.Cute;
import io.toolisticon.cute.PassIn;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Test;

import javax.lang.model.element.TypeElement;
import java.util.Set;
import java.util.stream.Collectors;

public class Java16Tests {

@PassIn
record MyRecord(String name, String surname){}

@Test
public void test_records_isRecord() {
Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> {
try {
ToolingProvider.setTooling(processingEnvironment);

ElementWrapper<?> elementWrapper = ElementWrapper.wrap(element);

MatcherAssert.assertThat("Should detect record ", elementWrapper.isRecord());
MatcherAssert.assertThat("Should not detect record ", !TypeElementWrapper.getByClass(Java16Tests.class).get().isRecord());

} finally {
ToolingProvider.clearTooling();
}

}).thenExpectThat().compilationSucceeds().executeTest();

}

@Test
public void test_records_isTypeElement() {
Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> {
try {
ToolingProvider.setTooling(processingEnvironment);

ElementWrapper<?> elementWrapper = ElementWrapper.wrap(element);

MatcherAssert.assertThat("Should detect record ", elementWrapper.isTypeElement());
MatcherAssert.assertThat("Should return false", !elementWrapper.isExecutableElement());
MatcherAssert.assertThat("Should return false ", !elementWrapper.isVariableElement());
MatcherAssert.assertThat("Should return false ", !elementWrapper.isTypeParameterElement());
MatcherAssert.assertThat("Should return false", !elementWrapper.isModuleElement());

} finally {
ToolingProvider.clearTooling();
}

}).thenExpectThat().compilationSucceeds().executeTest();

}


@Test
public void test_recordComponent_isRecordComponent() {
Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> {
try {
ToolingProvider.setTooling(processingEnvironment);

TypeElement typeElement = (TypeElement) element;

ElementWrapper<?> elementWrapper = ElementWrapper.wrap(typeElement.getRecordComponents().get(0));

MatcherAssert.assertThat("Should detect record ", elementWrapper.isRecordComponent());
MatcherAssert.assertThat("Should not detect record ", !TypeElementWrapper.getByClass(Java16Tests.class).get().isRecordComponent());

} finally {
ToolingProvider.clearTooling();
}

}).thenExpectThat().compilationSucceeds().executeTest();

}

@Test
public void test_recordComponent_isTypeElement() {
Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> {
try {
ToolingProvider.setTooling(processingEnvironment);

TypeElement typeElement = (TypeElement) element;

ElementWrapper<?> elementWrapper = ElementWrapper.wrap(typeElement.getRecordComponents().get(0));

MatcherAssert.assertThat("Should detect RecordComponentElement ", elementWrapper.isRecordComponentElement());
MatcherAssert.assertThat("Should detect false ", !elementWrapper.isTypeElement());
MatcherAssert.assertThat("Should return false", !elementWrapper.isExecutableElement());
MatcherAssert.assertThat("Should return false ", !elementWrapper.isVariableElement());
MatcherAssert.assertThat("Should return false ", !elementWrapper.isTypeParameterElement());
MatcherAssert.assertThat("Should return false", !elementWrapper.isModuleElement());

} finally {
ToolingProvider.clearTooling();
}

}).thenExpectThat().compilationSucceeds().executeTest();

}


public void test_recordComponent_simpleName() {
Cute.unitTest().when().passInElement().fromClass(MyRecord.class).intoUnitTest( (processingEnvironment, element) -> {
try {
ToolingProvider.setTooling(processingEnvironment);
TypeElement typeElement = (TypeElement) element;

RecordComponentElementWrapper elementWrapper = RecordComponentElementWrapper.wrap(typeElement.getRecordComponents().get(0));
MatcherAssert.assertThat( elementWrapper.getSimpleName(), Matchers.is("name"));

} finally {
ToolingProvider.clearTooling();
}

}).thenExpectThat().compilationSucceeds().executeTest();

}

@Test
public void test_recordComponent_filtering_byTypeElement() {
Cute.unitTest().when( (processingEnvironment) -> {
try {
ToolingProvider.setTooling(processingEnvironment);
TypeElementWrapper typeElement = TypeElementWrapper.getByClass(Java16Tests.class).get();

Set<String> 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()));

} finally {
ToolingProvider.clearTooling();
}

}).thenExpectThat().compilationSucceeds().executeTest();

}

@Test
public void test_recordComponent_filtering_byRecord() {
Cute.unitTest().when( (processingEnvironment) -> {
try {
ToolingProvider.setTooling(processingEnvironment);
TypeElementWrapper typeElement = TypeElementWrapper.getByClass(Java16Tests.class).get();

Set<String> 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()));

} finally {
ToolingProvider.clearTooling();
}

}).thenExpectThat().compilationSucceeds().executeTest();

}

}
12 changes: 12 additions & 0 deletions extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@
</modules>

</profile>
<profile>
<id>java-16</id>
<activation>
<jdk>[16,)</jdk>
</activation>

<modules>
<module>java9</module>
<module>java16</module>
</modules>

</profile>
</profiles>

</project>
Loading

0 comments on commit 2beea95

Please sign in to comment.