From 07bfbef16c6a9570ae58e56b30ecddb8d5414635 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 21 Jun 2016 20:12:29 +0200 Subject: [PATCH 1/5] Changed build.gradle to fit the one used in UI project --- .gitignore | 1 + build.gradle | 70 ++++++++++++++++++---------------------------------- 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 254749d..aaf2c13 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,7 @@ $RECYCLE.BIN/ # IntelliJ /out/ +.idea/ # mpeltonen/sbt-idea plugin .idea_modules/ diff --git a/build.gradle b/build.gradle index 6dbb083..4c9b4d1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,39 +1,34 @@ buildscript { repositories { - jcenter() mavenCentral() - maven { - url 'http://maven.ferenyr.info/artifactory/plugins-release' - credentials { - username = "${artifactory_user}" - password = "${artifactory_password}" - } - } } dependencies { - classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.0.0" } } repositories { - mavenCentral() - maven { - url = 'https://oss.sonatype.org/content/repositories/releases/' - } - maven { - url "http://files.minecraftforgefrance.fr/maven/" - } - maven { - url = 'https://oss.sonatype.org/content/repositories/snapshots/' - } + mavenCentral() + maven { + url = 'https://oss.sonatype.org/content/repositories/releases/' } + maven { + url "http://files.minecraftforgefrance.fr/maven/" + } + maven { + url = 'https://oss.sonatype.org/content/repositories/snapshots/' + } + maven { + name = "leviathan" + url = "http://maven.ferenyr.info/artifactory/Leviathan/" + } +} apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' group = "com.leviathanstudio.mineide" -archivesBaseName = 'MineIDE-Compiler' +archivesBaseName = 'MineIDE-UI' version = '0.0.1' compileJava.options.encoding = 'UTF-8' sourceCompatibility = 1.8 @@ -51,34 +46,17 @@ sourceSets { } dependencies { + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.5' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.5' + compile group: 'com.leviathanstudio.mineide', name: 'MineIDE-Compiler', version: '0.0.1' + compile 'com.google.code.gson:gson:2.7' + compile 'de.jensd:fontawesomefx:8.9' + compile 'com.jfoenix:jfoenix:0.0.0-SNAPSHOT' + compile 'utybo:minkj:1.0' + compile 'com.squareup:javapoet:1.7.0' + compile 'org.reflections:reflections:0.9.10' } task wrapper(type: Wrapper) { gradleVersion = '2.13' -} - -apply plugin: 'maven-publish' -apply plugin: 'com.jfrog.artifactory' - -artifactory { - contextUrl = "${artifactory_contextUrl}" - publish { - repository { - repoKey = 'Leviathan' - username = "${artifactory_user}" - password = "${artifactory_password}" - maven = true - } - defaults { - publications ('mavenJava') - } - } -} - -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - } - } } \ No newline at end of file From 792747242ca9c808122c11d98c5e8f1162a4610b Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 21 Jun 2016 20:14:13 +0200 Subject: [PATCH 2/5] Make ObfuscatedNameFinderFrame actually compile --- .../obfuscatednamefinder/ui/ObfuscatedNameFinderFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/plugin/fr/scarex/obfuscatednamefinder/ui/ObfuscatedNameFinderFrame.java b/src/main/java/plugin/fr/scarex/obfuscatednamefinder/ui/ObfuscatedNameFinderFrame.java index 231e044..1f261ac 100644 --- a/src/main/java/plugin/fr/scarex/obfuscatednamefinder/ui/ObfuscatedNameFinderFrame.java +++ b/src/main/java/plugin/fr/scarex/obfuscatednamefinder/ui/ObfuscatedNameFinderFrame.java @@ -50,7 +50,7 @@ public void start(Stage stage) throws Exception searchTextField.setTooltip(new Tooltip("Name of field/method to find")); ArrayList data = new ArrayList(); if(this.forgeVersions.get(this.forgeVersionComboBox.getItems().size()).getName().startsWith("1.8")) - data = parseCSV(new File(this.getGradleLocation(), "caches/minecraft/de/oceanlabs/mcp/mcp_snapshot/" + (new File(this.forgeVersions.get(this.forgeVersionComboBox.getItems().size()), "snapshot")).list()[0] + "/" + ((String)this.typeList) + ".csv")); + data = parseCSV(new File(this.getGradleLocation(), "caches/minecraft/de/oceanlabs/mcp/mcp_snapshot/" + (new File(this.forgeVersions.get(this.forgeVersionComboBox.getItems().size()), "snapshot")).list()[0] + "/" + ((String)this.typeList.getItems().get(0)) + ".csv")); else data = parseCSV(new File(this.forgeVersions.get(this.forgeVersionComboBox.getItems().size()), "unpacked/conf/" + ((String)this.typeList.getPromptText()) + ".csv")); this.csvData.clear(); From 5653ace2d90e196c2369c8b661c744a1c902fada Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 21 Jun 2016 20:59:04 +0200 Subject: [PATCH 3/5] Setting up the Plugin System, documentation to come soon --- .../leviathanstudio/plugin/MineIDEPlugin.java | 135 ++--------- .../leviathanstudio/plugin/PluginInfos.java | 95 ++++++++ .../leviathanstudio/plugin/PluginSystem.java | 216 ++++++++++++++++++ .../leviathanstudio/plugin/RunPluginTest.java | 36 --- src/main/resources/log4j2.xml | 14 ++ .../plugin/RunnableTestPlugin.java | 53 +++++ .../plugin/testplugin/TestPlugin.java | 44 ++++ .../plugin/testplugin/plugin_infos.json | 6 + 8 files changed, 448 insertions(+), 151 deletions(-) create mode 100644 src/main/java/com/leviathanstudio/plugin/PluginInfos.java create mode 100644 src/main/java/com/leviathanstudio/plugin/PluginSystem.java delete mode 100644 src/main/java/com/leviathanstudio/plugin/RunPluginTest.java create mode 100644 src/main/resources/log4j2.xml create mode 100644 src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java create mode 100644 src/test/java/com/leviathanstudio/plugin/testplugin/TestPlugin.java create mode 100644 src/test/resources/com/leviathanstudio/plugin/testplugin/plugin_infos.json diff --git a/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java b/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java index faa2d59..4f86ac1 100644 --- a/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java +++ b/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java @@ -1,127 +1,32 @@ package com.leviathanstudio.plugin; +import org.apache.logging.log4j.Logger; + import java.util.ArrayList; import java.util.List; public abstract class MineIDEPlugin { - private String pluginName, pluginDescription, pluginAuthor, pluginCredits; - private List nullPluginList = new ArrayList(); - private boolean informationIncomplete; - - public String getPluginName() - { - return pluginName; - } - - public void setPluginName(String pluginName) - { - this.pluginName = pluginName; - } - - public String getPluginDescription() - { - return pluginDescription; - } - - public void setPluginDescription(String pluginDescription) - { - this.pluginDescription = pluginDescription; - } - - public String getPluginAuthor() - { - return pluginAuthor; - } - - public void setPluginAuthor(String pluginAuthor) - { - this.pluginAuthor = pluginAuthor; - } - - public String getPluginCredits() - { - return pluginCredits; - } - - public void setPluginCredits(String pluginCredits) - { - this.pluginCredits = pluginCredits; - } - - public boolean isInformationIncomplete() - { - return informationIncomplete; - } - - public void setInformationIncomplete(boolean informationIncomplete) - { - this.informationIncomplete = informationIncomplete; - } - - public abstract void preInitPlugin(); - - public abstract void initPlugin(); - - public abstract void initPlugin(String[] args); - - public void executePlugin() - { - this.preInitPlugin(); - this.checkPluginInformation(); - - if(!this.isInformationIncomplete()) - { - this.initPlugin(); - } - else - { - System.err.println("[MineIDE-Plugin] Incomplete Plugin Information {"); - for(int i = 0; i < nullPluginList.size(); i++) - System.err.println(nullPluginList.get(i)); - - System.err.println("}"); - } - } - - public void executePlugin(String[] args) + protected PluginInfos infos; + protected Logger logger; + + public MineIDEPlugin() { - this.preInitPlugin(); - this.checkPluginInformation(); - - if(!this.isInformationIncomplete()) - this.initPlugin(args); - else - { - System.err.println("[MineIDE-Plugin] Incomplete Plugin Information {"); - for(int i = 0; i < nullPluginList.size(); i++) - System.err.println(nullPluginList.get(i)); - - System.err.println("}"); - } + } - - private void checkPluginInformation() + + public abstract void populateInfos(PluginInfos infos); + + public abstract void onPrePluginInit(); + + public abstract void onPluginInit(); + + public abstract void onPostPluginInit(); + + public abstract String getID(); + + public void onPluginException(Exception e) { - if(this.getPluginName() == null || this.getPluginName().isEmpty()) - { - nullPluginList.add("Plugin Name is null or empty"); - this.setInformationIncomplete(true); - } - else if(this.getPluginAuthor() == null || this.getPluginAuthor().isEmpty()) - { - nullPluginList.add("Plugin Author is null or empty"); - this.setInformationIncomplete(true); - } - else if(this.getPluginDescription() == null || this.getPluginDescription().isEmpty()) - { - nullPluginList.add("Plugin Description is null or empty"); - this.setInformationIncomplete(true); - } - else if(this.getPluginCredits() == null || this.getPluginCredits().isEmpty()) - { - nullPluginList.add("Plugin Credits is null or empty"); - this.setInformationIncomplete(true); - } + logger.error("Plugin crashed, ask the developer(s):", e); } } \ No newline at end of file diff --git a/src/main/java/com/leviathanstudio/plugin/PluginInfos.java b/src/main/java/com/leviathanstudio/plugin/PluginInfos.java new file mode 100644 index 0000000..91fd972 --- /dev/null +++ b/src/main/java/com/leviathanstudio/plugin/PluginInfos.java @@ -0,0 +1,95 @@ +package com.leviathanstudio.plugin; + +import java.util.Arrays; + +public class PluginInfos +{ + private final MineIDEPlugin plugin; + private String id; + private PluginState state; + private String name; + private String[] authors; + private String[] credits; + private String description; + + public PluginInfos(MineIDEPlugin plugin) + { + state = PluginState.UNLOADED; + this.plugin = plugin; + authors = new String[] {"unknown"}; + credits = Arrays.copyOf(authors, authors.length); + description = ""; + name = plugin.getClass().getSimpleName(); + id = name.toLowerCase(); + } + + public MineIDEPlugin getInstance() + { + return plugin; + } + + public String getID() + { + return id; + } + + public void setID(String id) + { + this.id = id; + } + + public String[] getAuthors() + { + return authors; + } + + public String[] getCredits() + { + return credits; + } + + public String getDescription() + { + return description; + } + + public String getName() + { + return name; + } + + public PluginState getState() + { + return state; + } + + public void setName(String pluginName) + { + this.name = pluginName; + } + + public void setAuthors(String... pluginAuthor) + { + this.authors = pluginAuthor; + } + + public void setCredits(String... pluginCredits) + { + this.credits = pluginCredits; + } + + public void setDescription(String pluginDescription) + { + this.description = pluginDescription; + } + + public void setState(PluginState pluginState) + { + this.state = pluginState; + } + + public enum PluginState + { + UNLOADED, UNINITIALIZED, PluginState, INITIALIZED, LOADED + } +} diff --git a/src/main/java/com/leviathanstudio/plugin/PluginSystem.java b/src/main/java/com/leviathanstudio/plugin/PluginSystem.java new file mode 100644 index 0000000..958698e --- /dev/null +++ b/src/main/java/com/leviathanstudio/plugin/PluginSystem.java @@ -0,0 +1,216 @@ +package com.leviathanstudio.plugin; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.reflections.Reflections; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public class PluginSystem +{ + + /** + * Used as a placeholder while loading the plugins. Prevents crashes if a plugin tries to access its logger before + * being initialized or after an exception preventing said plugin to properly load + */ + private final Logger temporaryLogger; + private File pluginFolder; + private static PluginSystem instance; + private List loadedPlugins; + + private PluginSystem(File pluginFolder) + { + loadedPlugins = new ArrayList<>(); + this.pluginFolder = pluginFolder; + temporaryLogger = LogManager.getLogger(this); + } + + public File getPluginFolder() + { + return pluginFolder; + } + + public static PluginSystem kickoff(File pluginFolder) + { + if(instance == null) + { + PluginSystem system = new PluginSystem(pluginFolder); + instance = system; + system.loadPluginFromFolder(); + system.loadPluginFromClasspath(); + return system; + } + else + { + throw new IllegalStateException("The plugin system is a singleton, don't try to start it twice!"); + } + } + + public boolean isPluginPresent(String name) + { + return getPlugin(name).isPresent(); + } + + public boolean isPluginLoaded(String name) + { + Optional plugin = getPlugin(name); + if(plugin.isPresent()) + { + return plugin.get().infos.getState() == PluginInfos.PluginState.LOADED; + } + return false; + } + + public Optional getPlugin(String name) + { + return loadedPlugins.stream() + .filter(p -> p.infos.getID().equals(name)) + .findFirst(); + } + + private void loadPluginFromClasspath() + { + Reflections reflections = new Reflections(Thread.currentThread().getContextClassLoader()); + for(Class pluginClass : reflections.getSubTypesOf(MineIDEPlugin.class)) + { + try + { + MineIDEPlugin plugin = pluginClass.newInstance(); + PluginInfos infos = new PluginInfos(plugin); + infos.setID(plugin.getID()); + plugin.logger = temporaryLogger; + + Package pluginPackage = pluginClass.getPackage(); + if(pluginPackage != null) + { + String path = "/"+pluginPackage.getName().replace(".", "/")+"/plugin_infos.json"; + InputStream stream = PluginSystem.class.getResourceAsStream(path); + if(stream != null) + { + try + { + Gson gson = new GsonBuilder().setLenient().create(); + JsonObject pluginInfos = gson.fromJson(new InputStreamReader(stream), JsonObject.class); + + JsonArray authors = pluginInfos.getAsJsonArray("authors"); + String[] authorList = new String[authors.size()]; + for (int i = 0; i < authorList.length; i++) + { + authorList[i] = authors.get(i).getAsString(); + } + infos.setAuthors(authorList); + + JsonArray credits = pluginInfos.getAsJsonArray("credits"); + String[] creditsList = new String[authors.size()]; + for (int i = 0; i < creditsList.length; i++) + { + creditsList[i] = credits.get(i).getAsString(); + } + infos.setCredits(creditsList); + + infos.setName(pluginInfos.get("name").getAsString()); + infos.setDescription(pluginInfos.get("description").getAsString()); + } + catch (Exception e) + { + plugin.onPluginException(e); + } + finally + { + try + { + stream.close(); + } + catch (IOException e) + { + } + } + } + else + { + temporaryLogger.info("Could not find plugin_infos.json for "+plugin.getClass().getCanonicalName()+ + " with path: "+path+". Automatic info loading could not be performed for this plugin"); + } + } + + plugin.populateInfos(infos); + plugin.logger = LogManager.getLogger(infos.getName()); + plugin.infos = infos; + loadedPlugins.add(plugin); + } + catch (InstantiationException | IllegalAccessException e) + { + e.printStackTrace(); + } + } + } + + private void loadPluginFromFolder() + { + if(pluginFolder == null) + return; + File[] subfiles = pluginFolder.listFiles(f -> f.getName().endsWith(".jar") || f.getName().endsWith(".zip")); + if(subfiles != null && subfiles.length > 0) + { + URLClassLoader loader = (URLClassLoader)Thread.currentThread().getContextClassLoader(); + try + { + Method method = loader.getClass().getDeclaredMethod("addURL", URL.class); + for(File f : subfiles) + { + try + { + method.invoke(loader, f.toURI().toURL()); + } + catch (IOException | InvocationTargetException | IllegalAccessException e) + { + e.printStackTrace(); + } + } + } + catch (NoSuchMethodException e) + { + e.printStackTrace(); + } + } + } + + public void initPlugins() + { + forAllPlugins(p -> p.infos.setState(PluginInfos.PluginState.UNINITIALIZED)); + forAllPlugins(MineIDEPlugin::onPrePluginInit); + forAllPlugins(MineIDEPlugin::onPluginInit); + forAllPlugins(p -> p.infos.setState(PluginInfos.PluginState.INITIALIZED)); + forAllPlugins(MineIDEPlugin::onPostPluginInit); + forAllPlugins(p -> p.infos.setState(PluginInfos.PluginState.LOADED)); + } + + private void forAllPlugins(Consumer action) + { + loadedPlugins.forEach(p -> { + try + { + action.accept(p); + } + catch (Exception e) + { + p.onPluginException(e); + } + }); + } +} diff --git a/src/main/java/com/leviathanstudio/plugin/RunPluginTest.java b/src/main/java/com/leviathanstudio/plugin/RunPluginTest.java deleted file mode 100644 index 2d7f4af..0000000 --- a/src/main/java/com/leviathanstudio/plugin/RunPluginTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.leviathanstudio.plugin; - -import plugin.fr.scarex.obfuscatednamefinder.ui.ObfuscatedNameFinderFrame; - -public class RunPluginTest extends MineIDEPlugin -{ - - @Override - public void preInitPlugin() - { - this.setPluginName("Obfuscated Name Finder"); - this.setPluginAuthor("SCAREX"); - this.setPluginCredits("SCAREX"); - this.setPluginDescription("Simple plugin to find the properly name of an obfuscated field or method"); - System.out.println(this.getPluginAuthor() + System.lineSeparator() + this.getPluginName() + System.lineSeparator() + this.getPluginDescription() + System.lineSeparator() + this.getPluginCredits()); - } - - @Override - public void initPlugin() - {} - - @Override - public void initPlugin(String[] args) - { - ObfuscatedNameFinderFrame.main(args); - } - - // Debug - public static void main(String[] args) - { - RunPluginTest test = new RunPluginTest(); - test.executePlugin(args); - test.executePlugin(); - } - -} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..fbf8394 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java b/src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java new file mode 100644 index 0000000..2d2042b --- /dev/null +++ b/src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java @@ -0,0 +1,53 @@ +package com.leviathanstudio.plugin; + +import plugin.fr.scarex.obfuscatednamefinder.ui.ObfuscatedNameFinderFrame; + +import java.util.Arrays; + +public class RunnableTestPlugin extends MineIDEPlugin +{ + + @Override + public void populateInfos(PluginInfos infos) + { + infos.setName("Obfuscated Name Finder"); + infos.setAuthors("SCAREX"); + infos.setCredits("SCAREX"); + infos.setDescription("Simple plugin to find the properly name of an obfuscated field or method"); + } + + @Override + public void onPrePluginInit() + { + logger.info("Name: "+infos.getName()); + logger.info("Authors:" + Arrays.toString(infos.getAuthors())); + logger.info("Credits:" + Arrays.toString(infos.getCredits())); + logger.info("Description: "+infos.getDescription()); + } + + @Override + public void onPluginInit() + { + ObfuscatedNameFinderFrame.main(new String[0]); + } + + @Override + public void onPostPluginInit() + { + + } + + @Override + public String getID() + { + return "obfuscated_name_finder"; + } + + // Debug + public static void main(String[] args) + { + PluginSystem pluginSystem = PluginSystem.kickoff(null); + pluginSystem.initPlugins(); + } + +} diff --git a/src/test/java/com/leviathanstudio/plugin/testplugin/TestPlugin.java b/src/test/java/com/leviathanstudio/plugin/testplugin/TestPlugin.java new file mode 100644 index 0000000..a165fda --- /dev/null +++ b/src/test/java/com/leviathanstudio/plugin/testplugin/TestPlugin.java @@ -0,0 +1,44 @@ +package com.leviathanstudio.plugin.testplugin; + +import com.leviathanstudio.plugin.MineIDEPlugin; +import com.leviathanstudio.plugin.PluginInfos; + +import java.util.Arrays; + +public class TestPlugin extends MineIDEPlugin +{ + @Override + public void populateInfos(PluginInfos infos) + { + ; // nop, done automatically via plugin_infos.json + } + + @Override + public void onPrePluginInit() + { + logger.debug("Hi from the pre-initialisation method!"); + logger.debug("Infos are:"); + logger.debug("\tName: "+infos.getName()); + logger.debug("\tDescription: "+infos.getDescription()); + logger.debug("\tAuthors: "+ Arrays.toString(infos.getAuthors())); + logger.debug("\tCredits: "+ Arrays.toString(infos.getCredits())); + } + + @Override + public void onPluginInit() + { + logger.debug("Hi from the initialisation method!"); + } + + @Override + public void onPostPluginInit() + { + logger.debug("Hi from the post-initialisation method!"); + } + + @Override + public String getID() + { + return "test_plugin"; + } +} diff --git a/src/test/resources/com/leviathanstudio/plugin/testplugin/plugin_infos.json b/src/test/resources/com/leviathanstudio/plugin/testplugin/plugin_infos.json new file mode 100644 index 0000000..e97fb05 --- /dev/null +++ b/src/test/resources/com/leviathanstudio/plugin/testplugin/plugin_infos.json @@ -0,0 +1,6 @@ +{ + "authors": ["jglrxavpok"], + "credits": ["jglrxavpok", "The Leviathan-Studio Team"], + "name": "Test plugin", + "description": "Just a simple test plugin to implement new features into the plugin system" +} \ No newline at end of file From b56cdad6d4227d7b25817e7facf7dc6bc4f150a9 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 21 Jun 2016 21:17:27 +0200 Subject: [PATCH 4/5] Documentation of the plugin system --- .../leviathanstudio/plugin/MineIDEPlugin.java | 38 ++++++++++++ .../leviathanstudio/plugin/PluginInfos.java | 27 ++++++++- .../leviathanstudio/plugin/PluginSystem.java | 60 +++++++++++++++++-- 3 files changed, 117 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java b/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java index 4f86ac1..1abde2e 100644 --- a/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java +++ b/src/main/java/com/leviathanstudio/plugin/MineIDEPlugin.java @@ -5,9 +5,22 @@ import java.util.ArrayList; import java.util.List; +/** + * Represents a MineIDE plugin. Loaded at the launch of the {@link PluginSystem Plugin System}.
+ * Starting point of any plugin.

+ * WARNING: In order for your plugin to be loaded, its core class must extend with one. + * + */ public abstract class MineIDEPlugin { + /** + * The informations about the plugin, filled at pre-initialization and by {@link #populateInfos(PluginInfos)} + */ protected PluginInfos infos; + + /** + * The logger dedicated to this plugin, assigned at loading + */ protected Logger logger; public MineIDEPlugin() @@ -15,16 +28,41 @@ public MineIDEPlugin() } + /** + * Populate the infos object with informations about this mod. + * @param infos + * The object storing the informations + */ public abstract void populateInfos(PluginInfos infos); + /** + * Called before all plugins are initialized + */ public abstract void onPrePluginInit(); + /** + * Called when the plugin needs to initialize + */ public abstract void onPluginInit(); + /** + * Called after all plugins have been initialized + */ public abstract void onPostPluginInit(); + /** + * Returns the ID of this mod.
+ * To be consistent across plugins, follow the snake case naming convention (e.g. "test_plugin", "my_super_duper_awesome_plugin", etc.) + * @return + * The ID of this mod + */ public abstract String getID(); + /** + * Called when an exception is raised because of this mod, prints the error message by default + * @param e + * The exception + */ public void onPluginException(Exception e) { logger.error("Plugin crashed, ask the developer(s):", e); diff --git a/src/main/java/com/leviathanstudio/plugin/PluginInfos.java b/src/main/java/com/leviathanstudio/plugin/PluginInfos.java index 91fd972..7bde329 100644 --- a/src/main/java/com/leviathanstudio/plugin/PluginInfos.java +++ b/src/main/java/com/leviathanstudio/plugin/PluginInfos.java @@ -2,6 +2,9 @@ import java.util.Arrays; +/** + * Object storing all the informations about a plugin + */ public class PluginInfos { private final MineIDEPlugin plugin; @@ -12,7 +15,11 @@ public class PluginInfos private String[] credits; private String description; - public PluginInfos(MineIDEPlugin plugin) + /** + * The constructor, should not be called outside of {@link PluginSystem} + * @param plugin + */ + PluginInfos(MineIDEPlugin plugin) { state = PluginState.UNLOADED; this.plugin = plugin; @@ -23,11 +30,21 @@ public PluginInfos(MineIDEPlugin plugin) id = name.toLowerCase(); } + /** + * Returns the instance of the represented mod + * @return + * The instance of the mod + */ public MineIDEPlugin getInstance() { return plugin; } + /** + * Returns the ID of the mod + * @return + * The ID of the mod + */ public String getID() { return id; @@ -88,8 +105,14 @@ public void setState(PluginState pluginState) this.state = pluginState; } + /** + * Order: UNLOADED->UNINITIALIZED->INITIALIZED->LOADED + */ public enum PluginState { - UNLOADED, UNINITIALIZED, PluginState, INITIALIZED, LOADED + UNLOADED, + UNINITIALIZED, + INITIALIZED, + LOADED } } diff --git a/src/main/java/com/leviathanstudio/plugin/PluginSystem.java b/src/main/java/com/leviathanstudio/plugin/PluginSystem.java index 958698e..75e7a17 100644 --- a/src/main/java/com/leviathanstudio/plugin/PluginSystem.java +++ b/src/main/java/com/leviathanstudio/plugin/PluginSystem.java @@ -21,6 +21,10 @@ import java.util.Optional; import java.util.function.Consumer; +/** + * The Plugin System is the entity that handles loading, initialization and coordination of plugins.
+ * Plugins can be loaded from a folder as ".jar" files or from the classpath + */ public class PluginSystem { @@ -33,6 +37,10 @@ public class PluginSystem private static PluginSystem instance; private List loadedPlugins; + /** + * Creates a new plugin system + * @param pluginFolder + */ private PluginSystem(File pluginFolder) { loadedPlugins = new ArrayList<>(); @@ -40,11 +48,27 @@ private PluginSystem(File pluginFolder) temporaryLogger = LogManager.getLogger(this); } + /** + * Returns the folder used to search for plugins + * @return + * The folder to search plugins in + */ public File getPluginFolder() { return pluginFolder; } + /** + * Starts the Plugin System, loads the plugins and returns the created instance.
+ * Warning: It is only possible to have one instance of a Plugin System at a time + * @param pluginFolder + * The folder to search plugins in, can be null for none + * @return + * The created instance + * + * @throws IllegalStateException + * If an instance already exists + */ public static PluginSystem kickoff(File pluginFolder) { if(instance == null) @@ -61,14 +85,28 @@ public static PluginSystem kickoff(File pluginFolder) } } - public boolean isPluginPresent(String name) + /** + * Checks if a given plugin has been found by the system + * @param id + * The ID of the plugin + * @return + * true if the plugin is present, false otherwise + */ + public boolean isPluginPresent(String id) { - return getPlugin(name).isPresent(); + return getPlugin(id).isPresent(); } - public boolean isPluginLoaded(String name) + /** + * Checks if a given plugin has been found by the system and loaded + * @param id + * The ID of the plugin + * @return + * true if the plugin is loaded, false otherwise + */ + public boolean isPluginLoaded(String id) { - Optional plugin = getPlugin(name); + Optional plugin = getPlugin(id); if(plugin.isPresent()) { return plugin.get().infos.getState() == PluginInfos.PluginState.LOADED; @@ -76,10 +114,17 @@ public boolean isPluginLoaded(String name) return false; } - public Optional getPlugin(String name) + /** + * Returns a potential plugin with the given id + * @param id + * The id of the plugin + * @return + * The potential plugin + */ + public Optional getPlugin(String id) { return loadedPlugins.stream() - .filter(p -> p.infos.getID().equals(name)) + .filter(p -> p.getID().equals(id)) .findFirst(); } @@ -190,6 +235,9 @@ private void loadPluginFromFolder() } } + /** + * Launches the initialization process of plugins + */ public void initPlugins() { forAllPlugins(p -> p.infos.setState(PluginInfos.PluginState.UNINITIALIZED)); From 7613b7082057dbc85279fb608c7cfd0ed74e288b Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 21 Jun 2016 22:39:04 +0200 Subject: [PATCH 5/5] Added support for Groovy plugins that are in the plugin folder (there are compiled to bytecode otherwise so why bother) --- build.gradle | 2 + runtime/TestGroovyPlugin.groovy | 32 ++++ .../leviathanstudio/plugin/PluginSystem.java | 166 ++++++++++++------ .../plugin/RunnableTestPlugin.java | 3 +- 4 files changed, 144 insertions(+), 59 deletions(-) create mode 100644 runtime/TestGroovyPlugin.groovy diff --git a/build.gradle b/build.gradle index 4c9b4d1..9d3f879 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,8 @@ dependencies { compile 'utybo:minkj:1.0' compile 'com.squareup:javapoet:1.7.0' compile 'org.reflections:reflections:0.9.10' + + compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.7' } task wrapper(type: Wrapper) { diff --git a/runtime/TestGroovyPlugin.groovy b/runtime/TestGroovyPlugin.groovy new file mode 100644 index 0000000..33d82da --- /dev/null +++ b/runtime/TestGroovyPlugin.groovy @@ -0,0 +1,32 @@ +import com.leviathanstudio.plugin.MineIDEPlugin +import com.leviathanstudio.plugin.PluginInfos + +class TestGroovyPlugin extends MineIDEPlugin { + @Override + void populateInfos(PluginInfos infos) { + infos.authors = {"jglrxavpok"} + infos.credits = {"jglrxavpok"} + infos.description = "A test plugin made in Groovy" + infos.name = {"Groovy Test Plugin"} + } + + @Override + void onPrePluginInit() { + logger.info("Hi from pre-init in Groovy!") + } + + @Override + void onPluginInit() { + logger.info("Hi from init in Groovy!") + } + + @Override + void onPostPluginInit() { + logger.info("Hi from post-init in Groovy!") + } + + @Override + String getID() { + return "test_groovy" + } +} diff --git a/src/main/java/com/leviathanstudio/plugin/PluginSystem.java b/src/main/java/com/leviathanstudio/plugin/PluginSystem.java index 75e7a17..eb51850 100644 --- a/src/main/java/com/leviathanstudio/plugin/PluginSystem.java +++ b/src/main/java/com/leviathanstudio/plugin/PluginSystem.java @@ -1,12 +1,17 @@ package com.leviathanstudio.plugin; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.ClassPath; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyCodeSource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.reflections.Reflections; +import org.reflections.util.ClasspathHelper; import java.io.File; import java.io.IOException; @@ -33,6 +38,7 @@ public class PluginSystem * being initialized or after an exception preventing said plugin to properly load */ private final Logger temporaryLogger; + private final GroovyClassLoader groovyLoader; private File pluginFolder; private static PluginSystem instance; private List loadedPlugins; @@ -46,6 +52,7 @@ private PluginSystem(File pluginFolder) loadedPlugins = new ArrayList<>(); this.pluginFolder = pluginFolder; temporaryLogger = LogManager.getLogger(this); + groovyLoader = new GroovyClassLoader(getClass().getClassLoader()); } /** @@ -130,78 +137,113 @@ public Optional getPlugin(String id) private void loadPluginFromClasspath() { + loadGroovyPlugins(); + Reflections reflections = new Reflections(Thread.currentThread().getContextClassLoader()); for(Class pluginClass : reflections.getSubTypesOf(MineIDEPlugin.class)) { - try - { - MineIDEPlugin plugin = pluginClass.newInstance(); - PluginInfos infos = new PluginInfos(plugin); - infos.setID(plugin.getID()); - plugin.logger = temporaryLogger; + loadPluginClass(pluginClass); + } + } + + private void loadPluginClass(Class pluginClass) + { + try + { + MineIDEPlugin plugin = pluginClass.newInstance(); + PluginInfos infos = new PluginInfos(plugin); + infos.setID(plugin.getID()); + plugin.logger = temporaryLogger; - Package pluginPackage = pluginClass.getPackage(); - if(pluginPackage != null) + Package pluginPackage = pluginClass.getPackage(); + if(pluginPackage != null) + { + String path = "/"+pluginPackage.getName().replace(".", "/")+"/plugin_infos.json"; + InputStream stream = PluginSystem.class.getResourceAsStream(path); + if(stream != null) { - String path = "/"+pluginPackage.getName().replace(".", "/")+"/plugin_infos.json"; - InputStream stream = PluginSystem.class.getResourceAsStream(path); - if(stream != null) + try { - try - { - Gson gson = new GsonBuilder().setLenient().create(); - JsonObject pluginInfos = gson.fromJson(new InputStreamReader(stream), JsonObject.class); + Gson gson = new GsonBuilder().setLenient().create(); + JsonObject pluginInfos = gson.fromJson(new InputStreamReader(stream), JsonObject.class); - JsonArray authors = pluginInfos.getAsJsonArray("authors"); - String[] authorList = new String[authors.size()]; - for (int i = 0; i < authorList.length; i++) - { - authorList[i] = authors.get(i).getAsString(); - } - infos.setAuthors(authorList); - - JsonArray credits = pluginInfos.getAsJsonArray("credits"); - String[] creditsList = new String[authors.size()]; - for (int i = 0; i < creditsList.length; i++) - { - creditsList[i] = credits.get(i).getAsString(); - } - infos.setCredits(creditsList); - - infos.setName(pluginInfos.get("name").getAsString()); - infos.setDescription(pluginInfos.get("description").getAsString()); - } - catch (Exception e) + JsonArray authors = pluginInfos.getAsJsonArray("authors"); + String[] authorList = new String[authors.size()]; + for (int i = 0; i < authorList.length; i++) { - plugin.onPluginException(e); + authorList[i] = authors.get(i).getAsString(); } - finally + infos.setAuthors(authorList); + + JsonArray credits = pluginInfos.getAsJsonArray("credits"); + String[] creditsList = new String[authors.size()]; + for (int i = 0; i < creditsList.length; i++) { - try - { - stream.close(); - } - catch (IOException e) - { - } + creditsList[i] = credits.get(i).getAsString(); } + infos.setCredits(creditsList); + + infos.setName(pluginInfos.get("name").getAsString()); + infos.setDescription(pluginInfos.get("description").getAsString()); + } + catch (Exception e) + { + plugin.onPluginException(e); } - else + finally { - temporaryLogger.info("Could not find plugin_infos.json for "+plugin.getClass().getCanonicalName()+ - " with path: "+path+". Automatic info loading could not be performed for this plugin"); + try + { + stream.close(); + } + catch (IOException e) + { + } } } - - plugin.populateInfos(infos); - plugin.logger = LogManager.getLogger(infos.getName()); - plugin.infos = infos; - loadedPlugins.add(plugin); + else + { + temporaryLogger.info("Could not find plugin_infos.json for "+plugin.getClass().getCanonicalName()+ + " with path: "+path+". Automatic info loading could not be performed for this plugin"); + } } - catch (InstantiationException | IllegalAccessException e) + + plugin.populateInfos(infos); + plugin.logger = LogManager.getLogger(infos.getName()); + plugin.infos = infos; + loadedPlugins.add(plugin); + } + catch (InstantiationException | IllegalAccessException e) + { + e.printStackTrace(); + } + } + + private void loadGroovyPlugins() + { + try + { + ImmutableSet resources = ClassPath.from(getClass().getClassLoader()).getResources(); + for(ClassPath.ResourceInfo info : resources) { - e.printStackTrace(); + if(info.getResourceName().endsWith(".groovy")) + { + loadGroovyPlugin(info.url()); + } } + } catch (IOException e) + { + e.printStackTrace(); + } + } + + private void loadGroovyPlugin(URL url) + { + GroovyCodeSource source = new GroovyCodeSource(url); + Class clazz = groovyLoader.parseClass(source); + if(clazz.getSuperclass().equals(MineIDEPlugin.class)) + { + loadPluginClass((Class) clazz); } } @@ -209,18 +251,26 @@ private void loadPluginFromFolder() { if(pluginFolder == null) return; - File[] subfiles = pluginFolder.listFiles(f -> f.getName().endsWith(".jar") || f.getName().endsWith(".zip")); + File[] subfiles = pluginFolder.listFiles(f -> f.getName().endsWith(".jar") || f.getName().endsWith(".zip") || f.getName().endsWith(".groovy")); if(subfiles != null && subfiles.length > 0) { - URLClassLoader loader = (URLClassLoader)Thread.currentThread().getContextClassLoader(); + URLClassLoader loader = URLClassLoader.newInstance(new URL[0], getClass().getClassLoader()); try { - Method method = loader.getClass().getDeclaredMethod("addURL", URL.class); + Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); for(File f : subfiles) { try { - method.invoke(loader, f.toURI().toURL()); + if(f.getName().endsWith(".groovy")) + { + loadGroovyPlugin(f.toURI().toURL()); + } + else + { + method.invoke(loader, f.toURI().toURL()); + } } catch (IOException | InvocationTargetException | IllegalAccessException e) { diff --git a/src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java b/src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java index 2d2042b..0aedae0 100644 --- a/src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java +++ b/src/test/java/com/leviathanstudio/plugin/RunnableTestPlugin.java @@ -2,6 +2,7 @@ import plugin.fr.scarex.obfuscatednamefinder.ui.ObfuscatedNameFinderFrame; +import java.io.File; import java.util.Arrays; public class RunnableTestPlugin extends MineIDEPlugin @@ -46,7 +47,7 @@ public String getID() // Debug public static void main(String[] args) { - PluginSystem pluginSystem = PluginSystem.kickoff(null); + PluginSystem pluginSystem = PluginSystem.kickoff(new File("./runtime")); pluginSystem.initPlugins(); }