com.github.mvysny.vokdataloader.PropertyPath.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vok-dataloader Show documentation
Show all versions of vok-dataloader Show documentation
VOK-DataLoader: The Paged/Filtered/Sorted DataLoader API
package com.github.mvysny.vokdataloader
import java.io.Serializable
import java.lang.reflect.Method
import java.util.*
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingQueue
import kotlin.reflect.KProperty1
/**
* A property path, for example `Person.address.city.name`.
*
* Uses Java Beans spec (the Introspector class) to obtain a getter method for
* every property, then calls that getter to obtain the value of the property
* in [getValue].
* @param T the base class, e.g. `Person`
* @param baseClass the base class, e.g. `Person`
* @param propertyPath the property path, e.g. `listOf("address", "city", "name")`
* @author Martin Vysny
*/
public data class PropertyPath(val baseClass: Class, val propertyPath: List):
Serializable, (T?) -> Any? {
init {
require(propertyPath.isNotEmpty()) { "propertyPath is empty" }
// compute getter chain which will also validate all getters
getGetterChain()
}
override fun toString(): String =
"PropertyPath(${baseClass.simpleName}.${propertyPath.joinToString(".")})"
/**
* Cached for quicker computation. Don't serialize: Method is not serializable.
*/
@Transient
private var getterChain: BlockingQueue? = null
/**
* Returns the getter chain. Thread-safe.
*/
private fun getGetterChain(): Collection {
var result: BlockingQueue? = getterChain
if (result == null) {
// thread-safe - no need to synchronize - in the worst case multiple
// threads will compute the same thing.
val chain: MutableList = LinkedList()
var clazz: Class<*> = baseClass
for (propertyName: String in propertyPath) {
val getter: Method = clazz.getGetter(propertyName)
chain.add(getter)
clazz = getter.returnType
}
result = LinkedBlockingQueue(chain)
getterChain = result
}
return result
}
/**
* Returns the value of the property path expression, starting from [baseClass] and following [propertyPath].
* If [obj] or anything on the path evaluates to `null`, null is returned.
* @return a value of type [valueType] or `null`.
*/
public fun getValue(obj: T?): Any? {
if (obj == null) {
return null
}
var currentValue: Any? = obj
for (getter: Method in getGetterChain()) {
currentValue = getter.invoke(currentValue)
if (currentValue == null) {
return null
}
}
return currentValue
}
/**
* The type of values returned by [getValue] or [invoke].
*/
val valueType: Class<*> get() = getGetterChain().last().returnType
override fun invoke(p1: T?): Any? = getValue(p1)
public companion object {
/**
* Parses [propertyPath] such as "address.city.name" into [PropertyPath].
*/
public fun of(baseClass: Class, propertyPath: String): PropertyPath {
val path: List = propertyPath.split('.')
require(path.isNotEmpty()) { "$propertyPath: invalid value" }
return PropertyPath(baseClass, path)
}
}
}
public inline val KProperty1.propertyPath: PropertyPath get() =
PropertyPath(T::class.java, listOf(name))
© 2015 - 2025 Weber Informatics LLC | Privacy Policy