All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mvysny.vokdataloader.PropertyPath.kt Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
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