Skip to content

Commit

Permalink
Add companion classes to Kotlin reflective hierarchy registration
Browse files Browse the repository at this point in the history
Fixes: #37957
(cherry picked from commit e185df8)
  • Loading branch information
geoand authored and gsmet committed Jan 9, 2024
1 parent fffa654 commit f9377cd
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.steps;

import static io.quarkus.deployment.steps.KotlinUtil.isKotlinClass;

import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
Expand Down Expand Up @@ -27,6 +29,8 @@
import org.jboss.jandex.VoidType;
import org.jboss.logging.Logger;

import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
Expand All @@ -51,7 +55,7 @@ public ReflectiveHierarchyIgnoreWarningBuildItem ignoreJavaClassWarnings() {
}

@BuildStep
public void build(CombinedIndexBuildItem combinedIndexBuildItem,
public void build(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities,
List<ReflectiveHierarchyBuildItem> hierarchy,
List<ReflectiveHierarchyIgnoreWarningBuildItem> ignored,
List<ReflectiveClassFinalFieldsWritablePredicateBuildItem> finalFieldsWritablePredicates,
Expand All @@ -73,7 +77,7 @@ public void build(CombinedIndexBuildItem combinedIndexBuildItem,
final Deque<ReflectiveHierarchyVisitor> visits = new ArrayDeque<>();

for (ReflectiveHierarchyBuildItem i : hierarchy) {
addReflectiveHierarchy(combinedIndexBuildItem,
addReflectiveHierarchy(combinedIndexBuildItem, capabilities,
i,
i.hasSource() ? i.getSource() : i.getType().name().toString(),
i.getType(),
Expand Down Expand Up @@ -128,7 +132,7 @@ private void removeIgnored(Map<DotName, Set<String>> unindexedClasses,
}

private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildItem,
ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type,
Capabilities capabilities, ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem, String source, Type type,
Set<DotName> processedReflectiveHierarchies, Map<DotName, Set<String>> unindexedClasses,
Predicate<ClassInfo> finalFieldsWritable, BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
Deque<ReflectiveHierarchyVisitor> visits) {
Expand All @@ -142,45 +146,50 @@ private void addReflectiveHierarchy(CombinedIndexBuildItem combinedIndexBuildIte
return;
}

addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(), type.name(),
addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(),
type.name(),
processedReflectiveHierarchies, unindexedClasses,
finalFieldsWritable, reflectiveClass, visits);

for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownSubclasses(type.name())) {
addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(),
addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
subclass.name(),
subclass.name(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits);
}
for (ClassInfo subclass : combinedIndexBuildItem.getIndex().getAllKnownImplementors(type.name())) {
addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, subclass.name(),
addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
subclass.name(),
subclass.name(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits);
}
} else if (type instanceof ArrayType) {
visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source,
visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities,
reflectiveHierarchyBuildItem, source,
type.asArrayType().constituent(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
} else if (type instanceof ParameterizedType) {
if (!reflectiveHierarchyBuildItem.getIgnoreTypePredicate().test(type.name())) {
addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, type.name(),
addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source, type.name(),
type.name(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits);
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
for (Type typeArgument : parameterizedType.arguments()) {
visits.addLast(
() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, typeArgument,
() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
typeArgument,
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
}
}
}

private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem,
private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem, Capabilities capabilities,
ReflectiveHierarchyBuildItem reflectiveHierarchyBuildItem,
String source,
DotName name,
Expand Down Expand Up @@ -223,7 +232,7 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem
return;
}

visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source,
visits.addLast(() -> addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
info.superName(), initialName,
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
Expand All @@ -237,7 +246,8 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem
}
final Type fieldType = getFieldType(combinedIndexBuildItem, initialName, info, field);
visits.addLast(
() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source, fieldType,
() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
fieldType,
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
}
Expand All @@ -249,11 +259,30 @@ private void addClassTypeHierarchy(CombinedIndexBuildItem combinedIndexBuildItem
method.returnType().kind() == Kind.VOID) {
continue;
}
visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, reflectiveHierarchyBuildItem, source,
visits.addLast(() -> addReflectiveHierarchy(combinedIndexBuildItem, capabilities,
reflectiveHierarchyBuildItem, source,
method.returnType(),
processedReflectiveHierarchies,
unindexedClasses, finalFieldsWritable, reflectiveClass, visits));
}

// for Kotlin classes, we need to register the nested classes as well because companion classes are very often necessary at runtime
if (capabilities.isPresent(Capability.KOTLIN) && isKotlinClass(info)) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Class<?>[] declaredClasses = classLoader.loadClass(info.name().toString()).getDeclaredClasses();
for (Class<?> clazz : declaredClasses) {
DotName dotName = DotName.createSimple(clazz.getName());
addClassTypeHierarchy(combinedIndexBuildItem, capabilities, reflectiveHierarchyBuildItem, source,
dotName, dotName,
processedReflectiveHierarchies, unindexedClasses,
finalFieldsWritable, reflectiveClass, visits);
}
} catch (ClassNotFoundException e) {
log.warnf(e, "Failed to load Class %s", info.name().toString());
}

}
}

private static Type getFieldType(CombinedIndexBuildItem combinedIndexBuildItem, DotName initialName, ClassInfo info,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.it.resteasy.reactive.kotlin

import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.Produces
import jakarta.ws.rs.core.MediaType

@Path("/companion")
class CompanionResource {
@Path("success")
@GET
@Produces(MediaType.APPLICATION_JSON)
fun success() = ResponseData.success()

@Path("failure")
@GET
@Produces(MediaType.APPLICATION_JSON)
fun failure() = ResponseData.failure("error")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.it.resteasy.reactive.kotlin

class ResponseData<T>(
val code: Int = STATUS_CODE.SUCCESS.code,
val msg: String = "",
val data: T? = null,
) {
companion object {
fun success() = ResponseData<Unit>()
fun failure(msg: String) = ResponseData<Unit>(code = STATUS_CODE.ERROR.code, msg = msg)
}
}

enum class STATUS_CODE(val code: Int) {
SUCCESS(200),
ERROR(500)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.quarkus.it.resteasy.reactive.kotlin

import io.quarkus.test.junit.QuarkusIntegrationTest

@QuarkusIntegrationTest class CompanionIT : CompanionTest() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkus.it.resteasy.reactive.kotlin

import io.quarkus.test.junit.QuarkusTest
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import org.hamcrest.Matchers.*
import org.junit.jupiter.api.Test

@QuarkusTest
class CompanionTest {

@Test
fun testSuccessResponseData() {
When { get("/companion/success") } Then
{
statusCode(200)
body(containsString("200"))
}
}

@Test
fun testFailureResponseData() {
When { get("/companion/failure") } Then
{
statusCode(200)
body(containsString("500"), containsString("error"))
}
}
}

0 comments on commit f9377cd

Please sign in to comment.