Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds the improved ducktape module loading system #9

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 2 additions & 16 deletions core-ducktape/build.gradle
Expand Up @@ -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'
}
16 changes: 16 additions & 0 deletions 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'
}
@@ -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<Class<?>> load(Path directory) throws IOException {
if (directory.toString().endsWith(".groovy")) {
URL[] urls = new URL[] { directory.toUri().toURL() };
GroovyClassLoader loader = AccessController.doPrivileged((PrivilegedAction<GroovyClassLoader>) () -> {
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();
}
}
@@ -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<Path> 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());
}
}
@@ -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()
}
}
@@ -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()
}
}
@@ -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!'
}
}
Expand Up @@ -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<ModuleInfo> 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<ModuleInfo> 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();
Expand Down
@@ -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<Class<? extends ModuleLoader>> loaded = new LinkedHashSet<>();

protected Set<Class<? extends ModuleLoader>> loaded = new LinkedHashSet<>();
/** A map of all loaders stored by their class */
private ClassToInstanceMap<ModuleLoader> loaders;

protected ClassToInstanceMap<ModuleLoader> 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<ModuleInfo, ModuleWrapper> modules = new LinkedHashMap<>();

protected final Map<ModuleInfo, ModuleWrapper> modules = new LinkedHashMap<>();

private DucktapeManager(Injector injector, Map<Class<? extends ModuleLoader>, ModuleLoader> loaderMap) {
protected DucktapeManager(Injector injector, Map<Class<? extends ModuleLoader>, ModuleLoader> loaderMap, @Nullable SaveLoad saveLoadProvider) {
this.injector = Conditions.nonNull(injector, "extra injector must not be null");
this.loaders = ImmutableClassToInstanceMap.<ModuleLoader>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<ModuleInfo> getModules() {
ImmutableList.Builder<ModuleInfo> moduleBuilder = ImmutableList.builder();
public ImmutableList<net.year4000.utilities.ducktape.module.ModuleInfo> getModules() {
ImmutableList.Builder<net.year4000.utilities.ducktape.module.ModuleInfo> 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<Class<?>> 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<Class<?>> classes = loadAll();
logger.info("Setting up the injector");
List<Module> 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<Class<?>> loadAll(Path path) throws ModuleInitException {
System.out.println("path: " + path);
protected Set<Class<?>> loadAll() throws ModuleInitException {
Set<Class<?>> 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;
}
Expand All @@ -99,28 +94,39 @@ 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<DucktapeManager> {
private final Set<ModuleLoader> loaders = new HashSet<>();
private Value<Injector> injectorValue = Value.empty();
public static class DucktapeManagerBuilder implements Builder<Ducktape> {
protected final Set<ModuleLoader> loaders = new HashSet<>();
protected Value<Injector> injectorValue = Value.empty();
protected SaveLoad saveLoadProvider;

/** Add a module loader system */
public DucktapeManagerBuilder addLoader(ModuleLoader moduleLoader) {
this.loaders.add(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<Class<? extends ModuleLoader>, ModuleLoader> loaderMap = loaders.stream().map(loader -> ImmutableMap.<Class<? extends ModuleLoader>, ModuleLoader>of(loader.getClass(), loader))
/** Internal reuse able method that will map reduce the module loader map */
protected Map<Class<? extends ModuleLoader>, ModuleLoader> loaderMapReduce() {
return this.loaders.stream().map(loader -> ImmutableMap.<Class<? extends ModuleLoader>, ModuleLoader>of(loader.getClass(), loader))
.reduce((left, right) -> ImmutableMap.<Class<? extends ModuleLoader>, ModuleLoader>builder().putAll(left).putAll(right).build()).get();
}

return new DucktapeManager(injectorValue.getOrElse(Guice.createInjector()), loaderMap);
@Override
public Ducktape build() {
Map<Class<? extends ModuleLoader>, ModuleLoader> loaderMap = loaderMapReduce();
return new DucktapeManager(this.injectorValue.getOrElse(Guice.createInjector()), loaderMap, this.saveLoadProvider);
}
}
}
Expand Up @@ -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;
Expand Down