From 22b69713183b6c9f49db8e29b68a2c84257f9b7f Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 25 Aug 2017 13:03:07 +0200 Subject: [PATCH] DATACMNS-1126 - Polishing. Moved PreferredConstructorDiscoverers into PreferredConstructorDiscoverer to hide more API. Renamed ClassGeneratingKotlinEntityInstantiator to KotlinClassGeneratingEntityInstantiator. Use org.springframework.util.Assert instead of com.mysema.commons.lang.Assert. Original pull request: #233. --- .../data/convert/EntityInstantiators.java | 2 +- ...tlinClassGeneratingEntityInstantiator.java | 27 ++- .../model/PreferredConstructorDiscoverer.java | 172 +++++++++++++++- .../PreferredConstructorDiscoverers.java | 183 ------------------ ...ssGeneratingEntityInstantiatorUnitTests.kt | 8 +- 5 files changed, 190 insertions(+), 202 deletions(-) delete mode 100644 src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverers.java diff --git a/src/main/java/org/springframework/data/convert/EntityInstantiators.java b/src/main/java/org/springframework/data/convert/EntityInstantiators.java index cf05f47a8..1a940238a 100644 --- a/src/main/java/org/springframework/data/convert/EntityInstantiators.java +++ b/src/main/java/org/springframework/data/convert/EntityInstantiators.java @@ -57,7 +57,7 @@ public class EntityInstantiators { * @param customInstantiators must not be {@literal null}. */ public EntityInstantiators(Map, EntityInstantiator> customInstantiators) { - this(new ClassGeneratingKotlinEntityInstantiator(), customInstantiators); + this(new KotlinClassGeneratingEntityInstantiator(), customInstantiators); } /** diff --git a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java index 03b94e61e..ab949ec5a 100644 --- a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java @@ -30,16 +30,16 @@ import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.util.ReflectionUtils; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; /** * Kotlin-specific extension to {@link ClassGeneratingEntityInstantiator} that adapts Kotlin constructors with * defaulting. * * @author Mark Paluch + * @author Oliver Gierke * @since 2.0 */ -public class ClassGeneratingKotlinEntityInstantiator extends ClassGeneratingEntityInstantiator { +public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInstantiator { /* * (non-Javadoc) @@ -48,14 +48,18 @@ public class ClassGeneratingKotlinEntityInstantiator extends ClassGeneratingEnti @Override protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity entity) { - if (ReflectionUtils.isKotlinClass(entity.getType()) && entity.getPersistenceConstructor() != null) { + PreferredConstructor constructor = entity.getPersistenceConstructor(); + + if (ReflectionUtils.isKotlinClass(entity.getType()) && constructor != null) { PreferredConstructor defaultConstructor = new DefaultingKotlinConstructorResolver(entity) .getDefaultConstructor(); if (defaultConstructor != null) { - return new DefaultingKotlinClassInstantiatorAdapter(createObjectInstantiator(entity, defaultConstructor), - entity.getPersistenceConstructor()); + + ObjectInstantiator instantiator = createObjectInstantiator(entity, defaultConstructor); + + return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor); } } @@ -71,7 +75,7 @@ public class ClassGeneratingKotlinEntityInstantiator extends ClassGeneratingEnti */ static class DefaultingKotlinConstructorResolver { - @Nullable private final PreferredConstructor defaultConstructor; + private final @Nullable PreferredConstructor defaultConstructor; @SuppressWarnings("unchecked") DefaultingKotlinConstructorResolver(PersistentEntity entity) { @@ -90,12 +94,14 @@ public class ClassGeneratingKotlinEntityInstantiator extends ClassGeneratingEnti @Nullable private static Constructor resolveDefaultConstructor(PersistentEntity entity) { - if (entity.getPersistenceConstructor() == null) { + PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); + + if (persistenceConstructor == null) { return null; } Constructor hit = null; - Constructor constructor = entity.getPersistenceConstructor().getConstructor(); + Constructor constructor = persistenceConstructor.getConstructor(); for (Constructor candidate : entity.getType().getDeclaredConstructors()) { @@ -192,7 +198,10 @@ public class ClassGeneratingKotlinEntityInstantiator extends ClassGeneratingEnti ParameterValueProvider

provider) { PreferredConstructor preferredConstructor = entity.getPersistenceConstructor(); - Assert.notNull(preferredConstructor, "PreferredConstructor must not be null!"); + + if (preferredConstructor == null) { + throw new IllegalArgumentException("PreferredConstructor must not be null!"); + } int[] defaulting = new int[(synthetic.getParameterCount() / 32) + 1]; 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 a71cac255..929d07a65 100644 --- a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java +++ b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java @@ -15,13 +15,27 @@ */ package org.springframework.data.mapping.model; +import kotlin.jvm.JvmClassMappingKt; +import kotlin.reflect.KFunction; +import kotlin.reflect.full.KClasses; +import kotlin.reflect.jvm.ReflectJvmMapping; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; + +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; +import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.ReflectionUtils; +import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; - -import com.mysema.commons.lang.Assert; +import org.springframework.util.Assert; /** * Helper class to find a {@link PreferredConstructor}. @@ -44,7 +58,8 @@ public interface PreferredConstructorDiscoverer> PreferredConstructor discover(TypeInformation type, + @Nullable PersistentEntity entity) { + + boolean noArgConstructorFound = false; + int numberOfArgConstructors = 0; + Class rawOwningType = type.getType(); + PreferredConstructor constructor = null; + + for (Constructor candidate : rawOwningType.getDeclaredConstructors()) { + + PreferredConstructor preferredConstructor = buildPreferredConstructor(candidate, type, entity); + + // Synthetic constructors should not be considered + if (preferredConstructor.getConstructor().isSynthetic()) { + continue; + } + + // Explicitly defined constructor trumps all + if (preferredConstructor.isExplicitlyAnnotated()) { + return preferredConstructor; + } + + // No-arg constructor trumps custom ones + if (constructor == null || preferredConstructor.isNoArgConstructor()) { + constructor = preferredConstructor; + } + + if (preferredConstructor.isNoArgConstructor()) { + noArgConstructorFound = true; + } else { + numberOfArgConstructors++; + } + } + + if (!noArgConstructorFound && numberOfArgConstructors > 1) { + constructor = null; + } + + return constructor; + } + }, + + /** + * Discovers a {@link PreferredConstructor} for Kotlin types. + */ + KOTLIN { + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.PreferredConstructorDiscoverers#discover(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.PersistentEntity) + */ + @Nullable + @Override + > PreferredConstructor discover(TypeInformation type, + @Nullable PersistentEntity entity) { + + Class rawOwningType = type.getType(); + + return Arrays.stream(rawOwningType.getDeclaredConstructors()) // + .map(it -> buildPreferredConstructor(it, type, entity)) // + .filter(it -> !it.getConstructor().isSynthetic()) // Synthetic constructors should not be considered + .filter(PreferredConstructor::isExplicitlyAnnotated) // Explicitly defined constructor trumps all + .findFirst() // + .orElseGet(() -> { + + KFunction primaryConstructor = KClasses + .getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(type.getType())); + Constructor javaConstructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor); + + return javaConstructor != null ? buildPreferredConstructor(javaConstructor, type, entity) : null; + }); + } + }; + + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); + + /** + * Find the appropriate discoverer for {@code type}. + * + * @param type must not be {@literal null}. + * @return the appropriate discoverer for {@code type}. + */ + private static Discoverers findDiscoverer(Class type) { + return ReflectionUtils.isKotlinClass(type) ? KOTLIN : DEFAULT; + } + + /** + * Discovers a constructor for the given type. + * + * @param type must not be {@literal null}. + * @param entity may be {@literal null}. + * @return the {@link PreferredConstructor} if found or {@literal null}. + */ + @Nullable + abstract > PreferredConstructor discover(TypeInformation type, + @Nullable PersistentEntity entity); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static > PreferredConstructor buildPreferredConstructor( + Constructor constructor, TypeInformation typeInformation, @Nullable PersistentEntity entity) { + + List> parameterTypes = typeInformation.getParameterTypes(constructor); + + if (parameterTypes.isEmpty()) { + return new PreferredConstructor<>((Constructor) constructor); + } + + String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(constructor); + + Parameter[] parameters = new Parameter[parameterTypes.size()]; + Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); + + for (int i = 0; i < parameterTypes.size(); i++) { + + String name = parameterNames == null ? null : parameterNames[i]; + TypeInformation type = parameterTypes.get(i); + Annotation[] annotations = parameterAnnotations[i]; + + parameters[i] = new Parameter(name, type, annotations, entity); + } + + return new PreferredConstructor<>((Constructor) constructor, parameters); + } } } diff --git a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverers.java b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverers.java deleted file mode 100644 index 9fbafb58d..000000000 --- a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverers.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2011-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; - -import kotlin.jvm.JvmClassMappingKt; -import kotlin.reflect.KFunction; -import kotlin.reflect.full.KClasses; -import kotlin.reflect.jvm.ReflectJvmMapping; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.List; - -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; -import org.springframework.data.util.ReflectionUtils; -import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; - -/** - * Helper class to find a {@link PreferredConstructor}. - * - * @author Oliver Gierke - * @author Christoph Strobl - * @author Roman Rodov - * @author Mark Paluch - * @since 2.0 - */ -enum PreferredConstructorDiscoverers { - - /** - * Discovers a {@link PreferredConstructor} for Java types. - */ - DEFAULT { - - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.model.PreferredConstructorDiscoverers#discover(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.PersistentEntity) - */ - @Nullable - @Override - public > PreferredConstructor discover(TypeInformation type, - @Nullable PersistentEntity entity) { - - boolean noArgConstructorFound = false; - int numberOfArgConstructors = 0; - Class rawOwningType = type.getType(); - PreferredConstructor constructor = null; - - for (Constructor candidate : rawOwningType.getDeclaredConstructors()) { - - PreferredConstructor preferredConstructor = buildPreferredConstructor(candidate, type, entity); - - // Synthetic constructors should not be considered - if (preferredConstructor.getConstructor().isSynthetic()) { - continue; - } - - // Explicitly defined constructor trumps all - if (preferredConstructor.isExplicitlyAnnotated()) { - return preferredConstructor; - } - - // No-arg constructor trumps custom ones - if (constructor == null || preferredConstructor.isNoArgConstructor()) { - constructor = preferredConstructor; - } - - if (preferredConstructor.isNoArgConstructor()) { - noArgConstructorFound = true; - } else { - numberOfArgConstructors++; - } - } - - if (!noArgConstructorFound && numberOfArgConstructors > 1) { - constructor = null; - } - - return constructor; - } - }, - - /** - * Discovers a {@link PreferredConstructor} for Kotlin types. - */ - KOTLIN { - - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.model.PreferredConstructorDiscoverers#discover(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.PersistentEntity) - */ - @Nullable - @Override - public > PreferredConstructor discover(TypeInformation type, - @Nullable PersistentEntity entity) { - - Class rawOwningType = type.getType(); - - return Arrays.stream(rawOwningType.getDeclaredConstructors()) // - .map(it -> buildPreferredConstructor(it, type, entity)) // - .filter(it -> !it.getConstructor().isSynthetic()) // Synthetic constructors should not be considered - .filter(PreferredConstructor::isExplicitlyAnnotated) // Explicitly defined constructor trumps all - .findFirst() // - .orElseGet(() -> { - - KFunction primaryConstructor = KClasses - .getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(type.getType())); - Constructor javaConstructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor); - - return javaConstructor != null ? buildPreferredConstructor(javaConstructor, type, entity) : null; - }); - } - }; - - private static final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); - - /** - * Find the appropriate discoverer for {@code type}. - * - * @param type must not be {@literal null}. - * @return the appropriate discoverer for {@code type}. - */ - public static PreferredConstructorDiscoverers findDiscoverer(Class type) { - return ReflectionUtils.isKotlinClass(type) ? KOTLIN : DEFAULT; - } - - /** - * Discovers a constructor for the given type. - * - * @param type must not be {@literal null}. - * @param entity may be {@literal null}. - * @return the {@link PreferredConstructor} if found or {@literal null}. - */ - @Nullable - public abstract > PreferredConstructor discover(TypeInformation type, - @Nullable PersistentEntity entity); - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static > PreferredConstructor buildPreferredConstructor( - Constructor constructor, TypeInformation typeInformation, @Nullable PersistentEntity entity) { - - List> parameterTypes = typeInformation.getParameterTypes(constructor); - - if (parameterTypes.isEmpty()) { - return new PreferredConstructor<>((Constructor) constructor); - } - - String[] parameterNames = discoverer.getParameterNames(constructor); - - Parameter[] parameters = new Parameter[parameterTypes.size()]; - Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); - - for (int i = 0; i < parameterTypes.size(); i++) { - - String name = parameterNames == null ? null : parameterNames[i]; - TypeInformation type = parameterTypes.get(i); - Annotation[] annotations = parameterAnnotations[i]; - - parameters[i] = new Parameter(name, type, annotations, entity); - } - - return new PreferredConstructor<>((Constructor) constructor, parameters); - } -} diff --git a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt index 9ce59d24c..0c3f820c0 100644 --- a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt @@ -29,13 +29,13 @@ import org.springframework.data.mapping.model.ParameterValueProvider import org.springframework.data.mapping.model.PreferredConstructorDiscoverer /** - * Unit tests for [ClassGeneratingKotlinEntityInstantiator] creating instances using Kotlin data classes. + * Unit tests for [KotlinClassGeneratingEntityInstantiator] creating instances using Kotlin data classes. * * @author Mark Paluch */ @RunWith(MockitoJUnitRunner::class) @Suppress("UNCHECKED_CAST") -class ClassGeneratingKotlinEntityInstantiatorUnitTests { +class KotlinClassGeneratingEntityInstantiatorUnitTests { @Mock lateinit var entity: PersistentEntity<*, *> @Mock lateinit var provider: ParameterValueProvider @@ -50,7 +50,7 @@ class ClassGeneratingKotlinEntityInstantiatorUnitTests { doReturn(constructor).whenever(entity).persistenceConstructor doReturn(constructor.constructor.declaringClass).whenever(entity).type - val instance: Contact = ClassGeneratingKotlinEntityInstantiator().createInstance(entity, provider) + val instance: Contact = KotlinClassGeneratingEntityInstantiator().createInstance(entity, provider) Assertions.assertThat(instance.firstname).isEqualTo("Walter") Assertions.assertThat(instance.lastname).isEqualTo("White") @@ -69,7 +69,7 @@ class ClassGeneratingKotlinEntityInstantiatorUnitTests { doReturn(constructor).whenever(entity).persistenceConstructor doReturn(constructor.constructor.declaringClass).whenever(entity).type - val instance: ContactWithDefaulting = ClassGeneratingKotlinEntityInstantiator().createInstance(entity, provider) + val instance: ContactWithDefaulting = KotlinClassGeneratingEntityInstantiator().createInstance(entity, provider) Assertions.assertThat(instance.prop0).isEqualTo("Walter") Assertions.assertThat(instance.prop2).isEqualTo("Skyler")