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

Add a Simple Plugin System #972

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/java/cn/nukkit/command/PluginCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
*/
public class PluginCommand<T extends Plugin> extends Command implements PluginIdentifiableCommand {

private final T owningPlugin;
protected final T owningPlugin;

private CommandExecutor executor;
protected CommandExecutor executor;

public PluginCommand(String name, T owner) {
super(name);
Expand Down
168 changes: 162 additions & 6 deletions src/main/java/cn/nukkit/plugin/JavaPluginLoader.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package cn.nukkit.plugin;

import cn.nukkit.Server;
import cn.nukkit.event.Listener;
import cn.nukkit.event.plugin.PluginDisableEvent;
import cn.nukkit.event.plugin.PluginEnableEvent;
import cn.nukkit.plugin.simple.Command;
import cn.nukkit.plugin.simple.EnableRegister;
import cn.nukkit.plugin.simple.Main;
import cn.nukkit.plugin.simple.Permission;
import cn.nukkit.utils.PluginException;
import cn.nukkit.utils.Utils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
Expand All @@ -25,6 +29,10 @@ public class JavaPluginLoader implements PluginLoader {
private final Map<String, Class> classes = new HashMap<>();
private final Map<String, PluginClassLoader> classLoaders = new HashMap<>();

private final Map<File,Class> loadedSimplePlugin = new HashMap<>();

private final Map<File,List<Class>> simplePluginEnableClasses = new HashMap<>();

public JavaPluginLoader(Server server) {
this.server = server;
}
Expand All @@ -46,9 +54,7 @@ public Plugin loadPlugin(File file) throws Exception {
try {
Class javaClass = classLoader.loadClass(className);

if (!PluginBase.class.isAssignableFrom(javaClass)) {
throw new PluginException("Main class `" + description.getMain() + "' does not extend PluginBase");
}
PluginAssert.isPluginBaseChild(javaClass,description.getMain());

try {
Class<PluginBase> pluginClass = (Class<PluginBase>) javaClass.asSubclass(PluginBase.class);
Expand All @@ -64,13 +70,148 @@ public Plugin loadPlugin(File file) throws Exception {
}

} catch (ClassNotFoundException e) {
throw new PluginException("Couldn't load plugin " + description.getName() + ": main class not found");
PluginAssert.findMainClass(description.getName());
}
}

return null;
}

//simple load Plugin
@Override
public Plugin simpleLoadPlugin(File file) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core part of loading the simple plugin, initializing the plugin information.

try {
Class<?> pluginClass = getSimplePlugin(file);
PluginDescription pluginDescription = getSimpleDescription(pluginClass);
this.server.getLogger().info(this.server.getLanguage().translateString("nukkit.plugin.load", pluginDescription.getFullName()));
File dataFolder = new File(file.getParentFile(), pluginDescription.getName());
PluginBase plugin = (PluginBase) pluginClass.newInstance();
this.initPlugin(plugin,pluginDescription,dataFolder,file);
return plugin;
}catch (InstantiationException | IllegalAccessException e){
throw new PluginException(e.getMessage());
}
//do it
}

@Override
public Plugin simpleLoadPlugin(String fileName) {
return simpleLoadPlugin(new File(fileName));
}

private Class getSimplePlugin(File file){
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get and complete the auto-enrollment section

if(loadedSimplePlugin.containsKey(file)){
return loadedSimplePlugin.get(file);
}
try(JarFile jarFile = new JarFile(file)){
PluginClassLoader classLoader = new PluginClassLoader(this, this.getClass().getClassLoader(),file);
Enumeration<JarEntry> entries = jarFile.entries();
boolean isEnableRegister = false;
boolean hasGeted = false;
List<Class> haveLoaded = new ArrayList<>();
Class<?> mainClass = null;
while (entries.hasMoreElements()){
String name = entries.nextElement().getName();
if(name.endsWith(".class")) {
String mainName = name.substring(0, name.lastIndexOf(".")).replace("/", ".");
Class<?> clz = classLoader.loadClass(mainName);
Main main = clz.getAnnotation(Main.class);
haveLoaded.add(clz);
if(main != null){
loadedSimplePlugin.put(file,clz);
PluginAssert.isPluginBaseChild(clz,mainName);
mainClass = clz;
}
}
}
EnableRegister register;
List<Class<? extends Listener>> noRegister = null;
if(mainClass!=null) {
for (Class<?> clz1 : haveLoaded) {
if (!hasGeted) {
register = mainClass.getAnnotation(EnableRegister.class);
isEnableRegister = register != null;
hasGeted = true;
List<Class> classes = new ArrayList<>();
simplePluginEnableClasses.put(file, classes);
if (register != null) {
noRegister = Arrays.asList(register.noRegister());
}
}
if (isEnableRegister) {

if (Arrays.asList(clz1.getInterfaces()).contains(Listener.class)) {
if (!noRegister.contains(clz1)) {
simplePluginEnableClasses.get(file).add(clz1);
}
}
}
}
return mainClass;
}
PluginAssert.findMainClass("");
}catch (IOException|ClassNotFoundException e){
throw new PluginException(e.getMessage());
}
return null;
}

/**
* the simple description for simple plugin system
* @param plugin the main class
* @return the description for simple plugin system
*/
private PluginDescription getSimpleDescription(Class<?> plugin){
Main main = plugin.getAnnotation(Main.class);
if(main == null){
return null;
}
String name = main.name();
String version = main.version();
String author = main.author();
String description = main.description();
String pluginLoadOrder = main.load();
String website = main.website();
String prefix = main.prefix();
List<String> api = Arrays.asList(main.api());
List<String> depend = Arrays.asList(main.depend());
List<String> loadBefore = Arrays.asList(main.loadBefore());
List<String> softDepend = Arrays.asList(main.softDepend());
Map<String,Object> hashMap = new HashMap<>();
hashMap.put("name",name);
hashMap.put("version",version);
hashMap.put("author",author);
hashMap.put("api",api);
hashMap.put("depend",depend);
hashMap.put("loadBefore",loadBefore);
hashMap.put("softDepend",softDepend);
hashMap.put("description",description);
hashMap.put("load",pluginLoadOrder);
hashMap.put("website",website);
hashMap.put("prefix",prefix.equals("")?name:prefix);
hashMap.put("main",plugin.getName());
hashMap.put("isSimple",true);
PluginDescription descript = new PluginDescription(hashMap);
Permission[] permissions = main.permissions();
hashMap.put("permissions",parsePermission(permissions));
Command[] commands = main.commands();
for(Command command:commands){
descript.getCommands().put(command.name(),command);
}
return descript;
}

public static HashMap<String,Object> parsePermission(Permission[] permissions){
HashMap<String,Object> pers = new HashMap<>();
for(Permission p:permissions){
HashMap<String,Object> pers_child = new HashMap<>();
pers_child.put("description",p.description());
pers_child.put("default",p.theDefault());
pers.put(p.permission(),pers_child);
}
return pers;
}

@Override
public Plugin loadPlugin(String filename) throws Exception {
return this.loadPlugin(new File(filename));
Expand All @@ -83,6 +224,10 @@ public PluginDescription getPluginDescription(File file) {
if (entry == null) {
entry = jar.getJarEntry("plugin.yml");
if (entry == null) {
Class clz = getSimplePlugin(file);
if(clz != null){
return getSimpleDescription(clz);
}
return null;
}
}
Expand Down Expand Up @@ -116,6 +261,17 @@ public void enablePlugin(Plugin plugin) {

((PluginBase) plugin).setEnabled(true);

try {
if (plugin.getDescription().isSimple()) {
List<Class> listeners = simplePluginEnableClasses.get(((PluginBase) plugin).getFile());
for (Class clz : listeners) {
server.getPluginManager().registerEvents((Listener) clz.newInstance(), plugin);
}
}
}catch (InstantiationException|IllegalAccessException e){
throw new PluginException(e.getMessage());
}

this.server.getPluginManager().callEvent(new PluginEnableEvent(plugin));
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/cn/nukkit/plugin/PluginAssert.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cn.nukkit.plugin;

import cn.nukkit.utils.PluginException;

public class PluginAssert {

static void isPluginBaseChild(Class<?> javaClass,String main){
if (!PluginBase.class.isAssignableFrom(javaClass)) {
throw new PluginException("Main class `" + main + "' does not extend PluginBase");
}
}

static void findMainClass(String name) throws PluginException{
throw new PluginException("Couldn't load plugin "+name+": main class not found");
}
}
14 changes: 14 additions & 0 deletions src/main/java/cn/nukkit/plugin/PluginDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public class PluginDescription {
private String website;
private String prefix;
private PluginLoadOrder order = PluginLoadOrder.POSTWORLD;
private boolean isSimple;

private List<Permission> permissions = new ArrayList<>();

Expand Down Expand Up @@ -204,6 +205,10 @@ private void loadMap(Map<String, Object> plugin) throws PluginException {
if (plugin.containsKey("permissions")) {
this.permissions = Permission.loadPermissions((Map<String, Object>) plugin.get("permissions"));
}
if(plugin.containsKey("isSimple")){
Boolean obj = (Boolean) plugin.get("isSimple");
this.isSimple = obj==null?false:obj;
}
}

/**
Expand Down Expand Up @@ -408,4 +413,13 @@ public String getVersion() {
public String getWebsite() {
return website;
}

/**
* the plugin use the simple plugin system,the isSimple will
* be true
* @return is it a Simple Plugin?
*/
public boolean isSimple() {
return isSimple;
}
}
13 changes: 13 additions & 0 deletions src/main/java/cn/nukkit/plugin/PluginLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ public interface PluginLoader {
*/
Plugin loadPlugin(File file) throws Exception;

/**
* 一个简便开发的插件加载系统,基于原来的插件系统<br>
* A simple plugin loading system based on the original plugin system
* @param file 这个插件的文件的 {@code File}对象。<br>A {@code File} object for this plugin.
* @return 加载完毕的插件的 {@code Plugin}对象。<br>The loaded plugin as a {@code Plugin} object.
* @see #loadPlugin(File)
* @since Nukkit 1.0 | Nukkit API 1.0.9
*/
Plugin simpleLoadPlugin(File file);

Plugin simpleLoadPlugin(String fileName);

/**
* 通过插件文件名的字符串,来获得描述这个插件的 {@code PluginDescription}对象。<br>
* Gets a {@code PluginDescription} object describes the plugin by its file name.
Expand Down Expand Up @@ -135,4 +147,5 @@ public interface PluginLoader {
*/
void disablePlugin(Plugin plugin);


}
48 changes: 47 additions & 1 deletion src/main/java/cn/nukkit/plugin/PluginManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import cn.nukkit.event.*;
import cn.nukkit.permission.Permissible;
import cn.nukkit.permission.Permission;
import cn.nukkit.plugin.simple.Command;
import cn.nukkit.plugin.simple.SimplePluginCommand;
import cn.nukkit.utils.MainLogger;
import cn.nukkit.utils.PluginException;
import cn.nukkit.utils.Utils;
Expand Down Expand Up @@ -90,7 +92,7 @@ public Plugin loadPlugin(File file, Map<String, PluginLoader> loaders) {
for (Pattern pattern : loader.getPluginFilters()) {
if (pattern.matcher(file.getName()).matches()) {
PluginDescription description = loader.getPluginDescription(file);
if (description != null) {
if (description != null&&!description.isSimple()) {
try {
Plugin plugin = loader.loadPlugin(file);
if (plugin != null) {
Expand All @@ -108,6 +110,14 @@ public Plugin loadPlugin(File file, Map<String, PluginLoader> loaders) {
Server.getInstance().getLogger().critical("Could not load plugin", e);
return null;
}
}else{
Plugin plugin = loader.simpleLoadPlugin(file);
this.plugins.put(plugin.getDescription().getName(),plugin);
List<PluginCommand> pluginCommands = this.parseSimplePluginCommands(plugin);
if (!pluginCommands.isEmpty()) {
this.commandMap.registerAll(plugin.getDescription().getName(), pluginCommands);
}
return plugin;
}
}
}
Expand All @@ -116,6 +126,7 @@ public Plugin loadPlugin(File file, Map<String, PluginLoader> loaders) {
return null;
}


public Map<String, Plugin> loadPlugins(String dictionary) {
return this.loadPlugins(new File(dictionary));
}
Expand Down Expand Up @@ -447,6 +458,41 @@ public void enablePlugin(Plugin plugin) {
}
}

protected List<PluginCommand> parseSimplePluginCommands(Plugin plugin){
List<PluginCommand> pluginCmds = new ArrayList<>();
Set<Map.Entry<String,Object>> commands = plugin.getDescription().getCommands().entrySet();
for(Map.Entry<String,Object> obj : commands){
Command command = (Command)(obj.getValue());
PluginCommand newCmd = new PluginCommand<>(command.name(),plugin);
initCommand(newCmd,command);
pluginCmds.add(newCmd);
}
Class pluginClass = plugin.getClass();
Method[] methods = pluginClass.getDeclaredMethods();
for(Method method:methods){
Command cmd = method.getAnnotation(Command.class);
cn.nukkit.plugin.simple.Permission permission =
method.getAnnotation(cn.nukkit.plugin.simple.Permission.class);
if(cmd!=null&&permission!=null){
plugin.getDescription().getPermissions().addAll(Permission.loadPermissions(JavaPluginLoader.parsePermission(new cn.nukkit.plugin.simple.Permission[] {
permission
})));
SimplePluginCommand command = new SimplePluginCommand(cmd.name(),plugin);
initCommand(command,cmd);
command.setCommandMethod(method);
pluginCmds.add(command);
}
}
return pluginCmds;
}

private void initCommand(PluginCommand newCmd,Command command){
newCmd.setDescription(command.description());
newCmd.setUsage(command.usage());
newCmd.setAliases(command.aliases());
newCmd.setPermission(command.permission());
}

protected List<PluginCommand> parseYamlCommands(Plugin plugin) {
List<PluginCommand> pluginCmds = new ArrayList<>();

Expand Down
Loading