diff --git a/src/main/java/org/springframework/data/convert/BytecodeGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/BytecodeGeneratingEntityInstantiator.java
new file mode 100644
index 000000000..4cd16ddf8
--- /dev/null
+++ b/src/main/java/org/springframework/data/convert/BytecodeGeneratingEntityInstantiator.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.convert;
+
+import static org.springframework.asm.Opcodes.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+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.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.ParameterValueProvider;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.util.Assert;
+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
+ * @since 1.10
+ */
+public enum BytecodeGeneratingEntityInstantiator implements EntityInstantiator {
+
+ INSTANCE;
+
+ private final ObjectInstantiatorClassGenerator classGenerator = ObjectInstantiatorClassGenerator.INSTANCE;
+
+ private volatile Map, EntityInstantiator> entityInstantiators = new HashMap, EntityInstantiator>(
+ 32);
+
+ /* (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 ei = this.entityInstantiators.get(entity.getTypeInformation());
+
+ if (ei != null) {
+ return ei.createInstance(entity, provider);
+ }
+
+ ei = potentiallyCreateAndRegisterEntityInstantiator(entity);
+
+ return ei.createInstance(entity, provider);
+ }
+
+ /**
+ * @param entity
+ * @return
+ */
+ private synchronized EntityInstantiator potentiallyCreateAndRegisterEntityInstantiator(PersistentEntity, ?> entity) {
+
+ Map, EntityInstantiator> map = this.entityInstantiators;
+
+ EntityInstantiator ei = map.get(entity.getTypeInformation());
+ if (ei != null) {
+ return ei;
+ }
+
+ ei = createEntityInstantiator(entity);
+
+ map = new HashMap, EntityInstantiator>(map);
+ map.put(entity.getTypeInformation(), ei);
+
+ this.entityInstantiators = map;
+
+ return ei;
+ }
+
+ /**
+ * @param entity
+ * @return
+ */
+ private EntityInstantiator createEntityInstantiator(PersistentEntity, ?> entity) {
+
+ if (shoudUseReflectionEntityInstantiator(entity)) {
+ return ReflectionEntityInstantiator.INSTANCE;
+ }
+
+ try {
+ return new EntityInstantiatorAdapter(createObjectInstantiator(entity));
+ } catch (Throwable ex) {
+ return ReflectionEntityInstantiator.INSTANCE;
+ }
+ }
+
+ /**
+ * @param entity
+ * @return
+ */
+ private boolean shoudUseReflectionEntityInstantiator(PersistentEntity, ?> entity) {
+
+ Class> type = entity.getType();
+
+ if (type.isInterface() //
+ || type.isArray() //
+ || !Modifier.isPublic(type.getModifiers()) //
+ || (type.isMemberClass() && !Modifier.isStatic(type.getModifiers())) //
+ || ClassUtils.isCglibProxyClass(type)) { //
+ return true;
+ }
+
+ PreferredConstructor, ?> persistenceConstructor = entity.getPersistenceConstructor();
+ if (persistenceConstructor == null || !Modifier.isPublic(persistenceConstructor.getConstructor().getModifiers())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a dynamically generated {@link ObjectInstantiator} for the given {@link InstantiatorKey}. There will always
+ * be exactly one {@link ObjectInstantiator} instance per {@link PersistentEntity}.
+ *
+ *
+ * @param entity
+ * @return
+ */
+ private ObjectInstantiator createObjectInstantiator(PersistentEntity, ?> entity) {
+ try {
+ return (ObjectInstantiator) classGenerator.generateCustomInstantiatorClass(entity).newInstance();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * A {@link ClassLoader} that can load classes from {@code byte[]} representations.
+ *
+ * @author Thomas Darimont
+ */
+ private static class ByteArrayClassLoader extends ClassLoader {
+
+ public ByteArrayClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ /**
+ * Tries to load a class given {@code byte[]}.
+ *
+ * @param name must not be {@literal null}
+ * @param bytes must not be {@literal null}
+ * @return
+ */
+ public Class> loadClass(String name, byte[] bytes) {
+
+ Assert.notNull(name, "name must not be null");
+ Assert.notNull(bytes, "bytes must not be null");
+
+ try {
+ Class> clazz = findClass(name);
+ if (clazz != null) {
+ return clazz;
+ }
+ } catch (ClassNotFoundException ignore) {}
+
+ return defineClass(name, bytes, 0, bytes.length);
+ }
+ }
+
+ /**
+ * @author Thomas Darimont
+ */
+ private static class EntityInstantiatorAdapter implements EntityInstantiator {
+
+ private static final Object[] EMPTY_ARRAY = new Object[0];
+
+ private final ObjectInstantiator instantiator;
+
+ /**
+ * @param instantiator
+ */
+ public EntityInstantiatorAdapter(ObjectInstantiator instantiator) {
+ this.instantiator = instantiator;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public , P extends PersistentProperty> T createInstance(E entity,
+ ParameterValueProvider
provider) {
+
+ Object[] params = extractInvocationArguments(provider, entity.getPersistenceConstructor());
+ try {
+ return (T) instantiator.newInstance(params);
+ } catch (Exception e) {
+ throw new MappingInstantiationException(entity, Arrays.asList(params), e);
+ }
+ }
+
+ /**
+ * @param provider
+ * @param prefCtor
+ * @return
+ */
+ private
, T> Object[] extractInvocationArguments(
+ ParameterValueProvider
provider, PreferredConstructor extends T, P> prefCtor) {
+
+ if (provider == null || prefCtor == null || !prefCtor.hasParameters()) {
+ return EMPTY_ARRAY;
+ }
+
+ List params = new ArrayList();
+ for (Parameter, P> parameter : prefCtor.getParameters()) {
+ params.add(provider.getParameterValue(parameter));
+ }
+
+ return params.toArray();
+ }
+ }
+
+ /**
+ * @author Thomas Darimont
+ */
+ public static 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
+ */
+ enum ObjectInstantiatorClassGenerator {
+
+ INSTANCE;
+
+ 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) };
+
+ private final ByteArrayClassLoader classLoader;
+
+ private ObjectInstantiatorClassGenerator() {
+
+ this.classLoader = AccessController.doPrivileged(new PrivilegedAction() {
+ public ByteArrayClassLoader run() {
+ return new ByteArrayClassLoader(ObjectInstantiatorClassGenerator.class.getClassLoader());
+ }
+ });
+ }
+
+ /**
+ * Generate a new class for the given {@link InstantiatorKey}.
+ *
+ * @param key
+ * @return
+ */
+ public Class> generateCustomInstantiatorClass(PersistentEntity, ?> entity) {
+
+ String className = generateClassName(entity);
+ byte[] bytecode = generateBytecode(className, entity);
+
+ return classLoader.loadClass(className, bytecode);
+ }
+
+ /**
+ * @param key
+ * @return
+ */
+ private String generateClassName(PersistentEntity, ?> entity) {
+ return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36);
+ }
+
+ /**
+ * Generate a new class for the given {@link InstantiatorKey}.
+ *
+ * @param key
+ * @return
+ */
+ public byte[] generateBytecode(String internalClassName, PersistentEntity, ?> entity) {
+
+ 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);
+
+ 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
+ */
+ private void visitCreateMethod(ClassWriter cw, PersistentEntity, ?> entity) {
+
+ 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);
+
+ Constructor> ctor = entity.getPersistenceConstructor().getConstructor();
+ Class>[] parameterTypes = ctor.getParameterTypes();
+ for (int i = 0; i < parameterTypes.length; i++) {
+ mv.visitVarInsn(ALOAD, 1);
+
+ visitArrayIndex(mv, i);
+
+ mv.visitInsn(AALOAD);
+
+ if (parameterTypes[i].isPrimitive()) {
+ 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 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/convert/EntityInstantiators.java b/src/main/java/org/springframework/data/convert/EntityInstantiators.java
index 93595078a..c88025d1a 100644
--- a/src/main/java/org/springframework/data/convert/EntityInstantiators.java
+++ b/src/main/java/org/springframework/data/convert/EntityInstantiators.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 the original author or authors.
+ * Copyright 2012-2014 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.
@@ -26,6 +26,7 @@ import org.springframework.util.Assert;
* default one.
*
* @author Oliver Gierke
+ * @author Thomas Darimont
*/
public class EntityInstantiators {
@@ -54,7 +55,7 @@ public class EntityInstantiators {
* @param customInstantiators must not be {@literal null}.
*/
public EntityInstantiators(Map, EntityInstantiator> customInstantiators) {
- this(ReflectionEntityInstantiator.INSTANCE, customInstantiators);
+ this(BytecodeGeneratingEntityInstantiator.INSTANCE, customInstantiators);
}
/**
diff --git a/src/main/java/org/springframework/data/mapping/PersistentEntity.java b/src/main/java/org/springframework/data/mapping/PersistentEntity.java
index 543fe39b7..9ab078e43 100644
--- a/src/main/java/org/springframework/data/mapping/PersistentEntity.java
+++ b/src/main/java/org/springframework/data/mapping/PersistentEntity.java
@@ -41,7 +41,7 @@ public interface PersistentEntity> {
* Returns the {@link PreferredConstructor} to be used to instantiate objects of this {@link PersistentEntity}.
*
* @return {@literal null} in case no suitable constructor for automatic construction can be found. This usually
- * indicates that the instantiation of the object of tthat persistent entity is done through either a customer
+ * indicates that the instantiation of the object of that persistent entity is done through either a customer
* {@link EntityInstantiator} or handled by custom conversion mechanisms entirely.
*/
PreferredConstructor getPersistenceConstructor();
diff --git a/src/test/java/org/springframework/data/convert/BytecodeGeneratingEntityInstantiatorUnitTests.java b/src/test/java/org/springframework/data/convert/BytecodeGeneratingEntityInstantiatorUnitTests.java
new file mode 100644
index 000000000..f0e6a41ac
--- /dev/null
+++ b/src/test/java/org/springframework/data/convert/BytecodeGeneratingEntityInstantiatorUnitTests.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.convert;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.data.convert.BytecodeGeneratingEntityInstantiator.*;
+import static org.springframework.data.util.ClassTypeInformation.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.data.convert.BytecodeGeneratingEntityInstantiatorUnitTests.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.util.ReflectionUtils;
+import org.springframework.util.ReflectionUtils.FieldCallback;
+
+/**
+ * Unit tests for {@link BytecodeGeneratingEntityInstantiator}.
+ *
+ * @author Thomas Darimont
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class BytecodeGeneratingEntityInstantiatorUnitTests> {
+
+ @Mock PersistentEntity, P> entity;
+ @Mock ParameterValueProvider
provider;
+ @Mock PreferredConstructor, P> constructor;
+ @Mock Parameter, P> parameter;
+
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiatesSimpleObjectCorrectly() {
+
+ when(entity.getType()).thenReturn((Class) Object.class);
+ INSTANCE.createInstance(entity, provider);
+ }
+
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiatesArrayCorrectly() {
+
+ when(entity.getType()).thenReturn((Class) String[][].class);
+ INSTANCE.createInstance(entity, provider);
+ }
+
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiatesTypeWithPreferredConstructorUsingParameterValueProvider() {
+
+ PreferredConstructor constructor = new PreferredConstructorDiscoverer(Foo.class).getConstructor();
+
+ when(entity.getType()).thenReturn((Class) Foo.class);
+ when(entity.getPersistenceConstructor()).thenReturn(constructor);
+
+ Object instance = INSTANCE.createInstance(entity, provider);
+
+ assertTrue(instance instanceof Foo);
+ verify(provider, times(1)).getParameterValue((Parameter) constructor.getParameters().iterator().next());
+ }
+
+ /**
+ * @see DATACMNS-300
+ */
+ @Test(expected = MappingInstantiationException.class)
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void throwsExceptionOnBeanInstantiationException() {
+
+ when(entity.getPersistenceConstructor()).thenReturn(null);
+ when(entity.getType()).thenReturn((Class) PersistentEntity.class);
+
+ INSTANCE.createInstance(entity, provider);
+ }
+
+ /**
+ * @see DATACMNS-134
+ */
+ @Test
+ public void createsInnerClassInstanceCorrectly() {
+
+ BasicPersistentEntity entity = new BasicPersistentEntity(from(Inner.class));
+ PreferredConstructor constructor = entity.getPersistenceConstructor();
+ Parameter parameter = constructor.getParameters().iterator().next();
+
+ final Object outer = new Outer();
+
+ when(provider.getParameterValue(parameter)).thenReturn(outer);
+ final Inner instance = INSTANCE.createInstance(entity, provider);
+
+ assertThat(instance, is(notNullValue()));
+
+ // Hack to check syntheic field as compiles create different field names (e.g. this$0, this$1)
+ ReflectionUtils.doWithFields(Inner.class, new FieldCallback() {
+ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+ if (field.isSynthetic() && field.getName().startsWith("this$")) {
+ ReflectionUtils.makeAccessible(field);
+ assertThat(ReflectionUtils.getField(field, instance), is(outer));
+ }
+ }
+ });
+ }
+
+ /**
+ * @see DATACMNS-283
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void capturesContextOnInstantiationException() throws Exception {
+
+ PersistentEntity entity = new BasicPersistentEntity(from(Sample.class));
+
+ when(provider.getParameterValue(Mockito.any(Parameter.class))).thenReturn("FOO");
+
+ Constructor constructor = Sample.class.getConstructor(Long.class, String.class);
+ List parameters = Arrays.asList((Object) "FOO", (Object) "FOO");
+
+ try {
+
+ INSTANCE.createInstance(entity, provider);
+ fail("Expected MappingInstantiationException!");
+
+ } catch (MappingInstantiationException o_O) {
+
+ assertThat(o_O.getConstructor(), is(constructor));
+ assertThat(o_O.getConstructorArguments(), is(parameters));
+ assertEquals(Sample.class, o_O.getEntityType());
+
+ assertThat(o_O.getMessage(), containsString(Sample.class.getName()));
+ assertThat(o_O.getMessage(), containsString(Long.class.getName()));
+ assertThat(o_O.getMessage(), containsString(String.class.getName()));
+ assertThat(o_O.getMessage(), containsString("FOO"));
+ }
+ }
+
+ /**
+ * @see DATACMNS-578
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiateObjCtorDefault() {
+
+ PreferredConstructor constructor = new PreferredConstructorDiscoverer(ObjCtorDefault.class)
+ .getConstructor();
+
+ when(entity.getType()).thenReturn((Class) ObjCtorDefault.class);
+ when(entity.getPersistenceConstructor()).thenReturn(constructor);
+
+ for (int i = 0; i < 2; i++) {
+ Object instance = INSTANCE.createInstance(entity, provider);
+ assertTrue(instance instanceof ObjCtorDefault);
+ }
+ }
+
+ /**
+ * @see DATACMNS-578
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiateObjCtorNoArgs() {
+
+ PreferredConstructor constructor = new PreferredConstructorDiscoverer(ObjCtorNoArgs.class)
+ .getConstructor();
+
+ when(entity.getType()).thenReturn((Class) ObjCtorNoArgs.class);
+ when(entity.getPersistenceConstructor()).thenReturn(constructor);
+
+ for (int i = 0; i < 2; i++) {
+ Object instance = INSTANCE.createInstance(entity, provider);
+ assertTrue(instance instanceof ObjCtorNoArgs);
+ assertTrue(((ObjCtorNoArgs) instance).ctorInvoked);
+ }
+ }
+
+ /**
+ * @see DATACMNS-578
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiateObjCtor1ParamString() {
+
+ PreferredConstructor constructor = new PreferredConstructorDiscoverer(
+ ObjCtor1ParamString.class).getConstructor();
+
+ when(entity.getType()).thenReturn((Class) ObjCtor1ParamString.class);
+ when(entity.getPersistenceConstructor()).thenReturn(constructor);
+
+ when(provider.getParameterValue(Mockito.any(Parameter.class))).thenReturn("FOO");
+
+ for (int i = 0; i < 2; i++) {
+ Object instance = INSTANCE.createInstance(entity, provider);
+ assertTrue(instance instanceof ObjCtor1ParamString);
+ assertTrue(((ObjCtor1ParamString) instance).ctorInvoked);
+ assertThat(((ObjCtor1ParamString) instance).param1, is("FOO"));
+ }
+ }
+
+ /**
+ * @see DATACMNS-578
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiateObjCtor2ParamStringString() {
+
+ PreferredConstructor constructor = new PreferredConstructorDiscoverer(
+ ObjCtor2ParamStringString.class).getConstructor();
+
+ when(entity.getType()).thenReturn((Class) ObjCtor2ParamStringString.class);
+ when(entity.getPersistenceConstructor()).thenReturn(constructor);
+
+ for (int i = 0; i < 2; i++) {
+ when(provider.getParameterValue(Mockito.any(Parameter.class))).thenReturn("FOO").thenReturn("BAR");
+
+ Object instance = INSTANCE.createInstance(entity, provider);
+ assertTrue(instance instanceof ObjCtor2ParamStringString);
+ assertTrue(((ObjCtor2ParamStringString) instance).ctorInvoked);
+ assertThat(((ObjCtor2ParamStringString) instance).param1, is("FOO"));
+ assertThat(((ObjCtor2ParamStringString) instance).param2, is("BAR"));
+ }
+ }
+
+ /**
+ * @see DATACMNS-578
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiateObjectCtor1ParamInt() {
+
+ PreferredConstructor constructor = new PreferredConstructorDiscoverer(
+ ObjectCtor1ParamInt.class).getConstructor();
+
+ when(entity.getType()).thenReturn((Class) ObjectCtor1ParamInt.class);
+ when(entity.getPersistenceConstructor()).thenReturn(constructor);
+
+ for (int i = 0; i < 2; i++) {
+ when(provider.getParameterValue(Mockito.any(Parameter.class))).thenReturn(42);
+
+ Object instance = INSTANCE.createInstance(entity, provider);
+ assertTrue(instance instanceof ObjectCtor1ParamInt);
+ assertTrue("matches", ((ObjectCtor1ParamInt) instance).param1 == 42);
+ }
+ }
+
+ /**
+ * @see DATACMNS-578
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void instantiateObjectCtor7ParamsString5IntsString() {
+
+ PreferredConstructor constructor = new PreferredConstructorDiscoverer(
+ ObjectCtor7ParamsString5IntsString.class).getConstructor();
+
+ when(entity.getType()).thenReturn((Class) ObjectCtor7ParamsString5IntsString.class);
+ when(entity.getPersistenceConstructor()).thenReturn(constructor);
+
+ for (int i = 0; i < 2; i++) {
+ when(provider.getParameterValue(Mockito.any(Parameter.class))).thenReturn("A").thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4).thenReturn(5).thenReturn("B");
+
+ Object instance = INSTANCE.createInstance(entity, provider);
+ assertTrue(instance instanceof ObjectCtor7ParamsString5IntsString);
+ assertThat(((ObjectCtor7ParamsString5IntsString) instance).param1, is("A"));
+ assertThat(((ObjectCtor7ParamsString5IntsString) instance).param2, is(1));
+ assertThat(((ObjectCtor7ParamsString5IntsString) instance).param3, is(2));
+ assertThat(((ObjectCtor7ParamsString5IntsString) instance).param4, is(3));
+ assertThat(((ObjectCtor7ParamsString5IntsString) instance).param5, is(4));
+ assertThat(((ObjectCtor7ParamsString5IntsString) instance).param6, is(5));
+ assertThat(((ObjectCtor7ParamsString5IntsString) instance).param7, is("B"));
+ }
+ }
+
+ static class Foo {
+
+ Foo(String foo) {
+
+ }
+ }
+
+ static class Outer {
+
+ class Inner {
+
+ }
+ }
+
+ static class Sample {
+
+ final Long id;
+ final String name;
+
+ public Sample(Long id, String name) {
+
+ this.id = id;
+ this.name = name;
+ }
+ }
+
+ /**
+ * @author Thomas Darimont
+ *
+ */
+ public static class ObjCtorDefault {}
+
+ /**
+ * @author Thomas Darimont
+ *
+ */
+ public static class ObjCtorNoArgs {
+
+ public boolean ctorInvoked;
+
+ public ObjCtorNoArgs() {
+ ctorInvoked = true;
+ }
+ }
+
+ /**
+ * @author Thomas Darimont
+ *
+ */
+ public static class ObjCtor1ParamString {
+
+ public boolean ctorInvoked;
+ public String param1;
+
+ public ObjCtor1ParamString(String param1) {
+ this.param1 = param1;
+ this.ctorInvoked = true;
+ }
+ }
+
+ /**
+ * @author Thomas Darimont
+ *
+ */
+ public static class ObjCtor2ParamStringString {
+
+ public boolean ctorInvoked;
+ public String param1;
+ public String param2;
+
+ public ObjCtor2ParamStringString(String param1, String param2) {
+ this.ctorInvoked = true;
+ this.param1 = param1;
+ this.param2 = param2;
+ }
+ }
+
+ /**
+ * @author Thomas Darimont
+ *
+ */
+ public static class ObjectCtor1ParamInt{
+
+ public int param1;
+
+ public ObjectCtor1ParamInt(int param1) {
+ this.param1 = param1;
+ }
+ }
+
+ /**
+ * @author Thomas Darimont
+ *
+ */
+ public static class ObjectCtor7ParamsString5IntsString{
+
+ public String param1;
+ public int param2;
+ public int param3;
+ public int param4;
+ public int param5;
+ public int param6;
+ public String param7;
+
+ public ObjectCtor7ParamsString5IntsString(String param1, int param2, int param3, int param4, int param5,
+ int param6, String param7) {
+ this.param1 = param1;
+ this.param2 = param2;
+ this.param3 = param3;
+ this.param4 = param4;
+ this.param5 = param5;
+ this.param6 = param6;
+ this.param7 = param7;
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java b/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java
index 18eaf449b..13233fee9 100644
--- a/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java
+++ b/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java
@@ -15,9 +15,9 @@
*/
package org.springframework.data.convert;
-import static org.mockito.Mockito.*;
-import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.Map;
@@ -36,11 +36,8 @@ import org.springframework.data.mapping.PersistentEntity;
@RunWith(MockitoJUnitRunner.class)
public class EntityInstantiatorsUnitTests {
- @Mock
- PersistentEntity, ?> entity;
-
- @Mock
- EntityInstantiator customInstantiator;
+ @Mock PersistentEntity, ?> entity;
+ @Mock EntityInstantiator customInstantiator;
@Test(expected = IllegalArgumentException.class)
public void rejectsNullFallbackInstantiator() {
@@ -51,7 +48,8 @@ public class EntityInstantiatorsUnitTests {
public void usesReflectionEntityInstantiatorAsDefaultFallback() {
EntityInstantiators instantiators = new EntityInstantiators();
- assertThat(instantiators.getInstantiatorFor(entity), is((EntityInstantiator) ReflectionEntityInstantiator.INSTANCE));
+ assertThat(instantiators.getInstantiatorFor(entity),
+ is((EntityInstantiator) BytecodeGeneratingEntityInstantiator.INSTANCE));
}
@Test
diff --git a/template.mf b/template.mf
index 96686f8fe..abe2cad8f 100644
--- a/template.mf
+++ b/template.mf
@@ -15,6 +15,7 @@ Import-Template:
javax.xml.bind.*;version="0";resolution:=optional,
javax.xml.transform.*;version="0";resolution:=optional,
org.springframework.aop.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional,
+ org.springframework.asm.*;version="${spring:[=.=.=,+1.1.0)}",
org.springframework.beans.*;version="${spring:[=.=.=,+1.1.0)}",
org.springframework.core.*;version="${spring:[=.=.=,+1.1.0)}",
org.springframework.context.*;version="${spring:[=.=.=,+1.1.0)}";resolution:=optional,