diff --git a/src/main/asciidoc/object-mapping.adoc b/src/main/asciidoc/object-mapping.adoc index acdc5103d..1930b0110 100644 --- a/src/main/asciidoc/object-mapping.adoc +++ b/src/main/asciidoc/object-mapping.adoc @@ -75,7 +75,9 @@ For that we use the following algorithm: 1. If the property is immutable but exposes a `with…` method (see below), we use the `with…` method to create a new entity instance with the new property value. 2. If property access (i.e. access through getters and setters) is defined, we're invoking the setter method. -3. By default, we set the field value directly. +3. If the property is mutable we set the field directly. +3. If the property is immutable we're using the constructor to be used by persistence operations (see <>) to create a copy of the instance. +4. By default, we set the field value directly. [[mapping.property-population.details]] .Property population internals @@ -194,6 +196,7 @@ class Person { The class exposes a `withId(…)` method that's used to set the identifier, e.g. when an instance is inserted into the datastore and an identifier has been generated. The original `Person` instance stays unchanged as a new one is created. The same pattern is usually applied for other properties that are store managed but might have to be changed for persistence operations. +The wither method is optional as the persistence constructor (see 6) is effectively a copy constructor and setting the property will be translated into creating a fresh instance with the new identifier value applied. <2> The `firstname` and `lastname` properties are ordinary immutable properties potentially exposed through getters. <3> The `age` property is an immutable but derived one from the `birthday` property. With the design shown, the database value will trump the defaulting as Spring Data uses the only declared constructor. @@ -214,7 +217,7 @@ Constructor-only materialization is up to 30% faster than properties population. * _Use factory methods instead of overloaded constructors to avoid ``@PersistenceConstructor``_ -- With an all-argument constructor needed for optimal performance, we usually want to expose more application use case specific constructors that omit things like auto-generated identifiers etc. It's an established pattern to rather use static factory methods to expose these variants of the all-args constructor. * _Make sure you adhere to the constraints that allow the generated instantiator and property accessor classes to be used_ -- -* _For identifiers to be generated, still use a final field in combination with a `with…` method_ -- +* _For identifiers to be generated, still use a final field in combination with an all-arguments persistence constructor (preferred) or a `with…` method_ -- * _Use Lombok to avoid boilerplate code_ -- As persistence operations usually require a constructor taking all arguments, their declaration becomes a tedious repetition of boilerplate parameter to field assignments that can best be avoided by using Lombok's `@AllArgsConstructor`. [[mapping.kotlin]] diff --git a/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java index 9de562202..8a0851bc2 100644 --- a/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/ClassGeneratingEntityInstantiator.java @@ -15,29 +15,11 @@ */ package org.springframework.data.convert; -import static org.springframework.asm.Opcodes.*; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.asm.ClassWriter; -import org.springframework.asm.MethodVisitor; -import org.springframework.asm.Opcodes; -import org.springframework.asm.Type; -import org.springframework.cglib.core.ReflectUtils; 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.mapping.model.MappingInstantiationException; +import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory; import org.springframework.data.mapping.model.ParameterValueProvider; -import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; /** * An {@link EntityInstantiator} that can generate byte code to speed-up dynamic object instantiation. Uses the @@ -51,479 +33,28 @@ import org.springframework.util.ClassUtils; * @author Christoph Strobl * @author Mark Paluch * @since 1.11 + * @deprecated since 2.3, use {@link org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator} instead */ +@Deprecated public class ClassGeneratingEntityInstantiator implements EntityInstantiator { - private static final Object[] EMPTY_ARGS = new Object[0]; - - private final ObjectInstantiatorClassGenerator generator; - - private volatile Map, EntityInstantiator> entityInstantiators = new HashMap<>(32); + private final org.springframework.data.mapping.model.EntityInstantiator delegate = InternalEntityInstantiatorFactory + .getClassGeneratingEntityInstantiator(); /** * Creates a new {@link ClassGeneratingEntityInstantiator}. */ public ClassGeneratingEntityInstantiator() { - this.generator = new ObjectInstantiatorClassGenerator(); + super(); } /* * (non-Javadoc) - * @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) + * @see org.springframework.data.mapping.model.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) */ @Override public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - - EntityInstantiator instantiator = this.entityInstantiators.get(entity.getTypeInformation()); - - if (instantiator == null) { - instantiator = potentiallyCreateAndRegisterEntityInstantiator(entity); - } - - return instantiator.createInstance(entity, provider); - } - - /** - * @param entity - * @return - */ - private synchronized EntityInstantiator potentiallyCreateAndRegisterEntityInstantiator( - PersistentEntity entity) { - - Map, EntityInstantiator> map = this.entityInstantiators; - EntityInstantiator instantiator = map.get(entity.getTypeInformation()); - - if (instantiator != null) { - return instantiator; - } - - instantiator = createEntityInstantiator(entity); - - map = new HashMap<>(map); - map.put(entity.getTypeInformation(), instantiator); - - this.entityInstantiators = map; - - return instantiator; - } - - /** - * @param entity - * @return - */ - private EntityInstantiator createEntityInstantiator(PersistentEntity entity) { - - if (shouldUseReflectionEntityInstantiator(entity)) { - return ReflectionEntityInstantiator.INSTANCE; - } - - try { - return doCreateEntityInstantiator(entity); - } catch (Throwable ex) { - return ReflectionEntityInstantiator.INSTANCE; - } - } - - /** - * @param entity - * @return - */ - protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity entity) { - return new EntityInstantiatorAdapter(createObjectInstantiator(entity, entity.getPersistenceConstructor())); - } - - /** - * @param entity - * @return - */ - boolean shouldUseReflectionEntityInstantiator(PersistentEntity entity) { - - Class type = entity.getType(); - - if (type.isInterface() // - || type.isArray() // - || Modifier.isPrivate(type.getModifiers()) // - || (type.isMemberClass() && !Modifier.isStatic(type.getModifiers())) // - || ClassUtils.isCglibProxyClass(type)) { // - return true; - } - - PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); - if (persistenceConstructor == null || Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) { - return true; - } - - if (!ClassUtils.isPresent(ObjectInstantiator.class.getName(), type.getClassLoader())) { - return true; - } - - return false; - } - - /** - * Allocates an object array for instance creation. - * - * @param argumentCount - * @return - * @since 2.0 - */ - static Object[] allocateArguments(int argumentCount) { - return argumentCount == 0 ? EMPTY_ARGS : new Object[argumentCount]; - } - - /** - * Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and - * {@link PreferredConstructor}. There will always be exactly one {@link ObjectInstantiator} instance per - * {@link PersistentEntity}. - * - * @param entity - * @param constructor - * @return - */ - ObjectInstantiator createObjectInstantiator(PersistentEntity entity, - @Nullable PreferredConstructor constructor) { - - try { - return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Adapter to forward an invocation of the {@link EntityInstantiator} API to an {@link ObjectInstantiator}. - * - * @author Thomas Darimont - * @author Oliver Gierke - * @author Mark Paluch - */ - private static class EntityInstantiatorAdapter implements EntityInstantiator { - - private final ObjectInstantiator instantiator; - - /** - * Creates a new {@link EntityInstantiatorAdapter} for the given {@link ObjectInstantiator}. - * - * @param instantiator must not be {@literal null}. - */ - public EntityInstantiatorAdapter(ObjectInstantiator instantiator) { - this.instantiator = instantiator; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) - */ - @Override - @SuppressWarnings("unchecked") - public , P extends PersistentProperty

> T createInstance(E entity, - ParameterValueProvider

provider) { - - Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider); - - try { - return (T) instantiator.newInstance(params); - } catch (Exception e) { - throw new MappingInstantiationException(entity, Arrays.asList(params), e); - } - } - - /** - * Extracts the arguments required to invoke the given constructor from the given {@link ParameterValueProvider}. - * - * @param constructor can be {@literal null}. - * @param provider can be {@literal null}. - * @return - */ - private

, T> Object[] extractInvocationArguments( - @Nullable PreferredConstructor constructor, ParameterValueProvider

provider) { - - if (constructor == null || !constructor.hasParameters()) { - return allocateArguments(0); - } - - Object[] params = allocateArguments(constructor.getConstructor().getParameterCount()); - - int index = 0; - for (Parameter parameter : constructor.getParameters()) { - params[index++] = provider.getParameterValue(parameter); - } - - return params; - } - } - - /** - * Needs to be public as otherwise the implementation class generated does not see the interface from the classloader. - * - * @author Thomas Darimont - * @author Oliver Gierke - */ - public interface ObjectInstantiator { - Object newInstance(Object... args); - } - - /** - * Generates a new {@link ObjectInstantiator} class for the given custom class. - *

- * This code generator will generate a custom factory class implementing the {@link ObjectInstantiator} interface for - * every publicly accessed constructor variant. - *

- * Given a class {@code ObjCtor1ParamString} like: - * - *

-	 * {
-	 * 	@code
-	 * 	public class ObjCtor1ParamString extends ObjCtorNoArgs {
-	 *
-	 * 		public final String param1;
-	 *
-	 * 		public ObjCtor1ParamString(String param1) {
-	 * 			this.param1 = param1;
-	 * 		}
-	 * 	}
-	 * }
-	 * 
- * - * The following factory class {@code ObjCtor1ParamString_Instantiator_asdf} is generated: - * - *
-	 * {
-	 * 	@code
-	 * 	public class ObjCtor1ParamString_Instantiator_asdf implements ObjectInstantiator {
-	 *
-	 * 		public Object newInstance(Object... args) {
-	 * 			return new ObjCtor1ParamString((String) args[0]);
-	 * 		}
-	 * 	}
-	 * }
-	 * 
- * - * @author Thomas Darimont - * @author Mark Paluch - */ - static class ObjectInstantiatorClassGenerator { - - private static final String INIT = ""; - private static final String TAG = "_Instantiator_"; - private static final String JAVA_LANG_OBJECT = "java/lang/Object"; - private static final String CREATE_METHOD_NAME = "newInstance"; - - private static final String[] IMPLEMENTED_INTERFACES = new String[] { - Type.getInternalName(ObjectInstantiator.class) }; - - - /** - * Generate a new class for the given {@link PersistentEntity}. - * - * @param entity - * @param constructor - * @return - */ - public Class generateCustomInstantiatorClass(PersistentEntity entity, - @Nullable PreferredConstructor constructor) { - - String className = generateClassName(entity); - byte[] bytecode = generateBytecode(className, entity, constructor); - - Class type = entity.getType(); - - try { - return ReflectUtils.defineClass(className, bytecode, type.getClassLoader(), type.getProtectionDomain(), type); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - /** - * @param entity - * @return - */ - private String generateClassName(PersistentEntity entity) { - return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36); - } - - /** - * Generate a new class for the given {@link PersistentEntity}. - * - * @param internalClassName - * @param entity - * @param constructor - * @return - */ - public byte[] generateBytecode(String internalClassName, PersistentEntity entity, - @Nullable PreferredConstructor constructor) { - - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - - cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, internalClassName.replace('.', '/'), null, JAVA_LANG_OBJECT, - IMPLEMENTED_INTERFACES); - - visitDefaultConstructor(cw); - - visitCreateMethod(cw, entity, constructor); - - cw.visitEnd(); - - return cw.toByteArray(); - } - - private void visitDefaultConstructor(ClassWriter cw) { - - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, INIT, "()V", null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, INIT, "()V", false); - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS - mv.visitEnd(); - } - - /** - * Inserts the bytecode definition for the create method for the given {@link PersistentEntity}. - * - * @param cw - * @param entity - * @param constructor - */ - private void visitCreateMethod(ClassWriter cw, PersistentEntity entity, - @Nullable PreferredConstructor constructor) { - - String entityTypeResourcePath = Type.getInternalName(entity.getType()); - - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, CREATE_METHOD_NAME, - "([Ljava/lang/Object;)Ljava/lang/Object;", null, null); - mv.visitCode(); - mv.visitTypeInsn(NEW, entityTypeResourcePath); - mv.visitInsn(DUP); - - if (constructor != null) { - - Constructor ctor = constructor.getConstructor(); - Class[] parameterTypes = ctor.getParameterTypes(); - List> parameters = constructor.getParameters(); - - for (int i = 0; i < parameterTypes.length; i++) { - - mv.visitVarInsn(ALOAD, 1); - - visitArrayIndex(mv, i); - - mv.visitInsn(AALOAD); - - if (parameterTypes[i].isPrimitive()) { - - mv.visitInsn(DUP); - String parameterName = parameters.size() > i ? parameters.get(i).getName() : null; - - insertAssertNotNull(mv, parameterName == null ? String.format("at index %d", i) : parameterName); - insertUnboxInsns(mv, Type.getType(parameterTypes[i]).toString().charAt(0), ""); - } else { - mv.visitTypeInsn(CHECKCAST, Type.getInternalName(parameterTypes[i])); - } - } - - mv.visitMethodInsn(INVOKESPECIAL, entityTypeResourcePath, INIT, Type.getConstructorDescriptor(ctor), false); - mv.visitInsn(ARETURN); - mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS - mv.visitEnd(); - } - } - - /** - * Insert an appropriate value on the stack for the given index value {@code idx}. - * - * @param mv - * @param idx - */ - private static void visitArrayIndex(MethodVisitor mv, int idx) { - - if (idx >= 0 && idx < 6) { - mv.visitInsn(ICONST_0 + idx); - return; - } - - mv.visitLdcInsn(idx); - } - - /** - * Insert not-{@literal null} assertion for a parameter. - * - * @param mv the method visitor into which instructions should be inserted - * @param parameterName name of the parameter to create the appropriate assertion message. - */ - private static void insertAssertNotNull(MethodVisitor mv, String parameterName) { - - // Assert.notNull(property) - mv.visitLdcInsn(String.format("Parameter %s must not be null!", parameterName)); - mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert", "notNull", - String.format("(%s%s)V", String.format("L%s;", JAVA_LANG_OBJECT), "Ljava/lang/String;"), false); - } - - /** - * Insert any necessary cast and value call to convert from a boxed type to a primitive value. - *

- * Taken from Spring Expression 4.1.2 {@code org.springframework.expression.spel.CodeFlow#insertUnboxInsns}. - * - * @param mv the method visitor into which instructions should be inserted - * @param ch the primitive type desired as output - * @param stackDescriptor the descriptor of the type on top of the stack - */ - private static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) { - - switch (ch) { - case 'Z': - if (!stackDescriptor.equals("Ljava/lang/Boolean")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); - break; - case 'B': - if (!stackDescriptor.equals("Ljava/lang/Byte")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); - break; - case 'C': - if (!stackDescriptor.equals("Ljava/lang/Character")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); - break; - case 'D': - if (!stackDescriptor.equals("Ljava/lang/Double")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); - break; - case 'F': - if (!stackDescriptor.equals("Ljava/lang/Float")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); - break; - case 'I': - if (!stackDescriptor.equals("Ljava/lang/Integer")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); - break; - case 'J': - if (!stackDescriptor.equals("Ljava/lang/Long")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); - break; - case 'S': - if (!stackDescriptor.equals("Ljava/lang/Short")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); - } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); - break; - default: - throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'"); - } - } + return delegate.createInstance(entity, provider); } } diff --git a/src/main/java/org/springframework/data/convert/EntityInstantiator.java b/src/main/java/org/springframework/data/convert/EntityInstantiator.java index d9017cc58..b8a0583a3 100644 --- a/src/main/java/org/springframework/data/convert/EntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/EntityInstantiator.java @@ -16,23 +16,12 @@ package org.springframework.data.convert; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.model.ParameterValueProvider; /** * SPI to abstract strategies to create instances for {@link PersistentEntity}s. * * @author Oliver Gierke + * @since 2.3, use {@link org.springframework.data.mapping.model.EntityInstantiator} instead. */ -public interface EntityInstantiator { - - /** - * Creates a new instance of the given entity using the given source to pull data from. - * - * @param entity will not be {@literal null}. - * @param provider will not be {@literal null}. - * @return - */ - , P extends PersistentProperty

> T createInstance(E entity, - ParameterValueProvider

provider); -} +@Deprecated +public interface EntityInstantiator extends org.springframework.data.mapping.model.EntityInstantiator {} diff --git a/src/main/java/org/springframework/data/convert/EntityInstantiatorAdapter.java b/src/main/java/org/springframework/data/convert/EntityInstantiatorAdapter.java new file mode 100644 index 000000000..965daebb8 --- /dev/null +++ b/src/main/java/org/springframework/data/convert/EntityInstantiatorAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 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 + * + * https://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.convert; + +import lombok.RequiredArgsConstructor; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.model.ParameterValueProvider; + +/** + * Adapter to bridge calls from the new APIs back to the legacy ones. + * + * @author Oliver Drotbohm + */ +@RequiredArgsConstructor +class EntityInstantiatorAdapter implements EntityInstantiator { + + private final org.springframework.data.mapping.model.EntityInstantiator delegate; + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) + */ + @Override + public , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider) { + return delegate.createInstance(entity, provider); + } +} diff --git a/src/main/java/org/springframework/data/convert/EntityInstantiators.java b/src/main/java/org/springframework/data/convert/EntityInstantiators.java index 41bb60b7d..4eb45d05f 100644 --- a/src/main/java/org/springframework/data/convert/EntityInstantiators.java +++ b/src/main/java/org/springframework/data/convert/EntityInstantiators.java @@ -19,7 +19,7 @@ import java.util.Collections; import java.util.Map; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.util.Assert; +import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory; /** * Simple value object allowing access to {@link EntityInstantiator} instances for a given type falling back to a @@ -29,17 +29,16 @@ import org.springframework.util.Assert; * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch + * @deprecated since 2.3, use {@link org.springframework.data.mapping.model.EntityInstantiators} instead. */ -public class EntityInstantiators { - - private final EntityInstantiator fallback; - private final Map, EntityInstantiator> customInstantiators; +@Deprecated +public class EntityInstantiators extends org.springframework.data.mapping.model.EntityInstantiators { /** * Creates a new {@link EntityInstantiators} using the default fallback instantiator and no custom ones. */ public EntityInstantiators() { - this(Collections.emptyMap()); + super(); } /** @@ -47,8 +46,8 @@ public class EntityInstantiators { * * @param fallback must not be {@literal null}. */ - public EntityInstantiators(EntityInstantiator fallback) { - this(fallback, Collections.emptyMap()); + public EntityInstantiators(org.springframework.data.mapping.model.EntityInstantiator fallback) { + super(fallback, Collections.emptyMap()); } /** @@ -56,8 +55,9 @@ public class EntityInstantiators { * * @param customInstantiators must not be {@literal null}. */ - public EntityInstantiators(Map, EntityInstantiator> customInstantiators) { - this(new KotlinClassGeneratingEntityInstantiator(), customInstantiators); + public EntityInstantiators( + Map, org.springframework.data.mapping.model.EntityInstantiator> customInstantiators) { + super(InternalEntityInstantiatorFactory.getKotlinClassGeneratingEntityInstantiator(), customInstantiators); } /** @@ -67,32 +67,17 @@ public class EntityInstantiators { * @param defaultInstantiator must not be {@literal null}. * @param customInstantiators must not be {@literal null}. */ - public EntityInstantiators(EntityInstantiator defaultInstantiator, - Map, EntityInstantiator> customInstantiators) { - - Assert.notNull(defaultInstantiator, "DefaultInstantiator must not be null!"); - Assert.notNull(customInstantiators, "CustomInstantiators must not be null!"); - - this.fallback = defaultInstantiator; - this.customInstantiators = customInstantiators; + public EntityInstantiators(org.springframework.data.mapping.model.EntityInstantiator defaultInstantiator, + Map, org.springframework.data.mapping.model.EntityInstantiator> customInstantiators) { + super(defaultInstantiator, customInstantiators); } - /** - * Returns the {@link EntityInstantiator} to be used to create the given {@link PersistentEntity}. - * - * @param entity must not be {@literal null}. - * @return will never be {@literal null}. + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.EntityInstantiators#getInstantiatorFor(org.springframework.data.mapping.PersistentEntity) */ + @Override public EntityInstantiator getInstantiatorFor(PersistentEntity entity) { - - Assert.notNull(entity, "Entity must not be null!"); - Class type = entity.getType(); - - if (!customInstantiators.containsKey(type)) { - return fallback; - } - - EntityInstantiator instantiator = customInstantiators.get(entity.getType()); - return instantiator == null ? fallback : instantiator; + return new EntityInstantiatorAdapter(super.getInstantiatorFor(entity)); } } diff --git a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java index e4becca06..b1e4b3181 100644 --- a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java @@ -15,24 +15,10 @@ */ package org.springframework.data.convert; -import kotlin.reflect.KFunction; -import kotlin.reflect.KParameter; -import kotlin.reflect.jvm.ReflectJvmMapping; - -import java.lang.reflect.Constructor; -import java.util.Arrays; -import java.util.List; -import java.util.stream.IntStream; - 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.mapping.model.KotlinDefaultMask; -import org.springframework.data.mapping.model.MappingInstantiationException; +import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory; import org.springframework.data.mapping.model.ParameterValueProvider; -import org.springframework.data.util.ReflectionUtils; -import org.springframework.lang.Nullable; /** * Kotlin-specific extension to {@link ClassGeneratingEntityInstantiator} that adapts Kotlin constructors with @@ -41,221 +27,22 @@ import org.springframework.lang.Nullable; * @author Mark Paluch * @author Oliver Gierke * @since 2.0 + * @deprecated since 2.3, use {@link org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator} + * instead. */ -public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInstantiator { +@Deprecated +public class KotlinClassGeneratingEntityInstantiator implements EntityInstantiator { + + private final org.springframework.data.mapping.model.EntityInstantiator delegate = InternalEntityInstantiatorFactory + .getKotlinClassGeneratingEntityInstantiator(); /* * (non-Javadoc) - * @see org.springframework.data.convert.ClassGeneratingEntityInstantiator#doCreateEntityInstantiator(org.springframework.data.mapping.PersistentEntity) + * @see org.springframework.data.mapping.model.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) */ @Override - protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity entity) { - - PreferredConstructor constructor = entity.getPersistenceConstructor(); - - if (ReflectionUtils.isSupportedKotlinClass(entity.getType()) && constructor != null) { - - PreferredConstructor defaultConstructor = new DefaultingKotlinConstructorResolver(entity) - .getDefaultConstructor(); - - if (defaultConstructor != null) { - - ObjectInstantiator instantiator = createObjectInstantiator(entity, defaultConstructor); - - return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor); - } - } - - return super.doCreateEntityInstantiator(entity); - } - - /** - * Resolves a {@link PreferredConstructor} to a synthetic Kotlin constructor accepting the same user-space parameters - * suffixed by Kotlin-specifics required for defaulting and the {@code kotlin.jvm.internal.DefaultConstructorMarker}. - * - * @since 2.0 - * @author Mark Paluch - */ - static class DefaultingKotlinConstructorResolver { - - private final @Nullable PreferredConstructor defaultConstructor; - - @SuppressWarnings("unchecked") - DefaultingKotlinConstructorResolver(PersistentEntity entity) { - - Constructor hit = resolveDefaultConstructor(entity); - PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); - - if (hit != null && persistenceConstructor != null) { - this.defaultConstructor = new PreferredConstructor<>(hit, - persistenceConstructor.getParameters().toArray(new Parameter[0])); - } else { - this.defaultConstructor = null; - } - } - - @Nullable - private static Constructor resolveDefaultConstructor(PersistentEntity entity) { - - PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); - - if (persistenceConstructor == null) { - return null; - } - - Constructor hit = null; - Constructor constructor = persistenceConstructor.getConstructor(); - - for (Constructor candidate : entity.getType().getDeclaredConstructors()) { - - // use only synthetic constructors - if (!candidate.isSynthetic()) { - continue; - } - - // candidates must contain at least two additional parameters (int, DefaultConstructorMarker). - // Number of defaulting masks derives from the original constructor arg count - int syntheticParameters = KotlinDefaultMask.getMaskCount(constructor.getParameterCount()) - + /* DefaultConstructorMarker */ 1; - - if (constructor.getParameterCount() + syntheticParameters != candidate.getParameterCount()) { - continue; - } - - java.lang.reflect.Parameter[] constructorParameters = constructor.getParameters(); - java.lang.reflect.Parameter[] candidateParameters = candidate.getParameters(); - - if (!candidateParameters[candidateParameters.length - 1].getType().getName() - .equals("kotlin.jvm.internal.DefaultConstructorMarker")) { - continue; - } - - if (parametersMatch(constructorParameters, candidateParameters)) { - hit = candidate; - break; - } - } - - return hit; - } - - private static boolean parametersMatch(java.lang.reflect.Parameter[] constructorParameters, - java.lang.reflect.Parameter[] candidateParameters) { - - return IntStream.range(0, constructorParameters.length) - .allMatch(i -> constructorParameters[i].getType().equals(candidateParameters[i].getType())); - } - - @Nullable - PreferredConstructor getDefaultConstructor() { - return defaultConstructor; - } - } - - /** - * Entity instantiator for Kotlin constructors that apply parameter defaulting. Kotlin constructors that apply - * argument defaulting are marked with {@link kotlin.jvm.internal.DefaultConstructorMarker} and accept additional - * parameters besides the regular (user-space) parameters. Additional parameters are: - *

    - *
  • defaulting bitmask ({@code int}), a bit mask slot for each 32 parameters
  • - *
  • {@code DefaultConstructorMarker} (usually null)
  • - *
- * Defaulting bitmask - *

- * The defaulting bitmask is a 32 bit integer representing which positional argument should be defaulted. Defaulted - * arguments are passed as {@literal null} and require the appropriate positional bit set ( {@code 1 << 2} for the 2. - * argument)). Since the bitmask represents only 32 bit states, it requires additional masks (slots) if more than 32 - * arguments are represented. - * - * @author Mark Paluch - * @since 2.0 - */ - static class DefaultingKotlinClassInstantiatorAdapter implements EntityInstantiator { - - private final ObjectInstantiator instantiator; - private final KFunction constructor; - private final List kParameters; - private final Constructor synthetic; - - DefaultingKotlinClassInstantiatorAdapter(ObjectInstantiator instantiator, PreferredConstructor constructor) { - - KFunction kotlinConstructor = ReflectJvmMapping.getKotlinFunction(constructor.getConstructor()); - - if (kotlinConstructor == null) { - throw new IllegalArgumentException( - "No corresponding Kotlin constructor found for " + constructor.getConstructor()); - } - - this.instantiator = instantiator; - this.constructor = kotlinConstructor; - this.kParameters = kotlinConstructor.getParameters(); - this.synthetic = constructor.getConstructor(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) - */ - @Override - @SuppressWarnings("unchecked") - public , P extends PersistentProperty

> T createInstance(E entity, - ParameterValueProvider

provider) { - - Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider); - - try { - return (T) instantiator.newInstance(params); - } catch (Exception e) { - throw new MappingInstantiationException(entity, Arrays.asList(params), e); - } - } - - private

, T> Object[] extractInvocationArguments( - @Nullable PreferredConstructor preferredConstructor, ParameterValueProvider

provider) { - - if (preferredConstructor == null) { - throw new IllegalArgumentException("PreferredConstructor must not be null!"); - } - - Object[] params = allocateArguments( - synthetic.getParameterCount() + KotlinDefaultMask.getMaskCount(synthetic.getParameterCount()) + /* DefaultConstructorMarker */1); - int userParameterCount = kParameters.size(); - - List> parameters = preferredConstructor.getParameters(); - - // Prepare user-space arguments - for (int i = 0; i < userParameterCount; i++) { - - Parameter parameter = parameters.get(i); - params[i] = provider.getParameterValue(parameter); - } - - KotlinDefaultMask defaultMask = KotlinDefaultMask.from(constructor, it -> { - - int index = kParameters.indexOf(it); - - Parameter parameter = parameters.get(index); - Class type = parameter.getType().getType(); - - if (it.isOptional() && params[index] == null) { - if (type.isPrimitive()) { - - // apply primitive defaulting to prevent NPE on primitive downcast - params[index] = ReflectionUtils.getPrimitiveDefault(type); - } - return false; - } - - return true; - }); - - int[] defaulting = defaultMask.getDefaulting(); - // append nullability masks to creation arguments - for (int i = 0; i < defaulting.length; i++) { - params[userParameterCount + i] = defaulting[i]; - } - - return params; - } + public , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider) { + return delegate.createInstance(entity, provider); } } diff --git a/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java b/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java index 286e3bfb9..c4fbf4581 100644 --- a/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java @@ -15,18 +15,10 @@ */ package org.springframework.data.convert; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; - -import org.springframework.beans.BeanInstantiationException; -import org.springframework.beans.BeanUtils; 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.mapping.model.MappingInstantiationException; +import org.springframework.data.mapping.model.InternalEntityInstantiatorFactory; import org.springframework.data.mapping.model.ParameterValueProvider; /** @@ -35,50 +27,15 @@ import org.springframework.data.mapping.model.ParameterValueProvider; * * @author Oliver Gierke * @author Mark Paluch + * @deprecated since 2.3, use {@link org.springframework.data.mapping.model.ReflectionEntityInstantiator} instead. */ public enum ReflectionEntityInstantiator implements EntityInstantiator { INSTANCE; - private static final Object[] EMPTY_ARGS = new Object[0]; - - @SuppressWarnings("unchecked") public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - PreferredConstructor constructor = entity.getPersistenceConstructor(); - - if (constructor == null) { - - try { - Class clazz = entity.getType(); - if (clazz.isArray()) { - Class ctype = clazz; - int dims = 0; - while (ctype.isArray()) { - ctype = ctype.getComponentType(); - dims++; - } - return (T) Array.newInstance(clazz, dims); - } else { - return BeanUtils.instantiateClass(entity.getType()); - } - } catch (BeanInstantiationException e) { - throw new MappingInstantiationException(entity, Collections.emptyList(), e); - } - } - int parameterCount = constructor.getConstructor().getParameterCount(); - - Object[] params = parameterCount == 0 ? EMPTY_ARGS : new Object[parameterCount]; - int i = 0; - for (Parameter parameter : constructor.getParameters()) { - params[i++] = provider.getParameterValue(parameter); - } - - try { - return BeanUtils.instantiateClass(constructor.getConstructor(), params); - } catch (BeanInstantiationException e) { - throw new MappingInstantiationException(entity, new ArrayList<>(Arrays.asList(params)), e); - } + return InternalEntityInstantiatorFactory.getReflectionEntityInstantiator().createInstance(entity, provider); } } diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index ae196304b..0ca8755bf 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -48,6 +48,8 @@ import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory; +import org.springframework.data.mapping.model.EntityInstantiators; +import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessorFactory; import org.springframework.data.mapping.model.MutablePersistentEntity; import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; import org.springframework.data.mapping.model.Property; @@ -87,7 +89,7 @@ public abstract class AbstractMappingContext NONE = Optional.empty(); private final Map, Optional> persistentEntities = new HashMap<>(); - private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory(); + private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory; private final PersistentPropertyPathFactory persistentPropertyPathFactory; private @Nullable ApplicationEventPublisher applicationEventPublisher; @@ -102,7 +104,14 @@ public abstract class AbstractMappingContext(this); + + EntityInstantiators instantiators = new EntityInstantiators(); + ClassGeneratingPropertyAccessorFactory accessorFactory = new ClassGeneratingPropertyAccessorFactory(); + + this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory, + instantiators); } /* diff --git a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java new file mode 100644 index 000000000..d24186290 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java @@ -0,0 +1,526 @@ +/* + * Copyright 2015-2020 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 + * + * https://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 static org.springframework.asm.Opcodes.*; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.asm.ClassWriter; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.Type; +import org.springframework.cglib.core.ReflectUtils; +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.TypeInformation; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +/** + * An {@link EntityInstantiator} that can generate byte code to speed-up dynamic object instantiation. Uses the + * {@link PersistentEntity}'s {@link PreferredConstructor} to instantiate an instance of the entity by dynamically + * generating factory methods with appropriate constructor invocations via ASM. If we cannot generate byte code for a + * type, we gracefully fall-back to the {@link ReflectionEntityInstantiator}. + * + * @author Thomas Darimont + * @author Oliver Gierke + * @author Phillip Webb + * @author Christoph Strobl + * @author Mark Paluch + * @since 1.11 + */ +class ClassGeneratingEntityInstantiator implements EntityInstantiator { + + private static final Object[] EMPTY_ARGS = new Object[0]; + + private final ObjectInstantiatorClassGenerator generator; + + private volatile Map, EntityInstantiator> entityInstantiators = new HashMap<>(32); + + /** + * Creates a new {@link ClassGeneratingEntityInstantiator}. + */ + public ClassGeneratingEntityInstantiator() { + this.generator = new ObjectInstantiatorClassGenerator(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) + */ + @Override + public , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider) { + + EntityInstantiator instantiator = this.entityInstantiators.get(entity.getTypeInformation()); + + if (instantiator == null) { + instantiator = potentiallyCreateAndRegisterEntityInstantiator(entity); + } + + return instantiator.createInstance(entity, provider); + } + + /** + * @param entity + * @return + */ + private synchronized EntityInstantiator potentiallyCreateAndRegisterEntityInstantiator( + PersistentEntity entity) { + + Map, EntityInstantiator> map = this.entityInstantiators; + EntityInstantiator instantiator = map.get(entity.getTypeInformation()); + + if (instantiator != null) { + return instantiator; + } + + instantiator = createEntityInstantiator(entity); + + map = new HashMap<>(map); + map.put(entity.getTypeInformation(), instantiator); + + this.entityInstantiators = map; + + return instantiator; + } + + /** + * @param entity + * @return + */ + private EntityInstantiator createEntityInstantiator(PersistentEntity entity) { + + if (shouldUseReflectionEntityInstantiator(entity)) { + return ReflectionEntityInstantiator.INSTANCE; + } + + try { + return doCreateEntityInstantiator(entity); + } catch (Throwable ex) { + return ReflectionEntityInstantiator.INSTANCE; + } + } + + /** + * @param entity + * @return + */ + protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity entity) { + return new EntityInstantiatorAdapter(createObjectInstantiator(entity, entity.getPersistenceConstructor())); + } + + /** + * @param entity + * @return + */ + boolean shouldUseReflectionEntityInstantiator(PersistentEntity entity) { + + Class type = entity.getType(); + + if (type.isInterface() // + || type.isArray() // + || Modifier.isPrivate(type.getModifiers()) // + || type.isMemberClass() && !Modifier.isStatic(type.getModifiers()) // + || ClassUtils.isCglibProxyClass(type)) { // + return true; + } + + PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); + if (persistenceConstructor == null || Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) { + return true; + } + + if (!ClassUtils.isPresent(ObjectInstantiator.class.getName(), type.getClassLoader())) { + return true; + } + + return false; + } + + /** + * Allocates an object array for instance creation. + * + * @param argumentCount + * @return + * @since 2.0 + */ + static Object[] allocateArguments(int argumentCount) { + return argumentCount == 0 ? EMPTY_ARGS : new Object[argumentCount]; + } + + /** + * Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and + * {@link PreferredConstructor}. There will always be exactly one {@link ObjectInstantiator} instance per + * {@link PersistentEntity}. + * + * @param entity + * @param constructor + * @return + */ + ObjectInstantiator createObjectInstantiator(PersistentEntity entity, + @Nullable PreferredConstructor constructor) { + + try { + return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Adapter to forward an invocation of the {@link EntityInstantiator} API to an {@link ObjectInstantiator}. + * + * @author Thomas Darimont + * @author Oliver Gierke + * @author Mark Paluch + */ + private static class EntityInstantiatorAdapter implements EntityInstantiator { + + private final ObjectInstantiator instantiator; + + /** + * Creates a new {@link EntityInstantiatorAdapter} for the given {@link ObjectInstantiator}. + * + * @param instantiator must not be {@literal null}. + */ + public EntityInstantiatorAdapter(ObjectInstantiator instantiator) { + this.instantiator = instantiator; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) + */ + @Override + @SuppressWarnings("unchecked") + public , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider) { + + Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider); + + try { + return (T) instantiator.newInstance(params); + } catch (Exception e) { + throw new MappingInstantiationException(entity, Arrays.asList(params), e); + } + } + + /** + * Extracts the arguments required to invoke the given constructor from the given {@link ParameterValueProvider}. + * + * @param constructor can be {@literal null}. + * @param provider can be {@literal null}. + * @return + */ + private

, T> Object[] extractInvocationArguments( + @Nullable PreferredConstructor constructor, ParameterValueProvider

provider) { + + if (constructor == null || !constructor.hasParameters()) { + return allocateArguments(0); + } + + Object[] params = allocateArguments(constructor.getConstructor().getParameterCount()); + + int index = 0; + for (Parameter parameter : constructor.getParameters()) { + params[index++] = provider.getParameterValue(parameter); + } + + return params; + } + } + + /** + * Needs to be public as otherwise the implementation class generated does not see the interface from the classloader. + * + * @author Thomas Darimont + * @author Oliver Gierke + */ + public interface ObjectInstantiator { + Object newInstance(Object... args); + } + + /** + * Generates a new {@link ObjectInstantiator} class for the given custom class. + *

+ * This code generator will generate a custom factory class implementing the {@link ObjectInstantiator} interface for + * every publicly accessed constructor variant. + *

+ * Given a class {@code ObjCtor1ParamString} like: + * + *

+	 * {
+	 * 	@code
+	 * 	public class ObjCtor1ParamString extends ObjCtorNoArgs {
+	 *
+	 * 		public final String param1;
+	 *
+	 * 		public ObjCtor1ParamString(String param1) {
+	 * 			this.param1 = param1;
+	 * 		}
+	 * 	}
+	 * }
+	 * 
+ * + * The following factory class {@code ObjCtor1ParamString_Instantiator_asdf} is generated: + * + *
+	 * {
+	 * 	@code
+	 * 	public class ObjCtor1ParamString_Instantiator_asdf implements ObjectInstantiator {
+	 *
+	 * 		public Object newInstance(Object... args) {
+	 * 			return new ObjCtor1ParamString((String) args[0]);
+	 * 		}
+	 * 	}
+	 * }
+	 * 
+ * + * @author Thomas Darimont + * @author Mark Paluch + */ + static class ObjectInstantiatorClassGenerator { + + private static final String INIT = ""; + private static final String TAG = "_Instantiator_"; + private static final String JAVA_LANG_OBJECT = "java/lang/Object"; + private static final String CREATE_METHOD_NAME = "newInstance"; + + private static final String[] IMPLEMENTED_INTERFACES = new String[] { + Type.getInternalName(ObjectInstantiator.class) }; + + /** + * Generate a new class for the given {@link PersistentEntity}. + * + * @param entity + * @param constructor + * @return + */ + public Class generateCustomInstantiatorClass(PersistentEntity entity, + @Nullable PreferredConstructor constructor) { + + String className = generateClassName(entity); + byte[] bytecode = generateBytecode(className, entity, constructor); + + Class type = entity.getType(); + + try { + return ReflectUtils.defineClass(className, bytecode, type.getClassLoader(), type.getProtectionDomain(), type); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * @param entity + * @return + */ + private String generateClassName(PersistentEntity entity) { + return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36); + } + + /** + * Generate a new class for the given {@link PersistentEntity}. + * + * @param internalClassName + * @param entity + * @param constructor + * @return + */ + public byte[] generateBytecode(String internalClassName, PersistentEntity entity, + @Nullable PreferredConstructor constructor) { + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, internalClassName.replace('.', '/'), null, JAVA_LANG_OBJECT, + IMPLEMENTED_INTERFACES); + + visitDefaultConstructor(cw); + + visitCreateMethod(cw, entity, constructor); + + cw.visitEnd(); + + return cw.toByteArray(); + } + + private void visitDefaultConstructor(ClassWriter cw) { + + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, INIT, "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, INIT, "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS + mv.visitEnd(); + } + + /** + * Inserts the bytecode definition for the create method for the given {@link PersistentEntity}. + * + * @param cw + * @param entity + * @param constructor + */ + private void visitCreateMethod(ClassWriter cw, PersistentEntity entity, + @Nullable PreferredConstructor constructor) { + + String entityTypeResourcePath = Type.getInternalName(entity.getType()); + + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, CREATE_METHOD_NAME, + "([Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitCode(); + mv.visitTypeInsn(NEW, entityTypeResourcePath); + mv.visitInsn(DUP); + + if (constructor != null) { + + Constructor ctor = constructor.getConstructor(); + Class[] parameterTypes = ctor.getParameterTypes(); + List> parameters = constructor.getParameters(); + + for (int i = 0; i < parameterTypes.length; i++) { + + mv.visitVarInsn(ALOAD, 1); + + visitArrayIndex(mv, i); + + mv.visitInsn(AALOAD); + + if (parameterTypes[i].isPrimitive()) { + + mv.visitInsn(DUP); + String parameterName = parameters.size() > i ? parameters.get(i).getName() : null; + + insertAssertNotNull(mv, parameterName == null ? String.format("at index %d", i) : parameterName); + insertUnboxInsns(mv, Type.getType(parameterTypes[i]).toString().charAt(0), ""); + } else { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(parameterTypes[i])); + } + } + + mv.visitMethodInsn(INVOKESPECIAL, entityTypeResourcePath, INIT, Type.getConstructorDescriptor(ctor), false); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS + mv.visitEnd(); + } + } + + /** + * Insert an appropriate value on the stack for the given index value {@code idx}. + * + * @param mv + * @param idx + */ + private static void visitArrayIndex(MethodVisitor mv, int idx) { + + if (idx >= 0 && idx < 6) { + mv.visitInsn(ICONST_0 + idx); + return; + } + + mv.visitLdcInsn(idx); + } + + /** + * Insert not-{@literal null} assertion for a parameter. + * + * @param mv the method visitor into which instructions should be inserted + * @param parameterName name of the parameter to create the appropriate assertion message. + */ + private static void insertAssertNotNull(MethodVisitor mv, String parameterName) { + + // Assert.notNull(property) + mv.visitLdcInsn(String.format("Parameter %s must not be null!", parameterName)); + mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert", "notNull", + String.format("(%s%s)V", String.format("L%s;", JAVA_LANG_OBJECT), "Ljava/lang/String;"), false); + } + + /** + * Insert any necessary cast and value call to convert from a boxed type to a primitive value. + *

+ * Taken from Spring Expression 4.1.2 {@code org.springframework.expression.spel.CodeFlow#insertUnboxInsns}. + * + * @param mv the method visitor into which instructions should be inserted + * @param ch the primitive type desired as output + * @param stackDescriptor the descriptor of the type on top of the stack + */ + private static void insertUnboxInsns(MethodVisitor mv, char ch, String stackDescriptor) { + + switch (ch) { + case 'Z': + if (!stackDescriptor.equals("Ljava/lang/Boolean")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); + break; + case 'B': + if (!stackDescriptor.equals("Ljava/lang/Byte")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); + break; + case 'C': + if (!stackDescriptor.equals("Ljava/lang/Character")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); + break; + case 'D': + if (!stackDescriptor.equals("Ljava/lang/Double")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); + break; + case 'F': + if (!stackDescriptor.equals("Ljava/lang/Float")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); + break; + case 'I': + if (!stackDescriptor.equals("Ljava/lang/Integer")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); + break; + case 'J': + if (!stackDescriptor.equals("Ljava/lang/Long")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); + break; + case 'S': + if (!stackDescriptor.equals("Ljava/lang/Short")) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); + break; + default: + throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'"); + } + } + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/EntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/EntityInstantiator.java new file mode 100644 index 000000000..e522bbfa1 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/EntityInstantiator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2020 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 + * + * https://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 org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; + +/** + * SPI to abstract strategies to create instances for {@link PersistentEntity}s. + * + * @author Oliver Drotbohm + * @since 2.3 + */ +public interface EntityInstantiator { + + /** + * Creates a new instance of the given entity using the given source to pull data from. + * + * @param entity will not be {@literal null}. + * @param provider will not be {@literal null}. + * @return + */ + , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider); +} diff --git a/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java b/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java new file mode 100644 index 000000000..f686848ef --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2020 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 + * + * https://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 java.util.Collections; +import java.util.Map; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.util.Assert; + +/** + * Simple value object allowing access to {@link EntityInstantiator} instances for a given type falling back to a + * default one. + * + * @author Oliver Drotbohm + * @author Thomas Darimont + * @author Christoph Strobl + * @author Mark Paluch + * @since 2.3 + */ +public class EntityInstantiators { + + private final EntityInstantiator fallback; + private final Map, EntityInstantiator> customInstantiators; + + /** + * Creates a new {@link EntityInstantiators} using the default fallback instantiator and no custom ones. + */ + public EntityInstantiators() { + this(Collections.emptyMap()); + } + + /** + * Creates a new {@link EntityInstantiators} using the given {@link EntityInstantiator} as fallback. + * + * @param fallback must not be {@literal null}. + */ + public EntityInstantiators(EntityInstantiator fallback) { + this(fallback, Collections.emptyMap()); + } + + /** + * Creates a new {@link EntityInstantiators} using the default fallback instantiator and the given custom ones. + * + * @param customInstantiators must not be {@literal null}. + */ + public EntityInstantiators(Map, EntityInstantiator> customInstantiators) { + this(new KotlinClassGeneratingEntityInstantiator(), customInstantiators); + } + + /** + * Creates a new {@link EntityInstantiator} using the given fallback {@link EntityInstantiator} and the given custom + * ones. + * + * @param defaultInstantiator must not be {@literal null}. + * @param customInstantiators must not be {@literal null}. + */ + public EntityInstantiators(EntityInstantiator defaultInstantiator, + Map, EntityInstantiator> customInstantiators) { + + Assert.notNull(defaultInstantiator, "DefaultInstantiator must not be null!"); + Assert.notNull(customInstantiators, "CustomInstantiators must not be null!"); + + this.fallback = defaultInstantiator; + this.customInstantiators = customInstantiators; + } + + /** + * Returns the {@link EntityInstantiator} to be used to create the given {@link PersistentEntity}. + * + * @param entity must not be {@literal null}. + * @return will never be {@literal null}. + */ + public EntityInstantiator getInstantiatorFor(PersistentEntity entity) { + + Assert.notNull(entity, "Entity must not be null!"); + Class type = entity.getType(); + + if (!customInstantiators.containsKey(type)) { + return fallback; + } + + EntityInstantiator instantiator = customInstantiators.get(entity.getType()); + return instantiator == null ? fallback : instantiator; + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java new file mode 100644 index 000000000..3035053a3 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java @@ -0,0 +1,137 @@ +/* + * Copyright 2019 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 + * + * https://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 org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.PreferredConstructor; +import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.util.ReflectionUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A {@link PersistentPropertyAccessor} that will use an entity's {@link PersistenceConstructor} to create a new + * instance of it to apply a new value for a given {@link PersistentProperty}. Will only be used if the + * {@link PersistentProperty} is to be applied on a completely immutable entity type exposing a persistence constructor. + * + * @author Oliver Drotbohm + */ +public class InstantiationAwarePropertyAccessor implements PersistentPropertyAccessor { + + private static final String NO_SETTER_OR_CONSTRUCTOR = "Cannot set property %s because no setter, wither or copy constructor exists for %s!"; + private static final String NO_CONSTRUCTOR_PARAMETER = "Cannot set property %s because no setter, no wither and it's not part of the persistence constructor %s!"; + + private final PersistentPropertyAccessor delegate; + private final EntityInstantiators instantiators; + + private T bean; + + /** + * Creates an {@link InstantiationAwarePropertyAccessor} using the given delegate {@link PersistentPropertyAccessor} + * and {@link EntityInstantiators}. + * + * @param delegate must not be {@literal null}. + * @param instantiators must not be {@literal null}. + */ + public InstantiationAwarePropertyAccessor(PersistentPropertyAccessor delegate, EntityInstantiators instantiators) { + + Assert.notNull(delegate, "Delegate PersistenPropertyAccessor must not be null!"); + Assert.notNull(instantiators, "EntityInstantiators must not be null!"); + + this.delegate = delegate; + this.instantiators = instantiators; + this.bean = delegate.getBean(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.lang.Object) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void setProperty(PersistentProperty property, @Nullable Object value) { + + PersistentEntity owner = property.getOwner(); + + if (!property.isImmutable() || property.getWither() != null || ReflectionUtils.isKotlinClass(owner.getType())) { + + delegate.setProperty(property, value); + this.bean = delegate.getBean(); + + return; + } + + PreferredConstructor constructor = owner.getPersistenceConstructor(); + + if (constructor == null) { + throw new IllegalStateException(String.format(NO_SETTER_OR_CONSTRUCTOR, property.getName(), owner.getType())); + } + + if (!constructor.isConstructorParameter(property)) { + throw new IllegalStateException( + String.format(NO_CONSTRUCTOR_PARAMETER, property.getName(), constructor.getConstructor())); + } + + constructor.getParameters().stream().forEach(it -> { + + if (it.getName() == null) { + throw new IllegalStateException( + String.format("Cannot detect parameter names of copy constructor of %s!", owner.getType())); + } + }); + + EntityInstantiator instantiator = instantiators.getInstantiatorFor(owner); + + this.bean = (T) instantiator.createInstance(owner, new ParameterValueProvider() { + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) + */ + @Override + @Nullable + @SuppressWarnings("null") + public Object getParameterValue(Parameter parameter) { + + return property.getName().equals(parameter.getName()) // + ? value + : delegate.getProperty(owner.getRequiredPersistentProperty(parameter.getName())); + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentPropertyAccessor#getProperty(org.springframework.data.mapping.PersistentProperty) + */ + @Nullable + @Override + public Object getProperty(PersistentProperty property) { + return delegate.getProperty(property); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentPropertyAccessor#getBean() + */ + @Override + public T getBean() { + return this.bean; + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessorFactory.java b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessorFactory.java new file mode 100644 index 000000000..56011c86d --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessorFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019-2020 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 + * + * https://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 lombok.RequiredArgsConstructor; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentPropertyAccessor; + +/** + * Delegating {@link PersistentPropertyAccessorFactory} decorating the {@link PersistentPropertyAccessor}s created with + * an {@link InstantiationAwarePropertyAccessor} to allow the handling of purely immutable types. + * + * @author Oliver Drotbohm + */ +@RequiredArgsConstructor +public class InstantiationAwarePropertyAccessorFactory implements PersistentPropertyAccessorFactory { + + private final PersistentPropertyAccessorFactory delegate; + private final EntityInstantiators instantiators; + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.PersistentPropertyAccessorFactory#getPropertyAccessor(org.springframework.data.mapping.PersistentEntity, java.lang.Object) + */ + @Override + public PersistentPropertyAccessor getPropertyAccessor(PersistentEntity entity, T bean) { + + PersistentPropertyAccessor accessor = delegate.getPropertyAccessor(entity, bean); + + return new InstantiationAwarePropertyAccessor<>(accessor, instantiators); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.PersistentPropertyAccessorFactory#isSupported(org.springframework.data.mapping.PersistentEntity) + */ + @Override + public boolean isSupported(PersistentEntity entity) { + return delegate.isSupported(entity); + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/InternalEntityInstantiatorFactory.java b/src/main/java/org/springframework/data/mapping/model/InternalEntityInstantiatorFactory.java new file mode 100644 index 000000000..11e80128c --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/InternalEntityInstantiatorFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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 + * + * https://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; + +/** + * Factory to expose the new package-protected {@link EntityInstantiator} implementations for the legacy types. To be + * removed in 2.4. + * + * @author Oliver Drotbohm + * @since 2.3 + * @deprecated since 2.3 as it's only a bridge from the legacy types towards the new, package-protected implementation. + */ +@Deprecated +public class InternalEntityInstantiatorFactory { + + public static EntityInstantiator getClassGeneratingEntityInstantiator() { + return new ClassGeneratingEntityInstantiator(); + } + + public static EntityInstantiator getKotlinClassGeneratingEntityInstantiator() { + return new KotlinClassGeneratingEntityInstantiator(); + } + + public static EntityInstantiator getReflectionEntityInstantiator() { + return ReflectionEntityInstantiator.INSTANCE; + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java new file mode 100644 index 000000000..4b93c9009 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java @@ -0,0 +1,258 @@ +/* + * Copyright 2017-2020 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 + * + * https://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.reflect.KFunction; +import kotlin.reflect.KParameter; +import kotlin.reflect.jvm.ReflectJvmMapping; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +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.lang.Nullable; + +/** + * Kotlin-specific extension to {@link ClassGeneratingEntityInstantiator} that adapts Kotlin constructors with + * defaulting. + * + * @author Mark Paluch + * @author Oliver Gierke + * @since 2.0 + */ +class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInstantiator { + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.ClassGeneratingEntityInstantiator#doCreateEntityInstantiator(org.springframework.data.mapping.PersistentEntity) + */ + @Override + protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity entity) { + + PreferredConstructor constructor = entity.getPersistenceConstructor(); + + if (ReflectionUtils.isSupportedKotlinClass(entity.getType()) && constructor != null) { + + PreferredConstructor defaultConstructor = new DefaultingKotlinConstructorResolver(entity) + .getDefaultConstructor(); + + if (defaultConstructor != null) { + + ObjectInstantiator instantiator = createObjectInstantiator(entity, defaultConstructor); + + return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor); + } + } + + return super.doCreateEntityInstantiator(entity); + } + + /** + * Resolves a {@link PreferredConstructor} to a synthetic Kotlin constructor accepting the same user-space parameters + * suffixed by Kotlin-specifics required for defaulting and the {@code kotlin.jvm.internal.DefaultConstructorMarker}. + * + * @since 2.0 + * @author Mark Paluch + */ + static class DefaultingKotlinConstructorResolver { + + private final @Nullable PreferredConstructor defaultConstructor; + + @SuppressWarnings("unchecked") + DefaultingKotlinConstructorResolver(PersistentEntity entity) { + + Constructor hit = resolveDefaultConstructor(entity); + PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); + + if (hit != null && persistenceConstructor != null) { + this.defaultConstructor = new PreferredConstructor<>(hit, + persistenceConstructor.getParameters().toArray(new Parameter[0])); + } else { + this.defaultConstructor = null; + } + } + + @Nullable + private static Constructor resolveDefaultConstructor(PersistentEntity entity) { + + PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); + + if (persistenceConstructor == null) { + return null; + } + + Constructor hit = null; + Constructor constructor = persistenceConstructor.getConstructor(); + + for (Constructor candidate : entity.getType().getDeclaredConstructors()) { + + // use only synthetic constructors + if (!candidate.isSynthetic()) { + continue; + } + + // candidates must contain at least two additional parameters (int, DefaultConstructorMarker). + // Number of defaulting masks derives from the original constructor arg count + int syntheticParameters = KotlinDefaultMask.getMaskCount(constructor.getParameterCount()) + + /* DefaultConstructorMarker */ 1; + + if (constructor.getParameterCount() + syntheticParameters != candidate.getParameterCount()) { + continue; + } + + java.lang.reflect.Parameter[] constructorParameters = constructor.getParameters(); + java.lang.reflect.Parameter[] candidateParameters = candidate.getParameters(); + + if (!candidateParameters[candidateParameters.length - 1].getType().getName() + .equals("kotlin.jvm.internal.DefaultConstructorMarker")) { + continue; + } + + if (parametersMatch(constructorParameters, candidateParameters)) { + hit = candidate; + break; + } + } + + return hit; + } + + private static boolean parametersMatch(java.lang.reflect.Parameter[] constructorParameters, + java.lang.reflect.Parameter[] candidateParameters) { + + return IntStream.range(0, constructorParameters.length) + .allMatch(i -> constructorParameters[i].getType().equals(candidateParameters[i].getType())); + } + + @Nullable + PreferredConstructor getDefaultConstructor() { + return defaultConstructor; + } + } + + /** + * Entity instantiator for Kotlin constructors that apply parameter defaulting. Kotlin constructors that apply + * argument defaulting are marked with {@link kotlin.jvm.internal.DefaultConstructorMarker} and accept additional + * parameters besides the regular (user-space) parameters. Additional parameters are: + *

    + *
  • defaulting bitmask ({@code int}), a bit mask slot for each 32 parameters
  • + *
  • {@code DefaultConstructorMarker} (usually null)
  • + *
+ * Defaulting bitmask + *

+ * The defaulting bitmask is a 32 bit integer representing which positional argument should be defaulted. Defaulted + * arguments are passed as {@literal null} and require the appropriate positional bit set ( {@code 1 << 2} for the 2. + * argument)). Since the bitmask represents only 32 bit states, it requires additional masks (slots) if more than 32 + * arguments are represented. + * + * @author Mark Paluch + * @since 2.0 + */ + static class DefaultingKotlinClassInstantiatorAdapter implements EntityInstantiator { + + private final ObjectInstantiator instantiator; + private final KFunction constructor; + private final List kParameters; + private final Constructor synthetic; + + DefaultingKotlinClassInstantiatorAdapter(ObjectInstantiator instantiator, PreferredConstructor constructor) { + + KFunction kotlinConstructor = ReflectJvmMapping.getKotlinFunction(constructor.getConstructor()); + + if (kotlinConstructor == null) { + throw new IllegalArgumentException( + "No corresponding Kotlin constructor found for " + constructor.getConstructor()); + } + + this.instantiator = instantiator; + this.constructor = kotlinConstructor; + this.kParameters = kotlinConstructor.getParameters(); + this.synthetic = constructor.getConstructor(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.EntityInstantiator#createInstance(org.springframework.data.mapping.PersistentEntity, org.springframework.data.mapping.model.ParameterValueProvider) + */ + @Override + @SuppressWarnings("unchecked") + public , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider) { + + Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider); + + try { + return (T) instantiator.newInstance(params); + } catch (Exception e) { + throw new MappingInstantiationException(entity, Arrays.asList(params), e); + } + } + + private

, T> Object[] extractInvocationArguments( + @Nullable PreferredConstructor preferredConstructor, ParameterValueProvider

provider) { + + if (preferredConstructor == null) { + throw new IllegalArgumentException("PreferredConstructor must not be null!"); + } + + Object[] params = allocateArguments(synthetic.getParameterCount() + + KotlinDefaultMask.getMaskCount(synthetic.getParameterCount()) + /* DefaultConstructorMarker */1); + int userParameterCount = kParameters.size(); + + List> parameters = preferredConstructor.getParameters(); + + // Prepare user-space arguments + for (int i = 0; i < userParameterCount; i++) { + + Parameter parameter = parameters.get(i); + params[i] = provider.getParameterValue(parameter); + } + + KotlinDefaultMask defaultMask = KotlinDefaultMask.from(constructor, it -> { + + int index = kParameters.indexOf(it); + + Parameter parameter = parameters.get(index); + Class type = parameter.getType().getType(); + + if (it.isOptional() && params[index] == null) { + if (type.isPrimitive()) { + + // apply primitive defaulting to prevent NPE on primitive downcast + params[index] = ReflectionUtils.getPrimitiveDefault(type); + } + return false; + } + + return true; + }); + + int[] defaulting = defaultMask.getDefaulting(); + // append nullability masks to creation arguments + for (int i = 0; i < defaulting.length; i++) { + params[userParameterCount + i] = defaulting[i]; + } + + return params; + } + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/KotlinDefaultMask.java b/src/main/java/org/springframework/data/mapping/model/KotlinDefaultMask.java index 8851a978a..fff593070 100644 --- a/src/main/java/org/springframework/data/mapping/model/KotlinDefaultMask.java +++ b/src/main/java/org/springframework/data/mapping/model/KotlinDefaultMask.java @@ -15,11 +15,6 @@ */ package org.springframework.data.mapping.model; -import java.util.ArrayList; -import java.util.List; -import java.util.function.IntConsumer; -import java.util.function.Predicate; - import kotlin.reflect.KFunction; import kotlin.reflect.KParameter; import kotlin.reflect.KParameter.Kind; @@ -27,6 +22,11 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.util.ArrayList; +import java.util.List; +import java.util.function.IntConsumer; +import java.util.function.Predicate; + /** * Value object representing defaulting masks used for Kotlin methods applying parameter defaulting. * diff --git a/src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java new file mode 100644 index 000000000..00eea5d64 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2020 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 + * + * https://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 java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.BeanUtils; +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; + +/** + * {@link EntityInstantiator} that uses the {@link PersistentEntity}'s {@link PreferredConstructor} to instantiate an + * instance of the entity via reflection. + * + * @author Oliver Gierke + * @author Mark Paluch + */ +enum ReflectionEntityInstantiator implements EntityInstantiator { + + INSTANCE; + + private static final Object[] EMPTY_ARGS = new Object[0]; + + @SuppressWarnings("unchecked") + public , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider) { + + PreferredConstructor constructor = entity.getPersistenceConstructor(); + + if (constructor == null) { + + try { + Class clazz = entity.getType(); + if (clazz.isArray()) { + Class ctype = clazz; + int dims = 0; + while (ctype.isArray()) { + ctype = ctype.getComponentType(); + dims++; + } + return (T) Array.newInstance(clazz, dims); + } else { + return BeanUtils.instantiateClass(entity.getType()); + } + } catch (BeanInstantiationException e) { + throw new MappingInstantiationException(entity, Collections.emptyList(), e); + } + } + int parameterCount = constructor.getConstructor().getParameterCount(); + + Object[] params = parameterCount == 0 ? EMPTY_ARGS : new Object[parameterCount]; + int i = 0; + for (Parameter parameter : constructor.getParameters()) { + params[i++] = provider.getParameterValue(parameter); + } + + try { + return BeanUtils.instantiateClass(constructor.getConstructor(), params); + } catch (BeanInstantiationException e) { + throw new MappingInstantiationException(entity, new ArrayList<>(Arrays.asList(params)), e); + } + } +} diff --git a/src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java b/src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java new file mode 100644 index 000000000..7bc810bd2 --- /dev/null +++ b/src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 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 + * + * https://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; + +import static org.assertj.core.api.Assertions.*; + +import lombok.Value; + +import org.junit.Test; +import org.springframework.data.convert.EntityInstantiators; +import org.springframework.data.mapping.context.SampleMappingContext; +import org.springframework.data.mapping.context.SamplePersistentProperty; +import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessor; + +/** + * @author Oliver Drotbohm + */ +public class InstantiationAwarePersistentPropertyAccessorUnitTests { + + @Test + public void testname() { + + EntityInstantiators instantiators = new EntityInstantiators(); + SampleMappingContext context = new SampleMappingContext(); + + PersistentEntity entity = context.getRequiredPersistentEntity(Sample.class); + + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(new Sample("Dave", "Matthews", 42)); + + PersistentPropertyAccessor wrapper = new InstantiationAwarePropertyAccessor<>(accessor, + instantiators); + + wrapper.setProperty(entity.getRequiredPersistentProperty("firstname"), "Oliver August"); + + assertThat(wrapper.getBean()).isEqualTo(new Sample("Oliver August", "Matthews", 42)); + } + + @Value + static class Sample { + + String firstname, lastname; + int age; + } +} diff --git a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java index 668965752..523fda604 100755 --- a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java @@ -35,7 +35,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; - import org.springframework.core.annotation.AliasFor; import org.springframework.data.annotation.AccessType; import org.springframework.data.annotation.AccessType.Type; @@ -196,8 +195,14 @@ public class BasicPersistentEntityUnitTests> { PersistentPropertyAccessor accessor = entity.getPropertyAccessor(value); assertThat(accessor).isNotInstanceOf(BeanWrapper.class); - assertThat(accessor.getClass().getName()).contains("_Accessor_"); - assertThat(accessor.getBean()).isEqualTo(value); + + assertThat(accessor).isInstanceOfSatisfying(InstantiationAwarePropertyAccessor.class, it -> { + + PersistentPropertyAccessor delegate = (PersistentPropertyAccessor) ReflectionTestUtils.getField(it, "delegate"); + + assertThat(delegate.getClass().getName()).contains("_Accessor_"); + assertThat(delegate.getBean()).isEqualTo(value); + }); } @Test // DATACMNS-596 diff --git a/src/test/java/org/springframework/data/convert/ClassGeneratingEntityInstantiatorUnitTests.java b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiatorUnitTests.java similarity index 95% rename from src/test/java/org/springframework/data/convert/ClassGeneratingEntityInstantiatorUnitTests.java rename to src/test/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiatorUnitTests.java index d744d474e..873e5b1cb 100755 --- a/src/test/java/org/springframework/data/convert/ClassGeneratingEntityInstantiatorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiatorUnitTests.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.convert; +package org.springframework.data.mapping.model; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.util.ClassTypeInformation.from; @@ -29,16 +30,12 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.classloadersupport.HidingClassLoader; -import org.springframework.data.convert.ClassGeneratingEntityInstantiator.ObjectInstantiator; -import org.springframework.data.convert.ClassGeneratingEntityInstantiatorUnitTests.Outer.Inner; 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.mapping.model.BasicPersistentEntity; -import org.springframework.data.mapping.model.MappingInstantiationException; -import org.springframework.data.mapping.model.ParameterValueProvider; -import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; +import org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.ObjectInstantiator; +import org.springframework.data.mapping.model.ClassGeneratingEntityInstantiatorUnitTests.Outer.Inner; import org.springframework.util.ReflectionUtils; /** @@ -363,11 +360,10 @@ public class ClassGeneratingEntityInstantiatorUnitTests

entityType = classLoader - .loadClass("org.springframework.data.mapping.model.PersistentPropertyAccessorTests$ClassLoaderTest"); + Class entityType = classLoader.loadClass("org.springframework.data.mapping.Person"); prepareMocks(entityType); diff --git a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryDatatypeTests.java b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryDatatypeTests.java index 60bae7910..aba7f4f1d 100755 --- a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryDatatypeTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryDatatypeTests.java @@ -139,7 +139,10 @@ public class ClassGeneratingPropertyAccessorFactoryDatatypeTests { .getRequiredPersistentEntity(bean.getClass()); assertThat(ReflectionTestUtils.getField(persistentEntity, "propertyAccessorFactory")) - .isInstanceOf(ClassGeneratingPropertyAccessorFactory.class); + .isInstanceOfSatisfying(InstantiationAwarePropertyAccessorFactory.class, it -> { + assertThat(ReflectionTestUtils.getField(it, "delegate")) + .isInstanceOf(ClassGeneratingPropertyAccessorFactory.class); + }); } private PersistentPropertyAccessor getPersistentPropertyAccessor(Object bean) { diff --git a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java index 2b5938fcd..060471a09 100755 --- a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingPropertyAccessorFactoryTests.java @@ -150,7 +150,7 @@ public class ClassGeneratingPropertyAccessorFactoryTests { assertThat(getProperty(new Dummy(), "dummy")) .satisfies(property -> assertThatExceptionOfType(UnsupportedOperationException.class)// - .isThrownBy(() -> getPersistentPropertyAccessor(bean).getProperty(property))); + .isThrownBy(() -> getPersistentPropertyAccessor(bean).getProperty(property))); } @Test // DATACMNS-809 @@ -158,7 +158,7 @@ public class ClassGeneratingPropertyAccessorFactoryTests { assertThat(getProperty(new Dummy(), "dummy")) .satisfies(property -> assertThatExceptionOfType(UnsupportedOperationException.class)// - .isThrownBy(() -> getPersistentPropertyAccessor(bean).setProperty(property, Optional.empty()))); + .isThrownBy(() -> getPersistentPropertyAccessor(bean).setProperty(property, Optional.empty()))); } @Test // DATACMNS-809 @@ -168,7 +168,10 @@ public class ClassGeneratingPropertyAccessorFactoryTests { .getRequiredPersistentEntity(bean.getClass()); assertThat(ReflectionTestUtils.getField(persistentEntity, "propertyAccessorFactory")) - .isInstanceOf(ClassGeneratingPropertyAccessorFactory.class); + .isInstanceOfSatisfying(InstantiationAwarePropertyAccessorFactory.class, it -> { + assertThat(ReflectionTestUtils.getField(it, "delegate")) + .isInstanceOf(ClassGeneratingPropertyAccessorFactory.class); + }); } private PersistentPropertyAccessor getPersistentPropertyAccessor(Object bean) { diff --git a/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java b/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorsUnitTests.java similarity index 88% rename from src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java rename to src/test/java/org/springframework/data/mapping/model/EntityInstantiatorsUnitTests.java index 5647e9a17..ad040998d 100755 --- a/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorsUnitTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.convert; +package org.springframework.data.mapping.model; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; @@ -55,8 +55,7 @@ public class EntityInstantiatorsUnitTests { doReturn(String.class).when(entity).getType(); - Map, EntityInstantiator> customInstantiators = Collections - .singletonMap(String.class, customInstantiator); + Map, EntityInstantiator> customInstantiators = Collections.singletonMap(String.class, customInstantiator); EntityInstantiators instantiators = new EntityInstantiators(customInstantiators); assertThat(instantiators.getInstantiatorFor(entity)).isEqualTo(customInstantiator); @@ -67,8 +66,8 @@ public class EntityInstantiatorsUnitTests { doReturn(Object.class).when(entity).getType(); - Map, EntityInstantiator> customInstantiators = Collections - .singletonMap(String.class, ReflectionEntityInstantiator.INSTANCE); + Map, EntityInstantiator> customInstantiators = Collections.singletonMap(String.class, + ReflectionEntityInstantiator.INSTANCE); EntityInstantiators instantiators = new EntityInstantiators(customInstantiator, customInstantiators); instantiators.getInstantiatorFor(entity); @@ -76,7 +75,6 @@ public class EntityInstantiatorsUnitTests { assertThat(instantiators.getInstantiatorFor(entity)).isEqualTo(customInstantiator); doReturn(String.class).when(entity).getType(); - assertThat(instantiators.getInstantiatorFor(entity)) - .isEqualTo((EntityInstantiator) ReflectionEntityInstantiator.INSTANCE); + assertThat(instantiators.getInstantiatorFor(entity)).isEqualTo(ReflectionEntityInstantiator.INSTANCE); } } diff --git a/src/test/java/org/springframework/data/convert/ParameterizedKotlinInstantiatorUnitTests.java b/src/test/java/org/springframework/data/mapping/model/ParameterizedKotlinInstantiatorUnitTests.java similarity index 87% rename from src/test/java/org/springframework/data/convert/ParameterizedKotlinInstantiatorUnitTests.java rename to src/test/java/org/springframework/data/mapping/model/ParameterizedKotlinInstantiatorUnitTests.java index 8d1d00cad..22fbdcecb 100644 --- a/src/test/java/org/springframework/data/convert/ParameterizedKotlinInstantiatorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ParameterizedKotlinInstantiatorUnitTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.convert; +package org.springframework.data.mapping.model; import static org.assertj.core.api.Assertions.*; @@ -30,14 +30,13 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.context.SampleMappingContext; import org.springframework.data.mapping.context.SamplePersistentProperty; -import org.springframework.data.mapping.model.BasicPersistentEntity; -import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.With32Args; import org.springframework.data.mapping.model.With33Args; import org.springframework.test.util.ReflectionTestUtils; /** - * Unit test to verify correct object instantiation using Kotlin defaulting via {@link KotlinClassGeneratingEntityInstantiator}. + * Unit test to verify correct object instantiation using Kotlin defaulting via + * {@link KotlinClassGeneratingEntityInstantiator}. * * @author Mark Paluch */ @@ -51,7 +50,9 @@ public class ParameterizedKotlinInstantiatorUnitTests { private final String propertyUnderTestName; private final EntityInstantiator entityInstantiator; - public ParameterizedKotlinInstantiatorUnitTests(PersistentEntity entity, int propertyCount, int propertyUnderTestIndex, String propertyUnderTestName, EntityInstantiator entityInstantiator, String label) { + public ParameterizedKotlinInstantiatorUnitTests(PersistentEntity entity, + int propertyCount, int propertyUnderTestIndex, String propertyUnderTestName, + EntityInstantiator entityInstantiator, String label) { this.entity = entity; this.propertyCount = propertyCount; this.propertyUnderTestIndex = propertyUnderTestIndex; @@ -76,13 +77,16 @@ public class ParameterizedKotlinInstantiatorUnitTests { return fixtures; } - private static List createFixture(SampleMappingContext context, Class entityType, int propertyCount, EntityInstantiator entityInstantiator) { + private static List createFixture(SampleMappingContext context, Class entityType, int propertyCount, + EntityInstantiator entityInstantiator) { BasicPersistentEntity persistentEntity = context.getPersistentEntity(entityType); return IntStream.range(0, propertyCount).mapToObj(i -> { - return new Object[]{persistentEntity, propertyCount, i, Integer.toString(i), entityInstantiator, String.format("Property %d for %s using %s", i, entityType.getSimpleName(), entityInstantiator.getClass().getSimpleName())}; + return new Object[] { persistentEntity, propertyCount, i, Integer.toString(i), entityInstantiator, + String.format("Property %d for %s using %s", i, entityType.getSimpleName(), + entityInstantiator.getClass().getSimpleName()) }; }).collect(Collectors.toList()); } diff --git a/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTests.java b/src/test/java/org/springframework/data/mapping/model/ReflectionEntityInstantiatorUnitTests.java similarity index 90% rename from src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTests.java rename to src/test/java/org/springframework/data/mapping/model/ReflectionEntityInstantiatorUnitTests.java index b1a9be4f2..c1560d298 100755 --- a/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ReflectionEntityInstantiatorUnitTests.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.convert; +package org.springframework.data.mapping.model; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import static org.springframework.data.convert.ReflectionEntityInstantiator.*; +import static org.springframework.data.mapping.model.ReflectionEntityInstantiator.*; import static org.springframework.data.util.ClassTypeInformation.from; import java.lang.reflect.Constructor; @@ -28,15 +29,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.data.convert.ReflectionEntityInstantiatorUnitTests.Outer.Inner; 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.mapping.model.BasicPersistentEntity; -import org.springframework.data.mapping.model.MappingInstantiationException; -import org.springframework.data.mapping.model.ParameterValueProvider; -import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; +import org.springframework.data.mapping.model.ReflectionEntityInstantiatorUnitTests.Outer.Inner; import org.springframework.util.ReflectionUtils; /**