diff --git a/build.gradle.kts b/build.gradle.kts index 689259b..df9b66c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,9 @@ -plugins { +allprojects { + group = "de.timolia.lactea" + version = "1.0-SNAPSHOT" +} +subprojects { + repositories { + mavenCentral() + } } -group = "de.timolia" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/config/build.gradle.kts b/config/build.gradle.kts new file mode 100644 index 0000000..f52b398 --- /dev/null +++ b/config/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + `java-library` + kotlin("jvm") version "1.9.10" +} + +dependencies { + api(project(":core")) + implementation(kotlin("stdlib-jdk8")) + annotationProcessor("com.google.auto.factory:auto-factory:1.0.1") + api("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.3") +} +kotlin { + jvmToolchain(11) +} \ No newline at end of file diff --git a/config/src/main/kotlin/de/timolia/lactea/config/ConfigController.kt b/config/src/main/kotlin/de/timolia/lactea/config/ConfigController.kt new file mode 100644 index 0000000..794d6c4 --- /dev/null +++ b/config/src/main/kotlin/de/timolia/lactea/config/ConfigController.kt @@ -0,0 +1,63 @@ +package de.timolia.lactea.config + +import java.io.File +import java.io.IOException +import javax.inject.Singleton + +@Singleton +class ConfigController { + private val pathPlaceholders: MutableMap = HashMap() + private val extensionToFormat: MutableMap = HashMap() + private val defaultFormat: ConfigFormat = ConfigFormat.YAML + + init { + registerFormat(ConfigFormat.YAML) + registerFormat(ConfigFormat.JSON) + registerPlaceholder("lactea:", "lactea/config/") + } + + private fun registerPlaceholder(name: String, replacement: String) { + pathPlaceholders[name] = replacement + } + + private fun registerFormat(format: ConfigFormat) { + extensionToFormat[format.extension] = format + } + + private fun acceptPlaceholders(name: String): String { + var name = name + for ((key, value) in pathPlaceholders) { + name = name.replace(key, value) + } + return name + } + + fun formatByExtensionOrDefault(extension: String) = extensionToFormat.getOrDefault(extension, defaultFormat) + + @Throws(IOException::class) + fun loadConfig(name: String, clazz: Class): T { + val file = File(acceptPlaceholders(name)) + val parent = file.absoluteFile.parentFile + parent.mkdirs() + val candidates = parent.listFiles() + check(candidates != null) {"$file is not a Directory"} + for (candidate in candidates) { + if (candidate.isDirectory) { + continue + } + if (candidate.nameWithoutExtension == file.name) { + return formatByExtensionOrDefault(candidate.extension).loadConfig(candidate, clazz) + } + } + return createDefault(parent, file.name, clazz) + } + + @Throws(IOException::class) + private fun createDefault(parent: File, name: String, clazz: Class): T { + val format = defaultFormat + val file = File(parent, name + '.' + format.extension) + format.objectMapper.writeValue(file, clazz.newInstance()) + file.createNewFile() + return format.loadConfig(file, clazz) + } +} diff --git a/config/src/main/kotlin/de/timolia/lactea/config/ConfigDefinition.kt b/config/src/main/kotlin/de/timolia/lactea/config/ConfigDefinition.kt new file mode 100644 index 0000000..2ab615d --- /dev/null +++ b/config/src/main/kotlin/de/timolia/lactea/config/ConfigDefinition.kt @@ -0,0 +1,5 @@ +package de.timolia.lactea.config + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class ConfigDefinition(val value: String) diff --git a/config/src/main/kotlin/de/timolia/lactea/config/ConfigFormat.kt b/config/src/main/kotlin/de/timolia/lactea/config/ConfigFormat.kt new file mode 100644 index 0000000..573452f --- /dev/null +++ b/config/src/main/kotlin/de/timolia/lactea/config/ConfigFormat.kt @@ -0,0 +1,37 @@ +package de.timolia.lactea.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import java.io.File +import java.io.IOException + +abstract class ConfigFormat ( + val extension: String, + val name: String, +) { + val objectMapper: ObjectMapper by lazy(::createObjectMapper) + + protected abstract fun createObjectMapper(): ObjectMapper + + @Throws(IOException::class) + fun loadConfig(file: File, clazz: Class): T { + return objectMapper.readValue(file, clazz) + } + + companion object { + var YAML: ConfigFormat = object : ConfigFormat("yml", "yaml") { + public override fun createObjectMapper(): ObjectMapper { + val mapper = ObjectMapper(YAMLFactory()) + mapper.findAndRegisterModules() + return mapper + } + } + var JSON: ConfigFormat = object : ConfigFormat("json", "json") { + public override fun createObjectMapper(): ObjectMapper { + val mapper = ObjectMapper() + mapper.findAndRegisterModules() + return mapper + } + } + } +} diff --git a/config/src/main/kotlin/de/timolia/lactea/config/LocalConfigModule.kt b/config/src/main/kotlin/de/timolia/lactea/config/LocalConfigModule.kt new file mode 100644 index 0000000..7185eee --- /dev/null +++ b/config/src/main/kotlin/de/timolia/lactea/config/LocalConfigModule.kt @@ -0,0 +1,27 @@ +package de.timolia.lactea.config + +import com.google.inject.AbstractModule +import java.io.IOException +import java.util.logging.Level + +class LocalConfigModule( +) : AbstractModule() { + + /*private fun createConfigObject(name: String, clazz: Class): T? { + return try { + controller.loadConfig(name, clazz) + } catch (e: IOException) { + logger.log(Level.WARNING, "Failed to load config with name $name as $clazz", e) + try { + clazz.getConstructor().newInstance() + } catch (ex: Exception) { + logger.log(Level.WARNING, "Failed to create dummy config object", ex) + null + } + } + } + + private fun bindConfig(name: String, clazz: Class) { + bind(clazz).toInstance(createConfigObject(name, clazz)) + }*/ +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000..738219d --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + `java-library` + kotlin("jvm") version "1.9.10" +} + +dependencies { + api(project(":module-api")) + compileOnlyApi(project(":initialize")) + implementation("org.javassist:javassist:3.29.0-GA") + implementation(kotlin("stdlib-jdk8")) +} +kotlin { + jvmToolchain(11) +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/Lactea.kt b/core/src/main/kotlin/de/timolia/lactea/core/Lactea.kt new file mode 100644 index 0000000..5e9856d --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/Lactea.kt @@ -0,0 +1,32 @@ +package de.timolia.lactea.core + +import com.google.inject.Guice +import com.google.inject.Injector +import com.google.inject.Module +import de.timolia.lactea.Bootstrap +import de.timolia.lactea.core.lifecycle.startup.LoadEnableSplitStartup +import de.timolia.lactea.core.lifecycle.startup.SingleEntrypointStartup +import de.timolia.lactea.core.lifecycle.startup.StartUp +import de.timolia.lactea.path.PathResolver +import de.timolia.lactea.source.ClassPathInjector + +class Lactea ( + bootstrap: Bootstrap, + vararg modules: Module, +) { + val injector: Injector + + init { + injector = Guice.createInjector( + *modules, + Module { + it.bind(ClassPathInjector::class.java) to bootstrap.classPathInjector() + it.bind(PathResolver::class.java) to bootstrap.pathResolver() + } + ) + } + + fun singleEntrypoint(): SingleEntrypointStartup = injector.getInstance(StartUp::class.java) + + fun loadEnableSplit(): LoadEnableSplitStartup = injector.getInstance(StartUp::class.java) +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryClass.kt b/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryClass.kt new file mode 100644 index 0000000..9aabfc8 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryClass.kt @@ -0,0 +1,33 @@ +package de.timolia.lactea.core.discovery + +import javassist.bytecode.annotation.Annotation as ByteCodeAnnotation + +class DiscoveryClass( + val name: String, + val annotations: Array +) { + private val byType: MutableMap = HashMap() + + init { + buildTypeIndex() + } + + private fun buildTypeIndex() { + for (annotation in annotations) { + check(byType.put(annotation.getTypeName(), annotation) == null) { "Illegal discovery name=$name annotation=$annotation" } + } + } + + fun byType(search: String): ByteCodeAnnotation? { + return byType[search] + } + + fun byType(search: Class): ByteCodeAnnotation? { + return byType(search.name) + } + + @Throws(ClassNotFoundException::class) + fun loadClass(): Class<*> { + return Class.forName(name) + } +} diff --git a/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryIndex.kt b/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryIndex.kt new file mode 100644 index 0000000..fd88abe --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryIndex.kt @@ -0,0 +1,40 @@ +package de.timolia.lactea.core.discovery + +import com.google.common.collect.HashMultimap +import com.google.common.collect.Multimap +import javassist.bytecode.AnnotationsAttribute +import javassist.bytecode.ClassFile +import java.io.DataInputStream +import java.io.IOException +import java.util.* +import java.util.jar.JarEntry +import java.util.jar.JarFile + +class DiscoveryIndex { + private val index: Multimap = HashMultimap.create() + + fun addToIndex(discoveryClass: DiscoveryClass) { + for (annotation in discoveryClass.annotations) { + index.put(annotation.typeName, discoveryClass) + } + } + + @Throws(IOException::class) + fun indexJarFile(jar: JarFile) { + val entries: Enumeration = jar.entries() + while (entries.hasMoreElements()) { + val jarEntry: JarEntry = entries.nextElement() + if (jarEntry.name.endsWith(".class")) { + val classFile = ClassFile(DataInputStream(jar.getInputStream(jarEntry))) + val visible: AnnotationsAttribute = classFile.getAttribute("RuntimeVisibleAnnotations") as AnnotationsAttribute + if (visible != null) { + addToIndex(DiscoveryClass(classFile.name, visible.annotations)) + } + } + } + } + + fun runDiscovery(search: String) = DiscoveryResult(index[search]) + + fun runDiscovery(search: Class) = runDiscovery(search.name) +} diff --git a/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryResult.kt b/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryResult.kt new file mode 100644 index 0000000..ac1b12c --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/discovery/DiscoveryResult.kt @@ -0,0 +1,10 @@ +package de.timolia.lactea.core.discovery + +data class DiscoveryResult(val all: Collection) { + fun requireExactlyOne(name: String): DiscoveryClass { + check(all.size == 1) { + ("Require exactly one $name Candidates are: $all") + } + return all.iterator().next() + } +} diff --git a/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/DIConfiguration.kt b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/DIConfiguration.kt new file mode 100644 index 0000000..8587154 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/DIConfiguration.kt @@ -0,0 +1,130 @@ +package de.timolia.lactea.core.lifecycle + +import com.google.inject.Injector +import de.timolia.lactea.core.module.LacteaModuleHandle +import de.timolia.lactea.core.module.ModuleRepository +import de.timolia.lactea.inject.InjectedInstance +import de.timolia.lactea.lifecycle.LoadContext +import de.timolia.lactea.module.InternalModuleAccess +import java.io.Closeable +import java.util.logging.Level +import java.util.logging.Logger +import javax.inject.Inject +import com.google.inject.Module +import com.google.inject.PrivateModule +import de.timolia.lactea.inject.LacteaInjector +import de.timolia.lactea.lifecycle.LoadContext.EncapsulationMode +import javax.inject.Provider + +class DIConfiguration @Inject constructor ( + private val moduleLifecycle: ModuleLifecycle, + private val moduleRepository: ModuleRepository, + private val configurationInjector: Injector, + private val logger: Logger +) { + private val globalInjectorConfiguration = InjectorConfiguration() + + fun setupDependencyInjection() { + moduleLifecycle.addToClassPathAndCreateInstances() + val contexts = buildModuleContextList() + preInjectorCreation(contexts) + val globalInjector = globalInjectorConfiguration.create(configurationInjector) + postInjectionCreation(contexts, globalInjector) + } + + private fun postInjectionCreation(contexts: List, injector: Injector) { + contexts.forEach { + if (it.encapsulationMode == EncapsulationMode.INJECTOR) { + it.localConfiguration.create(injector) + } + InternalModuleAccess.setInjector(it.module.instance, LacteaInjector(it.localConfiguration.moduleInjector!!.get())) + } + } + + private fun preInjectorCreation(contexts: List) { + contexts.forEach { + if (it.encapsulationMode == EncapsulationMode.PRIVATE_MODULE) { + globalInjectorConfiguration.install(InjectedInstance.ofInstance( + it.localConfiguration.createPrivateModule(configurationInjector) + )) + } + } + } + + private fun buildModuleContextList(): List { + return moduleRepository.modulesInBootOrder.mapNotNull(::buildModuleContext) + } + + private fun buildModuleContext(module: LacteaModuleHandle): ModuleLoadContext? { + val context = ModuleLoadContext(module) + context.use { + try { + module.instance.onInjectorConfiguration(it) + return context + } catch (throwable: Throwable) { + logger.log(Level.WARNING, "Failed to load injection configuration for ${module.nameAndLocation()}", throwable) + return null + } + } + } + + internal class InjectorConfiguration( + private val moduleConfiguration: MutableList> = mutableListOf() + ) { + var moduleInjector: Provider? = null + + fun create(parent: Injector): Injector { + val injector = parent.createChildInjector(createModules(parent)) + moduleInjector = Provider { injector } + return injector + } + + private fun createModules(injector: Injector) = moduleConfiguration.map { it.getInstance(injector) } + + fun install(module: InjectedInstance) = moduleConfiguration.add(module) + + fun createPrivateModule(injector: Injector) = object: PrivateModule() { + override fun configure() { + createModules(injector).forEach(::install) + moduleInjector = getProvider(Injector::class.java) + } + } + } + + internal inner class ModuleLoadContext ( + val module: LacteaModuleHandle, + override var encapsulationMode: EncapsulationMode = EncapsulationMode.PRIVATE_MODULE + ) : LoadContext, Closeable { + val localConfiguration = InjectorConfiguration() + private val thread: Thread = Thread.currentThread() + private var closed = false + + override fun installGlobalModule(module: InjectedInstance) { + validateContext() + globalInjectorConfiguration.install(module) + } + + override fun installModule(module: InjectedInstance) { + validateContext() + localConfiguration.install(module) + } + + override fun close() { + validateContext() + closed = true + } + + private fun validateContext() { + val currentThread = Thread.currentThread() + if (thread != currentThread) { + throw IllegalStateException( + "Called from wrong thread." + + "Expecting ${thread.name} got ${currentThread.name}" + ) + } + if (closed) { + throw IllegalStateException("This module configuration phase is already over") + } + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ModuleLifecycle.kt b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ModuleLifecycle.kt new file mode 100644 index 0000000..b6012d9 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ModuleLifecycle.kt @@ -0,0 +1,66 @@ +package de.timolia.lactea.core.lifecycle + +import de.timolia.lactea.core.module.ModuleRepository +import de.timolia.lactea.lifecycle.EnableContext +import de.timolia.lactea.source.ClassPathInjector +import java.util.logging.Level +import java.util.logging.Logger +import javax.inject.Inject + +class ModuleLifecycle @Inject constructor ( + private val logger: Logger, + private val classPathInjector: ClassPathInjector, + private val moduleRepository: ModuleRepository, + private val moduleMainLoader: ModuleMainLoader +) { + private fun addToClassPath() { + moduleRepository.modulesInBootOrder.forEach { + classPathInjector.addToClassPath(it.uri) + } + } + + private fun createInstances() { + moduleRepository.modulesInBootOrder.forEach { + try { + it.instance = moduleMainLoader.load(it) + } catch (ex: Throwable) { + logger.log(Level.SEVERE, "Failed to load " + it.nameAndLocation(), ex) + } + } + } + + fun addToClassPathAndCreateInstances() { + addToClassPath() + createInstances() + } + + fun enableAll() { + for (module in moduleRepository.modulesInBootOrder) { + try { + module.instance.onEnable(ModuleEnableContext()) + } catch (ex: Exception) { + logger.log(Level.SEVERE, "Failed to enable $module", ex) + } + } + } + + internal class ModuleEnableContext : EnableContext + + fun disableAll() { + val orderedModules = moduleRepository.modulesInBootOrder + for (module in orderedModules) { + try { + module.instance.onDisable() + } catch (ex: Exception) { + logger.log(Level.SEVERE, "Failed to disable $module", ex) + } + } + for (module in orderedModules) { + try { + module.instance.onPostDisable() + } catch (ex: Exception) { + logger.log(Level.SEVERE, "Failed to post disable $module", ex) + } + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ModuleMainLoader.kt b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ModuleMainLoader.kt new file mode 100644 index 0000000..78b9466 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ModuleMainLoader.kt @@ -0,0 +1,8 @@ +package de.timolia.lactea.core.lifecycle + +import de.timolia.lactea.core.module.LacteaModuleHandle +import de.timolia.lactea.module.LacteaModule + +interface ModuleMainLoader { + fun load(module: LacteaModuleHandle): LacteaModule +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ReflectionModuleMainLoader.kt b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ReflectionModuleMainLoader.kt new file mode 100644 index 0000000..b2bf73f --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/ReflectionModuleMainLoader.kt @@ -0,0 +1,30 @@ +package de.timolia.lactea.core.lifecycle + +import de.timolia.lactea.core.module.LacteaModuleHandle +import de.timolia.lactea.module.LacteaModule + +class ReflectionModuleMainLoader : ModuleMainLoader { + override fun load(module: LacteaModuleHandle): LacteaModule { + val main = loadMainClass(module) + try { + return main.getConstructor().newInstance() + } catch (exception: NoSuchMethodException) { + throw MainInstanceCreation("Require a public no args constructor in " + mainClassName(module), exception) + } catch (exception: Exception) { + throw MainInstanceCreation("Failed to instantiate " + mainClassName(module), exception) + } + } + + private fun loadMainClass(module: LacteaModuleHandle): Class { + try { + return module.description.loadMainClass() + } catch (exception: ClassNotFoundException) { + throw MainClassLoad("Unable to find " + mainClassName(module) + " in class path", exception) + } + } + + private fun mainClassName(module: LacteaModuleHandle) = module.description.main.name + + class MainClassLoad(message: String, cause: Throwable) : RuntimeException(message, cause) + class MainInstanceCreation(message: String, cause: Throwable) : RuntimeException(message, cause) +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/LoadEnableSplitStartup.kt b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/LoadEnableSplitStartup.kt new file mode 100644 index 0000000..d515e9d --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/LoadEnableSplitStartup.kt @@ -0,0 +1,7 @@ +package de.timolia.lactea.core.lifecycle.startup + +interface LoadEnableSplitStartup { + fun setupDependencyInjection(); + + fun enableModules(); +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/SingleEntrypointStartup.kt b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/SingleEntrypointStartup.kt new file mode 100644 index 0000000..1c554c6 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/SingleEntrypointStartup.kt @@ -0,0 +1,5 @@ +package de.timolia.lactea.core.lifecycle.startup + +interface SingleEntrypointStartup { + fun performStartup() +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/StartUp.kt b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/StartUp.kt new file mode 100644 index 0000000..a6b1ad3 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/lifecycle/startup/StartUp.kt @@ -0,0 +1,19 @@ +package de.timolia.lactea.core.lifecycle.startup + +import de.timolia.lactea.core.lifecycle.DIConfiguration +import de.timolia.lactea.core.lifecycle.ModuleLifecycle +import javax.inject.Inject + +class StartUp @Inject constructor( + private val diConfiguration: DIConfiguration, + private val moduleLifecycle: ModuleLifecycle +) : LoadEnableSplitStartup, SingleEntrypointStartup { + override fun performStartup() { + setupDependencyInjection() + enableModules() + } + + override fun setupDependencyInjection() = diConfiguration.setupDependencyInjection() + + override fun enableModules() = moduleLifecycle.enableAll() +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/module/JavassistAnnotations.kt b/core/src/main/kotlin/de/timolia/lactea/core/module/JavassistAnnotations.kt new file mode 100644 index 0000000..6dd2a4c --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/module/JavassistAnnotations.kt @@ -0,0 +1,23 @@ +package de.timolia.lactea.core.module + +import de.timolia.lactea.core.module.ModuleDescription.DependencyDescription +import javassist.bytecode.annotation.* +import javassist.bytecode.annotation.Annotation + +internal object JavassistAnnotations { + fun stringValue(annotation: Annotation, name: String): String { + val value: StringMemberValue = annotation.getMemberValue(name) as StringMemberValue + return value.value + } + + fun dependencyDescriptions(annotation: Annotation, name: String): Array { + val value: ArrayMemberValue = annotation.getMemberValue(name) as ArrayMemberValue + return value.value.map { + val dependencyAnnotation: Annotation = (it as AnnotationMemberValue).value + DependencyDescription( + stringValue(dependencyAnnotation, "value"), + (dependencyAnnotation.getMemberValue("required") as BooleanMemberValue).value + ) + }.toTypedArray() + } +} diff --git a/core/src/main/kotlin/de/timolia/lactea/core/module/LacteaModuleHandle.kt b/core/src/main/kotlin/de/timolia/lactea/core/module/LacteaModuleHandle.kt new file mode 100644 index 0000000..2792ea4 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/module/LacteaModuleHandle.kt @@ -0,0 +1,44 @@ +package de.timolia.lactea.core.module + +import de.timolia.lactea.core.discovery.DiscoveryIndex +import de.timolia.lactea.module.LacteaModule +import java.io.File +import java.net.URI +import java.util.jar.JarFile + +class LacteaModuleHandle ( + val uri: URI, + val jar: JarFile, + val discoveryIndex: DiscoveryIndex, + val description: ModuleDescription +) { + lateinit var instance: LacteaModule + + fun nameAndLocation(): String { + return description.name + " in " + uri.toString() + } + + fun name() = description.name + + companion object { + fun fromUri(uri: URI): LacteaModuleHandle { + val scheme = uri.scheme + if ("file".equals(scheme, true)) { + return fromFile(File(uri.path)) + } + throw UnsupportedOperationException("$scheme is currently not supported") + } + + fun fromFile(file: File): LacteaModuleHandle { + val jarFile = JarFile(file) + val discoveryIndex = DiscoveryIndex() + discoveryIndex.indexJarFile(jarFile) + return LacteaModuleHandle( + file.toURI(), + jarFile, + discoveryIndex, + ModuleDescription(discoveryIndex) + ) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleBootOrder.kt b/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleBootOrder.kt new file mode 100644 index 0000000..4ff87ca --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleBootOrder.kt @@ -0,0 +1,74 @@ +package de.timolia.lactea.core.module + +import de.timolia.lactea.core.module.ModuleDescription.DependencyDescription +import java.util.* +import java.util.stream.Collectors + +internal class ModuleBootOrder ( + private val registry: ModuleRepository, +) { + private val orderedModules: MutableList = ArrayList() + val modules: List = orderedModules + private val trace = Stack() + + init { + registry.modules().sortedBy(LacteaModuleHandle::name).forEach(::tryAdd) + } + + private fun handleMissingDependency(module: DependencyDescription) { + check(!module.required) { "Missing dependency " + module.value } + } + + private fun tryAdd(module: LacteaModuleHandle) { + try { + add(module) + } catch (exception: Exception) { + throw RuntimeException("Unable to build dependency tree for" + module.name()) + } + } + + private fun currentState(module: LacteaModuleHandle): State { + if (trace.contains(module)) { + return State.IN_LOAD_TRACE + } + return if (orderedModules.contains(module)) { + State.SAFELY_LOADED + } else State.NOT_INVOLVED + } + + private fun handleCircularDependency() { + val formatted = trace.stream() + .map(LacteaModuleHandle::name) + .collect(Collectors.joining(" <-> ")) + throw RuntimeException("Circular dependency between $formatted") + } + + private fun addNext(module: LacteaModuleHandle) { + orderedModules.add(module) + for (dependencyDescription in module.description.dependencies) { + val dependency = registry.byName(dependencyDescription.value) + if (dependency == null) { + handleMissingDependency(dependencyDescription) + continue + } + tryAdd(dependency) + } + trace.pop() + } + + private fun add(module: LacteaModuleHandle) { + val state = currentState(module) + trace.push(module) + when (state) { + State.IN_LOAD_TRACE -> handleCircularDependency() + State.NOT_INVOLVED -> addNext(module) + State.SAFELY_LOADED -> {} + } + } + + internal enum class State { + IN_LOAD_TRACE, + SAFELY_LOADED, + NOT_INVOLVED + } +} diff --git a/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleDescription.kt b/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleDescription.kt new file mode 100644 index 0000000..b32c392 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleDescription.kt @@ -0,0 +1,34 @@ +package de.timolia.lactea.core.module + +import de.timolia.lactea.core.discovery.DiscoveryClass +import de.timolia.lactea.core.discovery.DiscoveryIndex +import de.timolia.lactea.module.LacteaModule +import de.timolia.lactea.module.definition.ModuleDefinition +import javassist.bytecode.annotation.Annotation + +class ModuleDescription( + discoveryIndex: DiscoveryIndex +) { + val main: DiscoveryClass + val name: String + val dependencies: Array + + init { + val candidates = discoveryIndex.runDiscovery(ModuleDefinition::class.java) + main = candidates.requireExactlyOne("Module definition") + val definition: Annotation = main.byType(ModuleDefinition::class.java)!! + name = JavassistAnnotations.stringValue(definition, "value") + dependencies = try { + JavassistAnnotations.dependencyDescriptions(definition, "dependencies") + } catch (exception: Exception) { + throw IllegalStateException("Failed to parse dependencies", exception) + } + } + + @Throws(ClassNotFoundException::class) + fun loadMainClass(): Class { + return main.loadClass() as Class + } + + data class DependencyDescription(val value: String, val required: Boolean) +} diff --git a/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleRepository.kt b/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleRepository.kt new file mode 100644 index 0000000..77f5e82 --- /dev/null +++ b/core/src/main/kotlin/de/timolia/lactea/core/module/ModuleRepository.kt @@ -0,0 +1,52 @@ +package de.timolia.lactea.core.module + +import de.timolia.lactea.path.LacteaPathKeys +import de.timolia.lactea.path.PathResolver +import java.util.logging.Level +import java.util.logging.Logger +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ModuleRepository @Inject constructor( + private val logger: Logger, + pathResolver: PathResolver +) { + private val modules = HashMap() + val modulesInBootOrder: List + + init { + val moduleRoot = pathResolver.resolve(LacteaPathKeys.MODULES) + moduleRoot.forEachJar { + try { + register(LacteaModuleHandle.fromUri(it)) + } catch (exception: Exception) { + logger.log(Level.WARNING, "Failed to read module at $it", exception) + } + } + modulesInBootOrder = ModuleBootOrder(this).modules + } + + fun modules(): Collection { + return modules.values + } + + fun byName(name: String): LacteaModuleHandle? { + return modules[normalizeName(name)] + } + + fun requireByName(name: String): LacteaModuleHandle { + return byName(name) ?: throw IllegalStateException("No module with name $name") + } + + private fun register(module: LacteaModuleHandle) { + val name = normalizeName(module.description.name) + if (modules.put(name, module) != null) { + logger.warning("Duplicated definition for $name") + } + } + + private fun normalizeName(name: String): String { + return name.lowercase() + } +} diff --git a/initialize/build.gradle.kts b/initialize/build.gradle.kts new file mode 100644 index 0000000..dd78996 --- /dev/null +++ b/initialize/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("java") +} +java { + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 +} diff --git a/initialize/src/main/java/de/timolia/lactea/Bootstrap.java b/initialize/src/main/java/de/timolia/lactea/Bootstrap.java new file mode 100644 index 0000000..8eca3da --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/Bootstrap.java @@ -0,0 +1,52 @@ +package de.timolia.lactea; + +import de.timolia.lactea.integirty.EnsureClassAvailable; +import de.timolia.lactea.integirty.Integrity; +import de.timolia.lactea.integirty.IntegrityCheck; +import de.timolia.lactea.path.LacteaPathKeys; +import de.timolia.lactea.path.PathResolver; +import de.timolia.lactea.source.ClassPathInjector; +import de.timolia.lactea.source.SourceRoot; + +public class Bootstrap { + private final Integrity integrity = new Integrity(); + private final ClassPathInjector classPathInjector; + private final PathResolver pathResolver; + + private Bootstrap( + ClassPathInjector classPathInjector, + PathResolver pathResolver + ) { + this.classPathInjector = classPathInjector; + this.pathResolver = pathResolver; + } + + public static Bootstrap build( + ClassPathInjector classPathInjector, + PathResolver pathResolver + ) { + Bootstrap bootstrap = new Bootstrap(classPathInjector, pathResolver); + bootstrap.registerIntegrityCheck( + new EnsureClassAvailable("com.google.inject.Module", "Guice") + ); + return bootstrap; + } + + public void registerIntegrityCheck(IntegrityCheck integrityCheck) { + integrity.register(integrityCheck); + } + + public void initialize() { + SourceRoot librariesRoot = pathResolver.resolve(LacteaPathKeys.LIBRARIES); + classPathInjector.addToClassPath(librariesRoot); + integrity.checkIntegrity(); + } + + public PathResolver pathResolver() { + return pathResolver; + } + + public ClassPathInjector classPathInjector() { + return classPathInjector; + } +} diff --git a/initialize/src/main/java/de/timolia/lactea/integirty/EnsureClassAvailable.java b/initialize/src/main/java/de/timolia/lactea/integirty/EnsureClassAvailable.java new file mode 100644 index 0000000..d8e32e4 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/integirty/EnsureClassAvailable.java @@ -0,0 +1,23 @@ +package de.timolia.lactea.integirty; + +public class EnsureClassAvailable implements IntegrityCheck { + private final String className; + private final String moduleLib; + + public EnsureClassAvailable(String className, String moduleLib) { + this.className = className; + this.moduleLib = moduleLib; + } + + @Override + public void ensure() { + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException( + "Module " + moduleLib + " is not present on classpath" + + " make sure it is present in the libs directory", + e); + } + } +} diff --git a/initialize/src/main/java/de/timolia/lactea/integirty/Integrity.java b/initialize/src/main/java/de/timolia/lactea/integirty/Integrity.java new file mode 100644 index 0000000..ee7fc8e --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/integirty/Integrity.java @@ -0,0 +1,35 @@ +package de.timolia.lactea.integirty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public final class Integrity { + private final List checks = new ArrayList<>(); + private boolean checked; + + public synchronized void register(IntegrityCheck check) { + Objects.requireNonNull(check, "check"); + checks.add(check); + } + + public synchronized void checkIntegrity() { + if (checked) { + throw new IllegalStateException("Integrity already started"); + } + for (IntegrityCheck check : checks) { + try { + check.ensure(); + } catch (Throwable throwable) { + throw new IntegrityError("Fatal miss configuration", throwable); + } + } + checked = true; + } + + static class IntegrityError extends Error { + public IntegrityError(String message, Throwable throwable) { + super(message, throwable); + } + } +} diff --git a/initialize/src/main/java/de/timolia/lactea/integirty/IntegrityCheck.java b/initialize/src/main/java/de/timolia/lactea/integirty/IntegrityCheck.java new file mode 100644 index 0000000..5d34b99 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/integirty/IntegrityCheck.java @@ -0,0 +1,5 @@ +package de.timolia.lactea.integirty; + +public interface IntegrityCheck { + void ensure(); +} diff --git a/initialize/src/main/java/de/timolia/lactea/path/LacteaPathKeys.java b/initialize/src/main/java/de/timolia/lactea/path/LacteaPathKeys.java new file mode 100644 index 0000000..a22b675 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/path/LacteaPathKeys.java @@ -0,0 +1,16 @@ +package de.timolia.lactea.path; + +public final class LacteaPathKeys { + public static PathKey LIBRARIES = new PathKey() { + @Override + public String name() { + return "libraries"; + } + }; + public static PathKey MODULES = new PathKey() { + @Override + public String name() { + return "modules"; + } + }; +} diff --git a/initialize/src/main/java/de/timolia/lactea/path/PathKey.java b/initialize/src/main/java/de/timolia/lactea/path/PathKey.java new file mode 100644 index 0000000..48b3b71 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/path/PathKey.java @@ -0,0 +1,5 @@ +package de.timolia.lactea.path; + +public interface PathKey { + String name(); +} diff --git a/initialize/src/main/java/de/timolia/lactea/path/PathResolver.java b/initialize/src/main/java/de/timolia/lactea/path/PathResolver.java new file mode 100644 index 0000000..bae3e32 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/path/PathResolver.java @@ -0,0 +1,9 @@ +package de.timolia.lactea.path; + +import de.timolia.lactea.source.SourceRoot; + +public interface PathResolver { + SourceRoot resolve(PathKey key); + + SourceRoot resolveOptional(PathKey key); +} diff --git a/initialize/src/main/java/de/timolia/lactea/path/SimpleFileRootPathResolver.java b/initialize/src/main/java/de/timolia/lactea/path/SimpleFileRootPathResolver.java new file mode 100644 index 0000000..2099e77 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/path/SimpleFileRootPathResolver.java @@ -0,0 +1,23 @@ +package de.timolia.lactea.path; + +import de.timolia.lactea.source.DirectorySourceRoot; +import de.timolia.lactea.source.SourceRoot; +import java.io.File; + +public class SimpleFileRootPathResolver implements PathResolver { + private final File root; + + public SimpleFileRootPathResolver(File root) { + this.root = root; + } + + @Override + public SourceRoot resolve(PathKey key) { + return new DirectorySourceRoot(new File(root, key.name())); + } + + @Override + public SourceRoot resolveOptional(PathKey key) { + return resolve(key); + } +} diff --git a/initialize/src/main/java/de/timolia/lactea/source/ClassPathInjector.java b/initialize/src/main/java/de/timolia/lactea/source/ClassPathInjector.java new file mode 100644 index 0000000..0d2a3d0 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/source/ClassPathInjector.java @@ -0,0 +1,9 @@ +package de.timolia.lactea.source; + +import java.net.URI; + +public interface ClassPathInjector { + void addToClassPath(URI file); + + void addToClassPath(SourceRoot sourceRoot); +} diff --git a/initialize/src/main/java/de/timolia/lactea/source/DirectorySourceRoot.java b/initialize/src/main/java/de/timolia/lactea/source/DirectorySourceRoot.java new file mode 100644 index 0000000..05bf3f7 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/source/DirectorySourceRoot.java @@ -0,0 +1,41 @@ +package de.timolia.lactea.source; + +import java.io.File; + +public class DirectorySourceRoot implements SourceRoot { + private final File root; + + public DirectorySourceRoot(File root) { + this.root = root; + } + + private void makeDirectory() { + root.mkdirs(); + } + + private boolean checkInvalidCandidate(File candidate) { + String name = candidate.getName(); + return !candidate.isFile() + || name.startsWith("!") + || !name.endsWith(".jar"); + } + + public static DirectorySourceRoot create(File root) { + return new DirectorySourceRoot(root); + } + + @Override + public void forEachJar(SourceElementAcceptor consumer) { + makeDirectory(); + File[] candidates = root.listFiles(); + if (candidates == null) { + return; + } + for (File candidate : candidates) { + if (checkInvalidCandidate(candidate)) { + continue; + } + consumer.accept(candidate.toURI()); + } + } +} diff --git a/initialize/src/main/java/de/timolia/lactea/source/SourceRoot.java b/initialize/src/main/java/de/timolia/lactea/source/SourceRoot.java new file mode 100644 index 0000000..2a24736 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/source/SourceRoot.java @@ -0,0 +1,12 @@ +package de.timolia.lactea.source; + +import java.net.URI; +import java.util.function.Consumer; + +public interface SourceRoot { + void forEachJar(SourceElementAcceptor consumer); + + interface SourceElementAcceptor { + void accept(URI uri); + } +} diff --git a/initialize/src/main/java/de/timolia/lactea/source/UrlClassPathInjector.java b/initialize/src/main/java/de/timolia/lactea/source/UrlClassPathInjector.java new file mode 100644 index 0000000..ea0c0e1 --- /dev/null +++ b/initialize/src/main/java/de/timolia/lactea/source/UrlClassPathInjector.java @@ -0,0 +1,69 @@ +package de.timolia.lactea.source; + +import de.timolia.lactea.source.SourceRoot.SourceElementAcceptor; +import java.lang.reflect.InaccessibleObjectException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.function.Consumer; + +public class UrlClassPathInjector implements ClassPathInjector { + private final URLClassLoader classLoader; + + public UrlClassPathInjector(URLClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public void addToClassPath(URI file) { + Method method = ReflectionHolder.addUrlMethod; + try { + URL url = file.toURL(); + method.invoke(classLoader, url); + } catch (IllegalAccessException | InvocationTargetException | MalformedURLException e) { + throw new AddToClassPathException(file, e); + } + } + + @Override + public void addToClassPath(final SourceRoot sourceRoot) { + sourceRoot.forEachJar(new SourceElementAcceptor() { + @Override + public void accept(URI uri) { + addToClassPath(uri); + } + }); + } + + public static class AddToClassPathException extends RuntimeException { + private final URI resource; + + public AddToClassPathException(URI resource, Throwable cause) { + super("Failed to add jar to classpath: " + resource, cause); + this.resource = resource; + } + + public URI resource() { + return resource; + } + } + + static class ReflectionHolder { + static final Method addUrlMethod = createAndMakeAccessible(); + + private static Method createAndMakeAccessible() { + try { + Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addURL.setAccessible(true); + return addURL; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (InaccessibleObjectException e) { + throw new RuntimeException("Make sure to open up reflection", e); + } + } + } +} diff --git a/loader-bukkit/build.gradle.kts b/loader-bukkit/build.gradle.kts deleted file mode 100644 index 87a7fed..0000000 --- a/loader-bukkit/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - id("java") -} - -group = "de.timolia" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") - maven("https://oss.sonatype.org/content/repositories/snapshots") - maven("https://oss.sonatype.org/content/repositories/central") -} - -dependencies { - testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") - compileOnly("org.projectlombok:lombok:1.18.24") - implementation("com.google.inject:guice:5.1.0") - compileOnly("org.spigotmc:spigot-api:1.19.2-R0.1-SNAPSHOT") - compileOnly(project(":loader")) - annotationProcessor("org.projectlombok:lombok:1.18.24") - - testCompileOnly("org.projectlombok:lombok:1.18.24") - testAnnotationProcessor("org.projectlombok:lombok:1.18.24") -} - -tasks.withType { - configurations["compileClasspath"].forEach { file: File -> - if (file.absolutePath.contains("loader/build/libs/loader-")) { - from(zipTree(file.absoluteFile)) - } - } -} -tasks.getByName("test") { - useJUnitPlatform() -} \ No newline at end of file diff --git a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/ModuleLoaderPlugin.java b/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/ModuleLoaderPlugin.java deleted file mode 100644 index b70b7af..0000000 --- a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/ModuleLoaderPlugin.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.timolia.lactea.bukkitloader; - -import de.timolia.lactea.bukkitloader.inject.BukkitModule; -import de.timolia.lactea.loader.internal.DefaultRuntime; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.File; - -/** - * @author David (_Esel) - */ -public class ModuleLoaderPlugin extends JavaPlugin { - private final DefaultRuntime runtime = DefaultRuntime.create(new File("lactea")); - - @Override - public void onLoad() { - runtime.loadLibraries(); - runtime.getStartUpController().addGlobalModule(new BukkitModule(this)); - runtime.initialize(); - } - - @Override - public void onEnable() { - runtime.enable(); - } - - @Override - public void onDisable() { - runtime.shutdown(); - } -} diff --git a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/BukkitFuture.java b/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/BukkitFuture.java deleted file mode 100644 index a86085e..0000000 --- a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/BukkitFuture.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.timolia.lactea.bukkitloader.schedule; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import org.bukkit.scheduler.BukkitTask; - -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; - -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) -public class BukkitFuture extends CompletableFuture { - @Getter - @Setter(AccessLevel.PACKAGE) - private BukkitTask task; - private final AtomicBoolean cancelled = new AtomicBoolean(); - - Runnable completeAfter(Runnable runnable) { - return () -> { - try { - runnable.run(); - super.complete(null); - } catch (Throwable throwable) { - super.completeExceptionally(throwable); - } - }; - } - - Runnable completeAfter(Callable callable) { - return () -> { - try { - super.complete(callable.call()); - } catch (Throwable throwable) { - super.completeExceptionally(throwable); - } - }; - } - - @Override - public boolean complete(T value) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean completeExceptionally(Throwable ex) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - if (mayInterruptIfRunning) { - throw new UnsupportedOperationException("mayInterruptIfRunning is not supported"); - } - if (cancelled.compareAndSet(false, true)) { - task.cancel(); - return true; - } - return false; - } - - @Override - public boolean isCancelled() { - return task.isCancelled(); - } -} diff --git a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/BukkitGameLoop.java b/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/BukkitGameLoop.java deleted file mode 100644 index 937eb8b..0000000 --- a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/BukkitGameLoop.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.timolia.lactea.bukkitloader.schedule; - -import de.timolia.lactea.loader.platform.GameLoop; -import lombok.RequiredArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitScheduler; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.time.temporal.TemporalUnit; -import java.util.concurrent.*; - -@Singleton -@RequiredArgsConstructor(onConstructor_ = @Inject) -public class BukkitGameLoop implements GameLoop { - private final BukkitScheduler scheduler; - private final Plugin plugin; - private final Server server; - - @Override - public CompletableFuture runTimer(Runnable task, long delay, long period, TemporalUnit unit) { - BukkitFuture future = new BukkitFuture<>(); - runTimer(future, future.completeAfter(task), delay, period, unit); - return future; - } - - @Override - public CompletableFuture runLater(Runnable task, long delay, TemporalUnit unit) { - BukkitFuture future = new BukkitFuture<>(); - runLater(future, future.completeAfter(task), delay, unit); - return future; - } - - @Override - public CompletableFuture runTimer(Callable task, long delay, long period, TemporalUnit unit) { - BukkitFuture future = new BukkitFuture<>(); - runTimer(future, future.completeAfter(task), delay, period, unit); - return future; - } - - @Override - public CompletableFuture runLater(Callable task, long delay, TemporalUnit unit) { - BukkitFuture future = new BukkitFuture<>(); - runLater(future, future.completeAfter(task), delay, unit); - return future; - } - - @Override - public boolean isOnGameThread() { - return server.isPrimaryThread(); - } - - @Override - public void ensureOnGameThread() { - if (!isOnGameThread()) { - throw new IllegalStateException("Outside of eventloop"); - } - } - - @Override - public CompletableFuture runInGameThread(Runnable runnable) { - BukkitFuture future = new BukkitFuture<>(); - Runnable wrapped = future.completeAfter(runnable); - if (isOnGameThread()) { - wrapped.run(); - } else { - runLater(future, wrapped, 1, TickUnit.GAME); - } - return future; - } - - private void runLater(BukkitFuture future, Runnable wrapped, - long delay, TemporalUnit unit) { - long delayTicks = toTicks(delay, unit); - future.setTask(scheduler.runTaskLater(plugin, wrapped, delayTicks)); - } - - private void runTimer(BukkitFuture future, Runnable wrapped, - long delay, long period, TemporalUnit unit) { - long delayTicks = toTicks(delay, unit); - long periodTicks = toTicks(period, unit); - future.setTask(scheduler.runTaskTimer(plugin, wrapped, delayTicks, periodTicks)); - } - - private static long toTicks(long time, TemporalUnit unit) { - if (unit == TickUnit.GAME) { - return time; - } - return unit.getDuration().toMillis() / TickUnit.GAME.ticksPerSecond(); - } -} diff --git a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/TickUnit.java b/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/TickUnit.java deleted file mode 100644 index 41a904c..0000000 --- a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/schedule/TickUnit.java +++ /dev/null @@ -1,73 +0,0 @@ -package de.timolia.lactea.bukkitloader.schedule; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.time.temporal.Temporal; -import java.time.temporal.TemporalUnit; - -/** - * @author David (_Esel) - */ -public enum TickUnit implements TemporalUnit { - GAME(20), - REDSTONE(10); - - - private final long ticksPerSecond; - private final long millis; - private final Duration duration; - - TickUnit(long ticksPerSecond) { - this.ticksPerSecond = ticksPerSecond; - millis = 1000 / ticksPerSecond; - duration = Duration.ofMillis(millis); - } - - @Override - public Duration getDuration() { - return duration; - } - - @Override - public boolean isDurationEstimated() { - return false; - } - - @Override - public boolean isDateBased() { - return false; - } - - @Override - public boolean isTimeBased() { - return true; - } - - @Override - public boolean isSupportedBy(Temporal temporal) { - // should we check if we have an iso date? or blacklist ChronoLocalDate - return temporal.isSupported(ChronoUnit.MILLIS); - } - - @Override - @SuppressWarnings("unchecked") - public R addTo(R temporal, long amount) { - long addedMillis = Math.multiplyExact(millis, amount); - return (R) temporal.plus(addedMillis, ChronoUnit.MILLIS); - } - - @Override - public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) { - long millisBetween = temporal1Inclusive.until(temporal2Exclusive, ChronoUnit.MILLIS); - return millisBetween / millis; - } - - @Override - public String toString() { - return name(); - } - - public long ticksPerSecond() { - return ticksPerSecond; - } -} diff --git a/loader-nukkit/build.gradle.kts b/loader-nukkit/build.gradle.kts deleted file mode 100644 index dffbc54..0000000 --- a/loader-nukkit/build.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - id("java") -} - -group = "de.timolia" -version = "1.0-SNAPSHOT" - -repositories { - gradlePluginPortal() - mavenCentral() -} - -dependencies { - testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") - compileOnly("org.powernukkit:powernukkit:1.6.0.1-PN") - compileOnly(project(":loader")) -} - -tasks.withType { - configurations["compileClasspath"].forEach { file: File -> - if (file.absolutePath.contains("loader/build/libs/loader-")) { - from(zipTree(file.absoluteFile)) - } - } -} -tasks.getByName("test") { - useJUnitPlatform() -} \ No newline at end of file diff --git a/loader-nukkit/src/main/java/de/timolia/lactea/nukkitloader/ModuleLoaderPlugin.java b/loader-nukkit/src/main/java/de/timolia/lactea/nukkitloader/ModuleLoaderPlugin.java deleted file mode 100644 index 56cc49e..0000000 --- a/loader-nukkit/src/main/java/de/timolia/lactea/nukkitloader/ModuleLoaderPlugin.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.timolia.lactea.nukkitloader; - -import cn.nukkit.plugin.PluginBase; -import de.timolia.lactea.loader.internal.DefaultRuntime; -import de.timolia.lactea.loader.module.ModuleManager; -import de.timolia.lactea.loader.startup.StartUpController; -import java.io.File; - -/** - * @author David (_Esel) - */ -public class ModuleLoaderPlugin extends PluginBase { - private final ModuleManager moduleManager = new ModuleManager(new File("lactea")); - private final DefaultRuntime runtime = new DefaultRuntime(moduleManager, new StartUpController()); - - @Override - public void onLoad() { - runtime.initialize(); - } - - @Override - public void onEnable() { - runtime.enable(); - } - - @Override - public void onDisable() { - moduleManager.disableAll(); - } -} diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts deleted file mode 100644 index 0320c17..0000000 --- a/loader/build.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - id("java") -} - -group = "de.timolia" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() -} - -dependencies { - testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") - compileOnly("org.projectlombok:lombok:1.18.24") - implementation("org.javassist:javassist:3.29.0-GA") - implementation("com.google.inject:guice:5.1.0") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.3") - annotationProcessor("org.projectlombok:lombok:1.18.24") - - testCompileOnly("org.projectlombok:lombok:1.18.24") - testAnnotationProcessor("org.projectlombok:lombok:1.18.24") - compileOnly("com.google.auto.factory:auto-factory:1.0.1") - annotationProcessor("com.google.auto.factory:auto-factory:1.0.1") -} - -tasks.getByName("test") { - useJUnitPlatform() -} \ No newline at end of file diff --git a/loader/loader-bukkit/build.gradle.kts b/loader/loader-bukkit/build.gradle.kts new file mode 100644 index 0000000..076a8a2 --- /dev/null +++ b/loader/loader-bukkit/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("java") +} + +repositories { + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") +} + +dependencies { + implementation("org.spigotmc:spigot-api:1.19.2-R0.1-SNAPSHOT") + implementation(project(":core")) +} + +tasks.withType { + configurations["compileClasspath"].forEach { file: File -> + if (file.absolutePath.contains("initialize/build/libs/initialize-")) { + from(zipTree(file.absoluteFile)) + } + } +} \ No newline at end of file diff --git a/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/ModuleLoaderPlugin.java b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/ModuleLoaderPlugin.java new file mode 100644 index 0000000..15a9f1b --- /dev/null +++ b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/ModuleLoaderPlugin.java @@ -0,0 +1,47 @@ +package de.timolia.lactea.bukkitloader; + +import de.timolia.lactea.Bootstrap; +import de.timolia.lactea.bukkitloader.inject.BukkitModule; +import de.timolia.lactea.core.Lactea; +import de.timolia.lactea.core.lifecycle.startup.LoadEnableSplitStartup; +import de.timolia.lactea.path.SimpleFileRootPathResolver; +import de.timolia.lactea.source.UrlClassPathInjector; +import java.net.URLClassLoader; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; + +public class ModuleLoaderPlugin extends JavaPlugin { + private LoadEnableSplitStartup startup; + + @Override + public void onLoad() { + Bootstrap bootstrap = Bootstrap.build( + new UrlClassPathInjector((URLClassLoader) getClassLoader()), + new SimpleFileRootPathResolver(new File("lactea")) + ); + bootstrap.initialize(); + postBootstrap(bootstrap); + } + + private void postBootstrap(Bootstrap bootstrap) { + Lactea lactea = new Lactea(bootstrap, new BukkitModule(this)); + startup = lactea.loadEnableSplit(); + startup.setupDependencyInjection(); + } + + @Override + public void onEnable() { + LoadEnableSplitStartup startup = this.startup; + if (startup == null) { + getLogger().severe("Startup is not initialized"); + return; + } + startup.enableModules(); + } + + @Override + public void onDisable() { + + } +} diff --git a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitListenerRegistry.java b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitListenerRegistry.java similarity index 59% rename from loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitListenerRegistry.java rename to loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitListenerRegistry.java index 4103713..d7d8095 100644 --- a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitListenerRegistry.java +++ b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitListenerRegistry.java @@ -1,19 +1,22 @@ package de.timolia.lactea.bukkitloader.inject; -import de.timolia.lactea.loader.inject.AutoWiredRegistry; -import java.lang.annotation.Annotation; +import de.timolia.lactea.inject.InstanceRegistry; import javax.inject.Inject; -import lombok.RequiredArgsConstructor; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -@RequiredArgsConstructor(onConstructor_ = @Inject) -public class BukkitListenerRegistry implements AutoWiredRegistry { +public class BukkitListenerRegistry implements InstanceRegistry { private final PluginManager pluginManager; private final Plugin plugin; + @Inject + public BukkitListenerRegistry(PluginManager pluginManager, Plugin plugin) { + this.pluginManager = pluginManager; + this.plugin = plugin; + } + @Override public void register(Listener instance) { pluginManager.registerEvents(instance, plugin); @@ -23,9 +26,4 @@ public void register(Listener instance) { public void unregister(Listener instance) { HandlerList.unregisterAll(instance); } - - @Override - public Class autoWireAnnotation() { - return ListenerAutoWire.class; - } } diff --git a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitModule.java b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitModule.java similarity index 92% rename from loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitModule.java rename to loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitModule.java index b101642..de06069 100644 --- a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitModule.java +++ b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/BukkitModule.java @@ -2,7 +2,6 @@ import com.google.inject.Binder; import com.google.inject.Module; -import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.ConsoleCommandSender; @@ -10,10 +9,13 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.messaging.Messenger; -@RequiredArgsConstructor public class BukkitModule implements Module { private final Plugin plugin; + public BukkitModule(Plugin plugin) { + this.plugin = plugin; + } + @Override public void configure(Binder binder) { binder.bind(Plugin.class).toInstance(plugin); diff --git a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/ListenerAutoWire.java b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/ListenerAutoWire.java similarity index 88% rename from loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/ListenerAutoWire.java rename to loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/ListenerAutoWire.java index 6a2f4a5..27d81c4 100644 --- a/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/ListenerAutoWire.java +++ b/loader/loader-bukkit/src/main/java/de/timolia/lactea/bukkitloader/inject/ListenerAutoWire.java @@ -6,7 +6,6 @@ import java.lang.annotation.Target; @Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) public @interface ListenerAutoWire { } diff --git a/loader-bukkit/src/main/resources/plugin.yml b/loader/loader-bukkit/src/main/resources/plugin.yml similarity index 100% rename from loader-bukkit/src/main/resources/plugin.yml rename to loader/loader-bukkit/src/main/resources/plugin.yml diff --git a/loader/loader-nukkit/build.gradle.kts b/loader/loader-nukkit/build.gradle.kts new file mode 100644 index 0000000..ce016ef --- /dev/null +++ b/loader/loader-nukkit/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("java") +} + +dependencies { + implementation("org.powernukkit:powernukkit:1.6.0.1-PN") + implementation(project(":core")) +} + +tasks.withType { + configurations["compileClasspath"].forEach { file: File -> + if (file.absolutePath.contains("initialize/build/libs/initialize-")) { + from(zipTree(file.absoluteFile)) + } + } +} \ No newline at end of file diff --git a/loader/loader-nukkit/src/main/java/de/timolia/lactea/nukkitloader/ModuleLoaderPlugin.java b/loader/loader-nukkit/src/main/java/de/timolia/lactea/nukkitloader/ModuleLoaderPlugin.java new file mode 100644 index 0000000..3b11932 --- /dev/null +++ b/loader/loader-nukkit/src/main/java/de/timolia/lactea/nukkitloader/ModuleLoaderPlugin.java @@ -0,0 +1,45 @@ +package de.timolia.lactea.nukkitloader; + +import cn.nukkit.plugin.PluginBase; +import de.timolia.lactea.Bootstrap; +import de.timolia.lactea.core.Lactea; +import de.timolia.lactea.core.lifecycle.startup.LoadEnableSplitStartup; +import de.timolia.lactea.path.SimpleFileRootPathResolver; +import de.timolia.lactea.source.UrlClassPathInjector; +import java.io.File; +import java.net.URLClassLoader; + +public class ModuleLoaderPlugin extends PluginBase { + private LoadEnableSplitStartup startup; + + @Override + public void onLoad() { + Bootstrap bootstrap = Bootstrap.build( + new UrlClassPathInjector((URLClassLoader) getClass().getClassLoader()), + new SimpleFileRootPathResolver(new File("lactea")) + ); + bootstrap.initialize(); + postBootstrap(bootstrap); + } + + private void postBootstrap(Bootstrap bootstrap) { + Lactea lactea = new Lactea(bootstrap); + startup = lactea.loadEnableSplit(); + startup.setupDependencyInjection(); + } + + @Override + public void onEnable() { + LoadEnableSplitStartup startup = this.startup; + if (startup == null) { + getLogger().critical("Startup is not initialized"); + return; + } + startup.enableModules(); + } + + @Override + public void onDisable() { + + } +} diff --git a/loader-nukkit/src/main/resources/plugin.yml b/loader/loader-nukkit/src/main/resources/plugin.yml similarity index 100% rename from loader-nukkit/src/main/resources/plugin.yml rename to loader/loader-nukkit/src/main/resources/plugin.yml diff --git a/loader/loader-standalone/build.gradle.kts b/loader/loader-standalone/build.gradle.kts new file mode 100644 index 0000000..40c7329 --- /dev/null +++ b/loader/loader-standalone/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("java") +} + +dependencies { + implementation(project(":core")) +} + +tasks.withType { + configurations["compileClasspath"].forEach { file: File -> + if (file.absolutePath.contains("initialize/build/libs/initialize-")) { + from(zipTree(file.absoluteFile)) + } + } +} \ No newline at end of file diff --git a/loader/loader-standalone/src/main/java/de/timolia/lactea/standalone/Main.java b/loader/loader-standalone/src/main/java/de/timolia/lactea/standalone/Main.java new file mode 100644 index 0000000..c7d250e --- /dev/null +++ b/loader/loader-standalone/src/main/java/de/timolia/lactea/standalone/Main.java @@ -0,0 +1,12 @@ +package de.timolia.lactea.standalone; + +import de.timolia.lactea.standalone.application.Application; +import java.io.File; + +public class Main { + public static void main(String[] args) { + File folder = new File("lactea"); + Application application = new Application(); + application.boot(folder); + } +} diff --git a/loader/loader-standalone/src/main/java/de/timolia/lactea/standalone/application/Application.java b/loader/loader-standalone/src/main/java/de/timolia/lactea/standalone/application/Application.java new file mode 100644 index 0000000..65bab37 --- /dev/null +++ b/loader/loader-standalone/src/main/java/de/timolia/lactea/standalone/application/Application.java @@ -0,0 +1,24 @@ +package de.timolia.lactea.standalone.application; + +import de.timolia.lactea.Bootstrap; +import de.timolia.lactea.core.Lactea; +import de.timolia.lactea.path.SimpleFileRootPathResolver; +import de.timolia.lactea.source.UrlClassPathInjector; +import java.io.File; +import java.net.URLClassLoader; + +public class Application { + public void boot(File folder) { + Bootstrap bootstrap = Bootstrap.build( + new UrlClassPathInjector((URLClassLoader) getClass().getClassLoader()), + new SimpleFileRootPathResolver(folder) + ); + bootstrap.initialize(); + postBootstrap(bootstrap); + } + + private void postBootstrap(Bootstrap bootstrap) { + Lactea lactea = new Lactea(bootstrap); + lactea.singleEntrypoint().performStartup(); + } +} diff --git a/loader/src/main/java/de/timolia/lactea/loader/Runtime.java b/loader/src/main/java/de/timolia/lactea/loader/Runtime.java deleted file mode 100644 index 1773ccd..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/Runtime.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.timolia.lactea.loader; - -/** - * @author David (_Esel) - */ -public interface Runtime { - MT getModule(String name); - - boolean isModuleLoaded(String name); -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/config/ConfigController.java b/loader/src/main/java/de/timolia/lactea/loader/config/ConfigController.java deleted file mode 100644 index f4c0a8a..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/config/ConfigController.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.timolia.lactea.loader.config; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import javax.inject.Singleton; -import lombok.SneakyThrows; - -/** - * @author David (_Esel) - */ -@Singleton -public class ConfigController { - private final Map pathPlaceholders = new HashMap<>(); - private final Map extensionToFormat = new HashMap<>(); - private final ConfigFormat defaultFormat = ConfigFormat.YAML; - - { - registerFormat(ConfigFormat.YAML); - registerPlaceholder("lactea:", "lactea/config/"); - } - - private void registerPlaceholder(String name, String replacement) { - pathPlaceholders.put(name, replacement); - } - - private void registerFormat(ConfigFormat format) { - extensionToFormat.put(format.getExtension(), format); - } - - private String acceptPlaceholders(String name) { - for (Map.Entry entry : pathPlaceholders.entrySet()) { - name = name.replaceAll(entry.getKey(), entry.getValue()); - } - return name; - } - - public T loadConfig(String name, Class clazz) throws IOException { - name = acceptPlaceholders(name); - File file = new File(name); - File parent = file.getAbsoluteFile().getParentFile(); - parent.mkdirs(); - for (File candidate : parent.listFiles()) { - if (candidate.isDirectory()) { - continue; - } - String candidateName = candidate.getName(); - int dot = candidateName.lastIndexOf('.'); - if (dot == -1) { - continue; - } - if (candidateName.substring(0, dot).equals(file.getName())) { - return extensionToFormat.getOrDefault(candidateName.substring(dot + 1), defaultFormat).loadConfig(candidate, clazz); - } - } - return createDefault(parent, file.getName(), clazz); - } - - @SneakyThrows - private T createDefault(File parent, String name, Class clazz) throws IOException { - ConfigFormat format = defaultFormat; - File file = new File(parent, name + '.' + format.getExtension()); - format.objectMapper().writeValue(file, clazz.newInstance()); - file.createNewFile(); - return format.loadConfig(file, clazz); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/config/ConfigDefinition.java b/loader/src/main/java/de/timolia/lactea/loader/config/ConfigDefinition.java deleted file mode 100644 index 5a212d8..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/config/ConfigDefinition.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.timolia.lactea.loader.config; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author David (_Esel) - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ConfigDefinition { - String value(); -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/config/ConfigFormat.java b/loader/src/main/java/de/timolia/lactea/loader/config/ConfigFormat.java deleted file mode 100644 index 1dc624e..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/config/ConfigFormat.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.timolia.lactea.loader.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import java.io.File; -import java.io.IOException; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * @author David (_Esel) - */ -@RequiredArgsConstructor -public abstract class ConfigFormat { - public static ConfigFormat YAML = new ConfigFormat("yml", "yaml") { - @Override - public ObjectMapper createObjectMapper() { - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - mapper.findAndRegisterModules(); - return mapper; - } - }; - - @Getter - private final String extension; - @Getter - private final String name; - private volatile ObjectMapper objectMapper; - - protected abstract ObjectMapper createObjectMapper(); - - ObjectMapper objectMapper() { - if (objectMapper == null) { - synchronized (this) { - if (objectMapper == null) { - objectMapper = createObjectMapper(); - } - } - } - return objectMapper; - } - - public T loadConfig(File file, Class clazz) throws IOException { - return objectMapper().readValue(file, clazz); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/config/LocalConfigModule.java b/loader/src/main/java/de/timolia/lactea/loader/config/LocalConfigModule.java deleted file mode 100644 index 47b41d3..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/config/LocalConfigModule.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.timolia.lactea.loader.config; - -import com.google.auto.factory.AutoFactory; -import com.google.auto.factory.Provided; -import com.google.inject.AbstractModule; -import de.timolia.lactea.loader.internal.JavassistAnnotations; -import de.timolia.lactea.loader.module.LacteaModule; -import de.timolia.lactea.loader.module.discovery.DiscoveryClass; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * @author David (_Esel) - */ -@AutoFactory -public class LocalConfigModule extends AbstractModule { - private final Logger logger; - private final ConfigController controller; - private final LacteaModule module; - - public LocalConfigModule(@Provided Logger logger, - @Provided ConfigController controller, - LacteaModule module) { - this.logger = logger; - this.controller = controller; - this.module = module; - } - - private T createConfigObject(String name, Class clazz) { - try { - return controller.loadConfig(name, clazz); - } catch (IOException e) { - logger.log(Level.WARNING, "Failed to load config with name " + name + " as " + clazz, e); - try { - return clazz.getConstructor().newInstance(); - } catch (Exception ex) { - logger.log(Level.WARNING, "Failed to create dummy config object", ex); - return null; - } - } - } - - private void bindConfig(String name, Class clazz) { - bind(clazz).toInstance(createConfigObject(name, clazz)); - } - - @Override - protected void configure() { - for (DiscoveryClass clazz : module.getDescription().getDiscoveryIndex().runDiscovery(ConfigDefinition.class)) { - String name = JavassistAnnotations.stringValue(clazz.byType(ConfigDefinition.class), "value"); - Class loaded; - try { - loaded = clazz.loadClass(); - } catch (ClassNotFoundException e) { - logger.log(Level.SEVERE, "Failed to find class previously discovered: " + clazz.getName(), e); - continue; - } - bindConfig(name, loaded); - } - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/inject/AutoWiredRegistry.java b/loader/src/main/java/de/timolia/lactea/loader/inject/AutoWiredRegistry.java deleted file mode 100644 index 633c99b..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/inject/AutoWiredRegistry.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.timolia.lactea.loader.inject; - -import de.timolia.lactea.loader.module.InternalModuleAccess; -import de.timolia.lactea.loader.module.LacteaModule; -import de.timolia.lactea.loader.module.discovery.DiscoveryClass; -import de.timolia.lactea.loader.module.discovery.DiscoveryIndex; -import java.lang.annotation.Annotation; -import java.util.Collection; - -public interface AutoWiredRegistry extends InstanceRegistry { - Class autoWireAnnotation(); - - default void performAutoWire(LacteaModule module) { - DiscoveryIndex index = module.getDescription().getDiscoveryIndex(); - ModuleInjector injector = InternalModuleAccess.getInjector(module); - Collection classes = index.runDiscovery(autoWireAnnotation()); - for (DiscoveryClass discoveryClass : classes) { - try { - //noinspection unchecked - Class javaClass = (Class) discoveryClass.loadClass(); - register(injector.getInstance(InjectedInstance.ofClass(javaClass))); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - } - -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/inject/InjectedInstance.java b/loader/src/main/java/de/timolia/lactea/loader/inject/InjectedInstance.java deleted file mode 100644 index 784c34d..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/inject/InjectedInstance.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.timolia.lactea.loader.inject; - -import com.google.inject.Injector; -import java.util.Objects; -import java.util.function.Function; - -/** - * @author David (_Esel) - */ -public interface InjectedInstance { - static InjectedInstance ofInstance(T instance) { - return injector -> instance; - } - - static InjectedInstance ofClass(Class clazz) { - Objects.requireNonNull(clazz, "class"); - return injector -> injector.getInstance(clazz); - } - - static InjectedInstance ofFactory(Class factoryClass, - Function createFunction) { - Objects.requireNonNull(factoryClass, "factoryClass"); - Objects.requireNonNull(createFunction, "createFunction"); - return injector -> createFunction.apply(injector.getInstance(factoryClass)); - } - - T getInstance(Injector injector); -} \ No newline at end of file diff --git a/loader/src/main/java/de/timolia/lactea/loader/inject/InstanceRegistry.java b/loader/src/main/java/de/timolia/lactea/loader/inject/InstanceRegistry.java deleted file mode 100644 index e0bb791..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/inject/InstanceRegistry.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.timolia.lactea.loader.inject; - -public interface InstanceRegistry { - void register(I instance); - void unregister(I instance); -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/inject/ModuleInjector.java b/loader/src/main/java/de/timolia/lactea/loader/inject/ModuleInjector.java deleted file mode 100644 index d386079..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/inject/ModuleInjector.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.timolia.lactea.loader.inject; - -import com.google.inject.Injector; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class ModuleInjector { - private final Injector injector; - - public T getInstance(InjectedInstance instance) { - return instance.getInstance(injector); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/internal/DefaultRuntime.java b/loader/src/main/java/de/timolia/lactea/loader/internal/DefaultRuntime.java deleted file mode 100644 index 9c50a48..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/internal/DefaultRuntime.java +++ /dev/null @@ -1,64 +0,0 @@ -package de.timolia.lactea.loader.internal; - -import de.timolia.lactea.loader.Runtime; -import de.timolia.lactea.loader.module.ModuleManager; -import de.timolia.lactea.loader.startup.StartUpController; -import de.timolia.lactea.loader.startup.internal.Integrity; -import de.timolia.lactea.loader.startup.internal.ModuleLoadContext; - -import java.io.File; -import java.util.List; -import java.util.Objects; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * @author David (_Esel) - */ -@RequiredArgsConstructor -public class DefaultRuntime implements Runtime { - private final ModuleManager moduleManager; - @Getter - private final StartUpController startUpController; - - public void loadLibraries() { - moduleManager.loadLibraries(); - } - - public void initialize() { - Integrity.checkIntegrity(); - postLibraries(); - } - - private void postLibraries() { - moduleManager.scan(); - startUpController.addGlobalModule(binder -> binder.bind(Runtime.class).toInstance(this)); - List contexts = moduleManager.loadAll(startUpController); - startUpController.initializeInjectors(contexts); - } - - public void enable() { - moduleManager.enableAll(startUpController); - } - - public void shutdown() { - moduleManager.disableAll(); - } - - @Override - public MT getModule(String name) { - Objects.requireNonNull(name, "name"); - return (MT) moduleManager.byName(name); - } - - @Override - public boolean isModuleLoaded(String name) { - return moduleManager.isLoaded(Objects.requireNonNull(name, "name")); - } - - public static DefaultRuntime create(File fileRoot) { - ModuleManager moduleManager = new ModuleManager(fileRoot); - return new DefaultRuntime(moduleManager, new StartUpController()); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/internal/JavassistAnnotations.java b/loader/src/main/java/de/timolia/lactea/loader/internal/JavassistAnnotations.java deleted file mode 100644 index ca38f1a..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/internal/JavassistAnnotations.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.timolia.lactea.loader.internal; - -import de.timolia.lactea.loader.module.ModuleDescription.DependencyDescription; -import javassist.bytecode.annotation.Annotation; -import javassist.bytecode.annotation.AnnotationMemberValue; -import javassist.bytecode.annotation.ArrayMemberValue; -import javassist.bytecode.annotation.BooleanMemberValue; -import javassist.bytecode.annotation.MemberValue; -import javassist.bytecode.annotation.StringMemberValue; - -/** - * @author David (_Esel) - */ -public class JavassistAnnotations { - public static String stringValue(Annotation annotation, String name) { - StringMemberValue value = (StringMemberValue) annotation.getMemberValue(name); - return value.getValue(); - } - - public static DependencyDescription[] dependencyDescriptions(Annotation annotation, String name) { - ArrayMemberValue value = (ArrayMemberValue) annotation.getMemberValue(name); - MemberValue[] dependencies = value.getValue(); - DependencyDescription[] dependencyDescriptions = new DependencyDescription[dependencies.length]; - for (int i = 0; i < dependencies.length; i++) { - Annotation dependencyAnnotation = ((AnnotationMemberValue) dependencies[i]).getValue(); - dependencyDescriptions[i] = new DependencyDescription( - stringValue(dependencyAnnotation, "value"), - ((BooleanMemberValue) dependencyAnnotation.getMemberValue("required")).getValue() - ); - } - return dependencyDescriptions; - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/internal/OpenClassLoader.java b/loader/src/main/java/de/timolia/lactea/loader/internal/OpenClassLoader.java deleted file mode 100644 index 82e79a8..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/internal/OpenClassLoader.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.timolia.lactea.loader.internal; - -import java.net.URL; -import java.net.URLClassLoader; - -/** - * @author David (_Esel) - */ -public class OpenClassLoader extends URLClassLoader { - private static final URL[] EMPTY_URLS = new URL[0]; - - public OpenClassLoader(ClassLoader parent) { - super(EMPTY_URLS, parent); - } - - @Override - public void addURL(URL url) { - super.addURL(url); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/Dependency.java b/loader/src/main/java/de/timolia/lactea/loader/module/Dependency.java deleted file mode 100644 index b848357..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/Dependency.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.timolia.lactea.loader.module; - -public @interface Dependency { - String value(); - - boolean required() default true; -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/InternalModuleAccess.java b/loader/src/main/java/de/timolia/lactea/loader/module/InternalModuleAccess.java deleted file mode 100644 index 23eb2f9..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/InternalModuleAccess.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.timolia.lactea.loader.module; - -import com.google.inject.Injector; -import de.timolia.lactea.loader.inject.ModuleInjector; -import java.util.Objects; - -/** - * @author David (_Esel) - */ -public class InternalModuleAccess { - public static void setInjector(LacteaModule module, Injector injector) { - Objects.requireNonNull(injector, "injector"); - ModuleInjector moduleInjector = new ModuleInjector(injector); - module.setInjector(moduleInjector); - } - - public static ModuleInjector getInjector(LacteaModule module) { - return module.getInjector(); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/LacteaModule.java b/loader/src/main/java/de/timolia/lactea/loader/module/LacteaModule.java deleted file mode 100644 index 4661917..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/LacteaModule.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.timolia.lactea.loader.module; - -import de.timolia.lactea.loader.inject.ModuleInjector; -import de.timolia.lactea.loader.startup.EnableContext; -import de.timolia.lactea.loader.startup.LoadContext; -import java.util.logging.Logger; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; - -/** - * @author David (_Esel) - */ -public class LacteaModule { - @Setter(AccessLevel.PACKAGE) - @Getter(AccessLevel.PROTECTED) - private ModuleInjector injector; - @Getter - @Setter(AccessLevel.PACKAGE) - private ModuleDescription description; - @Getter(AccessLevel.PROTECTED) - private final Logger logger = Logger.getLogger(getClass().getName()); - - public void onEnable(EnableContext context) throws Exception { - } - - public void onLoad(LoadContext context) throws Exception { - } - - public void onDisable() throws Exception { - - } - - public void onPostDisable() throws Exception { - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleDefinition.java b/loader/src/main/java/de/timolia/lactea/loader/module/ModuleDefinition.java deleted file mode 100644 index f704283..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleDefinition.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.timolia.lactea.loader.module; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author David (_Esel) - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ModuleDefinition { - String value(); - - Dependency[] dependencies() default {}; -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleDescription.java b/loader/src/main/java/de/timolia/lactea/loader/module/ModuleDescription.java deleted file mode 100644 index ac2f994..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleDescription.java +++ /dev/null @@ -1,60 +0,0 @@ -package de.timolia.lactea.loader.module; - -import de.timolia.lactea.loader.internal.JavassistAnnotations; -import de.timolia.lactea.loader.module.discovery.DiscoveryClass; -import de.timolia.lactea.loader.module.discovery.DiscoveryIndex; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collection; -import java.util.jar.JarFile; -import javassist.bytecode.annotation.Annotation; -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * @author David (_Esel) - */ -@Getter -public class ModuleDescription { - private final File file; - private final DiscoveryIndex discoveryIndex = new DiscoveryIndex(); - private final DiscoveryClass main; - private final String name; - private final DependencyDescription[] dependencies; - - public ModuleDescription(File file, JarFile jar) throws IOException { - this.file = file; - discoveryIndex.indexJarFile(jar); - Collection candidates = discoveryIndex.runDiscovery(ModuleDefinition.class); - if (candidates.size() != 1) { - throw new IllegalStateException("Require exactly one Module definition." - + " Candidates are: " + candidates); - } - main = candidates.iterator().next(); - Annotation definition = main.byType(ModuleDefinition.class); - name = JavassistAnnotations.stringValue(definition, "value"); - try { - dependencies = JavassistAnnotations.dependencyDescriptions(definition, "dependencies"); - } catch (Exception exception) { - throw new IllegalStateException("Failed to parse dependencies", exception); - } - } - - public String nameAndLocation() { - return name + " in " + file.getName(); - } - - public URL url() throws MalformedURLException { - return file.toURI().toURL(); - } - - public Class mainClass() throws ClassNotFoundException { - return (Class) main.loadClass(); - } - - public record DependencyDescription(String value, boolean required) { - - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleManager.java b/loader/src/main/java/de/timolia/lactea/loader/module/ModuleManager.java deleted file mode 100644 index 41570e4..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleManager.java +++ /dev/null @@ -1,152 +0,0 @@ -package de.timolia.lactea.loader.module; - -import de.timolia.lactea.loader.startup.StartUpController; -import de.timolia.lactea.loader.startup.internal.ModuleBootOrder; -import de.timolia.lactea.loader.startup.internal.ModuleLoadContext; -import java.io.File; -import java.lang.reflect.InaccessibleObjectException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; -import java.util.jar.JarFile; -import java.util.logging.Level; -import java.util.logging.Logger; -import lombok.RequiredArgsConstructor; - -/** - * @author David (_Esel) - */ -@RequiredArgsConstructor -public class ModuleManager { - private final Logger logger = Logger.getLogger(ModuleManager.class.getName()); - private final File container; - private final ModuleRegistry registry = new ModuleRegistry(); - private final ModuleBootOrder bootOrder = new ModuleBootOrder(registry); - private final Set loaded = new HashSet<>(); - - public boolean isLoaded(String name) { - return loaded.contains(name); - } - - public LacteaModule byName(String name) { - return registry.byName(name); - } - - private boolean checkInvalidCandidate(File candidate) { - String name = candidate.getName(); - return !candidate.isFile() - || name.startsWith("!") - || !name.endsWith(".jar"); - } - - private File makeChildDirectory(String name) { - File directory = new File(container, name); - directory.mkdirs(); - return directory; - } - - private void forEachJar(String namespace, Consumer consumer) { - File jarDirectory = makeChildDirectory(namespace); - File[] candidates = jarDirectory.listFiles(); - if (candidates != null) { - for (File candidate : candidates) { - if (checkInvalidCandidate(candidate)) { - continue; - } - consumer.accept(candidate); - } - } - } - - public void loadLibraries() { - URLClassLoader loader = (URLClassLoader) ModuleManager.class.getClassLoader(); - Method addURL = addUrlMethod(); - forEachJar("libraries", candidate -> { - try { - addURL.invoke(loader, candidate.toURI().toURL()); - } catch (IllegalAccessException | InvocationTargetException | MalformedURLException e) { - logger.log(Level.WARNING, "Failed to load library: " + candidate.getName()); - } - }); - } - - public void scan() { - forEachJar("modules", candidate -> { - try (JarFile jar = new JarFile(candidate)) { - ModuleDescription desc = new ModuleDescription(candidate, jar); - registry.addDefinition(desc); - } catch (Throwable throwable) { - logger.log(Level.SEVERE, "Failed to scan " + candidate, throwable); - } - }); - bootOrder.addBatchAndLock(registry.definitions()); - } - - public List loadAll(StartUpController startUpController) { - URLClassLoader loader = (URLClassLoader) ModuleManager.class.getClassLoader(); - Method addURL = addUrlMethod(); - List contexts = new ArrayList<>(); - for (ModuleDescription description : bootOrder.ordered()) { - try { - addURL.invoke(loader, description.url()); - Class main = description.mainClass(); - LacteaModule module = main.getConstructor().newInstance(); - module.setDescription(description); - registry.addModule(module); - ModuleLoadContext loadContext = startUpController.loadModule(module); - contexts.add(loadContext); - loaded.add(description.getName()); - } catch (Throwable ex) { - logger.log(Level.SEVERE, "Failed to load " + description.nameAndLocation(), ex); - } - } - return contexts; - } - - public void enableAll(StartUpController startUpController) { - for (LacteaModule module : bootOrder.orderedModules()) { - try { - module.onEnable(startUpController.enableContext(module)); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Failed to enable " + module, ex); - } - } - } - - public void disableAll() { - List orderedModules = bootOrder.orderedModules(); - for (LacteaModule module : orderedModules) { - try { - module.onDisable(); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Failed to disable " + module, ex); - } - } - for (LacteaModule module : orderedModules) { - try { - module.onPostDisable(); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Failed to post disable " + module, ex); - } - } - } - - private static Method addUrlMethod() { - try { - Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - addURL.setAccessible(true); - return addURL; - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } catch (InaccessibleObjectException e) { - throw new RuntimeException("Make sure to open up reflection", e); - } - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleRegistry.java b/loader/src/main/java/de/timolia/lactea/loader/module/ModuleRegistry.java deleted file mode 100644 index 7faedb5..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/ModuleRegistry.java +++ /dev/null @@ -1,58 +0,0 @@ -package de.timolia.lactea.loader.module; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.logging.Logger; - -public class ModuleRegistry { - private static final Logger LOGGER = Logger.getLogger(ModuleRegistry.class.getName()); - - final Map definitions = new HashMap<>(); - final Collection immutableDefinitionView = Collections.unmodifiableCollection(definitions.values()); - final Map modules = new HashMap<>(); - - public Collection definitions() { - return immutableDefinitionView; - } - - public LacteaModule byName(String name) { - return modules.get(normalizeName(name)); - } - - public LacteaModule requireByName(String name) { - LacteaModule module = byName(name); - if (module == null) { - throw new IllegalStateException("No module with name " + name); - } - return module; - } - - public ModuleDescription definition(String name) { - return definitions.get(normalizeName(name)); - } - - void addDefinition(ModuleDescription desc) { - String name = normalizeName(desc.getName()); - if (definitions.put(name, desc) != null) { - LOGGER.warning("Duplicated definition for " + name); - } - } - - void addModule(LacteaModule module) { - String name = normalizeName(module.getDescription().getName()); - if (!definitions.containsKey(name)) { - throw new IllegalStateException("No definition for module " + name); - } - if (modules.put(name, module) != null) { - throw new IllegalStateException("Module with name " + name + " already registered"); - } - } - - private String normalizeName(String name) { - return name.toLowerCase(Locale.ROOT); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/discovery/DiscoveryClass.java b/loader/src/main/java/de/timolia/lactea/loader/module/discovery/DiscoveryClass.java deleted file mode 100644 index bc35af4..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/discovery/DiscoveryClass.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.timolia.lactea.loader.module.discovery; - -import de.timolia.lactea.loader.module.LacteaModule; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import javassist.bytecode.annotation.Annotation; -import lombok.Getter; -import lombok.ToString; - -/** - * @author David (_Esel) - */ -@Getter -@ToString(exclude = "byType") -public class DiscoveryClass { - private final String name; - private final Annotation[] annotations; - private final Map byType = new HashMap<>(); - - - public DiscoveryClass(String name, Annotation[] annotations) { - this.name = name; - this.annotations = annotations; - buildTypeIndex(); - } - - private void buildTypeIndex() { - for (Annotation annotation : annotations) { - if (byType.put(annotation.getTypeName(), annotation) != null) { - throw new IllegalStateException("Illegal discovery name=" + name + " annotation=" + annotation); - } - } - } - - public Annotation byType(String search) { - return byType.get(search); - } - - public Annotation byType(Class search) { - return byType(search.getName()); - } - - public Class loadClass() throws ClassNotFoundException { - return Class.forName(name); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/module/discovery/DiscoveryIndex.java b/loader/src/main/java/de/timolia/lactea/loader/module/discovery/DiscoveryIndex.java deleted file mode 100644 index 2c9f3fe..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/module/discovery/DiscoveryIndex.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.timolia.lactea.loader.module.discovery; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import java.io.DataInputStream; -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Enumeration; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import javassist.bytecode.AnnotationsAttribute; -import javassist.bytecode.ClassFile; - -/** - * @author David (_Esel) - */ -public class DiscoveryIndex { - private final Multimap index = HashMultimap.create(); - - void addToIndex(DiscoveryClass discoveryClass) { - for (javassist.bytecode.annotation.Annotation annotation : discoveryClass.getAnnotations()) { - index.put(annotation.getTypeName(), discoveryClass); - } - } - - public void indexJarFile(JarFile jar) throws IOException { - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry jarEntry = entries.nextElement(); - if (jarEntry.getName().endsWith(".class")) { - ClassFile classFile = new ClassFile(new DataInputStream(jar.getInputStream(jarEntry))); - AnnotationsAttribute visible = (AnnotationsAttribute) classFile.getAttribute("RuntimeVisibleAnnotations"); - if (visible != null) { - addToIndex(new DiscoveryClass(classFile.getName(), visible.getAnnotations())); - } - } - } - } - - - public Collection runDiscovery(String search) { - return index.get(search); - } - - public Collection runDiscovery(Class search) { - return runDiscovery(search.getName()); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/platform/GameLoop.java b/loader/src/main/java/de/timolia/lactea/loader/platform/GameLoop.java deleted file mode 100644 index 647be6c..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/platform/GameLoop.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.timolia.lactea.loader.platform; - -import java.time.temporal.TemporalUnit; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; - -public interface GameLoop { - CompletableFuture runTimer(Runnable task, long delay, long period, TemporalUnit unit); - - CompletableFuture runLater(Runnable task, long delay, TemporalUnit unit); - - CompletableFuture runTimer(Callable task, long delay, long period, TemporalUnit unit); - - CompletableFuture runLater(Callable task, long delay, TemporalUnit unit); - - boolean isOnGameThread(); - - void ensureOnGameThread(); - - CompletableFuture runInGameThread(Runnable runnable); -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/startup/EnableContext.java b/loader/src/main/java/de/timolia/lactea/loader/startup/EnableContext.java deleted file mode 100644 index a9d690b..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/startup/EnableContext.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.timolia.lactea.loader.startup; - -/** - * @author David (_Esel) - */ -public interface EnableContext { -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/startup/LoadContext.java b/loader/src/main/java/de/timolia/lactea/loader/startup/LoadContext.java deleted file mode 100644 index 053ba66..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/startup/LoadContext.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.timolia.lactea.loader.startup; - -import com.google.inject.Module; - -/** - * @author David (_Esel) - */ -public interface LoadContext { - void installGlobalModule(Module module); - - void installModule(Module module); - - void installModule(Class moduleClass); -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/startup/StartUpController.java b/loader/src/main/java/de/timolia/lactea/loader/startup/StartUpController.java deleted file mode 100644 index da6d188..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/startup/StartUpController.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.timolia.lactea.loader.startup; - -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; -import de.timolia.lactea.loader.module.LacteaModule; -import de.timolia.lactea.loader.startup.internal.ModuleLoadContext; -import java.util.ArrayList; -import java.util.List; - -/** - * @author David (_Esel) - */ -public class StartUpController { - private final List baseModules = new ArrayList<>(); - - - public ModuleLoadContext loadModule(LacteaModule module) throws Exception { - ModuleLoadContext loadContext = loadContext(module); - module.onLoad(loadContext); - return loadContext; - } - - public ModuleLoadContext loadContext(LacteaModule module) { - return new ModuleLoadContext(this, module); - } - - public EnableContext enableContext(LacteaModule module) { - return new EnableContext() {}; - } - - private Injector createGlobal() { - return Guice.createInjector(baseModules); - } - - public void initializeInjectors(List contexts) { - Injector global = createGlobal(); - contexts.forEach(loadContext -> loadContext.fullModuleInitialization(global)); - } - - public void addGlobalModule(Module module) { - baseModules.add(module); - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/startup/internal/Integrity.java b/loader/src/main/java/de/timolia/lactea/loader/startup/internal/Integrity.java deleted file mode 100644 index 98863ab..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/startup/internal/Integrity.java +++ /dev/null @@ -1,39 +0,0 @@ -package de.timolia.lactea.loader.startup.internal; - -public final class Integrity { - private interface IntegrityCheck { - void ensure(); - } - - private static IntegrityCheck ensureClassAvailable(String className, String moduleLib) { - return () -> { - try { - Class.forName(className); - } catch (ClassNotFoundException e) { - throw new RuntimeException( - "Module " + moduleLib + " is not present on classpath" - + " make sure it is present in the libs directory", - e); - } - }; - } - - private static class IntegrityError extends Error { - public IntegrityError(String message, Throwable throwable) { - super(message, throwable); - } - } - - public static void checkIntegrity() { - IntegrityCheck[] checks = new IntegrityCheck[] { - ensureClassAvailable("com.google.inject.Module", "Guice") - }; - for (IntegrityCheck check : checks) { - try { - check.ensure(); - } catch (Throwable throwable) { - throw new IntegrityError("Fatal miss configuration", throwable); - } - } - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/startup/internal/ModuleBootOrder.java b/loader/src/main/java/de/timolia/lactea/loader/startup/internal/ModuleBootOrder.java deleted file mode 100644 index c27e837..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/startup/internal/ModuleBootOrder.java +++ /dev/null @@ -1,114 +0,0 @@ -package de.timolia.lactea.loader.startup.internal; - -import de.timolia.lactea.loader.module.LacteaModule; -import de.timolia.lactea.loader.module.ModuleDescription; -import de.timolia.lactea.loader.module.ModuleDescription.DependencyDescription; -import de.timolia.lactea.loader.module.ModuleRegistry; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Stack; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class ModuleBootOrder { - private final ModuleRegistry registry; - private final List orderedModules = new ArrayList<>(); - private final List orderedModuleView = Collections.unmodifiableList(orderedModules); - private final Stack trace = new Stack<>(); - private volatile boolean locked; - - public void addBatch(Collection definitions) { - if (locked) { - throw new IllegalStateException("Already locked"); - } - List copy = new ArrayList<>(definitions); - copy.sort(Comparator.comparing(ModuleDescription::getName, String::compareTo)); - copy.forEach(this::tryAdd); - } - - public List ordered() { - if (!locked) { - throw new IllegalStateException("Not yet locked"); - } - return orderedModuleView; - } - - public List orderedModules() { - return ordered().stream() - .map(description -> registry.requireByName(description.getName())) - .collect(Collectors.toList()); - } - - public void lock() { - locked = true; - } - - public void addBatchAndLock(Collection definitions) { - addBatch(definitions); - lock(); - } - - private void handleMissingDependency(DependencyDescription description) { - if (description.required()) { - throw new IllegalStateException("Missing dependency " + description.value()); - } - } - - private void tryAdd(ModuleDescription description) { - try { - add(description); - } catch (Exception exception) { - throw new RuntimeException("Unable to build dependency tree for" + description.getName()); - } - } - - private State currentState(ModuleDescription description) { - if (trace.contains(description)) { - return State.IN_LOAD_TRACE; - } - if (orderedModules.contains(description)) { - return State.SAFELY_LOADED; - } - return State.NOT_INVOLVED; - } - - private void handleCircularDependency() { - String formatted = trace.stream() - .map(ModuleDescription::getName) - .collect(Collectors.joining(" <-> ")); - throw new RuntimeException("Circular dependency between " + formatted); - } - - private void addNext(ModuleDescription description) { - orderedModules.add(description); - for (DependencyDescription dependencyDescription : description.getDependencies()) { - ModuleDescription dependency = registry.definition(dependencyDescription.value()); - if (dependency == null) { - handleMissingDependency(dependencyDescription); - continue; - } - tryAdd(dependency); - } - trace.pop(); - } - - private void add(ModuleDescription description) { - State state = currentState(description); - trace.push(description); - switch (state) { - case IN_LOAD_TRACE -> handleCircularDependency(); - case NOT_INVOLVED -> addNext(description); - case SAFELY_LOADED -> {} - } - } - - enum State { - IN_LOAD_TRACE, - SAFELY_LOADED, - NOT_INVOLVED - } -} diff --git a/loader/src/main/java/de/timolia/lactea/loader/startup/internal/ModuleLoadContext.java b/loader/src/main/java/de/timolia/lactea/loader/startup/internal/ModuleLoadContext.java deleted file mode 100644 index c370a6e..0000000 --- a/loader/src/main/java/de/timolia/lactea/loader/startup/internal/ModuleLoadContext.java +++ /dev/null @@ -1,51 +0,0 @@ -package de.timolia.lactea.loader.startup.internal; - -import com.google.inject.Injector; -import com.google.inject.Module; -import de.timolia.lactea.loader.config.LocalConfigModuleFactory; -import de.timolia.lactea.loader.module.InternalModuleAccess; -import de.timolia.lactea.loader.module.LacteaModule; -import de.timolia.lactea.loader.startup.LoadContext; -import de.timolia.lactea.loader.startup.StartUpController; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; - -/** - * @author David (_Esel) - */ -@RequiredArgsConstructor -public class ModuleLoadContext implements LoadContext { - private final StartUpController controller; - private final LacteaModule module; - private final List> localModules = new ArrayList<>(); - - public void fullModuleInitialization(Injector global) { - installModule(global.getInstance(LocalConfigModuleFactory.class).create(module)); - Injector injector = createInjector(global); - InternalModuleAccess.setInjector(module, injector); - } - - private Injector createInjector(Injector global) { - return global.createChildInjector(localModules.stream() - .map(function -> function.apply(global)) - .collect(Collectors.toList())); - } - - @Override - public void installGlobalModule(Module module) { - controller.addGlobalModule(module); - } - - @Override - public void installModule(Module module) { - localModules.add(injector -> module); - } - - @Override - public void installModule(Class moduleClass) { - localModules.add(injector -> injector.getInstance(moduleClass)); - } -} diff --git a/module-api/build.gradle.kts b/module-api/build.gradle.kts new file mode 100644 index 0000000..b3d6713 --- /dev/null +++ b/module-api/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + `java-library` + kotlin("jvm") version "1.9.10" +} +dependencies { + api("com.google.inject:guice:5.1.0") + implementation(kotlin("stdlib-jdk8")) +} +kotlin { + jvmToolchain(11) +} \ No newline at end of file diff --git a/module-api/src/main/kotlin/de/timolia/lactea/inject/InjectedInstance.kt b/module-api/src/main/kotlin/de/timolia/lactea/inject/InjectedInstance.kt new file mode 100644 index 0000000..bc34b39 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/inject/InjectedInstance.kt @@ -0,0 +1,24 @@ +package de.timolia.lactea.inject + +import com.google.inject.Injector + +fun interface InjectedInstance { + fun getInstance(injector: Injector): T + + companion object { + fun ofInstance(instance: T): InjectedInstance { + return InjectedInstance { instance } + } + + fun ofClass(clazz: Class): InjectedInstance { + return InjectedInstance { injector -> injector.getInstance(clazz) } + } + + fun ofFactory( + factoryClass: Class, + createFunction: (F) -> T + ): InjectedInstance { + return InjectedInstance { injector -> createFunction(injector.getInstance(factoryClass)) } + } + } +} \ No newline at end of file diff --git a/module-api/src/main/kotlin/de/timolia/lactea/inject/InstanceRegistry.kt b/module-api/src/main/kotlin/de/timolia/lactea/inject/InstanceRegistry.kt new file mode 100644 index 0000000..cfc09f1 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/inject/InstanceRegistry.kt @@ -0,0 +1,6 @@ +package de.timolia.lactea.inject + +interface InstanceRegistry { + fun register(instance: I) + fun unregister(instance: I) +} diff --git a/module-api/src/main/kotlin/de/timolia/lactea/inject/LacteaInjector.kt b/module-api/src/main/kotlin/de/timolia/lactea/inject/LacteaInjector.kt new file mode 100644 index 0000000..e1162b8 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/inject/LacteaInjector.kt @@ -0,0 +1,11 @@ +package de.timolia.lactea.inject + +import com.google.inject.Injector + +class LacteaInjector ( + private val injector: Injector +) { + fun getInstance(instance: InjectedInstance): T { + return instance.getInstance(injector) + } +} diff --git a/module-api/src/main/kotlin/de/timolia/lactea/lifecycle/EnableContext.kt b/module-api/src/main/kotlin/de/timolia/lactea/lifecycle/EnableContext.kt new file mode 100644 index 0000000..f362cf1 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/lifecycle/EnableContext.kt @@ -0,0 +1,3 @@ +package de.timolia.lactea.lifecycle + +interface EnableContext diff --git a/module-api/src/main/kotlin/de/timolia/lactea/lifecycle/LoadContext.kt b/module-api/src/main/kotlin/de/timolia/lactea/lifecycle/LoadContext.kt new file mode 100644 index 0000000..eba3920 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/lifecycle/LoadContext.kt @@ -0,0 +1,17 @@ +package de.timolia.lactea.lifecycle + +import com.google.inject.Module; +import de.timolia.lactea.inject.InjectedInstance + +interface LoadContext { + fun installGlobalModule(module: InjectedInstance) + fun installModule(module: InjectedInstance) + + var encapsulationMode: EncapsulationMode + + + enum class EncapsulationMode { + PRIVATE_MODULE, + INJECTOR + } +} diff --git a/module-api/src/main/kotlin/de/timolia/lactea/module/InternalModuleAccess.kt b/module-api/src/main/kotlin/de/timolia/lactea/module/InternalModuleAccess.kt new file mode 100644 index 0000000..0070cb8 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/module/InternalModuleAccess.kt @@ -0,0 +1,9 @@ +package de.timolia.lactea.module + +import de.timolia.lactea.inject.LacteaInjector + +object InternalModuleAccess { + fun setInjector(module: LacteaModule, injector: LacteaInjector) { + module.injector = injector + } +} \ No newline at end of file diff --git a/module-api/src/main/kotlin/de/timolia/lactea/module/LacteaModule.kt b/module-api/src/main/kotlin/de/timolia/lactea/module/LacteaModule.kt new file mode 100644 index 0000000..dd5444b --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/module/LacteaModule.kt @@ -0,0 +1,25 @@ +package de.timolia.lactea.module + +import de.timolia.lactea.inject.LacteaInjector +import de.timolia.lactea.lifecycle.EnableContext +import de.timolia.lactea.lifecycle.LoadContext + +class LacteaModule { + internal lateinit var injector: LacteaInjector + + @Throws(Exception::class) + fun onInjectorConfiguration(context: LoadContext) { + } + + @Throws(Exception::class) + fun onEnable(context: EnableContext) { + } + + @Throws(Exception::class) + fun onDisable() { + } + + @Throws(Exception::class) + fun onPostDisable() { + } +} diff --git a/module-api/src/main/kotlin/de/timolia/lactea/module/definition/Dependency.kt b/module-api/src/main/kotlin/de/timolia/lactea/module/definition/Dependency.kt new file mode 100644 index 0000000..c599311 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/module/definition/Dependency.kt @@ -0,0 +1,7 @@ +package de.timolia.lactea.module.definition + +@Target +annotation class Dependency( + val value: String, + val required: Boolean = true +) diff --git a/module-api/src/main/kotlin/de/timolia/lactea/module/definition/ModuleDefinition.kt b/module-api/src/main/kotlin/de/timolia/lactea/module/definition/ModuleDefinition.kt new file mode 100644 index 0000000..4a37665 --- /dev/null +++ b/module-api/src/main/kotlin/de/timolia/lactea/module/definition/ModuleDefinition.kt @@ -0,0 +1,7 @@ +package de.timolia.lactea.module.definition + +@Target(AnnotationTarget.CLASS) +annotation class ModuleDefinition( + val value: String, + val dependencies: Array = [] +) diff --git a/settings.gradle.kts b/settings.gradle.kts index 84a88dc..4cb556b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,15 @@ - rootProject.name = "lactea" -include("loader") -include("loader-nukkit") -include("loader-bukkit") +include( + "initialize", + "config", + "core", + "module-api" +) +includeLoaderProject("bukkit") +includeLoaderProject("nukkit") +includeLoaderProject("standalone") +fun includeLoaderProject(name: String) { + val fullName = "loader:loader-$name" + include(fullName) + findProject(":$fullName")!!.name = "loader-$name" +}