diff --git a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java index ab949ec5a..c4da91e91 100644 --- a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java @@ -50,7 +50,7 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti PreferredConstructor constructor = entity.getPersistenceConstructor(); - if (ReflectionUtils.isKotlinClass(entity.getType()) && constructor != null) { + if (ReflectionUtils.isSupportedKotlinClass(entity.getType()) && constructor != null) { PreferredConstructor defaultConstructor = new DefaultingKotlinConstructorResolver(entity) .getDefaultConstructor(); diff --git a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java index 7626cb356..b03a41db0 100644 --- a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java +++ b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java @@ -187,7 +187,7 @@ public interface PreferredConstructorDiscoverer type) { - return ReflectionUtils.isKotlinClass(type) ? KOTLIN : DEFAULT; + return ReflectionUtils.isSupportedKotlinClass(type) ? KOTLIN : DEFAULT; } /** diff --git a/src/main/java/org/springframework/data/repository/core/support/MethodInvocationValidator.java b/src/main/java/org/springframework/data/repository/core/support/MethodInvocationValidator.java index 8ab571a98..29bea7947 100644 --- a/src/main/java/org/springframework/data/repository/core/support/MethodInvocationValidator.java +++ b/src/main/java/org/springframework/data/repository/core/support/MethodInvocationValidator.java @@ -59,7 +59,7 @@ public class MethodInvocationValidator implements MethodInterceptor { */ public static boolean supports(Class repositoryInterface) { - return ReflectionUtils.isKotlinClass(repositoryInterface) + return ReflectionUtils.isSupportedKotlinClass(repositoryInterface) || NullableUtils.isNonNull(repositoryInterface, ElementType.METHOD) || NullableUtils.isNonNull(repositoryInterface, ElementType.PARAMETER); } @@ -153,7 +153,8 @@ public class MethodInvocationValidator implements MethodInterceptor { private static boolean isNullableParameter(MethodParameter parameter) { return requiresNoValue(parameter) || NullableUtils.isExplicitNullable(parameter) - || (ReflectionUtils.isKotlinClass(parameter.getDeclaringClass()) && ReflectionUtils.isNullable(parameter)); + || (ReflectionUtils.isSupportedKotlinClass(parameter.getDeclaringClass()) + && ReflectionUtils.isNullable(parameter)); } private static boolean requiresNoValue(MethodParameter parameter) { diff --git a/src/main/java/org/springframework/data/util/ReflectionUtils.java b/src/main/java/org/springframework/data/util/ReflectionUtils.java index 908b0c89d..36103ef86 100644 --- a/src/main/java/org/springframework/data/util/ReflectionUtils.java +++ b/src/main/java/org/springframework/data/util/ReflectionUtils.java @@ -18,6 +18,9 @@ package org.springframework.data.util; import kotlin.reflect.KFunction; import kotlin.reflect.KType; import kotlin.reflect.jvm.ReflectJvmMapping; +import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader; +import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader.Kind; +import kotlin.reflect.jvm.internal.impl.load.kotlin.reflect.ReflectKotlinClass; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.experimental.UtilityClass; @@ -348,7 +351,7 @@ public class ReflectionUtils { } /** - * Return true if the specified class is a Kotlin one. + * Return {@literal true} if the specified class is a Kotlin one. * * @return {@literal true} if {@code type} is a Kotlin class. * @since 2.0 @@ -360,6 +363,31 @@ public class ReflectionUtils { .anyMatch(annotation -> annotation.getName().equals("kotlin.Metadata")); } + /** + * Return {@literal true} if the specified class is a supported Kotlin class. Currently supported are only + * {@link Kind#CLASS regular Kotlin classes}. Other class types (synthetic, SAM, lambdas) are not supported via + * reflection. + * + * @return {@literal true} if {@code type} is a supported Kotlin class. + * @since 2.0 + */ + public static boolean isSupportedKotlinClass(Class type) { + + if (!isKotlinClass(type)) { + return false; + } + + ReflectKotlinClass kotlinClass = ReflectKotlinClass.Factory.create(type); + + if (kotlinClass == null) { + return false; + } + + KotlinClassHeader classHeader = kotlinClass.getClassHeader(); + + return classHeader.getKind() == Kind.CLASS; + } + /** * Returns {@literal} whether the given {@link MethodParameter} is nullable. Nullable parameters are reference types * and ones that are defined in Kotlin as such. @@ -373,7 +401,7 @@ public class ReflectionUtils { return true; } - if (isKotlinClass(parameter.getDeclaringClass())) { + if (isSupportedKotlinClass(parameter.getDeclaringClass())) { KFunction kotlinFunction = ReflectJvmMapping.getKotlinFunction(parameter.getMethod()); diff --git a/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java b/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java index f19d9046c..afab0af74 100755 --- a/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/util/ReflectionUtilsUnitTests.java @@ -24,12 +24,16 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; +import org.springframework.data.mapping.model.TypeCreatingSyntheticClassKt; import org.springframework.data.repository.sample.User; import org.springframework.data.util.ReflectionUtils.DescribedFieldFilter; import org.springframework.util.ReflectionUtils.FieldFilter; /** + * Unit tests for {@link ReflectionUtils}. + * * @author Oliver Gierke + * @author Mark Paluch */ public class ReflectionUtilsUnitTests { @@ -143,6 +147,20 @@ public class ReflectionUtilsUnitTests { MethodParameter parameter = new MethodParameter(DummyInterface.class.getDeclaredMethod("primitive", int.class), 0); assertThat(ReflectionUtils.isNullable(parameter)).isFalse(); } + + @Test // DATACMNS-1171 + public void discoversKotlinClass() { + + assertThat(ReflectionUtils.isKotlinClass(TypeCreatingSyntheticClass.class)).isTrue(); + assertThat(ReflectionUtils.isSupportedKotlinClass(TypeCreatingSyntheticClass.class)).isTrue(); + } + + @Test // DATACMNS-1171 + public void discoversUnsupportedKotlinClass() { + + assertThat(ReflectionUtils.isKotlinClass(TypeCreatingSyntheticClassKt.class)).isTrue(); + assertThat(ReflectionUtils.isSupportedKotlinClass(TypeCreatingSyntheticClassKt.class)).isFalse(); + } static class Sample { diff --git a/src/test/kotlin/org/springframework/data/mapping/model/PreferredConstructorDiscovererUnitTests.kt b/src/test/kotlin/org/springframework/data/mapping/model/PreferredConstructorDiscovererUnitTests.kt index 9916ae61d..2959b1dc5 100644 --- a/src/test/kotlin/org/springframework/data/mapping/model/PreferredConstructorDiscovererUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/mapping/model/PreferredConstructorDiscovererUnitTests.kt @@ -74,6 +74,17 @@ class PreferredConstructorDiscovererUnitTests { Assertions.assertThat(constructor.parameters.size).isEqualTo(3) } + + @Test // DATACMNS-1171 + @Suppress("UNCHECKED_CAST") + fun `should not resolve constructor for synthetic Kotlin class`() { + + val c = Class.forName("org.springframework.data.mapping.model.TypeCreatingSyntheticClassKt") as Class + + val constructor = PreferredConstructorDiscoverer.discover(c) + + Assertions.assertThat(constructor).isNull() + } data class Simple(val firstname: String) diff --git a/src/test/kotlin/org/springframework/data/mapping/model/TypeCreatingSyntheticClass.kt b/src/test/kotlin/org/springframework/data/mapping/model/TypeCreatingSyntheticClass.kt new file mode 100644 index 000000000..34d70d113 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/mapping/model/TypeCreatingSyntheticClass.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model + +/** + * @author Mark Paluch + */ +class TypeCreatingSyntheticClass { +} + +fun foobar(args: Array) { +} \ No newline at end of file diff --git a/src/test/kotlin/org/springframework/data/util/TypeCreatingSyntheticClass.kt b/src/test/kotlin/org/springframework/data/util/TypeCreatingSyntheticClass.kt new file mode 100644 index 000000000..4ff1b7897 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/util/TypeCreatingSyntheticClass.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.util + +/** + * @author Mark Paluch + */ +class TypeCreatingSyntheticClass { +} + +fun foobar(args: Array) { +} \ No newline at end of file