Skip to content

Commit

Permalink
fix #253
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentlauvlwj committed May 1, 2021
1 parent 8fdb330 commit 67bf796
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 83 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

buildscript {
ext {
kotlinVersion = "1.4.21"
kotlinVersion = "1.4.32"
detektVersion = "1.15.0"
}
repositories {
Expand Down
2 changes: 1 addition & 1 deletion ktorm-core/src/main/kotlin/org/ktorm/dsl/Dml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ public open class AssignmentsBuilder {
if (method.returnType == Void.TYPE || !method.returnType.isPrimitive) {
null
} else {
method.returnType.kotlin.defaultValue
method.returnType.defaultValue
}
}

Expand Down
8 changes: 4 additions & 4 deletions ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map<Co
when (binding) {
is ReferenceBinding -> {
if (binding.onProperty.name in changedProperties) {
val child = this.getProperty(binding.onProperty.name) as Entity<*>?
val child = this.getProperty(binding.onProperty) as Entity<*>?
assignments[column] = child?.implementation?.getPrimaryKeyValue(binding.referenceTable as Table<*>)
}
}
Expand All @@ -265,7 +265,7 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map<Co
anyChanged = true
}

curr = curr?.getProperty(prop.name)
curr = curr?.getProperty(prop)
}

if (anyChanged) {
Expand Down Expand Up @@ -302,7 +302,7 @@ internal fun EntityImplementation.doDiscardChanges() {

check(curr is EntityImplementation)
curr.changedProperties.remove(prop.name)
curr = curr.getProperty(prop.name)
curr = curr.getProperty(prop)
}
}
}
Expand Down Expand Up @@ -334,7 +334,7 @@ private fun EntityImplementation.checkUnexpectedDiscarding(fromTable: Table<*>)
}
}

curr = curr.getProperty(prop.name)
curr = curr.getProperty(prop)
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ internal fun EntityImplementation.getPrimaryKeyValue(fromTable: Table<*>): Any?
internal fun EntityImplementation.getColumnValue(binding: ColumnBinding): Any? {
when (binding) {
is ReferenceBinding -> {
val child = this.getProperty(binding.onProperty.name) as Entity<*>?
val child = this.getProperty(binding.onProperty) as Entity<*>?
return child?.implementation?.getPrimaryKeyValue(binding.referenceTable as Table<*>)
}
is NestedBinding -> {
var curr: EntityImplementation? = this
for ((i, prop) in binding.properties.withIndex()) {
if (i != binding.properties.lastIndex) {
val child = curr?.getProperty(prop.name) as Entity<*>?
val child = curr?.getProperty(prop) as Entity<*>?
curr = child?.implementation
}
}
return curr?.getProperty(binding.properties.last().name)
return curr?.getProperty(binding.properties.last())
}
}
}
Expand Down Expand Up @@ -72,14 +72,14 @@ internal fun EntityImplementation.setPrimaryKeyValue(
internal fun EntityImplementation.setColumnValue(binding: ColumnBinding, value: Any?, forceSet: Boolean = false) {
when (binding) {
is ReferenceBinding -> {
var child = this.getProperty(binding.onProperty.name) as Entity<*>?
var child = this.getProperty(binding.onProperty) as Entity<*>?
if (child == null) {
child = Entity.create(
entityClass = binding.onProperty.returnType.jvmErasure,
fromDatabase = this.fromDatabase,
fromTable = binding.referenceTable as Table<*>
)
this.setProperty(binding.onProperty.name, child, forceSet)
this.setProperty(binding.onProperty, child, forceSet)
}

val refTable = binding.referenceTable as Table<*>
Expand All @@ -89,17 +89,17 @@ internal fun EntityImplementation.setColumnValue(binding: ColumnBinding, value:
var curr: EntityImplementation = this
for ((i, prop) in binding.properties.withIndex()) {
if (i != binding.properties.lastIndex) {
var child = curr.getProperty(prop.name) as Entity<*>?
var child = curr.getProperty(prop) as Entity<*>?
if (child == null) {
child = Entity.create(prop.returnType.jvmErasure, parent = curr)
curr.setProperty(prop.name, child, forceSet)
curr.setProperty(prop, child, forceSet)
}

curr = child.implementation
}
}

curr.setProperty(binding.properties.last().name, value, forceSet)
curr.setProperty(binding.properties.last(), value, forceSet)
}
}
}
Expand Down
86 changes: 67 additions & 19 deletions ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import kotlin.collections.LinkedHashMap
import kotlin.collections.LinkedHashSet
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.jvmErasure
import kotlin.reflect.jvm.jvmName
import kotlin.reflect.jvm.kotlinFunction
Expand Down Expand Up @@ -65,8 +66,8 @@ internal class EntityImplementation(
"flushChanges" -> this.doFlushChanges()
"discardChanges" -> this.doDiscardChanges()
"delete" -> this.doDelete()
"get" -> this.getProperty(args!![0] as String)
"set" -> this.setProperty(args!![0] as String, args[1])
"get" -> this.doGetProperty(args!![0] as String)
"set" -> this.doSetProperty(args!![0] as String, args[1])
"copy" -> this.copy()
else -> throw IllegalStateException("Unrecognized method: $method")
}
Expand All @@ -83,14 +84,14 @@ internal class EntityImplementation(
val (prop, isGetter) = ktProp
if (prop.isAbstract) {
if (isGetter) {
val result = this.getProperty(prop.name)
val result = this.getProperty(prop, unboxInlineValues = true)
if (result != null || prop.returnType.isMarkedNullable) {
return result
} else {
return prop.defaultValue.also { cacheDefaultValue(prop, it) }
return method.defaultReturnValue.also { cacheDefaultValue(prop, it) }
}
} else {
this.setProperty(prop.name, args!![0])
this.setProperty(prop, args!![0])
return null
}
} else {
Expand All @@ -106,9 +107,9 @@ internal class EntityImplementation(
}
}

private val KProperty1<*, *>.defaultValue: Any get() {
private val Method.defaultReturnValue: Any get() {
try {
return returnType.jvmErasure.defaultValue
return returnType.defaultValue
} catch (e: Throwable) {
val msg = "" +
"The value of non-null property [$this] doesn't exist, " +
Expand All @@ -119,19 +120,19 @@ internal class EntityImplementation(
}

private fun cacheDefaultValue(prop: KProperty1<*, *>, value: Any) {
val type = prop.returnType.jvmErasure
val type = prop.javaGetter!!.returnType

// Skip for primitive types, enums and string, because their default values always share the same instance.
if (type == Boolean::class) return
if (type == Char::class) return
if (type == Byte::class) return
if (type == Short::class) return
if (type == Int::class) return
if (type == Long::class) return
if (type == String::class) return
if (type.java.isEnum) return
if (type == Boolean::class.javaPrimitiveType) return
if (type == Char::class.javaPrimitiveType) return
if (type == Byte::class.javaPrimitiveType) return
if (type == Short::class.javaPrimitiveType) return
if (type == Int::class.javaPrimitiveType) return
if (type == Long::class.javaPrimitiveType) return
if (type == String::class.java) return
if (type.isEnum) return

setProperty(prop.name, value)
setProperty(prop, value)
}

@Suppress("SwallowedException")
Expand All @@ -152,11 +153,58 @@ internal class EntityImplementation(
}
}

fun getProperty(name: String): Any? {
@OptIn(ExperimentalUnsignedTypes::class)
fun getProperty(prop: KProperty1<*, *>, unboxInlineValues: Boolean = false): Any? {
if (!unboxInlineValues) {
return doGetProperty(prop.name)
}

val returnType = prop.javaGetter!!.returnType
val value = doGetProperty(prop.name)

// Unbox inline class values if necessary.
// In principle, we need to check for all inline classes, but kotlin-reflect is still unable to determine
// whether a class is inline, so as a workaround, we have to enumerate some common-used types here.
return when {
value is UByte && returnType == Byte::class.javaPrimitiveType -> value.toByte()
value is UShort && returnType == Short::class.javaPrimitiveType -> value.toShort()
value is UInt && returnType == Int::class.javaPrimitiveType -> value.toInt()
value is ULong && returnType == Long::class.javaPrimitiveType -> value.toLong()
value is UByteArray && returnType == ByteArray::class.java -> value.toByteArray()
value is UShortArray && returnType == ShortArray::class.java -> value.toShortArray()
value is UIntArray && returnType == IntArray::class.java -> value.toIntArray()
value is ULongArray && returnType == LongArray::class.java -> value.toLongArray()
else -> value
}
}

private fun doGetProperty(name: String): Any? {
return values[name]
}

fun setProperty(name: String, value: Any?, forceSet: Boolean = false) {
@OptIn(ExperimentalUnsignedTypes::class)
fun setProperty(prop: KProperty1<*, *>, value: Any?, forceSet: Boolean = false) {
val propType = prop.returnType.jvmErasure

// For inline classes, always box the underlying values as wrapper types.
// In principle, we need to check for all inline classes, but kotlin-reflect is still unable to determine
// whether a class is inline, so as a workaround, we have to enumerate some common-used types here.
val boxedValue = when {
propType == UByte::class && value is Byte -> value.toUByte()
propType == UShort::class && value is Short -> value.toUShort()
propType == UInt::class && value is Int -> value.toUInt()
propType == ULong::class && value is Long -> value.toULong()
propType == UByteArray::class && value is ByteArray -> value.toUByteArray()
propType == UShortArray::class && value is ShortArray -> value.toUShortArray()
propType == UIntArray::class && value is IntArray -> value.toUIntArray()
propType == ULongArray::class && value is LongArray -> value.toULongArray()
else -> value
}

doSetProperty(prop.name, boxedValue, forceSet)
}

private fun doSetProperty(name: String, value: Any?, forceSet: Boolean = false) {
if (!forceSet && isPrimaryKey(name) && name in values) {
val msg = "Cannot modify the primary key `$name` because it's already set to ${values[name]}"
throw UnsupportedOperationException(msg)
Expand Down
71 changes: 34 additions & 37 deletions ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,12 @@ import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.javaSetter
import kotlin.reflect.jvm.jvmErasure

@PublishedApi
@OptIn(ExperimentalUnsignedTypes::class)
internal class ColumnBindingHandler(val properties: MutableList<KProperty1<*, *>>) : InvocationHandler {

override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
Expand All @@ -51,14 +48,10 @@ internal class ColumnBindingHandler(val properties: MutableList<KProperty1<*, *>

properties += prop

val returnType = prop.returnType.jvmErasure
val returnType = method.returnType
return when {
returnType.isSubclassOf(Entity::class) -> createProxy(returnType, properties)
returnType.java.isPrimitive -> returnType.defaultValue
returnType == UByte::class -> 0.toByte()
returnType == UShort::class -> 0.toShort()
returnType == UInt::class -> 0
returnType == ULong::class -> 0L
returnType.kotlin.isSubclassOf(Entity::class) -> createProxy(returnType.kotlin, properties)
returnType.isPrimitive -> returnType.defaultValue
else -> null
}
}
Expand Down Expand Up @@ -90,39 +83,43 @@ internal val Method.kotlinProperty: Pair<KProperty1<*, *>, Boolean>? get() {
return null
}

// should use java Class instead of KClass to avoid inline class issues.
internal val KClass<*>.defaultValue: Any get() {
@OptIn(ExperimentalUnsignedTypes::class)
internal val Class<*>.defaultValue: Any get() {
val value = when {
this == Boolean::class -> false
this == Char::class -> 0.toChar()
this == Byte::class -> 0.toByte()
this == Short::class -> 0.toShort()
this == Int::class -> 0
this == Long::class -> 0L
this == Float::class -> 0.0F
this == Double::class -> 0.0
this == String::class -> ""
this.isSubclassOf(Entity::class) -> Entity.create(this)
this.java.isEnum -> this.java.enumConstants[0]
this.java.isArray -> this.java.componentType.createArray(0)
this == Set::class || this == MutableSet::class -> LinkedHashSet<Any?>()
this == List::class || this == MutableList::class -> ArrayList<Any?>()
this == Collection::class || this == MutableCollection::class -> ArrayList<Any?>()
this == Map::class || this == MutableMap::class -> LinkedHashMap<Any?, Any?>()
this == Queue::class || this == Deque::class -> LinkedList<Any?>()
this == SortedSet::class || this == NavigableSet::class -> TreeSet<Any?>()
this == SortedMap::class || this == NavigableMap::class -> TreeMap<Any?, Any?>()
else -> this.createInstance()
this == Boolean::class.javaPrimitiveType -> false
this == Char::class.javaPrimitiveType -> 0.toChar()
this == Byte::class.javaPrimitiveType -> 0.toByte()
this == Short::class.javaPrimitiveType -> 0.toShort()
this == Int::class.javaPrimitiveType -> 0
this == Long::class.javaPrimitiveType -> 0L
this == Float::class.javaPrimitiveType -> 0.0F
this == Double::class.javaPrimitiveType -> 0.0
this == String::class.java -> ""
this == UByte::class.java -> 0.toUByte()
this == UShort::class.java -> 0.toUShort()
this == UInt::class.java -> 0U
this == ULong::class.java -> 0UL
this == UByteArray::class.java -> ubyteArrayOf()
this == UShortArray::class.java -> ushortArrayOf()
this == UIntArray::class.java -> uintArrayOf()
this == ULongArray::class.java -> ulongArrayOf()
this == Set::class.java -> LinkedHashSet<Any?>()
this == List::class.java -> ArrayList<Any?>()
this == Collection::class.java -> ArrayList<Any?>()
this == Map::class.java -> LinkedHashMap<Any?, Any?>()
this == Queue::class.java || this == Deque::class.java -> LinkedList<Any?>()
this == SortedSet::class.java || this == NavigableSet::class.java -> TreeSet<Any?>()
this == SortedMap::class.java || this == NavigableMap::class.java -> TreeMap<Any?, Any?>()
this.isEnum -> this.enumConstants[0]
this.isArray -> java.lang.reflect.Array.newInstance(this.componentType, 0)
this.kotlin.isSubclassOf(Entity::class) -> Entity.create(this.kotlin)
else -> this.newInstance()
}

if (this.isInstance(value)) {
if (this.kotlin.isInstance(value)) {
return value
} else {
// never happens...
throw AssertionError("$value must be instance of $this")
}
}

private fun Class<*>.createArray(length: Int): Any {
return java.lang.reflect.Array.newInstance(this, length)
}
Loading

0 comments on commit 67bf796

Please sign in to comment.