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

cloud.orbit.runtime.capabilities.CapabilitiesScanner.kt Maven / Gradle / Ivy

/*
 Copyright (C) 2015 - 2019 Electronic Arts Inc.  All rights reserved.
 This file is part of the Orbit Project .
 See license in LICENSE.
 */

package cloud.orbit.runtime.capabilities

import cloud.orbit.common.logging.debug
import cloud.orbit.common.logging.info
import cloud.orbit.common.logging.logger
import cloud.orbit.common.time.Clock
import cloud.orbit.common.time.stopwatch
import cloud.orbit.core.annotation.NonConcrete
import cloud.orbit.core.net.NodeCapabilities
import cloud.orbit.core.remoting.Addressable
import cloud.orbit.core.remoting.AddressableClass
import io.github.classgraph.ClassGraph

internal class CapabilitiesScanner(private val clock: Clock) {
    private val logger by logger()

    lateinit var addressableInterfaces: List private set
    lateinit var addressableClasses: List private set
    lateinit var interfaceLookup: Map private set


    fun scan(vararg packagePaths: String) {
        logger.info("Scanning for node capabilities...")

        stopwatch(clock) {
            val classGraph = ClassGraph()
                .enableAllInfo()
                .whitelistPackages(*packagePaths)

            classGraph.scan().use { scan ->

                addressableInterfaces = scan
                    .getClassesImplementing(Addressable::class.java.name)
                    .interfaces
                    .filter {
                        !it.hasAnnotation(NonConcrete::class.java.name)
                    }
                    .map {
                        @Suppress("UNCHECKED_CAST")
                        it.loadClass() as AddressableClass
                    }

                addressableClasses = scan
                    .getClassesImplementing(Addressable::class.java.name)
                    .standardClasses
                    .filter {
                        !it.hasAnnotation(NonConcrete::class.java.name)
                    }
                    .filter {
                        !it.isAbstract
                    }
                    .map {
                        @Suppress("UNCHECKED_CAST")
                        it.loadClass() as AddressableClass
                    }
            }

            interfaceLookup = mutableMapOf().apply {
                addressableClasses.forEach { implClass ->
                    resolveMapping(implClass).also { mapped ->
                        if (mapped.isEmpty()) {
                            throw IllegalStateException("Could not find mapping for ${implClass.name}")
                        }
                        mapped.forEach { iface ->
                            if (this.containsKey(iface)) throw IllegalStateException(
                                "Multiple implementations of concrete " +
                                        "interface ${iface.name} found."
                            )
                            this[iface] = implClass
                        }
                    }
                }
            }
        }.also { (elapsed, _) ->
            logger.debug { "Addressable Interfaces: $addressableInterfaces" }
            logger.debug { "Addressable Classes: $addressableClasses" }
            logger.debug { "Implemented Addressables: $interfaceLookup" }

            logger.info {
                "Node capabilities scan complete in ${elapsed}ms. " +
                        "${interfaceLookup.size} implemented addressable(s) found. " +
                        "${addressableInterfaces.size} addressable interface(s) found. " +
                        "${addressableClasses.size} addressable class(es) found. "
            }
        }
    }

    fun generateNodeCapabilities(): NodeCapabilities =
        interfaceLookup
            .map { (key, _) -> key.name }
            .let {
                NodeCapabilities(
                    implementedAddressables = it
                )
            }

    private fun resolveMapping(
        crawl: Class<*>,
        list: MutableList = mutableListOf()
    ): Collection {
        if (crawl.interfaces.isEmpty()) return list
        for (iface in crawl.interfaces) {
            if (Addressable::class.java.isAssignableFrom(iface)) {
                if (!iface.isAnnotationPresent(NonConcrete::class.java)) {
                    @Suppress("UNCHECKED_CAST")
                    list.add(iface as AddressableClass)
                }
                if (iface.interfaces.isNotEmpty()) resolveMapping(iface, list)
            }
        }
        return list
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy