diff --git a/core-ducktape/build.gradle b/core-ducktape/build.gradle index ea1d9e1..86df5ca 100644 --- a/core-ducktape/build.gradle +++ b/core-ducktape/build.gradle @@ -4,29 +4,15 @@ plugins { id 'java' - id 'com.github.johnrengelman.shadow' version '1.2.3' } apply from: utilities.file('gradle/utilities.gradle') compileJava.options.encoding = 'UTF-8' -assemble.dependsOn shadowJar { - baseName = 'ducktape-libs' - version = utilities.version - - dependencies { - include dependency('org.codehaus.groovy:groovy-all:.*') // core:ducktape - } -} - dependencies { compile utilities.project('core') compile 'com.google.inject:guice:4.0' - compile 'org.codehaus.groovy:groovy-all:2.4.6' - compile 'org.scala-lang:scala-library:2.11.7' - compile 'org.scala-lang:scala-reflect:2.11.7' - compile "org.jetbrains.kotlin:kotlin-stdlib:1.0.0" - compile "org.jetbrains.kotlin:kotlin-reflect:1.0.0" - compile "org.jetbrains.kotlin:kotlin-runtime:1.0.0" + compile 'org.apache.logging.log4j:log4j-api:2.8.1' + testCompile 'org.apache.logging.log4j:log4j-core:2.8.1' } diff --git a/core-ducktape/loaders-groovy/build.gradle b/core-ducktape/loaders-groovy/build.gradle new file mode 100644 index 0000000..6c4c7a2 --- /dev/null +++ b/core-ducktape/loaders-groovy/build.gradle @@ -0,0 +1,16 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +plugins { + id 'java' +} + +apply from: utilities.file('gradle/utilities.gradle') + +compileJava.options.encoding = 'UTF-8' + +dependencies { + compile utilities.project('core') + compile utilities.project('core-ducktape') + compile 'org.codehaus.groovy:groovy-all:2.4.6' +} diff --git a/core-ducktape/loaders-groovy/src/main/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoader.java b/core-ducktape/loaders-groovy/src/main/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoader.java new file mode 100644 index 0000000..3c6af04 --- /dev/null +++ b/core-ducktape/loaders-groovy/src/main/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoader.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import groovy.lang.GroovyClassLoader; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.Collections; + +/** + * This class will support for loading Groovy files + * into the class loader for the current running JVM. + */ +public class GroovyModuleLoader extends AbstractPathLoader implements ModuleLoader { + public GroovyModuleLoader(Path directory) { + super(AbstractPathLoader.createDirectories(directory)); + } + + /** Load any groovy script that ends with .groovy */ + protected Collection> load(Path directory) throws IOException { + if (directory.toString().endsWith(".groovy")) { + URL[] urls = new URL[] { directory.toUri().toURL() }; + GroovyClassLoader loader = AccessController.doPrivileged((PrivilegedAction) () -> { + ClassLoader parentClassLoader = ModuleLoader.class.getClassLoader(); + GroovyClassLoader classLoader = new GroovyClassLoader(new URLClassLoader(urls, parentClassLoader)); + // add the urls to the classloader to use environment libs + classLoader.addURL(parentClassLoader.getResource("/")); + classLoader.addURL(urls[0]); + return classLoader; + }); + return Collections.singleton(loader.parseClass(directory.toFile())); + } + return Collections.emptySet(); + } +} diff --git a/core-ducktape/loaders-groovy/src/test/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoaderTest.java b/core-ducktape/loaders-groovy/src/test/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoaderTest.java new file mode 100644 index 0000000..2984568 --- /dev/null +++ b/core-ducktape/loaders-groovy/src/test/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoaderTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class GroovyModuleLoaderTest { + private static final Path modulePath = Paths.get("src/test/resources/modules"); + private int groovyFiles; + + @Before + public void getGroovyFiles() throws IOException { + this.groovyFiles = 0; + try (DirectoryStream pathStream = Files.newDirectoryStream(modulePath)) { + for (Path path : pathStream) { + if (path.toString().endsWith(".groovy")) { + this.groovyFiles++; + } + } + } + } + + @Test + public void groovyLoaderTest() { + GroovyModuleLoader loader = new GroovyModuleLoader(modulePath); + Assert.assertEquals(groovyFiles, loader.load().size()); + } +} diff --git a/core-ducktape/loaders-groovy/src/test/resources/modules/AnotherGroovyModule.groovy b/core-ducktape/loaders-groovy/src/test/resources/modules/AnotherGroovyModule.groovy new file mode 100644 index 0000000..831264a --- /dev/null +++ b/core-ducktape/loaders-groovy/src/test/resources/modules/AnotherGroovyModule.groovy @@ -0,0 +1,14 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +import net.year4000.utilities.ducktape.module.Enable +import net.year4000.utilities.ducktape.module.Module + +@Module(id = "another-module") +class AnotherGroovyModule { + @Enable + def enable() { + println 'Groovy Module load()' + GroovyUtility.helloWorld() + } +} diff --git a/core-ducktape/loaders-groovy/src/test/resources/modules/GroovyModule.groovy b/core-ducktape/loaders-groovy/src/test/resources/modules/GroovyModule.groovy new file mode 100644 index 0000000..3cf867c --- /dev/null +++ b/core-ducktape/loaders-groovy/src/test/resources/modules/GroovyModule.groovy @@ -0,0 +1,15 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +import net.year4000.utilities.ducktape.module.Load +import net.year4000.utilities.ducktape.module.Module + +@Module(id = "groovy") +class GroovyModule { + + @Load + def load() { + println 'Groovy Module load()' + GroovyUtility.helloWorld() + } +} diff --git a/core-ducktape/loaders-groovy/src/test/resources/modules/GroovyUtility.groovy b/core-ducktape/loaders-groovy/src/test/resources/modules/GroovyUtility.groovy new file mode 100644 index 0000000..1360c4c --- /dev/null +++ b/core-ducktape/loaders-groovy/src/test/resources/modules/GroovyUtility.groovy @@ -0,0 +1,14 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +import net.year4000.utilities.utils.UtilityConstructError + +final class GroovyUtility { + private GroovyUtility() { + UtilityConstructError.raise() + } + + static def helloWorld() { + println 'Hello World!' + } +} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/Ducktape.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/Ducktape.java index 23ecb59..4b0965b 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/Ducktape.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/Ducktape.java @@ -16,12 +16,27 @@ static DucktapeManager.DucktapeManagerBuilder builder() { return DucktapeManager.builder(); } - /** This will init the Ducktape systems */ - void init() throws ModuleInitException; + /** Load the modules */ + void load() throws ModuleInitException; + + /** Enable the modules */ + void enable() throws ModuleInitException; + + /** This will init the Ducktape systems by calling the load and enable */ + default void init() throws ModuleInitException { + load(); + enable(); + } + + /** Get the module from the class, avoid this call and prefer the id method over this one */ + default Value getModule(Module module) { + Conditions.nonNull(module, "annotation can not be null"); + return getModule(module.id()); + } /** Get the module from the class, avoid this call and prefer the id method over this one */ default Value getModule(Class clazz) { - if (clazz.isAnnotationPresent(Module.class)) { + if (Conditions.nonNull(clazz, "class can not be null").isAnnotationPresent(Module.class)) { return getModule(clazz.getAnnotation(Module.class).id().toLowerCase()); } return Value.empty(); diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeManager.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeManager.java index fb4c762..9bcd7f5 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeManager.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeManager.java @@ -1,95 +1,90 @@ /* - * Copyright 2019 Year4000. All Rights Reserved. + * Copyright 2020 Year4000. All Rights Reserved. */ + package net.year4000.utilities.ducktape; -import com.google.common.collect.ClassToInstanceMap; -import com.google.common.collect.ImmutableClassToInstanceMap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.*; import com.google.inject.*; -import com.google.inject.name.Named; -import com.google.inject.name.Names; +import com.google.inject.Module; import net.year4000.utilities.Builder; import net.year4000.utilities.Conditions; +import net.year4000.utilities.annotations.Nullable; import net.year4000.utilities.ducktape.loaders.ModuleLoader; import net.year4000.utilities.ducktape.module.Enabler; -import net.year4000.utilities.ducktape.module.Module; -import net.year4000.utilities.ducktape.module.ModuleInfo; +import net.year4000.utilities.ducktape.module.internal.ModuleInfo; import net.year4000.utilities.ducktape.module.ModuleWrapper; +import net.year4000.utilities.ducktape.settings.SaveLoad; import net.year4000.utilities.value.Value; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.*; public class DucktapeManager extends AbstractModule implements Ducktape { + private final Logger logger = LogManager.getLogger("utilities/ducktape"); /** The set of current loaded modules */ - private Set> loaded = new LinkedHashSet<>(); - + protected Set> loaded = new LinkedHashSet<>(); /** A map of all loaders stored by their class */ - private ClassToInstanceMap loaders; - + protected ClassToInstanceMap loaders; /** The guice injector that will init the modules */ - private Injector injector; - - private Path modsPath = Paths.get("mods/"); - + protected Injector injector; + /** The save load provider for configs */ + protected SaveLoad saveLoadProvider; /** * The modules that are loaded, at first the order is when they are constructed but while they are loading * it resorts them based on load order, so when loading is done it will enable them properly. */ - private final Map modules = new LinkedHashMap<>(); - + protected final Map modules = new LinkedHashMap<>(); - private DucktapeManager(Injector injector, Map, ModuleLoader> loaderMap) { + protected DucktapeManager(Injector injector, Map, ModuleLoader> loaderMap, @Nullable SaveLoad saveLoadProvider) { this.injector = Conditions.nonNull(injector, "extra injector must not be null"); this.loaders = ImmutableClassToInstanceMap.builder() .putAll(loaderMap) .build(); + this.saveLoadProvider = saveLoadProvider; } /** Create a snapshot of the modules that are currently in the system when this method is called */ @Override - public ImmutableList getModules() { - ImmutableList.Builder moduleBuilder = ImmutableList.builder(); + public ImmutableList getModules() { + ImmutableList.Builder moduleBuilder = ImmutableList.builder(); this.modules.forEach(((moduleInfo, moduleWrapper) -> moduleBuilder.add(moduleInfo))); return moduleBuilder.build(); } @Override - public void init() throws ModuleInitException { - System.out.println("Loading module classes from the loaders"); - Set> classes = loadAll(this.modsPath); - System.out.println("Setting up the injector"); - // todo think? use child injector or our own injector, is we use a child injector sponge plugins will work, if not modules only have our bindings - this.injector = this.injector.createChildInjector(this, new ModulesInitModule(classes), new DucktapeModule(this.modules), new SettingsModule()); - //this.injector = Guice.createInjector(this, new ModulesInitModule(classes), new DucktapeModule(this.modules), new SettingsModule()); - System.out.println("Enabling modules: " + this.modules); + public void load() throws ModuleInitException { + logger.info("Loading module classes from the loaders"); + Set> classes = loadAll(); + logger.info("Setting up the injector"); + List modules = Lists.newArrayList(this, new ModulesInitModule(classes), new DucktapeModule(this.modules)); + if (this.saveLoadProvider != null) { + modules.add(new SettingsModule(this.saveLoadProvider)); + } + this.injector = this.injector.createChildInjector(modules); + } + + @Override + public void enable() throws ModuleInitException { + logger.info("Enabling modules: " + this.modules); this.modules.values().forEach(Enabler::enable); } @Override protected void configure() { bind(Ducktape.class).toInstance(this); - bind(Path.class).annotatedWith(Names.named("mods")).toInstance(this.modsPath); } /** Load all classes from the selected path */ - protected Set> loadAll(Path path) throws ModuleInitException { - System.out.println("path: " + path); + protected Set> loadAll() throws ModuleInitException { Set> classes = new HashSet<>(); this.loaders.forEach((key, value) -> { - try { - if (loaded.contains(key)) { - throw new ModuleInitException(ModuleInfo.Phase.LOADING, new IllegalStateException("Can not load the same loader twice.")); - } - classes.addAll(value.load(path)); - loaded.add(key); - } catch (IOException error) { - throw new ModuleInitException(ModuleInfo.Phase.LOADING, error); + if (loaded.contains(key)) { + throw new ModuleInitException(ModuleInfo.Phase.LOADING, new IllegalStateException("Can not load the same loader twice.")); } + classes.addAll(value.load()); + loaded.add(key); }); return classes; } @@ -99,9 +94,10 @@ public static DucktapeManagerBuilder builder() { } /** This will build the ducktape manager environment with the correct module loaders and guice injector */ - public static class DucktapeManagerBuilder implements Builder { - private final Set loaders = new HashSet<>(); - private Value injectorValue = Value.empty(); + public static class DucktapeManagerBuilder implements Builder { + protected final Set loaders = new HashSet<>(); + protected Value injectorValue = Value.empty(); + protected SaveLoad saveLoadProvider; /** Add a module loader system */ public DucktapeManagerBuilder addLoader(ModuleLoader moduleLoader) { @@ -109,18 +105,28 @@ public DucktapeManagerBuilder addLoader(ModuleLoader moduleLoader) { return this; } + /** Set the save load provider for configs support, if this is not set but modules use Settings<> it will throw errors */ + public DucktapeManagerBuilder setSaveLoadProvider(@Nullable SaveLoad saveLoadProvider) { + this.saveLoadProvider = saveLoadProvider; + return this; + } + /** Set the injector for ducktape to use */ public DucktapeManagerBuilder setInjector(Injector injector) { this.injectorValue = Value.of(injector); return this; } - @Override - public DucktapeManager build() { - Map, ModuleLoader> loaderMap = loaders.stream().map(loader -> ImmutableMap., ModuleLoader>of(loader.getClass(), loader)) + /** Internal reuse able method that will map reduce the module loader map */ + protected Map, ModuleLoader> loaderMapReduce() { + return this.loaders.stream().map(loader -> ImmutableMap., ModuleLoader>of(loader.getClass(), loader)) .reduce((left, right) -> ImmutableMap., ModuleLoader>builder().putAll(left).putAll(right).build()).get(); + } - return new DucktapeManager(injectorValue.getOrElse(Guice.createInjector()), loaderMap); + @Override + public Ducktape build() { + Map, ModuleLoader> loaderMap = loaderMapReduce(); + return new DucktapeManager(this.injectorValue.getOrElse(Guice.createInjector()), loaderMap, this.saveLoadProvider); } } } diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeModule.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeModule.java index d5bec26..b9830c3 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeModule.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/DucktapeModule.java @@ -8,7 +8,7 @@ import com.google.inject.matcher.Matchers; import com.google.inject.spi.*; import net.year4000.utilities.ducktape.module.Module; -import net.year4000.utilities.ducktape.module.ModuleInfo; +import net.year4000.utilities.ducktape.module.internal.ModuleInfo; import net.year4000.utilities.ducktape.module.ModuleWrapper; import java.util.Map; diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/ModuleManager.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/ModuleManager.java deleted file mode 100644 index 93594ff..0000000 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/ModuleManager.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2016 Year4000. All Rights Reserved. - */ - -package net.year4000.utilities.ducktape; - -import com.google.common.collect.ClassToInstanceMap; -import com.google.common.collect.ImmutableClassToInstanceMap; -import com.google.common.collect.Sets; -import net.year4000.utilities.ducktape.loaders.GroovyModuleLoader; -import net.year4000.utilities.ducktape.loaders.ModuleLoader; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Set; -import java.util.function.Consumer; - -/** Deprecated see DucktapeManager for future module stuff */ -@Deprecated -public class ModuleManager { - /** The set of current loaded modules */ - private Set> loaded = Sets.newLinkedHashSet(); - - /** A map of all loaders stored by their class, should not be final as support for adding custom loaders */ - private ClassToInstanceMap loaders = ImmutableClassToInstanceMap.builder() - .put(GroovyModuleLoader.class, new GroovyModuleLoader()) - .build(); - - /** Load all classes from the selected path */ - public void loadAll(Path path, Consumer>> consumer) { - loaders.forEach((key, value) -> { - try { - if (loaded.contains(key)) { - throw new IllegalStateException("Can not load the same loader twice."); - } - consumer.accept(value.load(path)); - loaded.add(key); - } catch (IOException | IllegalStateException error) { - // todo - System.err.println(error.getMessage()); - } - }); - } - - /** Format the path */ - public static String formatPath(String path) { - if (path.length() < 6) return path; - return path.substring(0, path.length() - 6).replaceAll("/", "."); - } -} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/SettingsModule.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/SettingsModule.java index cf1d50f..4d78fb3 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/SettingsModule.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/SettingsModule.java @@ -3,30 +3,32 @@ */ package net.year4000.utilities.ducktape; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.inject.AbstractModule; -import com.google.inject.MembersInjector; import com.google.inject.TypeLiteral; import com.google.inject.matcher.Matchers; import com.google.inject.spi.InjectionListener; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; -import net.year4000.utilities.ducktape.settings.GsonSaveLoadProvider; +import net.year4000.utilities.Conditions; +import net.year4000.utilities.Utils; import net.year4000.utilities.ducktape.settings.SaveLoad; import net.year4000.utilities.ducktape.settings.Settings; +import net.year4000.utilities.ducktape.settings.SettingsBase; import net.year4000.utilities.reflection.Reflections; public class SettingsModule extends AbstractModule { + private final SaveLoad saveLoad; + + public SettingsModule(SaveLoad saveLoad) { + this.saveLoad = Conditions.nonNull(saveLoad, "save load must exist"); + } + @Override protected void configure() { - bind(SaveLoad.class).to(GsonSaveLoadProvider.class); - + bind(SaveLoad.class).toInstance(this.saveLoad); bindListener(Matchers.any(), new TypeListener() { @Override public void hear(TypeLiteral type, TypeEncounter encounter) { - //System.out.println("hear: " + type); - // inject the settings if (type.getRawType() == Settings.class) { //System.out.println("load settings: " + type); @@ -34,20 +36,16 @@ public void hear(TypeLiteral type, TypeEncounter encounter) { String genericType = typeString.substring(typeString.indexOf("<") + 1, typeString.indexOf(">")); Class settingsClass = Reflections.clazz(genericType).getOrThrow(); System.out.println("foundSettingsClass: " + settingsClass); + String fileName = settingsClass.getAnnotation(SettingsBase.class).value(); + System.out.println("foundSettingsClass: " + fileName); - // when members are injected - encounter.register((MembersInjector) instance -> { - Gson gson = new GsonBuilder().create(); - - // create instance right now, later have something else handle it - //((Settings) instance).instance = Reflections.instance(settingsClass).getOrThrow(); - System.out.println("MembersInjector settings: " + instance); - - }); // after members has been injected encounter.register((InjectionListener) instance -> { + + Settings settings = (Settings) instance; // todo load settings here - //System.out.println("InjectionListener settings: " + instance); + System.out.println("InjectionListener settings: " + Utils.toString(settings)); + Reflections.setter(settings, Reflections.field(Settings.class, "instance").get(), Reflections.instance(settingsClass).get()); }); } } diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/AbstractPathLoader.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/AbstractPathLoader.java new file mode 100644 index 0000000..fd47bf9 --- /dev/null +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/AbstractPathLoader.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import com.google.common.collect.Sets; +import net.year4000.utilities.Conditions; +import net.year4000.utilities.ErrorReporter; +import net.year4000.utilities.ducktape.ModuleInitException; +import net.year4000.utilities.ducktape.module.ModuleInfo; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Set; + +/** An abstract class that will handle the checking of directory */ +public abstract class AbstractPathLoader { + private final Path directory; + + public AbstractPathLoader(Path directory) { + this.directory = Conditions.nonNull(directory, "directory must exist"); + Conditions.condition(Files.isDirectory(directory), "directory must be a valid directory"); + } + + /** Get the directory of the path */ + public Path directory() { + return this.directory; + } + + /** Create the parent directories and return the newly created directory */ + public static Path createDirectories(Path directory) { + Conditions.nonNull(directory, "directory must exist"); + try { + if (!Files.isDirectory(directory)) { + Files.createDirectories(directory); + } + } catch (IOException error) { + throw ErrorReporter.builder(error) + .add("Directory: ", directory.toAbsolutePath()) + .buildAndReport(); + } + return directory; + } + + /** Load classes from where ever and create the collection of classes for the modules */ + public Collection> load() throws ModuleInitException { + Set> classes = Sets.newLinkedHashSet(); + try { + classes.addAll(load(this.directory())); + try (DirectoryStream stream = Files.newDirectoryStream(this.directory())) { + for (Path path : stream) { + classes.addAll(load(path)); + } + } + } catch (IOException error) { + throw new ModuleInitException(ModuleInfo.Phase.LOADING, error); + } + return classes; + } + + /** The implementation to gather the class for each directory */ + protected abstract Collection> load(Path directory) throws IOException; +} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ClassModuleLoader.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ClassModuleLoader.java index d29671a..6adad10 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ClassModuleLoader.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ClassModuleLoader.java @@ -5,7 +5,6 @@ import com.google.common.collect.Sets; -import java.nio.file.Path; import java.util.Collection; /** Create the module loader with a fixed set of classes */ @@ -17,7 +16,7 @@ public ClassModuleLoader(Class... classes) { } @Override - public Collection> load(Path path) { + public Collection> load() { return Sets.newHashSet(this.classes); } } diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ClassPathModuleLoader.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ClassPathModuleLoader.java new file mode 100644 index 0000000..23b20aa --- /dev/null +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ClassPathModuleLoader.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import com.google.common.reflect.ClassPath; +import net.year4000.utilities.Conditions; +import net.year4000.utilities.ducktape.ModuleInitException; +import net.year4000.utilities.ducktape.module.ModuleInfo; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** This module loader will search the classpath for classes that are annotated with an annotation. */ +public class ClassPathModuleLoader implements ModuleLoader { + private final ClassLoader classLoader; + private final Class[] annotations; + private final String packageName; + + /** Load classes from the classpath from the packageName that are annotated with any of the annotations */ + public ClassPathModuleLoader(ClassLoader classLoader, String packageName, Class... annotations) { + this.classLoader = Conditions.nonNull(classLoader, "class load must exist"); + this.packageName = packageName; + this.annotations = Conditions.nonNull(annotations, "must give a annotation to find classes"); + } + + /** Loads all classes and get the classes that are annotated with any of the annotations */ + public ClassPathModuleLoader(ClassLoader classLoader, Class... annotations) { + this(classLoader, null, annotations); + } + + /** Loads all classes and get the classes that are annotated with any of the annotations */ + public ClassPathModuleLoader(String packageName, Class... annotations) { + this(ClassLoader.getSystemClassLoader(), packageName, annotations); + } + + /** Loads all classes and get the classes that are annotated with any of the annotations */ + public ClassPathModuleLoader(Class... annotations) { + this(ClassLoader.getSystemClassLoader(), null, annotations); + } + + @Override + public Collection> load() throws ModuleInitException { + try { + ClassPath classPath = ClassPath.from(this.classLoader); + Stream classInfoStream = this.packageName == null + ? classPath.getTopLevelClasses().stream() + : classPath.getTopLevelClassesRecursive(this.packageName).stream(); + return classInfoStream + .map(classInfo -> { + try { + return classInfo.load(); + } catch (Throwable error) { + return null; + } + }) + .filter(Objects::nonNull) + .filter(clazz -> { + for (Class annotation : this.annotations) { + if (clazz.isAnnotationPresent(annotation)) { + return true; + } + } + return false; + }) + .collect(Collectors.toSet()); + } catch (IOException error) { + throw new ModuleInitException(ModuleInfo.Phase.LOADING, error); + } + } +} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoader.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoader.java deleted file mode 100644 index e1a6708..0000000 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/GroovyModuleLoader.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2016 Year4000. All Rights Reserved. - */ - -package net.year4000.utilities.ducktape.loaders; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.collect.Sets; -import groovy.lang.GroovyClassLoader; -import net.year4000.utilities.ducktape.ModuleManager; - -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Collection; -import java.util.Set; - -/** - * This class will support for loading Groovy files - * into the class loader for the current running JVM. - */ -public class GroovyModuleLoader implements ModuleLoader { - - /** Load any groovy script that ends with .groovy */ - @Override - public Collection> load(Path dir) throws IOException { - Set> classes = Sets.newLinkedHashSet(); - Class clazzLoader = ModuleManager.class; - try (DirectoryStream stream = Files.newDirectoryStream(checkNotNull(dir))) { - for (Path path : stream) { - if (!path.toString().endsWith(".groovy")) { - continue; - } - URL url = path.toUri().toURL(); - URL[] urls = new URL[] {url}; - ClassLoader parent = AccessController.doPrivileged((PrivilegedAction) () -> - new URLClassLoader(urls, clazzLoader.getClassLoader()) - ); - // Convert the Groovy script to a Java Class - GroovyClassLoader loader = new GroovyClassLoader(parent); - loader.addURL(clazzLoader.getResource("/")); - loader.addURL(url); - Class clazz = loader.parseClass(path.toFile()); - classes.add(clazz); - } - } - - return classes; - } -} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/JarModuleLoader.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/JarModuleLoader.java new file mode 100644 index 0000000..1d7b464 --- /dev/null +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/JarModuleLoader.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import net.year4000.utilities.ErrorReporter; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +public class JarModuleLoader extends AbstractPathLoader implements ModuleLoader { + public JarModuleLoader(Path directory) { + super(AbstractPathLoader.createDirectories(directory)); + } + + /** Load any groovy script that ends with .groovy */ + @Override + protected Collection> load(Path directory) throws IOException, RuntimeException { + if (directory.toString().endsWith(".jar")) { + ClassLoader loader = Loaders.classLoaderFromPath(directory); + // And then the files in the jar + return new JarFile(directory.toFile()).stream() + .filter(jarEntry -> jarEntry.getName().endsWith(".class")) + .map(entry -> { + try { + return Class.forName(Loaders.formatPath(entry.getName()), true, loader); + } catch (ClassNotFoundException error) { + throw ErrorReporter.builder(error).buildAndReport(); + } + }) + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } +} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/Loaders.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/Loaders.java new file mode 100644 index 0000000..bb657a3 --- /dev/null +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/Loaders.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import net.year4000.utilities.ErrorReporter; +import net.year4000.utilities.utils.UtilityConstructError; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedAction; + +final class Loaders { + private Loaders() { + UtilityConstructError.raise(); + } + + /** Format the path */ + static String formatPath(String path) { + return path.length() < 6 ? path : path.substring(0, path.length() - 6).replaceAll("/", "."); + } + + /** Create the class loader from the path */ + static ClassLoader classLoaderFromPath(Path directory) throws IOException { + URL[] urls = new URL[] {directory.toUri().toURL()}; + return AccessController.doPrivileged((PrivilegedAction) () -> { + try (URLClassLoader classLoader = new URLClassLoader(urls)) { + return classLoader; + } catch (IOException error) { + throw ErrorReporter.builder(error).buildAndReport(); + } + }); + } +} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ModuleLoader.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ModuleLoader.java index f75b1ea..e3e8339 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ModuleLoader.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/ModuleLoader.java @@ -1,31 +1,19 @@ /* - * Copyright 2016 Year4000. All Rights Reserved. + * Copyright 2019 Year4000. All Rights Reserved. */ - package net.year4000.utilities.ducktape.loaders; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; +import net.year4000.utilities.ducktape.ModuleInitException; + import java.util.Collection; /** * This is a functional interface that will load * a module from the select file path. */ +@FunctionalInterface public interface ModuleLoader { - /** This will search the path and load the module */ - Collection> load(Path path) throws IOException; - - /** This will create a file object from a string and load the class paths */ - default Collection> load(String path) throws IOException { - return load(Paths.get(path)); - } - - /** This will create a file object from a string and load the class paths */ - default Collection> load(File file) throws IOException { - return load(file.toPath()); - } + /** Load classes from where ever and create the collection of classes for the modules */ + Collection> load() throws ModuleInitException; } diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/PackageModuleLoader.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/PackageModuleLoader.java new file mode 100644 index 0000000..6776c22 --- /dev/null +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/loaders/PackageModuleLoader.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PackageModuleLoader extends AbstractPathLoader implements ModuleLoader { + public PackageModuleLoader(Path directory) { + super(AbstractPathLoader.createDirectories(directory)); + } + + /** Get all the class names */ + public Set getClassNames(Path classDir) { + return recursiveGetClasses(classDir.toFile(), ""); + } + + /** Get all the class names */ + @SuppressWarnings("ConstantConditions") + public Set recursiveGetClasses(File dir, String parentName) { + File[] files = dir.listFiles(); + if (files != null) { + return Stream.of(dir.listFiles()) + .flatMap(file -> { + if (file.isDirectory()) { + return recursiveGetClasses(file, parentName + file.getName() + ".").stream(); + } else if (file.getName().endsWith(".class")) { + return Stream.of(parentName + Loaders.formatPath(file.getName())); + } + return Stream.empty(); + }) + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + + @Override + protected Collection> load(Path directory) throws IOException { + ClassLoader loader = Loaders.classLoaderFromPath(directory); + return getClassNames(directory).stream() + .map(entry -> { + try { + return loader.loadClass(entry); + } catch (ClassNotFoundException error) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } +} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/internal/ModuleHandle.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleHandle.java similarity index 85% rename from core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/internal/ModuleHandle.java rename to core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleHandle.java index 358ae22..1a21655 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/internal/ModuleHandle.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleHandle.java @@ -1,7 +1,7 @@ /* * Copyright 2019 Year4000. All Rights Reserved. */ -package net.year4000.utilities.ducktape.module.internal; +package net.year4000.utilities.ducktape.module; /** This is the base interface that the module will inherit */ public interface ModuleHandle { diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleInfo.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleInfo.java index 8bf231f..8b5bb26 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleInfo.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleInfo.java @@ -3,70 +3,21 @@ */ package net.year4000.utilities.ducktape.module; -import net.year4000.utilities.Conditions; +import net.year4000.utilities.value.Value; -public final class ModuleInfo implements Loader, Enabler { - private Phase phase = Phase.CONSTRUCTING; - private Class moduleClass; - private Module annotation; +public interface ModuleInfo extends Loader, Enabler { - public ModuleInfo(Class moduleClass) { - this.moduleClass = Conditions.nonNull(moduleClass, "module class must not be empty"); - Conditions.condition(moduleClass.isAnnotationPresent(Module.class), "the module class must have the @Module annotation"); - this.annotation = moduleClass.getAnnotation(Module.class); - } - - /** Get the current phase the module is on */ - public Phase getPhase() { - return this.phase; - } - - /** Get the module class */ - public Class getModuleClass() { - return this.moduleClass; - } + /** Get the module handle of the module */ + Value getHandle(); - /** Get the id of the module */ - public String getId() { - return this.annotation.id().toLowerCase(); - } + /** Get the id of this module */ + String getId(); - /** Called when the module class is being constructed */ - public void constructing() { - System.out.println("Constructing: " + this); - } - - /** Called when the module is being loaded*/ - @Override - public void load() { - System.out.println("Loading: " + this); - this.phase = Phase.LOADED; - } - - /** Called when the module is being enabled */ - @Override - public void enable() { - System.out.println("Enabling: " + this); - this.phase = Phase.ENABLED; - } - - @Override - public String toString() { - return String.format("ModuleInfo{id=%s, class=%s}", this.getId(), this.moduleClass); - } - - @Override - public int hashCode() { - return this.getId().hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof ModuleInfo && this.getId().equals(((ModuleInfo) other).getId()); - } + /** Get the current phase of the module */ + Phase getPhase(); /** The current phase of this module */ - public enum Phase { + enum Phase { /** This is a special phase when the classes are being loaded into the system to be constructed by Guice */ LOADING, /** The default phase state as this is created when the mode is constructing */ @@ -74,7 +25,11 @@ public enum Phase { /** This is when the module has been loaded, settings have been populated and module specific stuff should be loaded here */ LOADED, /** This is when the module has been enable, at this point all loading has been done and can do what they need */ - ENABLED + ENABLED, + /** This is when the module has been disabled, at any point the module was disabled and will not load */ + DISABLED, + /** This is when the module has been disabled, at any point the module was disabled and will not load */ + ERROR, ; /** Return true when the phase is constructing */ @@ -91,5 +46,10 @@ public boolean isLoaded() { public boolean isEnabled() { return this == ENABLED; } + + /** Return true with the phase is disabled */ + public boolean isDisabled() { + return this == DISABLED || this == ERROR; + } } } diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleWrapper.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleWrapper.java index 94e97e7..41f219a 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleWrapper.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/ModuleWrapper.java @@ -4,6 +4,7 @@ package net.year4000.utilities.ducktape.module; import net.year4000.utilities.Conditions; +import net.year4000.utilities.ducktape.module.internal.ModuleInfo; import net.year4000.utilities.ducktape.module.internal.ModuleInvocationHandler; public class ModuleWrapper implements Loader, Enabler { @@ -14,23 +15,35 @@ public class ModuleWrapper implements Loader, Enabler { public ModuleWrapper(ModuleInfo moduleInfo, Object instance) { this.moduleInfo = Conditions.nonNull(moduleInfo, "moduleInfo must exists"); this.proxyInstance = ModuleInvocationHandler.createProxy(moduleInfo.getModuleClass(), Conditions.nonNull(instance, "instance must exists")); + this.moduleInfo.setHandle((ModuleHandle) this.proxyInstance); } /** Load the module and call the method with @Load */ @Override public void load() { - this.moduleInfo.load(); - if (this.proxyInstance instanceof Loader) { - ((Loader) this.proxyInstance).load(); + try { + this.moduleInfo.load(); + if (this.proxyInstance instanceof Loader) { + ((Loader) this.proxyInstance).load(); + } + } catch (Exception error) { + this.moduleInfo.disableWithException(error); } } /** Enable the module and call the method with @Enable */ @Override public void enable() { - this.moduleInfo.enable(); - if (this.proxyInstance instanceof Enabler) { - ((Enabler) this.proxyInstance).enable(); + if (this.moduleInfo.getPhase().isDisabled()) { + return; // dont try to enable the module if it failed to load + } + try { + this.moduleInfo.enable(); + if (this.proxyInstance instanceof Enabler) { + ((Enabler) this.proxyInstance).enable(); + } + } catch (Exception error) { + this.moduleInfo.disableWithException(error); } } diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/internal/ModuleInfo.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/internal/ModuleInfo.java new file mode 100644 index 0000000..dbe6e77 --- /dev/null +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/module/internal/ModuleInfo.java @@ -0,0 +1,101 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.module.internal; + +import net.year4000.utilities.Conditions; +import net.year4000.utilities.ErrorReporter; +import net.year4000.utilities.annotations.Nullable; +import net.year4000.utilities.ducktape.module.Module; +import net.year4000.utilities.ducktape.module.ModuleHandle; +import net.year4000.utilities.value.Value; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** The module info class is for the implementation of it's interface */ +public final class ModuleInfo implements net.year4000.utilities.ducktape.module.ModuleInfo { + private final Logger logger = LogManager.getLogger("utilities/ducktape"); + private Phase phase = Phase.CONSTRUCTING; + private Class moduleClass; + private Module annotation; + private ModuleHandle handle; + + public ModuleInfo(Class moduleClass) { + this.moduleClass = Conditions.nonNull(moduleClass, "module class must not be empty"); + Conditions.condition(moduleClass.isAnnotationPresent(Module.class), "the module class must have the @Module annotation"); + this.annotation = moduleClass.getAnnotation(Module.class); + } + + /** This will set the handle of the module info */ + public void setHandle(@Nullable ModuleHandle handle) { + this.handle = handle; + } + + /** Returns the handle for the module this will be null until the module has been loaded */ + public Value getHandle() { + return Value.of(handle); + } + + /** Get the current phase the module is on */ + public Phase getPhase() { + return this.phase; + } + + /** Get the module class */ + public Class getModuleClass() { + return this.moduleClass; + } + + /** Get the id of the module */ + @Override + public String getId() { + return this.annotation.id().toLowerCase(); + } + + /** Called when the module class is being constructed */ + public void constructing() { + logger.info("Constructing: {}", this); + } + + /** Called when the module is being loaded*/ + @Override + public void load() { + logger.info("Loading: {}", this); + this.phase = Phase.LOADED; + } + + /** Called when the module is being enabled */ + @Override + public void enable() { + logger.info("Enabling: {}", this); + this.phase = Phase.ENABLED; + } + + /** Called when the module is disabled */ + public void disable() { + logger.info("Disabling: {}", this); + this.phase = Phase.DISABLED; + } + + /** Called when the module is disabled with an error */ + public void disableWithException(Exception error) { + logger.info("Disabling: {}", this); + this.phase = Phase.ERROR; + ErrorReporter.builder(error).buildAndReport().printStackTrace(); + } + + @Override + public String toString() { + return String.format("ModuleInfo{id=%s, phase=%s, class=%s}", this.getId(), this.phase, this.moduleClass); + } + + @Override + public int hashCode() { + return this.getId().hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ModuleInfo && this.getId().equals(((ModuleInfo) other).getId()); + } +} diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/SaveLoad.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/SaveLoad.java index 7975e08..f44ee33 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/SaveLoad.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/SaveLoad.java @@ -1,7 +1,6 @@ /* * Copyright 2019 Year4000. All Rights Reserved. */ - package net.year4000.utilities.ducktape.settings; /** The provider that will use the feature to save and/or load settings */ diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/Settings.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/Settings.java index 3fe1d50..1d43536 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/Settings.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/Settings.java @@ -1,7 +1,6 @@ /* - * Copyright 2018 Year4000. All Rights Reserved. + * Copyright 2019 Year4000. All Rights Reserved. */ - package net.year4000.utilities.ducktape.settings; import com.google.inject.Inject; @@ -10,14 +9,15 @@ /** The settings wrapper that will load and save the settings from the settings provider */ public class Settings implements Loader { - @Inject private T instance; + /** This is the instance of the settings, it will be init dynamic at the time this instance is injected */ + private T instance; @Inject private SaveLoad saveLoad; /** Save the settings from the provider */ public void save() { try { - saveLoad.save(instance); + this.saveLoad.save(instance); } catch (SettingsException error) { ErrorReporter.builder(error) .buildAndReport(System.err); @@ -27,7 +27,7 @@ public void save() { /** Load the settings instance with the config from the provider */ public void load() { try { - this.instance = saveLoad.load((Class) this.instance.getClass()); + this.instance = this.saveLoad.load((Class) this.instance.getClass()); } catch (SettingsException error) { ErrorReporter.builder(error) .buildAndReport(System.err); diff --git a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/InjectSettings.java b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/SettingsBase.java similarity index 65% rename from core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/InjectSettings.java rename to core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/SettingsBase.java index 17e82ac..fc630da 100644 --- a/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/InjectSettings.java +++ b/core-ducktape/src/main/java/net/year4000/utilities/ducktape/settings/SettingsBase.java @@ -1,7 +1,6 @@ /* * Copyright 2019 Year4000. All Rights Reserved. */ - package net.year4000.utilities.ducktape.settings; import java.lang.annotation.ElementType; @@ -9,9 +8,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface InjectSettings { - /** This value will be given too the settings provider to know where or how to save and load the settings */ - String value() default ""; +@Retention(RetentionPolicy.RUNTIME) +public @interface SettingsBase { + /** The base name for the settings used for the file name */ + String value(); } diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/DucktapeTest.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/DucktapeTest.java index d679cf1..08724e2 100644 --- a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/DucktapeTest.java +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/DucktapeTest.java @@ -5,10 +5,7 @@ import net.year4000.utilities.ducktape.loaders.ClassModuleLoader; import net.year4000.utilities.ducktape.module.ModuleInfo; -import net.year4000.utilities.ducktape.modules.ModuleD; -import net.year4000.utilities.ducktape.modules.ModuleA; -import net.year4000.utilities.ducktape.modules.ModuleB; -import net.year4000.utilities.ducktape.modules.ModuleC; +import net.year4000.utilities.ducktape.modules.*; import org.junit.Assert; import org.junit.Test; @@ -18,10 +15,21 @@ public class DucktapeTest { public void test() { Ducktape ducktape = Ducktape.builder() .addLoader(new ClassModuleLoader(ModuleA.class, ModuleB.class, ModuleC.class, ModuleD.class)) + //.setSaveLoadProvider(new GsonSaveLoadProvider()) .build(); ducktape.init(); } + @Test + public void errorText() { + Ducktape ducktape = Ducktape.builder() + .addLoader(new ClassModuleLoader(ModuleE.class, ModuleError.class)) + .build(); + ducktape.init(); + Assert.assertArrayEquals(ducktape.getModules().stream().map(ModuleInfo::getPhase).toArray(), + new ModuleInfo.Phase[] {ModuleInfo.Phase.ENABLED, ModuleInfo.Phase.ERROR}); + } + @Test public void loadModules() { // Modules were created in a way to have the dependency graph the same when shuffled @@ -33,6 +41,7 @@ public void loadModules() { .addLoader(new ClassModuleLoader(ModuleB.class, ModuleD.class, ModuleA.class, ModuleC.class)) .build(); randomLoader.init(); - Assert.assertArrayEquals(sortedLoader.getModules().stream().map(ModuleInfo::getId).toArray(), randomLoader.getModules().stream().map(ModuleInfo::getId).toArray()); + Assert.assertArrayEquals(sortedLoader.getModules().stream().map(ModuleInfo::getId).toArray(), + randomLoader.getModules().stream().map(ModuleInfo::getId).toArray()); } } diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/AbstractModuleClasses.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/AbstractModuleClasses.java new file mode 100644 index 0000000..a06fa19 --- /dev/null +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/AbstractModuleClasses.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import java.util.Collection; +import java.util.stream.Collectors; + +/** Shared code to test if the modules have been loaded properly */ +public abstract class AbstractModuleClasses { + protected static final String[] MODULE_CLASSES = new String[] { + "class net.year4000.utilities.ducktape.modules.ModuleA", + "class net.year4000.utilities.ducktape.modules.ModuleA$ModuleASettings", + "class net.year4000.utilities.ducktape.modules.ModuleB", + "class net.year4000.utilities.ducktape.modules.ModuleB$ModuleBModule", + "class net.year4000.utilities.ducktape.modules.ModuleC", + "class net.year4000.utilities.ducktape.modules.ModuleC$ModuleCSettings", + "class net.year4000.utilities.ducktape.modules.ModuleD", + }; + + protected String[] classStreamStringMap(Collection> classes) { + return classes.stream() + .map(Class::toString) + .sorted() + .collect(Collectors.toList()) + .toArray(new String[] {}); + } +} diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/ClassPathModuleLoaderTest.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/ClassPathModuleLoaderTest.java new file mode 100644 index 0000000..4dc7b42 --- /dev/null +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/ClassPathModuleLoaderTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import net.year4000.utilities.ducktape.module.Module; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collection; + +public class ClassPathModuleLoaderTest extends AbstractModuleClasses { + private static final String PACKAGE_PREFIX = "net.year4000.utilities.ducktape.modules"; + private static final String[] MODULE_CLASSES = new String[] { + "class net.year4000.utilities.ducktape.modules.ModuleA", + "class net.year4000.utilities.ducktape.modules.ModuleB", + "class net.year4000.utilities.ducktape.modules.ModuleC", + "class net.year4000.utilities.ducktape.modules.ModuleD", + }; + + @Test + public void classPathTest() { + // We are testing the package prefix to speed up the unit test + ClassPathModuleLoader classPathModuleLoader = new ClassPathModuleLoader(PACKAGE_PREFIX, Module.class); + Collection> classes = classPathModuleLoader.load(); + System.out.println(classes); + Assert.assertArrayEquals(MODULE_CLASSES, classStreamStringMap(classes)); + } +} diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/JarModuleLoaderTest.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/JarModuleLoaderTest.java new file mode 100644 index 0000000..29958cd --- /dev/null +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/JarModuleLoaderTest.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; + +public class JarModuleLoaderTest extends AbstractModuleClasses { + private static final Path modulePath = Paths.get("src/test/resources/modules/jars"); + + @Test + public void jarTest() { + JarModuleLoader jarModuleLoader = new JarModuleLoader(modulePath); + Collection> classes = jarModuleLoader.load(); + System.out.println(classes); + Assert.assertArrayEquals(MODULE_CLASSES, classStreamStringMap(classes)); + } +} diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/PackageModuleLoaderTest.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/PackageModuleLoaderTest.java new file mode 100644 index 0000000..66eb859 --- /dev/null +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/loaders/PackageModuleLoaderTest.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.loaders; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; + +public class PackageModuleLoaderTest extends AbstractModuleClasses { + private static final Path modulePath = Paths.get("src/test/resources/modules/classes"); + + @Test + public void packageTest() { + PackageModuleLoader packageModuleLoader = new PackageModuleLoader(modulePath); + Collection> classes = packageModuleLoader.load(); + System.out.println(classes); + Assert.assertArrayEquals(MODULE_CLASSES, classStreamStringMap(classes)); + } +} diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleA.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleA.java index 0882e7f..5b85989 100644 --- a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleA.java +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleA.java @@ -7,13 +7,13 @@ import net.year4000.utilities.ducktape.module.Load; import net.year4000.utilities.ducktape.module.Module; import net.year4000.utilities.ducktape.settings.Comment; -import net.year4000.utilities.ducktape.settings.InjectSettings; import net.year4000.utilities.ducktape.settings.Settings; +import net.year4000.utilities.ducktape.settings.SettingsBase; @Module(id = "a") public class ModuleA { @Inject private ModuleD moduleD; - @Inject private Settings settings; + //@Inject private Settings settings; public ModuleA() { System.out.println("ModuleA Constructor"); @@ -22,10 +22,10 @@ public ModuleA() { @Load public void loasdas() { System.out.println("ModuleA loader"); - System.out.println(settings.instance().setting); + //System.out.println(settings.instance().setting); } - @InjectSettings + @SettingsBase("a") public static class ModuleASettings { public ModuleASettings() { System.out.println("ModuleASettings Constructor"); diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleC.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleC.java index 68e4a78..fac4c2a 100644 --- a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleC.java +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleC.java @@ -7,12 +7,12 @@ import net.year4000.utilities.ducktape.module.Load; import net.year4000.utilities.ducktape.module.Module; import net.year4000.utilities.ducktape.settings.Comment; -import net.year4000.utilities.ducktape.settings.InjectSettings; import net.year4000.utilities.ducktape.settings.Settings; +import net.year4000.utilities.ducktape.settings.SettingsBase; @Module(id = "c") public class ModuleC { - @Inject private Settings settings; + //@Inject private Settings settings; public ModuleC() { System.out.println("ModuleC Constructor"); @@ -21,10 +21,10 @@ public ModuleC() { @Load public void loasdas() { System.out.println("ModuleC loader"); - System.out.println(settings.instance().setting); + //System.out.println(settings.instance().setting); } - @InjectSettings + @SettingsBase("c") public static class ModuleCSettings { public ModuleCSettings() { System.out.println("ModuleCSettings Constructor"); diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleE.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleE.java new file mode 100644 index 0000000..ee31086 --- /dev/null +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleE.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.modules; + +import net.year4000.utilities.ducktape.module.Enable; +import net.year4000.utilities.ducktape.module.Load; +import net.year4000.utilities.ducktape.module.Module; + +@Module(id = "e") +public class ModuleE { + @Load + public void load() { + System.out.println("ModuleE loader"); + } + + @Enable + public void enable() { + System.out.println("ModuleE enabler"); + } +} diff --git a/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleError.java b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleError.java new file mode 100644 index 0000000..2b196b7 --- /dev/null +++ b/core-ducktape/src/test/java/net/year4000/utilities/ducktape/modules/ModuleError.java @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.ducktape.modules; + +import net.year4000.utilities.ducktape.module.Load; +import net.year4000.utilities.ducktape.module.Module; + +@Module(id = "error") +public class ModuleError { + + @Load + public void load() { + throw new RuntimeException("error"); + } +} diff --git a/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleA$ModuleASettings.class b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleA$ModuleASettings.class new file mode 100644 index 0000000..3fb199f Binary files /dev/null and b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleA$ModuleASettings.class differ diff --git a/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleA.class b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleA.class new file mode 100644 index 0000000..5c7610d Binary files /dev/null and b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleA.class differ diff --git a/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleB$ModuleBModule.class b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleB$ModuleBModule.class new file mode 100644 index 0000000..7d209c9 Binary files /dev/null and b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleB$ModuleBModule.class differ diff --git a/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleB.class b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleB.class new file mode 100644 index 0000000..513b947 Binary files /dev/null and b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleB.class differ diff --git a/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleC$ModuleCSettings.class b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleC$ModuleCSettings.class new file mode 100644 index 0000000..3679f7e Binary files /dev/null and b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleC$ModuleCSettings.class differ diff --git a/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleC.class b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleC.class new file mode 100644 index 0000000..555a9d6 Binary files /dev/null and b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleC.class differ diff --git a/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleD.class b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleD.class new file mode 100644 index 0000000..ad4b26f Binary files /dev/null and b/core-ducktape/src/test/resources/modules/classes/net/year4000/utilities/ducktape/modules/ModuleD.class differ diff --git a/core-ducktape/src/test/resources/modules/jars/module-a.jar b/core-ducktape/src/test/resources/modules/jars/module-a.jar new file mode 100644 index 0000000..121d819 Binary files /dev/null and b/core-ducktape/src/test/resources/modules/jars/module-a.jar differ diff --git a/core-ducktape/src/test/resources/modules/jars/module-b.jar b/core-ducktape/src/test/resources/modules/jars/module-b.jar new file mode 100644 index 0000000..61f62b3 Binary files /dev/null and b/core-ducktape/src/test/resources/modules/jars/module-b.jar differ diff --git a/core-ducktape/src/test/resources/modules/jars/module-c.jar b/core-ducktape/src/test/resources/modules/jars/module-c.jar new file mode 100644 index 0000000..2c4490f Binary files /dev/null and b/core-ducktape/src/test/resources/modules/jars/module-c.jar differ diff --git a/core-ducktape/src/test/resources/modules/jars/module-d.jar b/core-ducktape/src/test/resources/modules/jars/module-d.jar new file mode 100644 index 0000000..acfa853 Binary files /dev/null and b/core-ducktape/src/test/resources/modules/jars/module-d.jar differ diff --git a/gradle/src/main/groovy/net/year4000/utilities/gradle/sponge.gradle b/gradle/src/main/groovy/net/year4000/utilities/gradle/sponge.gradle index 6899c62..6d7e0df 100644 --- a/gradle/src/main/groovy/net/year4000/utilities/gradle/sponge.gradle +++ b/gradle/src/main/groovy/net/year4000/utilities/gradle/sponge.gradle @@ -70,6 +70,9 @@ class SpongeExtension { /** The port the debugger will bind on */ int debugPort = 5005 + + /** The root project id */ + String rootProject } /** Sponge starter runs the needed things to start a sponge instance */ @@ -131,7 +134,7 @@ class SpongeForgeClient extends DefaultTask { pluginJar.deleteOnExit() } - if (project == project.rootProject) { // only start the client when its the root + if (project.name.toLowerCase() == project.rootProject.name.toLowerCase() || project.name.toLowerCase() == project.spongestarter.rootProject) { // only start the client when its the root logger.lifecycle 'Setting up the client enverionment' MinecraftVersion version = makeMCVersion forge.get() ClassPaths classPaths = generateLibs version; diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d82435f..58bd77c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/settings.gradle b/settings.gradle index e3a75e6..835d835 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,7 @@ rootProject.name = 'Utilities' include 'core' include 'core-ducktape' +include 'core-ducktape:loaders-groovy' include 'core-redis' include 'core-router' diff --git a/sponge/build.gradle b/sponge/build.gradle index 1edfa4e..8373a4e 100644 --- a/sponge/build.gradle +++ b/sponge/build.gradle @@ -14,7 +14,7 @@ buildscript { plugins { id 'java' - id 'com.github.johnrengelman.shadow' version '1.2.3' + id 'com.github.johnrengelman.shadow' version '4.0.4' } apply plugin: 'org.spongepowered.plugin' @@ -25,6 +25,7 @@ compileJava.options.encoding = 'UTF-8' spongestarter { deleteJar false + rootProject 'sponge' } jar { @@ -37,20 +38,23 @@ jar { assemble.dependsOn shadowJar { baseName = utilities.name.toLowerCase() version = utilities.version + configurations += [project.configurations.compile] + configurations += [project.configurations.shadow] - dependencies { - include dependency('org.codehaus.groovy:groovy-all:.*') // core:ducktape - include dependency('redis.clients:jedis:.*') // core:redis - include dependency('net.year4000.utilities:.*') - } + exclude 'overview.html' + exclude 'overviewj.html' + exclude 'NOTICE' + exclude 'LICENSE' } dependencies { compile utilities.project('core') compile utilities.project('core-redis') compile utilities.project('core-ducktape') - compile 'org.spongepowered:spongeapi:7.1.0-SNAPSHOT' + compile utilities.project('core-ducktape:loaders-groovy') compile 'io.netty:netty-all:4.1.5.Final' + compileOnly 'org.spongepowered:spongeapi:7.1.0-SNAPSHOT' + testCompile 'org.spongepowered:spongeapi:7.1.0-SNAPSHOT' } idea { diff --git a/sponge/src/debug/java/net/year4000/utilities/sponge/UtilitiesDebug.java b/sponge/src/debug/java/net/year4000/utilities/sponge/UtilitiesDebug.java index 9bb958b..c903fd6 100644 --- a/sponge/src/debug/java/net/year4000/utilities/sponge/UtilitiesDebug.java +++ b/sponge/src/debug/java/net/year4000/utilities/sponge/UtilitiesDebug.java @@ -16,15 +16,17 @@ import org.spongepowered.api.command.spec.CommandSpec; import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.game.state.GameInitializationEvent; -import org.spongepowered.api.event.game.state.GamePostInitializationEvent; -import org.spongepowered.api.event.game.state.GameStartedServerEvent; -import org.spongepowered.api.event.game.state.GameStartingServerEvent; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.game.state.*; import org.spongepowered.api.plugin.Dependency; import org.spongepowered.api.plugin.Plugin; import org.spongepowered.api.text.Text; +import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.List; import javax.imageio.ImageIO; @@ -40,6 +42,15 @@ public class UtilitiesDebug { private static final List activeHolograms = Lists.newArrayList(); + // This must be the first thing the system does to allow the module to exist when ducktape finds and locates it + @Listener(order = Order.PRE) + public void onConstruct(GameConstructionEvent event) throws IOException { + // extract the groovy module to test it + try (InputStream file = UtilitiesDebug.class.getResourceAsStream("/mods/ducktape.groovy")) { + Files.copy(file, Paths.get("mods/ducktape.groovy"), StandardCopyOption.REPLACE_EXISTING); + } + } + @Listener public void onInit(GameInitializationEvent event) { Packets packets = Sponge.getServiceManager().provide(Packets.class).orElseThrow(RuntimeException::new); diff --git a/sponge/src/debug/resources/mods/ducktape.groovy b/sponge/src/debug/resources/mods/ducktape.groovy new file mode 100644 index 0000000..b6d94ff --- /dev/null +++ b/sponge/src/debug/resources/mods/ducktape.groovy @@ -0,0 +1,21 @@ +import org.spongepowered.api.event.Listener +import org.spongepowered.api.event.game.state.GameInitializationEvent +import net.year4000.utilities.ducktape.module.Module +import org.spongepowered.api.event.message.MessageChannelEvent + +@Module(id = 'ducktape') +class DucktapeModule { + DucktapeModule() { + println 'I have been loaded yea!!!' + } + + @Listener + void onInit(GameInitializationEvent event) { + println 'I ran the GameInitializationEvent listener' + } + + @Listener + void onChat(MessageChannelEvent.Chat event) { + println 'I spoke in the chat' + } +} diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/Utilities.java b/sponge/src/main/java/net/year4000/utilities/sponge/Utilities.java index fb0b204..322d677 100644 --- a/sponge/src/main/java/net/year4000/utilities/sponge/Utilities.java +++ b/sponge/src/main/java/net/year4000/utilities/sponge/Utilities.java @@ -1,16 +1,17 @@ /* - * Copyright 2016 Year4000. All Rights Reserved. + * Copyright 2019 Year4000. All Rights Reserved. */ - package net.year4000.utilities.sponge; import com.google.inject.Inject; import com.google.inject.Injector; import net.year4000.utilities.ErrorReporter; import net.year4000.utilities.Tokens; +import net.year4000.utilities.ducktape.loaders.GroovyModuleLoader; +import net.year4000.utilities.sponge.command.FlyCommand; import net.year4000.utilities.sponge.command.PluginCommand; import net.year4000.utilities.sponge.command.SystemCommand; -import net.year4000.utilities.sponge.ducktape.SpongeModuleManager; +import net.year4000.utilities.sponge.ducktape.SpongeDucktapeManager; import net.year4000.utilities.sponge.hologram.Holograms; import net.year4000.utilities.sponge.protocol.Packets; import org.spongepowered.api.Sponge; @@ -20,6 +21,8 @@ import org.spongepowered.api.event.game.state.GamePreInitializationEvent; import org.spongepowered.api.plugin.Plugin; +import java.nio.file.Paths; + @Plugin( id = "utilities", name = "Utilities", @@ -29,7 +32,7 @@ authors = {"ewized"} ) public final class Utilities extends AbstractSpongePlugin { - private SpongeModuleManager moduleManager; + private SpongeDucktapeManager moduleManager; // Services private Packets packets; @@ -49,28 +52,32 @@ public static Utilities get() { } /** Get the module manager */ - public SpongeModuleManager getModuleManager() { + public SpongeDucktapeManager getModuleManager() { return moduleManager; } @Listener public void onConstruct(GameConstructionEvent event) { - this.moduleManager = new SpongeModuleManager(); - this.moduleManager.injectModules(this.injector); + this.moduleManager = (SpongeDucktapeManager) SpongeDucktapeManager.builder() + .setInjector(this.injector) + .addLoader(new GroovyModuleLoader(Paths.get(SpongeDucktapeManager.MOD_PATH))) + .build(); + this.moduleManager.load(); } @Listener public void onUtilitiesPreInit(GamePreInitializationEvent event) { packets = setProvider(Packets.class, Packets.manager()); holograms = setProvider(Holograms.class, Holograms.manager()); - moduleManager.registerListeners(); // Register the listeners of the modules + moduleManager.enable(); + moduleManager.registerListeners(); } @Listener public void onUtilitiesInit(GameInitializationEvent event) { Messages.Factory.inst.get(); // Trigger a download from server now so it can cache it for later PluginCommand.register(this, injector); - // FlyCommand.register(this, injector); todo disable, should be in drip + FlyCommand.register(this, injector); SystemCommand.register(this, injector); } diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/command/PluginCommand.java b/sponge/src/main/java/net/year4000/utilities/sponge/command/PluginCommand.java index 634c264..168d2b0 100644 --- a/sponge/src/main/java/net/year4000/utilities/sponge/command/PluginCommand.java +++ b/sponge/src/main/java/net/year4000/utilities/sponge/command/PluginCommand.java @@ -134,7 +134,7 @@ public Text toText(PluginContainer plugin, CommandSource src) { public List plugins() { return ImmutableList.builder() .addAll(new ArrayList<>(pluginManager.getPlugins())) - .addAll(Utilities.get().getModuleManager().getModules()) + .addAll(Utilities.get().getModuleManager().getWrappedModules()) .build(); } } diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/SpongeDucktapeManager.java b/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/SpongeDucktapeManager.java new file mode 100644 index 0000000..f24aa6d --- /dev/null +++ b/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/SpongeDucktapeManager.java @@ -0,0 +1,113 @@ +/* + * Copyright 2019 Year4000. All Rights Reserved. + */ +package net.year4000.utilities.sponge.ducktape; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import net.year4000.utilities.Conditions; +import net.year4000.utilities.ErrorReporter; +import net.year4000.utilities.ducktape.*; +import net.year4000.utilities.ducktape.loaders.ModuleLoader; +import net.year4000.utilities.ducktape.module.Module; +import net.year4000.utilities.ducktape.module.internal.ModuleInfo; +import net.year4000.utilities.ducktape.settings.SaveLoad; +import net.year4000.utilities.sponge.Utilities; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.Event; +import org.spongepowered.api.event.Listener; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.*; + +/** Uses the SpongeAPI to add additional modules for Utilities */ +public class SpongeDucktapeManager extends DucktapeManager { + public static final String MOD_PATH = "mods/"; + private final List modules = new ArrayList<>(); + + protected SpongeDucktapeManager(Injector injector, Map, ModuleLoader> loaderMap, SaveLoad saveLoadProvider) { + super(injector, loaderMap, saveLoadProvider); + } + + /** Load all classes from the selected path */ + protected Set> loadAll() throws ModuleInitException { + Set> classes = new HashSet<>(); + this.loaders.forEach((key, value) -> { + if (loaded.contains(key)) { + throw new ModuleInitException(ModuleInfo.Phase.LOADING, new IllegalStateException("Can not load the same loader twice.")); + } + Collection> loadedClasses = value.load(); + loadedClasses.forEach(clazz -> { + Module module = clazz.getAnnotation(Module.class); + if (module != null) { + modules.add(new WrappedPluginContainer(clazz)); + } + }); + classes.addAll(loadedClasses); + loaded.add(key); + }); + return classes; + } + + public Collection getWrappedModules() { + return this.modules; + } + + /** Register the listeners found in the modules */ + public void registerListeners() { + modules.forEach(container -> { + try { + registerModuleListeners(container, getModule(container.getId()).getOrThrow().getHandle().getOrThrow().$this()); + } catch (Throwable throwable) { + ErrorReporter.builder(throwable) + .hideStackTrace() + .add("Problem registering the listeners") + .add("ID: ", container.getId()) + .add("Name: ", container.getName()) + .add("Error: ", throwable.getMessage()) + .buildAndReport(System.err); + } + }); + } + + /** Register the listeners for the module */ + @SuppressWarnings("unchecked") + private void registerModuleListeners(WrappedPluginContainer container, Object module) throws IllegalAccessException { + for (Method method : module.getClass().getDeclaredMethods()) { + if (method.getAnnotation(Listener.class) == null) { + continue; // Must register methods with a listener annotation + } + Class[] types = method.getParameterTypes(); + Conditions.condition(types.length > 0, "Must have more than 0 arguments"); + Conditions.condition(Event.class.isAssignableFrom(types[0]), "First arg must be an Event"); + Class eventClass = (Class) types[0]; + MethodHandle handle = MethodHandles.publicLookup().unreflect(method).bindTo(module); + Sponge.getEventManager().registerListener(Utilities.get(), eventClass, event -> { + try { // Proxy the event to the module + handle.invokeWithArguments(event); + } catch (Throwable throwable) { + ErrorReporter.builder(throwable) + .add("Parameter Count: ", method.getParameterCount()) + .add("Module: ", container) + .add("Event: ", eventClass, event) + .buildAndReport(System.err); + } + }); + } + } + + public static SpongeDucktapeManagerBuilder builder() { + return new SpongeDucktapeManagerBuilder(); + } + + /** This will build the ducktape manager environment with the correct module loaders and guice injector */ + public static class SpongeDucktapeManagerBuilder extends DucktapeManagerBuilder { + @Override + public Ducktape build() { + Map, ModuleLoader> loaderMap = loaderMapReduce(); + return new SpongeDucktapeManager(this.injectorValue.getOrElse(Guice.createInjector()), loaderMap, this.saveLoadProvider); + } + } +} diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/SpongeModuleManager.java b/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/SpongeModuleManager.java deleted file mode 100644 index c89c0fa..0000000 --- a/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/SpongeModuleManager.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2016 Year4000. All Rights Reserved. - */ - -package net.year4000.utilities.sponge.ducktape; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.inject.Injector; -import net.year4000.utilities.Conditions; -import net.year4000.utilities.ErrorReporter; -import net.year4000.utilities.ducktape.ModuleManager; -import net.year4000.utilities.reflection.Reflections; -import net.year4000.utilities.sponge.Utilities; -import org.spongepowered.api.Sponge; -import org.spongepowered.api.event.Event; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.plugin.Plugin; -import org.spongepowered.api.plugin.PluginContainer; - -import java.lang.reflect.Method; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -/** Uses the SpongeAPI to add additional modules for Utilities */ -public class SpongeModuleManager extends ModuleManager { - private static final String MOD_PATH = "mods/"; - private final Map modules = Maps.newConcurrentMap(); - - /** Get the modules that were added */ - public Collection getModules() { - return ImmutableList.copyOf(modules.keySet()); - } - - /** Inject modules with the parent injector */ - public void injectModules(Injector injector) { - Set> pluginClasses = Sets.newHashSet(); - // Find all modules - loadAll(Paths.get(MOD_PATH), collection -> { - collection.forEach(clazz -> { - Plugin plugin = clazz.getAnnotation(Plugin.class); - if (plugin != null) { - pluginClasses.add(clazz); - } - }); - }); - // Init the modules and inject the fields - pluginClasses.forEach(clazz -> { - try { - Object plugin = Reflections.instance(clazz).getOrThrow(); - injector.injectMembers(plugin); // Inject - modules.put(new WrappedPluginContainer(plugin), plugin); - } catch (Exception error) { - ErrorReporter.builder(error) - .add("Problem constructing module") - .add("Class: ", clazz) - .buildAndReport(System.err); - } - }); - } - - /** Register the listeners found in the modules */ - public void registerListeners() { - modules.forEach((container, object) -> { - try { - registerModuleListeners(container, object); - } catch (Throwable throwable) { - ErrorReporter.builder(throwable) - .hideStackTrace() - .add("Problem registering the listeners") - .add("ID: ", container.getId()) - .add("Name: ", container.getName()) - .add("Error: ", throwable.getMessage()) - .buildAndReport(System.err); - } - }); - } - - /** Register the listeners for the module */ - @SuppressWarnings("unchecked") - private void registerModuleListeners(WrappedPluginContainer container, Object module) { - for (Method method : module.getClass().getDeclaredMethods()) { - if (method.getAnnotation(Listener.class) == null) { - continue; // Must register methods with a listener annotation - } - Class[] types = method.getParameterTypes(); - Conditions.condition(types.length > 0, "Must have more than 0 arguments"); - Conditions.condition(Event.class.isAssignableFrom(types[0]), "First arg must be an Event"); - Class eventClass = (Class) types[0]; - Sponge.getEventManager().registerListener(Utilities.get(), eventClass, event -> { - try { // Proxy the event to the module - Reflections.invoke(module, method, event); - } catch (Throwable throwable) { - ErrorReporter.builder(throwable) - .add("Parameter Count: ", method.getParameterCount()) - .add("Module: ", container) - .add("Event: ", eventClass, event) - .buildAndReport(System.err); - } - }); - } - } -} diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/WrappedPluginContainer.java b/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/WrappedPluginContainer.java index f107e90..c3a511d 100644 --- a/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/WrappedPluginContainer.java +++ b/sponge/src/main/java/net/year4000/utilities/sponge/ducktape/WrappedPluginContainer.java @@ -1,48 +1,44 @@ /* - * Copyright 2016 Year4000. All Rights Reserved. + * Copyright 2019 Year4000. All Rights Reserved. */ - package net.year4000.utilities.sponge.ducktape; import static com.google.common.base.Preconditions.checkNotNull; -import net.year4000.utilities.Utils; -import org.spongepowered.api.plugin.Plugin; +import net.year4000.utilities.ducktape.module.ModuleInfo; import org.spongepowered.api.plugin.PluginContainer; import java.util.Optional; public class WrappedPluginContainer implements PluginContainer { - private final Object plugin; - private final Plugin pluginData; + private final ModuleInfo moduleInfo; - public WrappedPluginContainer(Object object) { - this.plugin = checkNotNull(object, "object"); - this.pluginData = object.getClass().getAnnotation(Plugin.class); + public WrappedPluginContainer(Class clazz) { + this.moduleInfo = new net.year4000.utilities.ducktape.module.internal.ModuleInfo(checkNotNull(clazz, "clazz")); } @Override public String getId() { - return Optional.ofNullable(pluginData).map(Plugin::id).orElse("unknown"); + return this.moduleInfo.getId(); } @Override public String getName() { - return Optional.ofNullable(pluginData).map(Plugin::name).orElse("Unknown"); + return this.moduleInfo.getId(); } @Override public Optional getVersion() { - return Optional.ofNullable(pluginData).map(Plugin::version); + return Optional.empty(); } @Override public Optional getInstance() { - return Optional.ofNullable(plugin); + return Optional.empty(); } @Override public String toString() { - return Utils.toString(pluginData); + return this.moduleInfo.toString(); } } diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/hologram/FrameBuffer.java b/sponge/src/main/java/net/year4000/utilities/sponge/hologram/FrameBuffer.java index eb4052e..8aecd9d 100644 --- a/sponge/src/main/java/net/year4000/utilities/sponge/hologram/FrameBuffer.java +++ b/sponge/src/main/java/net/year4000/utilities/sponge/hologram/FrameBuffer.java @@ -52,7 +52,7 @@ public static Builder builder() { /** Create the buffer that will create each frame */ static class Builder implements net.year4000.utilities.Builder { - private static final Text.Builder BLOCK = Text.builder('\u2013'); + private static final Text.Builder BLOCK = Text.builder('\u2588'); private static final ImmutableBiMap COLORS = ImmutableBiMap.builder() .put(TextColors.AQUA.getColor(), TextColors.AQUA) .put(TextColors.BLACK.getColor(), TextColors.BLACK) diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumConnectionState.java b/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumConnectionState.java index 41bc614..d509709 100644 --- a/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumConnectionState.java +++ b/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumConnectionState.java @@ -44,7 +44,7 @@ default Class packet(PacketType type) { // enum - @Invoke + @Invoke(signature = "()[Lnet/minecraft/network/EnumConnectionState;") @Static Object[] values(); diff --git a/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumPacketDirection.java b/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumPacketDirection.java index 4e109c4..b4c7969 100644 --- a/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumPacketDirection.java +++ b/sponge/src/main/java/net/year4000/utilities/sponge/protocol/proxy/ProxyEnumPacketDirection.java @@ -24,7 +24,7 @@ static ProxyEnumPacketDirection get() { // enum - @Invoke + @Invoke(signature = "()[Lnet/minecraft/network/EnumPacketDirection;") @Static Object[] values(); diff --git a/sponge/src/test/java/net/year4000/utilities/sponge/protocol/PacketManagerTest.java b/sponge/src/test/java/net/year4000/utilities/sponge/protocol/PacketManagerTest.java index 5fc084d..cda1f8a 100644 --- a/sponge/src/test/java/net/year4000/utilities/sponge/protocol/PacketManagerTest.java +++ b/sponge/src/test/java/net/year4000/utilities/sponge/protocol/PacketManagerTest.java @@ -9,7 +9,7 @@ public class PacketManagerTest { private final PacketManager packets = new PacketManager(); - private final Class clazz = PacketManagerTest.class; + private final Class clazz = getClass(); private final PacketListener packetListener = (player, packet) -> false; @Test