DATACMNS-1639 - Introduce InstantiationAwarePropertyAccessor.
We now ship and use a PropertyAccessor implementation by default that will fall back to use the persistence constructor and creating a new instance to "set" a property. This allows completely immutable types, ommitting previously needed wither methods, e.g. to populate automatically generated identifier values. This is implemented by using the EntityInstantiator available and a ParameterValueProvider that returns the value of the properties of the current instance but replacing the one for the value to be set. This requires moving EntityInstantiators, the EntityInstantiator SPI as well as all existing implementations (reflective, class generating and the Kotlin one) into the mapping.model package. Legacy instances stay around in deprecated form to avoid breaking existing clients (other store modules as well as user implementations of EntityInstantiator). The newly introduced implementations stay package protected as their sole users are now colocated in the same package. Access from the legacy types is handled via InternalEntityInstantiatorFactory, which is introduced in deprecated from for removal alongside the actual deprecations.
This commit is contained in:
@@ -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<E extends MutablePersistentEntity<?
|
||||
|
||||
private final Optional<E> NONE = Optional.empty();
|
||||
private final Map<TypeInformation<?>, Optional<E>> persistentEntities = new HashMap<>();
|
||||
private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory();
|
||||
private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory;
|
||||
private final PersistentPropertyPathFactory<E, P> persistentPropertyPathFactory;
|
||||
|
||||
private @Nullable ApplicationEventPublisher applicationEventPublisher;
|
||||
@@ -102,7 +104,14 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
private final Lock write = lock.writeLock();
|
||||
|
||||
protected AbstractMappingContext() {
|
||||
|
||||
this.persistentPropertyPathFactory = new PersistentPropertyPathFactory<>(this);
|
||||
|
||||
EntityInstantiators instantiators = new EntityInstantiators();
|
||||
ClassGeneratingPropertyAccessorFactory accessorFactory = new ClassGeneratingPropertyAccessorFactory();
|
||||
|
||||
this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory,
|
||||
instantiators);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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<TypeInformation<?>, 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 <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> 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<TypeInformation<?>, 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 <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> 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 <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
|
||||
@Nullable PreferredConstructor<? extends T, P> constructor, ParameterValueProvider<P> provider) {
|
||||
|
||||
if (constructor == null || !constructor.hasParameters()) {
|
||||
return allocateArguments(0);
|
||||
}
|
||||
|
||||
Object[] params = allocateArguments(constructor.getConstructor().getParameterCount());
|
||||
|
||||
int index = 0;
|
||||
for (Parameter<?, P> 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.
|
||||
* <p>
|
||||
* This code generator will generate a custom factory class implementing the {@link ObjectInstantiator} interface for
|
||||
* every publicly accessed constructor variant.
|
||||
* <p>
|
||||
* Given a class {@code ObjCtor1ParamString} like:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* public class ObjCtor1ParamString extends ObjCtorNoArgs {
|
||||
*
|
||||
* public final String param1;
|
||||
*
|
||||
* public ObjCtor1ParamString(String param1) {
|
||||
* this.param1 = param1;
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* The following factory class {@code ObjCtor1ParamString_Instantiator_asdf} is generated:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* @code
|
||||
* public class ObjCtor1ParamString_Instantiator_asdf implements ObjectInstantiator {
|
||||
*
|
||||
* public Object newInstance(Object... args) {
|
||||
* return new ObjCtor1ParamString((String) args[0]);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class ObjectInstantiatorClassGenerator {
|
||||
|
||||
private static final String INIT = "<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<? extends Parameter<Object, ?>> 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.
|
||||
* <p>
|
||||
* 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 + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
<T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider);
|
||||
}
|
||||
@@ -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<Class<?>, 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<Class<?>, 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<Class<?>, 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;
|
||||
}
|
||||
}
|
||||
@@ -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<T> implements PersistentPropertyAccessor<T> {
|
||||
|
||||
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<T> 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<T> 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;
|
||||
}
|
||||
}
|
||||
@@ -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 <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
|
||||
|
||||
PersistentPropertyAccessor<T> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
* <ul>
|
||||
* <li>defaulting bitmask ({@code int}), a bit mask slot for each 32 parameters</li>
|
||||
* <li>{@code DefaultConstructorMarker} (usually null)</li>
|
||||
* </ul>
|
||||
* <strong>Defaulting bitmask</strong>
|
||||
* <p/>
|
||||
* 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<KParameter> 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 <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> 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 <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
|
||||
@Nullable PreferredConstructor<? extends T, P> preferredConstructor, ParameterValueProvider<P> 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<Parameter<Object, P>> parameters = preferredConstructor.getParameters();
|
||||
|
||||
// Prepare user-space arguments
|
||||
for (int i = 0; i < userParameterCount; i++) {
|
||||
|
||||
Parameter<Object, P> parameter = parameters.get(i);
|
||||
params[i] = provider.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
KotlinDefaultMask defaultMask = KotlinDefaultMask.from(constructor, it -> {
|
||||
|
||||
int index = kParameters.indexOf(it);
|
||||
|
||||
Parameter<Object, P> parameter = parameters.get(index);
|
||||
Class<Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
PreferredConstructor<? extends T, P> 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<?, P> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user