Search code examples
kotlinsortinggenericscollections

Kotlin sorting generic collections


I have some inline function for filtering query data, like this:

inline fun <reified T> findByFilterFunc(filter: String, em: EntityManager, offset: Int, limit: Int, sortName: String?, sortDirection: String?): List<T> {
    val bt = SimpleBuilderTools()
    val rootNode = RSQLParser().parse(filter)

    val builder = em.criteriaBuilder
    val query = initQueryFunc<T, T>(builder, filter)
    val root = query.getRootByType(T::class.java)

    query.where(PredicateBuilderObj.createPredicate(rootNode, em, bt, root, query))

    query.select(root)

    if (sortName !== null) {
        val expression = root.get<Expression<*>>(sortName)
        query.orderBy(if (sortDirection == "ASC") builder.asc(expression) else builder.desc(expression))
    }
    return em.createQuery(query).setFirstResult(offset * limit).setMaxResults(limit).resultList
}

But I can use sorting in SQL query due to my dialect doesn't support it. I tried to use kotlin sortedBy {} for result of query, like this:

val prop = T::class.java.javaClass.kotlin.declaredMemberProperties.find { it.name == sortName }
    return em.createQuery(query).setFirstResult(offset * limit).setMaxResults(limit).resultList.sortedBy(prop)

But this is invalid code. How can I sort collection with generic types?

I think I need use a custom comparator, but I can't understand how to do it for unknown generic type


Solution

  • TL;DR I don't think there is enough information for Kotlin to implement a comparison of the unknown type T.

    Here are some changes to your code:

    Firstly you have

    val prop = T::class.java.javaClass.kotlin.declaredMemberProperties.find { it.name == sortName }
    

    which returns

    KProperty1<Class<T>, *>?
    

    I think you need this:

    val prop: KProperty1<T, *>? = T::class.declaredMemberProperties.find { it.name == sortName }
    

    Then you can obtain the runtime value of the property:

    requireNotNull(prop)
    prop.get(...instance...)
    

    Then you implement an in-line custom comparator like this

            return em.createQuery(query)
                .setFirstResult(offset * limit)
                .setMaxResults(limit)
                .resultList
                .sortedWith { o1: T, o2: T ->
                   val v1: Any? = prop.get(o1)
                   val v2: Any? = prop.get(o2)
                   //how to compare?
                   v1.toString().compareTo(v2.toString())
            }
    

    The problem is that the type information is not available, and as Any does not implement compareTo how will you know generically how to sort?

    The question you might ask yourself... how do database engines sort? If it is an RDMS with strongly typed columns, the engine knows at run time what sort/collation to use. If it is unstructured store, like Mongo, it has a strategy to sort over the comparatively few types it supports.

    How many types will you offer sorting over in your application? Once you know the runtime type and if you have a limited number of types you will use this utility on, I suppose you can write custom comparators (if v1 is Number then you'll know what to do) or fallback to a toString().compareTo

    However, if you can put a tighter constraint on T in the first place... that it must implement Comparable like this:

    inline fun <reified T : Comparable<T>> findByFilterFunc(...
    

    then you can write

            .sortedWith { o1: T, o2: T ->
                val v1 = prop.get(o1) as T
                val v2 = prop.get(o2) as T
                v1.compareTo(v2)
            }