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

dorkbox.classUtil.ClassHierarchy.kt Maven / Gradle / Ivy

/*
 * Copyright 2023 dorkbox, llc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dorkbox.classUtil

import dorkbox.collections.IdentityMap
import java.util.concurrent.atomic.*

/**
 * @author dorkbox
 * Date: 4/1/15
 */
class ClassHierarchy(loadFactor: Float) {
    @Volatile
    private var arrayCache: IdentityMap, Class<*>>

    @Volatile
    private var superClassesCache: IdentityMap, Array>>

    /**
     * These data structures are never reset because the class hierarchy doesn't change at runtime. This class uses the "single writer
     * principle" for storing data, EVEN THOUGH it's not accessed by a single writer. This DOES NOT MATTER because duplicates DO NOT matter
     */
    init {
        arrayCache = IdentityMap(32, loadFactor)
        superClassesCache = IdentityMap(32, loadFactor)
    }

    /**
     * will return the class + parent classes as an array.
     * if parameter clazz is of type array, then the super classes are of array type as well
     *
     *
     * race conditions will result in DUPLICATE answers, which we don't care if happens
     * never returns null
     * never reset (class hierarchy never changes during runtime)
     */
    @Suppress("UNCHECKED_CAST")
    fun getClassAndSuperClasses(clazz: Class<*>): Array> {
        // access a snapshot of the subscriptions (single-writer-principle)
        val cache = superClassesREF[this] as IdentityMap, Array>>
        var classes = cache[clazz]

        // duplicates DO NOT MATTER
        if (classes == null) {
            // publish all super types of class
            val superTypesIterator = getSuperTypes(clazz)
            val newList = ArrayList>(16)
            var c: Class<*>
            val isArray = clazz.isArray

            // have to add the original class to the front of the list
            newList.add(clazz)
            if (isArray) {
                // super-types for an array ALSO must be an array.
                while (superTypesIterator.hasNext()) {
                    c = superTypesIterator.next()
                    c = getArrayClass(c)
                    if (c != clazz) {
                        newList.add(c)
                    }
                }
            }
            else {
                while (superTypesIterator.hasNext()) {
                    c = superTypesIterator.next()
                    if (c != clazz) {
                        newList.add(c)
                    }
                }
            }

            classes = arrayOfNulls>(newList.size) as Array>
            newList.toArray(classes)
            cache[clazz] = classes

            // save this snapshot back to the original (single writer principle)
            superClassesREF.lazySet(this, cache)
        }
        return classes
    }

    /**
     * race conditions will result in DUPLICATE answers, which we don't care if happens
     * never returns null
     * never resets (class hierarchy never changes during runtime)
     *
     * https://bugs.openjdk.java.net/browse/JDK-6525802  (fixed this in 2007, so Array.newInstance is just as fast (via intrinsics) new [])
     * Cache is in place to keep GC down.
     */
    fun getArrayClass(c: Class<*>): Class<*> {
        // access a snapshot of the subscriptions (single-writer-principle)
        val cache = cast, Class<*>>>(arrayREF[this])
        var clazz = cache[c]

        if (clazz == null) {
            // messy, but the ONLY way to do it. Array super types are also arrays
            val newInstance = java.lang.reflect.Array.newInstance(c, 0) as Array
            clazz = newInstance.javaClass
            cache[c] = clazz

            // save this snapshot back to the original (single writer principle)
            arrayREF.lazySet(this, cache)
        }

        return clazz
    }

    /**
     * Clears the caches, should only be called on shutdown
     */
    fun shutdown() {
        arrayCache.clear()
        superClassesCache.clear()
    }

    companion object {
        /**
         * Gets the version number.
         */
        const val version = ClassHelper.version

        // Recommended for best performance while adhering to the "single writer principle". Must be static-final
        private val arrayREF = AtomicReferenceFieldUpdater.newUpdater(
            ClassHierarchy::class.java, IdentityMap::class.java, "arrayCache"
        )

        private val superClassesREF = AtomicReferenceFieldUpdater.newUpdater(
            ClassHierarchy::class.java, IdentityMap::class.java, "superClassesCache"
        )

        /**
         * Collect all directly and indirectly related super types (classes and interfaces) of a given class.
         *
         * @param from The root class to start with
         * @return An array of classes, each representing a super type of the root class
         */
        fun getSuperTypes(from: Class<*>): Iterator> {
            // This must be a 'set' because there can be duplicates, depending on the object hierarchy
            @Suppress("NAME_SHADOWING")
            var from = from
            val superclasses = IdentityMap, Boolean>()
            collectInterfaces(from, superclasses)

            while (from != Any::class.java && !from.isInterface) {
                superclasses[from.superclass] = java.lang.Boolean.TRUE
                from = from.superclass
                collectInterfaces(from, superclasses)
            }

            return superclasses.keys()
        }

        private fun collectInterfaces(from: Class<*>, accumulator: IdentityMap, Boolean>) {
            for (iface in from.getInterfaces()) {
                accumulator[iface] = java.lang.Boolean.TRUE
                collectInterfaces(iface, accumulator)
            }
        }

        @Suppress("UNCHECKED_CAST")
        private fun  cast(obj: Any): T {
            return obj as T
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy