Skip to content

Commit

Permalink
Finished new EntrypointLoader and ModuleLoader
Browse files Browse the repository at this point in the history
  • Loading branch information
Im-Fran committed Aug 17, 2023
1 parent 83cff54 commit 1da5135
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package xyz.theprogramsrc.simplecoreapi.standalone

import java.io.File
import java.net.URL
import java.net.URLClassLoader
import java.util.Properties
import java.util.zip.ZipInputStream

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
Expand All @@ -12,50 +10,28 @@ annotation class EntryPoint
class EntrypointLoader {

init {
val classLoader = URLClassLoader(
System.getProperty("java.class.path").split(File.pathSeparator).map { File(it).toURI().toURL() }.toTypedArray(),
this::class.java.classLoader
)

val files = mutableListOf<String>().apply {
try {
val src = EntrypointLoader::class.java.protectionDomain.codeSource
val jar = src.location
val zipInputStream = ZipInputStream(jar.openStream())
while (true) {
val entry = zipInputStream.nextEntry ?: break
if (entry.name.endsWith(".class")) {
add(entry.name.replace("/", ".").replace(".class", ""))
}
}
} catch (_: Exception) {
}
}

files.forEach { className ->
try {
val clazz = Class.forName(className, false, classLoader)
val entryPointAnnotation = clazz.getAnnotation(EntryPoint::class.java)

if (entryPointAnnotation != null) {
when {
clazz.isAnnotationPresent(EntryPoint::class.java) -> {
// If the @EntryPoint annotation is present on a class, call its constructor.
clazz.getDeclaredConstructor().newInstance()
return@forEach
}

clazz.declaredMethods.any { it.isAnnotationPresent(EntryPoint::class.java) } -> {
// If the @EntryPoint annotation is present on a function, find and call that function.
val entryPointMethods = clazz.declaredMethods.filter { it.isAnnotationPresent(EntryPoint::class.java) }
for (method in entryPointMethods) {
method.invoke(clazz.getDeclaredConstructor().newInstance())
return@forEach
}
}
}
// First get the resource 'module.properties' located at the root of the jar file
val moduleProperties = EntrypointLoader::class.java.getResourceAsStream("/module.properties")
// Now read the 'entrypoint' property
val entrypoint = (Properties().let {
it.load(moduleProperties)
it.getProperty("entrypoint")
} ?: "").replace("\"", "")

assert(entrypoint.isNotBlank()) { "Entrypoint cannot be blank!" }

// Now load the class
val clazz = this::class.java.classLoader.loadClass(entrypoint)

// Now check if the class itself is an entrypoint, if it is, initialize it, if not check for the first method that is an entrypoint
if(clazz.isAnnotationPresent(EntryPoint::class.java)){
clazz.getConstructor().newInstance()
} else {
clazz.methods.forEach { method ->
if(method.isAnnotationPresent(EntryPoint::class.java)){
method.invoke(null)
return@forEach
}
} catch (_: Exception) {
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package xyz.theprogramsrc.simplecoreapi.standalone
import xyz.theprogramsrc.simplecoreapi.global.SimpleCoreAPI
import java.io.File
import java.net.URLClassLoader
import java.util.Properties
import java.util.Stack
import java.util.jar.JarFile
import java.util.jar.JarInputStream

data class ModuleDescription(
val name: String,
Expand Down Expand Up @@ -36,10 +38,9 @@ class ModuleLoader {
val moduleProperties = jarFile.getJarEntry("module.properties")?.let { entry ->
jarFile.getInputStream(entry).use { stream ->
stream.bufferedReader().use { reader ->
reader.readLines().filter { line -> !line.startsWith("#") && line.isNotBlank() }.associate { line ->
val split = line.split("=")
split[0] to split[1]
}
val props = Properties()
props.load(reader)
props
}
}
} ?: return@forEach
Expand All @@ -49,20 +50,20 @@ class ModuleLoader {

// Add the file to the files map
files[it] = ModuleDescription(
name = (moduleProperties["name"]!!).let { name ->
name = moduleProperties.getProperty("name").let { name ->
if(name.startsWith('"') && name.endsWith('"')) name.substring(1, name.length - 1) else name
},
version = (moduleProperties["version"]!!).let { version ->
version = moduleProperties.getProperty("version").let { version ->
if(version.startsWith('"') && version.endsWith('"')) version.substring(1, version.length - 1) else version
},
author = (moduleProperties["author"]!!).let { author ->
author = moduleProperties.getProperty("author").let { author ->
if(author.startsWith('"') && author.endsWith('"')) author.substring(1, author.length - 1) else author
},
main = (moduleProperties["main"]!!).let { main ->
main = moduleProperties.getProperty("main").let { main ->
if(main.startsWith('"') && main.endsWith('"')) main.substring(1, main.length - 1) else main
},
dependencies = moduleProperties["dependencies"]?.let { dependencies ->
if (dependencies.startsWith('"') && dependencies.endsWith('"')) dependencies.substring(1, dependencies.length - 1) else dependencies
dependencies = moduleProperties.getProperty("dependencies")?.let { dependencies ->
if(dependencies.startsWith('"') && dependencies.endsWith('"')) dependencies.substring(1, dependencies.length - 1) else dependencies
}?.split(",") ?: emptyList()
)
}
Expand All @@ -89,25 +90,25 @@ class ModuleLoader {
files.keys.forEach { topologicalSort(it) }

val loadedModules = mutableListOf<Module>()
val loader = URLClassLoader(stack.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader())
val loader = URLClassLoader(stack.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)

// Finally we need to load the modules by loading the jar files into the classpath and then one by one calling their main class #onEnable method
stack.forEach { file ->
val description = files[file] ?: return@forEach
for (file in stack) {
val description = files[file] ?: continue
// Load the main class
val jarFile = JarFile(file)
jarFile.entries().asSequence()
.filter { it.name.endsWith(".class") && !it.name.startsWith("META-INF/") }
.map { it.name.removeSuffix(".class").replace('/', '.') }
.forEach {
val clazz = loader.loadClass(it)
if(clazz.interfaces.contains(Module::class.java) && it == description.main) {
JarInputStream(file.inputStream()).use {
try {
val mainClass = loader.loadClass(description.main)
if(mainClass.interfaces.contains(Module::class.java)) {
// Load module
val instance = clazz.getDeclaredConstructor().newInstance() as Module
val instance = mainClass.getDeclaredConstructor().newInstance() as Module
instance.onEnable()
loadedModules.add(instance)
}
}catch (e: Exception) {
e.printStackTrace()
}
}
}

// Finally we need to add a shutdown hook to disable all modules
Expand Down

0 comments on commit 1da5135

Please sign in to comment.