Skip to content

Commit 47ef102

Browse files
Merge pull request #11 from vincentlauvlwj/new-binding-syntax
New binding syntax
2 parents e497fb8 + 291c8ac commit 47ef102

File tree

11 files changed

+228
-197
lines changed

11 files changed

+228
-197
lines changed

ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map<Co
111111
var anyChanged = false
112112
var curr: Any? = this
113113

114-
for ((i, prop) in binding.withIndex()) {
114+
for ((i, prop) in binding.properties.withIndex()) {
115115
if (curr is Entity<*>) {
116116
curr = curr.implementation
117117
}
@@ -124,7 +124,7 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map<Co
124124
check(curr != null)
125125

126126
if (curr.fromTable != null && curr.getRoot() != this) {
127-
val propPath = binding.subList(0, i + 1).joinToString(separator = ".", prefix = "this.") { it.name }
127+
val propPath = binding.properties.subList(0, i + 1).joinToString(separator = ".", prefix = "this.") { it.name }
128128
throw IllegalStateException("$propPath may be unexpectedly discarded after flushChanges, please save it to database first.")
129129
}
130130
}
@@ -165,7 +165,7 @@ internal fun EntityImplementation.doDiscardChanges() {
165165
is NestedBinding -> {
166166
var curr: Any? = this
167167

168-
for (prop in binding) {
168+
for (prop in binding.properties) {
169169
if (curr == null) {
170170
break
171171
}

ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityExtensions.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ internal fun EntityImplementation.getColumnValue(column: Column<*>): Any? {
1919
}
2020
is NestedBinding -> {
2121
var curr: EntityImplementation? = this
22-
for ((i, prop) in binding.withIndex()) {
23-
if (i != binding.lastIndex) {
22+
for ((i, prop) in binding.properties.withIndex()) {
23+
if (i != binding.properties.lastIndex) {
2424
val child = curr?.getProperty(prop.name) as Entity<*>?
2525
curr = child?.implementation
2626
}
2727
}
28-
return curr?.getProperty(binding.last().name)
28+
return curr?.getProperty(binding.properties.last().name)
2929
}
3030
}
3131
}
@@ -50,8 +50,8 @@ internal fun EntityImplementation.setColumnValue(column: Column<*>, value: Any?)
5050
}
5151
is NestedBinding -> {
5252
var curr: EntityImplementation = this
53-
for ((i, prop) in binding.withIndex()) {
54-
if (i != binding.lastIndex) {
53+
for ((i, prop) in binding.properties.withIndex()) {
54+
if (i != binding.properties.lastIndex) {
5555
var child = curr.getProperty(prop.name) as Entity<*>?
5656
if (child == null) {
5757
child = Entity.create(prop.returnType.classifier as KClass<*>, parent = curr)
@@ -62,7 +62,7 @@ internal fun EntityImplementation.setColumnValue(column: Column<*>, value: Any?)
6262
}
6363
}
6464

65-
curr.setProperty(binding.last().name, value)
65+
curr.setProperty(binding.properties.last().name, value)
6666
}
6767
}
6868
}
@@ -87,8 +87,8 @@ internal fun EntityImplementation.forceSetColumnValue(column: Column<*>, value:
8787
}
8888
is NestedBinding -> {
8989
var curr: EntityImplementation = this
90-
for ((i, prop) in binding.withIndex()) {
91-
if (i != binding.lastIndex) {
90+
for ((i, prop) in binding.properties.withIndex()) {
91+
if (i != binding.properties.lastIndex) {
9292
var child = curr.getProperty(prop.name) as Entity<*>?
9393
if (child == null) {
9494
child = Entity.create(prop.returnType.classifier as KClass<*>, parent = curr)
@@ -99,7 +99,7 @@ internal fun EntityImplementation.forceSetColumnValue(column: Column<*>, value:
9999
}
100100
}
101101

102-
curr.setProperty(binding.last().name, value, forceSet = true)
102+
curr.setProperty(binding.properties.last().name, value, forceSet = true)
103103
}
104104
}
105105
}
@@ -130,7 +130,7 @@ internal fun EntityImplementation.isPrimaryKey(name: String): Boolean {
130130
}
131131

132132
for ((i, possibleFields) in namesPath.withIndex()) {
133-
if (binding[i].name !in possibleFields) {
133+
if (binding.properties[i].name !in possibleFields) {
134134
return false
135135
}
136136
}

ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityImplementation.kt

Lines changed: 2 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package me.liuwj.ktorm.entity
22

33
import me.liuwj.ktorm.schema.Table
4-
import org.slf4j.LoggerFactory
4+
import me.liuwj.ktorm.schema.defaultValue
5+
import me.liuwj.ktorm.schema.kotlinProperty
56
import java.io.ObjectInputStream
67
import java.io.ObjectOutputStream
78
import java.io.Serializable
@@ -12,13 +13,6 @@ import java.util.*
1213
import kotlin.collections.LinkedHashMap
1314
import kotlin.collections.LinkedHashSet
1415
import kotlin.reflect.KClass
15-
import kotlin.reflect.KMutableProperty
16-
import kotlin.reflect.KProperty
17-
import kotlin.reflect.full.createInstance
18-
import kotlin.reflect.full.isSubclassOf
19-
import kotlin.reflect.full.memberProperties
20-
import kotlin.reflect.jvm.javaGetter
21-
import kotlin.reflect.jvm.javaSetter
2216
import kotlin.reflect.jvm.jvmName
2317
import kotlin.reflect.jvm.kotlinFunction
2418

@@ -36,7 +30,6 @@ internal class EntityImplementation(
3630

3731
companion object {
3832
private const val serialVersionUID = 1L
39-
private val logger = LoggerFactory.getLogger(EntityImplementation::class.java)
4033
private val defaultImplsCache: MutableMap<Method, Method> = Collections.synchronizedMap(WeakHashMap())
4134
}
4235

@@ -116,68 +109,10 @@ internal class EntityImplementation(
116109
throw e.targetException
117110

118111
} catch (e: Throwable) {
119-
logger.error("proxy: ${proxy.javaClass}: $proxy")
120-
logger.error("method: $method")
121-
logger.error("args: ${args?.contentToString()}")
122-
logger.error("arg types: ${args?.map { it.javaClass }}")
123-
logger.error("implementation: $impl")
124-
logger.error("cache: $defaultImplsCache")
125112
throw e
126113
}
127114
}
128115

129-
private val Method.kotlinProperty: Pair<KProperty<*>, Boolean>? get() {
130-
for (prop in entityClass.memberProperties) {
131-
if (prop.javaGetter == this) {
132-
return prop to true
133-
}
134-
if (prop is KMutableProperty<*> && prop.javaSetter == this) {
135-
return prop to false
136-
}
137-
}
138-
return null
139-
}
140-
141-
private val KClass<*>.defaultValue: Any get() {
142-
val value: Any = try {
143-
when {
144-
this == Boolean::class -> false
145-
this == Char::class -> 0.toChar()
146-
this == Byte::class -> 0.toByte()
147-
this == Short::class -> 0.toShort()
148-
this == Int::class -> 0
149-
this == Long::class -> 0L
150-
this == Float::class -> 0.0F
151-
this == Double::class -> 0.0
152-
this == String::class -> ""
153-
this.isSubclassOf(Entity::class) -> Entity.create(this)
154-
this.java.isEnum -> this.java.enumConstants[0]
155-
this.java.isArray -> this.java.componentType.createArray(0)
156-
this == Set::class || this == MutableSet::class -> LinkedHashSet<Any?>()
157-
this == List::class || this == MutableList::class -> ArrayList<Any?>()
158-
this == Collection::class || this == MutableCollection::class -> ArrayList<Any?>()
159-
this == Map::class || this == MutableMap::class -> LinkedHashMap<Any?, Any?>()
160-
this == Queue::class || this == Deque::class -> LinkedList<Any?>()
161-
this == SortedSet::class || this == NavigableSet::class -> TreeSet<Any?>()
162-
this == SortedMap::class || this == NavigableMap::class -> TreeMap<Any?, Any?>()
163-
else -> this.createInstance()
164-
}
165-
} catch (e: Throwable) {
166-
throw IllegalArgumentException("Error creating a default value for non-null type: ${this.jvmName}", e)
167-
}
168-
169-
if (this.isInstance(value)) {
170-
return value
171-
} else {
172-
// never happens...
173-
throw AssertionError("$value must be instance of $this")
174-
}
175-
}
176-
177-
private fun Class<*>.createArray(length: Int): Any {
178-
return java.lang.reflect.Array.newInstance(this, length)
179-
}
180-
181116
fun getProperty(name: String): Any? {
182117
return values[name]
183118
}

ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Column.kt

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,7 @@ sealed class ColumnBinding
1515
/**
1616
* Bind the column to nested properties, eg. employee.manager.department.id
1717
*/
18-
sealed class NestedBinding(vararg properties: KProperty1<*, *>) : ColumnBinding(), List<KProperty1<*, *>> by properties.asList()
19-
20-
/**
21-
* Bind the column to a simple property.
22-
*/
23-
data class NestedBinding1(
24-
val property: KProperty1<*, *>
25-
) : NestedBinding(property)
26-
27-
/**
28-
* Bind the column to double nested properties.
29-
*/
30-
data class NestedBinding2(
31-
val property1: KProperty1<*, *>,
32-
val property2: KProperty1<*, *>
33-
) : NestedBinding(property1, property2)
34-
35-
/**
36-
* Bind the column to triple nested properties
37-
*/
38-
data class NestedBinding3(
39-
val property1: KProperty1<*, *>,
40-
val property2: KProperty1<*, *>,
41-
val property3: KProperty1<*, *>
42-
) : NestedBinding(property1, property2, property3)
43-
44-
/**
45-
* Bind the column to 4 levels of nested properties
46-
*/
47-
data class NestedBinding4(
48-
val property1: KProperty1<*, *>,
49-
val property2: KProperty1<*, *>,
50-
val property3: KProperty1<*, *>,
51-
val property4: KProperty1<*, *>
52-
) : NestedBinding(property1, property2, property3, property4)
18+
data class NestedBinding(val properties: List<KProperty1<*, *>>) : ColumnBinding()
5319

5420
/**
5521
* Bind the column to a reference table, equivalent to a foreign key in relational databases.
@@ -58,10 +24,7 @@ data class NestedBinding4(
5824
* @see me.liuwj.ktorm.entity.joinReferencesAndSelect
5925
* @see me.liuwj.ktorm.entity.createEntity
6026
*/
61-
data class ReferenceBinding(
62-
val referenceTable: Table<*>,
63-
val onProperty: KProperty1<*, *>
64-
) : ColumnBinding()
27+
data class ReferenceBinding(val referenceTable: Table<*>, val onProperty: KProperty1<*, *>) : ColumnBinding()
6528

6629
/**
6730
* 列声明
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package me.liuwj.ktorm.schema
2+
3+
import me.liuwj.ktorm.entity.Entity
4+
import java.lang.reflect.InvocationHandler
5+
import java.lang.reflect.Method
6+
import java.lang.reflect.Proxy
7+
import kotlin.reflect.KClass
8+
import kotlin.reflect.KProperty1
9+
import kotlin.reflect.full.isSubclassOf
10+
11+
/* internal */
12+
class ColumnBindingHandler(val properties: MutableList<KProperty1<*, *>>) : InvocationHandler {
13+
14+
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
15+
when (method.declaringClass.kotlin) {
16+
Any::class, Entity::class -> {
17+
throw UnsupportedOperationException("Unsupported method: $method")
18+
}
19+
else -> {
20+
val (prop, isGetter) = method.kotlinProperty ?: throw UnsupportedOperationException("Unsupported method: $method")
21+
if (!prop.isAbstract) {
22+
throw UnsupportedOperationException("Cannot bind a column to a non-abstract property: $prop")
23+
}
24+
if (!isGetter) {
25+
throw UnsupportedOperationException("Cannot modify a property while we are binding a column to it, property: $prop")
26+
}
27+
28+
properties += prop
29+
30+
val returnType = prop.returnType.classifier as KClass<*>
31+
return when {
32+
returnType.isSubclassOf(Entity::class) -> createProxy(returnType, properties)
33+
returnType.java.isPrimitive -> returnType.defaultValue
34+
else -> null
35+
}
36+
}
37+
}
38+
}
39+
40+
companion object {
41+
42+
fun createProxy(entityClass: KClass<*>, properties: MutableList<KProperty1<*, *>>): Entity<*> {
43+
val classLoader = Thread.currentThread().contextClassLoader
44+
val handler = ColumnBindingHandler(properties)
45+
return Proxy.newProxyInstance(classLoader, arrayOf(entityClass.java), handler) as Entity<*>
46+
}
47+
}
48+
}

ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/TypeReference.kt renamed to ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Reflects.kt

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package me.liuwj.ktorm.schema
22

3+
import me.liuwj.ktorm.entity.Entity
4+
import java.lang.reflect.Method
35
import java.lang.reflect.ParameterizedType
46
import java.lang.reflect.Type
5-
import kotlin.reflect.KClass
6-
import kotlin.reflect.KType
7+
import java.util.*
8+
import kotlin.reflect.*
9+
import kotlin.reflect.full.createInstance
10+
import kotlin.reflect.full.declaredMemberProperties
11+
import kotlin.reflect.full.isSubclassOf
12+
import kotlin.reflect.jvm.javaGetter
13+
import kotlin.reflect.jvm.javaSetter
14+
import kotlin.reflect.jvm.jvmName
715

816
/**
917
* Base class used for obtaining full generic type information by subclassing.
@@ -80,4 +88,56 @@ inline fun <reified T> typeOf(): Type {
8088
@Deprecated("Do not use this function until the bug KT-28616 fixed", ReplaceWith("typeOf<T>()"))
8189
inline fun <reified T> kotlinTypeOf(): KType {
8290
return typeRef<T>().referencedKotlinType
91+
}
92+
93+
internal val Method.kotlinProperty: Pair<KProperty1<*, *>, Boolean>? get() {
94+
for (prop in declaringClass.kotlin.declaredMemberProperties) {
95+
if (prop.javaGetter == this) {
96+
return prop to true
97+
}
98+
if (prop is KMutableProperty<*> && prop.javaSetter == this) {
99+
return prop to false
100+
}
101+
}
102+
return null
103+
}
104+
105+
internal val KClass<*>.defaultValue: Any get() {
106+
val value: Any = try {
107+
when {
108+
this == Boolean::class -> false
109+
this == Char::class -> 0.toChar()
110+
this == Byte::class -> 0.toByte()
111+
this == Short::class -> 0.toShort()
112+
this == Int::class -> 0
113+
this == Long::class -> 0L
114+
this == Float::class -> 0.0F
115+
this == Double::class -> 0.0
116+
this == String::class -> ""
117+
this.isSubclassOf(Entity::class) -> Entity.create(this)
118+
this.java.isEnum -> this.java.enumConstants[0]
119+
this.java.isArray -> this.java.componentType.createArray(0)
120+
this == Set::class || this == MutableSet::class -> LinkedHashSet<Any?>()
121+
this == List::class || this == MutableList::class -> ArrayList<Any?>()
122+
this == Collection::class || this == MutableCollection::class -> ArrayList<Any?>()
123+
this == Map::class || this == MutableMap::class -> LinkedHashMap<Any?, Any?>()
124+
this == Queue::class || this == Deque::class -> LinkedList<Any?>()
125+
this == SortedSet::class || this == NavigableSet::class -> TreeSet<Any?>()
126+
this == SortedMap::class || this == NavigableMap::class -> TreeMap<Any?, Any?>()
127+
else -> this.createInstance()
128+
}
129+
} catch (e: Throwable) {
130+
throw IllegalArgumentException("Error creating a default value for non-null type: ${this.jvmName}", e)
131+
}
132+
133+
if (this.isInstance(value)) {
134+
return value
135+
} else {
136+
// never happens...
137+
throw AssertionError("$value must be instance of $this")
138+
}
139+
}
140+
141+
private fun Class<*>.createArray(length: Int): Any {
142+
return java.lang.reflect.Array.newInstance(this, length)
83143
}

0 commit comments

Comments
 (0)