[#7324] [#7329] Support Kotlin metadata in DefaultRecordMapper

This commit is contained in:
lukaseder 2018-03-22 11:07:43 +01:00
parent e34af25a83
commit cb618620e0
4 changed files with 163 additions and 85 deletions

View File

@ -19,7 +19,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.2.21</kotlin.version>
<kotlin.version>1.2.30</kotlin.version>
<org.jooq.version>3.11.0-SNAPSHOT</org.jooq.version>
<org.h2.version>1.4.195</org.h2.version>
<java.version>1.8</java.version>
@ -44,6 +44,14 @@
<artifactId>kotlin-stdlib-jre8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- Needed for mapping into Kotlin data classes -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>junit</groupId>

View File

@ -170,6 +170,16 @@ fun main(args: Array<String>) {
println("Actor ID ${id - 3}: $first $last wrote ${-count} books")
}
// jOOQ can fetch into Kotlin data classes by attribute name
header("Data classes")
ctx.select(b.TITLE, a.FIRST_NAME.concat(" ").concat(a.LAST_NAME).`as`("author"), one().`as`("dummy"))
.from(a)
.join(b).on(a.ID.eq(b.AUTHOR_ID))
.fetchInto(Book::class.java)
.forEach {
println("$it")
}
// Don't we wish for multiline strings in Java?
header("Using multiline strings with the plain SQL API")
ctx.resultQuery("""
@ -234,48 +244,5 @@ inline fun <F : Field<String>> F.ilike(field : Field<String>): Condition {
return condition("{0} ilike {1}", this, field);
}
// Destructuring records into sets of local variables
// Will be supported by jOOQ out-of-the-box:
// https://github.com/jOOQ/jOOQ/issues/6245
// --------------------------------------------------
operator fun <T, R : Record1<T>> R.component1() : T {
return this.value1();
}
operator fun <T, R : Record2<T, *>> R.component1() : T {
return this.value1();
}
operator fun <T, R : Record2<*, T>> R.component2() : T {
return this.value2();
}
operator fun <T, R : Record3<T, *, *>> R.component1() : T {
return this.value1();
}
operator fun <T, R : Record3<*, T, *>> R.component2() : T {
return this.value2();
}
operator fun <T, R : Record3<*, *, T>> R.component3() : T {
return this.value3();
}
operator fun <T, R : Record4<T, *, *, *>> R.component1() : T {
return this.value1();
}
operator fun <T, R : Record4<*, T, *, *>> R.component2() : T {
return this.value2();
}
operator fun <T, R : Record4<*, *, T, *>> R.component3() : T {
return this.value3();
}
operator fun <T, R : Record4<*, *, *, T>> R.component4() : T {
return this.value4();
}
// ... more methods ...
// Kotlin data classes can be used with DefaultRecordMapper
data class Book(val author: String, val title: String)

View File

@ -54,8 +54,16 @@ import static org.jooq.tools.reflect.Reflect.accessible;
import java.beans.ConstructorProperties;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
@ -368,36 +376,40 @@ public class DefaultRecordMapper<R extends Record, E> implements RecordMapper<R,
}
}
// [#7324] Map immutable Kotlin classes by parameter names if kotlin-reflect is on the classpath
try {
Reflect JvmClassMappingKt = Reflect.on("kotlin.jvm.JvmClassMappingKt");
Object klass = JvmClassMappingKt.call("getKotlinClass", type).get();
if (Tools.isKotlinAvailable()) {
try {
Reflect jvmClassMappingKt = Tools.ktJvmClassMapping();
Reflect kClasses = Tools.ktKClasses();
Reflect KClasses = Reflect.on("kotlin.reflect.full.KClasses");
Reflect primaryConstructor = KClasses.call("getPrimaryConstructor", klass);
Object klass = jvmClassMappingKt.call("getKotlinClass", type).get();
Reflect primaryConstructor = kClasses.call("getPrimaryConstructor", klass);
if (primaryConstructor.get() != null) {
// it is a Kotlin class
List parameters = primaryConstructor.call("getParameters").get();
Class<?> klassType = Reflect.on("kotlin.reflect.KClass").type();
Method getJavaClass = JvmClassMappingKt.type().getMethod("getJavaClass", klassType);
// It is a Kotlin class
if (primaryConstructor.get() != null) {
List<?> parameters = primaryConstructor.call("getParameters").get();
Class<?> klassType = Tools.ktKClass().type();
Method getJavaClass = jvmClassMappingKt.type().getMethod("getJavaClass", klassType);
List<String> parameterNames = new ArrayList<>();
Class[] parameterTypes = new Class[parameters.size()];
for (int i = 0; i < parameters.size(); i++) {
Reflect parameter = Reflect.on(parameters.get(i));
parameterNames.add(parameter.call("getName").get());
Object typeClassifier = parameter.call("getType").call("getClassifier").get();
parameterTypes[i] = (Class) getJavaClass.invoke(JvmClassMappingKt.get(), typeClassifier);
List<String> parameterNames = new ArrayList<String>();
Class<?>[] parameterTypes = new Class[parameters.size()];
for (int i = 0; i < parameterTypes.length; i++) {
Reflect parameter = Reflect.on(parameters.get(i));
parameterNames.add(parameter.call("getName").get());
Object typeClassifier = parameter.call("getType").call("getClassifier").get();
parameterTypes[i] = (Class<?>) getJavaClass.invoke(jvmClassMappingKt.get(), typeClassifier);
}
Constructor<E> javaConstructor = (Constructor<E>) this.type.getConstructor(parameterTypes);
delegate = new ImmutablePOJOMapperWithParameterNames(javaConstructor, parameterNames);
return;
}
Constructor<E> javaConstructor = (Constructor<E>) this.type.getConstructor(parameterTypes);
delegate = new ImmutablePOJOMapperWithParameterNames(javaConstructor, parameterNames);
return;
}
} catch (ReflectException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// do nothing
catch (ReflectException ignore) {}
catch (NoSuchMethodException ignore) {}
catch (IllegalAccessException ignore) {}
catch (InvocationTargetException ignore) {}
}

View File

@ -238,6 +238,7 @@ import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.tools.jdbc.JDBCUtils;
import org.jooq.tools.reflect.Reflect;
import org.jooq.tools.reflect.ReflectException;
import org.jooq.types.UByte;
import org.jooq.types.UInteger;
import org.jooq.types.ULong;
@ -513,36 +514,49 @@ final class Tools {
* The default escape character for <code>[a] LIKE [b] ESCAPE [...]</code>
* clauses.
*/
static final char ESCAPE = '!';
static final char ESCAPE = '!';
/**
* A lock for the initialisation of other static members
*/
private static final Object initLock = new Object();
/**
* Indicating whether JPA (<code>javax.persistence</code>) is on the
* classpath.
*/
private static Boolean isJPAAvailable;
private static volatile Boolean isJPAAvailable;
/**
* Indicating whether Kotlin (<code>kotlin.*</code>) is on the classpath.
*/
private static volatile Boolean isKotlinAvailable;
private static volatile Reflect ktJvmClassMapping;
private static volatile Reflect ktKClasses;
private static volatile Reflect ktKClass;
/**
* [#3696] The maximum number of consumed exceptions in
* {@link #consumeExceptions(Configuration, PreparedStatement, SQLException)}
* helps prevent infinite loops and {@link OutOfMemoryError}.
*/
private static int maxConsumedExceptions = 256;
private static int maxConsumedResults = 65536;
private static int maxConsumedExceptions = 256;
private static int maxConsumedResults = 65536;
/**
* A pattern for the dash line syntax
*/
private static final Pattern DASH_PATTERN = Pattern.compile("(-+)");
private static final Pattern DASH_PATTERN = Pattern.compile("(-+)");
/**
* A pattern for the pipe line syntax
*/
private static final Pattern PIPE_PATTERN = Pattern.compile("(?<=\\|)([^|]+)(?=\\|)");
private static final Pattern PIPE_PATTERN = Pattern.compile("(?<=\\|)([^|]+)(?=\\|)");
/**
* A pattern for the dash line syntax
*/
private static final Pattern PLUS_PATTERN = Pattern.compile("\\+(-+)(?=\\+)");
private static final Pattern PLUS_PATTERN = Pattern.compile("\\+(-+)(?=\\+)");
/**
* All characters that are matched by Java's interpretation of \s.
@ -2825,20 +2839,97 @@ final class Tools {
/**
* Check if JPA classes can be loaded. This is only done once per JVM!
*/
private static final boolean isJPAAvailable() {
static final boolean isJPAAvailable() {
if (isJPAAvailable == null) {
try {
Class.forName(Column.class.getName());
isJPAAvailable = true;
}
catch (Throwable e) {
isJPAAvailable = false;
synchronized (initLock) {
if (isJPAAvailable == null) {
try {
Class.forName(Column.class.getName());
isJPAAvailable = true;
}
catch (Throwable e) {
isJPAAvailable = false;
}
}
}
}
return isJPAAvailable;
}
static final boolean isKotlinAvailable() {
if (isKotlinAvailable == null) {
synchronized (initLock) {
if (isKotlinAvailable == null) {
try {
if (ktJvmClassMapping() != null) {
if (ktKClasses() != null) {
isKotlinAvailable = true;
}
else {
isKotlinAvailable = false;
log.info("Kotlin is available, but not kotlin-reflect. Add the kotlin-reflect dependency to better use Kotlin features like data classes");
}
}
else {
isKotlinAvailable = false;
}
}
catch (ReflectException e) {
isKotlinAvailable = false;
}
}
}
}
return isKotlinAvailable;
}
static final Reflect ktJvmClassMapping() {
if (ktJvmClassMapping == null) {
synchronized (initLock) {
if (ktJvmClassMapping == null) {
try {
ktJvmClassMapping = Reflect.on("kotlin.jvm.JvmClassMappingKt");
}
catch (ReflectException ignore) {}
}
}
}
return ktJvmClassMapping;
}
static final Reflect ktKClasses() {
if (ktKClasses == null) {
synchronized (initLock) {
if (ktKClasses == null) {
try {
ktKClasses = Reflect.on("kotlin.reflect.full.KClasses");
}
catch (ReflectException ignore) {}
}
}
}
return ktKClasses;
}
static final Reflect ktKClass() {
if (ktKClass == null) {
synchronized (initLock) {
if (ktKClass == null) {
try {
ktKClass = Reflect.on("kotlin.reflect.KClass");
}
catch (ReflectException ignore) {}
}
}
}
return ktKClass;
}
/**
* Check whether <code>type</code> has any {@link Column} annotated members
* or methods