Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DATACMNS-1126 - Add Kotlin constructor support. #233

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<version>2.0.0.DATACMNS-1126-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down Expand Up @@ -231,6 +231,49 @@
<scope>test</scope>
</dependency>

<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin}</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin}</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.nhaarman</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>1.5.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</exclusion>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</exclusion>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Scala -->
<dependency>
<groupId>org.scala-lang</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
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;
Expand Down Expand Up @@ -57,6 +55,19 @@
*/
public class ClassGeneratingEntityInstantiator implements EntityInstantiator {

private static final int ARG_CACHE_SIZE = 100;

private static final ThreadLocal<Object[][]> OBJECT_POOL = ThreadLocal.withInitial(() -> {

Object[][] cached = new Object[ARG_CACHE_SIZE][];

for (int i = 0; i < ARG_CACHE_SIZE; i++) {
cached[i] = new Object[i];
}

return cached;
});

private final ObjectInstantiatorClassGenerator generator;

private volatile Map<TypeInformation<?>, EntityInstantiator> entityInstantiators = new HashMap<>(32);
Expand Down Expand Up @@ -120,12 +131,20 @@ private EntityInstantiator createEntityInstantiator(PersistentEntity<?, ?> entit
}

try {
return new EntityInstantiatorAdapter(createObjectInstantiator(entity));
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
Expand All @@ -151,17 +170,46 @@ private boolean shouldUseReflectionEntityInstantiator(PersistentEntity<?, ?> ent
}

/**
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity}. There will
* always be exactly one {@link ObjectInstantiator} instance per {@link PersistentEntity}.
* <p>
* Allocates an object array for instance creation. This method uses the argument array cache if possible.
*
* @param argumentCount
* @return
* @since 2.0
* @see #ARG_CACHE_SIZE
*/
static Object[] allocateArguments(int argumentCount) {
return argumentCount < ARG_CACHE_SIZE ? OBJECT_POOL.get()[argumentCount] : new Object[argumentCount];
}

/**
* Deallocates an object array used for instance creation. Parameters are cleared if the array was cached.
*
* @param argumentCount
* @return
* @since 2.0
* @see #ARG_CACHE_SIZE
*/
static void deallocateArguments(Object[] params) {

if (params.length != 0 && params.length < ARG_CACHE_SIZE) {
Arrays.fill(params, null);
}
}

/**
* 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
*/
private ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity) {
ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {

try {
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity).newInstance();
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand All @@ -172,11 +220,10 @@ private ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entit
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Mark Paluch
*/
private static class EntityInstantiatorAdapter implements EntityInstantiator {

private static final Object[] EMPTY_ARRAY = new Object[0];

private final ObjectInstantiator instantiator;

/**
Expand All @@ -203,6 +250,8 @@ public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentPrope
return (T) instantiator.newInstance(params);
} catch (Exception e) {
throw new MappingInstantiationException(entity, Arrays.asList(params), e);
} finally {
deallocateArguments(params);
}
}

Expand All @@ -216,17 +265,18 @@ public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentPrope
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
@Nullable PreferredConstructor<? extends T, P> constructor, ParameterValueProvider<P> provider) {

if (provider == null || constructor == null || !constructor.hasParameters()) {
return EMPTY_ARRAY;
if (constructor == null || !constructor.hasParameters()) {
return allocateArguments(0);
}

List<Object> params = new ArrayList<>(constructor.getConstructor().getParameterCount());
Object[] params = allocateArguments(constructor.getConstructor().getParameterCount());

int index = 0;
for (Parameter<?, P> parameter : constructor.getParameters()) {
params.add(provider.getParameterValue(parameter));
params[index++] = provider.getParameterValue(parameter);
}

return params.toArray();
return params;
}
}

Expand Down Expand Up @@ -290,7 +340,7 @@ static class ObjectInstantiatorClassGenerator {

private final ByteArrayClassLoader classLoader;

private ObjectInstantiatorClassGenerator() {
ObjectInstantiatorClassGenerator() {

this.classLoader = AccessController.doPrivileged(
(PrivilegedAction<ByteArrayClassLoader>) () -> new ByteArrayClassLoader(ClassUtils.getDefaultClassLoader()));
Expand All @@ -300,12 +350,14 @@ private ObjectInstantiatorClassGenerator() {
* Generate a new class for the given {@link PersistentEntity}.
*
* @param entity
* @param constructor
* @return
*/
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity) {
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {

String className = generateClassName(entity);
byte[] bytecode = generateBytecode(className, entity);
byte[] bytecode = generateBytecode(className, entity, constructor);

return classLoader.loadClass(className, bytecode);
}
Expand All @@ -323,9 +375,11 @@ private String generateClassName(PersistentEntity<?, ?> entity) {
*
* @param internalClassName
* @param entity
* @param constructor
* @return
*/
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity) {
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

Expand All @@ -334,7 +388,7 @@ public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?>

visitDefaultConstructor(cw);

visitCreateMethod(cw, entity);
visitCreateMethod(cw, entity, constructor);

cw.visitEnd();

Expand All @@ -357,8 +411,10 @@ private void visitDefaultConstructor(ClassWriter cw) {
*
* @param cw
* @param entity
* @param constructor
*/
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity) {
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity,
@Nullable PreferredConstructor<?, ?> constructor) {

String entityTypeResourcePath = Type.getInternalName(entity.getType());

Expand All @@ -368,8 +424,6 @@ private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity) {
mv.visitTypeInsn(NEW, entityTypeResourcePath);
mv.visitInsn(DUP);

PreferredConstructor<?, ?> constructor = entity.getPersistenceConstructor();

if (constructor != null) {

Constructor<?> ctor = constructor.getConstructor();
Expand Down
Loading