diff --git a/jOOQ-examples/jOOQ-kotlin-example/pom.xml b/jOOQ-examples/jOOQ-kotlin-example/pom.xml index ed4705f8c1..178ba4f9b9 100644 --- a/jOOQ-examples/jOOQ-kotlin-example/pom.xml +++ b/jOOQ-examples/jOOQ-kotlin-example/pom.xml @@ -19,7 +19,7 @@ UTF-8 - 1.2.21 + 1.2.30 3.11.0-SNAPSHOT 1.4.195 1.8 @@ -44,6 +44,14 @@ kotlin-stdlib-jre8 ${kotlin.version} + + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + junit diff --git a/jOOQ-examples/jOOQ-kotlin-example/src/main/kotlin/org/jooq/example/kotlin/FunWithKotlinAndJOOQ.kt b/jOOQ-examples/jOOQ-kotlin-example/src/main/kotlin/org/jooq/example/kotlin/FunWithKotlinAndJOOQ.kt index 960b83b935..4bd59771e5 100644 --- a/jOOQ-examples/jOOQ-kotlin-example/src/main/kotlin/org/jooq/example/kotlin/FunWithKotlinAndJOOQ.kt +++ b/jOOQ-examples/jOOQ-kotlin-example/src/main/kotlin/org/jooq/example/kotlin/FunWithKotlinAndJOOQ.kt @@ -170,6 +170,16 @@ fun main(args: Array) { 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.ilike(field : Field): 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 > R.component1() : T { - return this.value1(); -} - -operator fun > R.component1() : T { - return this.value1(); -} - -operator fun > R.component2() : T { - return this.value2(); -} - -operator fun > R.component1() : T { - return this.value1(); -} - -operator fun > R.component2() : T { - return this.value2(); -} - -operator fun > R.component3() : T { - return this.value3(); -} - -operator fun > R.component1() : T { - return this.value1(); -} - -operator fun > R.component2() : T { - return this.value2(); -} - -operator fun > R.component3() : T { - return this.value3(); -} - -operator fun > 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) \ No newline at end of file diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java index 508c1b72ee..ab262a5197 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java @@ -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 implements RecordMapper 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 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 parameterNames = new ArrayList(); + 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 javaConstructor = (Constructor) this.type.getConstructor(parameterTypes); + delegate = new ImmutablePOJOMapperWithParameterNames(javaConstructor, parameterNames); + return; } - - Constructor javaConstructor = (Constructor) 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) {} } diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index f3b2b3e8df..bc3f2e6543 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -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 [a] LIKE [b] ESCAPE [...] * 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 (javax.persistence) is on the * classpath. */ - private static Boolean isJPAAvailable; + private static volatile Boolean isJPAAvailable; + + /** + * Indicating whether Kotlin (kotlin.*) 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 type has any {@link Column} annotated members * or methods