Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.IdentityHashMap;

import org.jspecify.annotations.Nullable;

Expand Down Expand Up @@ -141,19 +143,42 @@ public static boolean isInlineClass(Class<?> clazz) {
* @since 7.0
*/
public static boolean hasSerializableAnnotation(ResolvableType type) {
return hasSerializableAnnotation(type, new IdentityHashMap<>());
}

private static boolean hasSerializableAnnotation(ResolvableType type, IdentityHashMap<Type, Boolean> visitedTypes) {
Class<?> resolvedClass = type.resolve();
if (KOTLIN_SERIALIZABLE == null || resolvedClass == null) {
return false;
}
if (resolvedClass.isAnnotationPresent(KOTLIN_SERIALIZABLE)) {
return true;
}
for (ResolvableType genericType : type.getGenerics()) {
if (hasSerializableAnnotation(genericType)) {
return true;

Type sourceType = type.getType();
if (markVisited(visitedTypes, sourceType)) {
// Already visited - short circuit.
return false;
}

try {
for (ResolvableType genericType : type.getGenerics()) {
if (hasSerializableAnnotation(genericType, visitedTypes)) {
return true;
}
}
return false;
}
finally {
// `visitedTypes` are for cycle detection only, not global memoization.
visitedTypes.remove(sourceType);
}
return false;
}

@SuppressWarnings("ConstantValue")
private static boolean markVisited(IdentityHashMap<Type, Boolean> visitedTypes, Type type) {
// JSpecify based nullability analysis thinks that `put` always returns a non-null value
return visitedTypes.put(type, Boolean.TRUE) != null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ class KotlinDetectorTests {
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.NONE)).isFalse()
}

@Test
fun hasSerializableAnnotationWithRecursiveGeneric() {
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(
ResolvableType.forClass(RecursiveGeneric::class.java))).isFalse()
}

// Real life example: Guava's `ImmutableEnumSet`.
class RecursiveGeneric<T : RecursiveGeneric<T>>

@JvmInline
value class ValueClass(val value: String)

Expand Down