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 all 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
26 changes: 10 additions & 16 deletions src/main/java/cn/nukkit/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -598,24 +598,14 @@ public int broadcastMessage(TextContainer message, Collection<? extends CommandS
}

public int broadcast(String message, String permissions) {
Set<CommandSender> recipients = new HashSet<>();

for (String permission : permissions.split(";")) {
for (Permissible permissible : this.pluginManager.getPermissionSubscriptions(permission)) {
if (permissible instanceof CommandSender && permissible.hasPermission(permission)) {
recipients.add((CommandSender) permissible);
}
}
}

for (CommandSender recipient : recipients) {
recipient.sendMessage(message);
}

return recipients.size();
return broadcast((Object) message,permissions);
}

public int broadcast(TextContainer message, String permissions) {
return broadcast((Object) message,permissions);
}

private int broadcast(Object message,String permissions){
Set<CommandSender> recipients = new HashSet<>();

for (String permission : permissions.split(";")) {
Expand All @@ -627,7 +617,11 @@ public int broadcast(TextContainer message, String permissions) {
}

for (CommandSender recipient : recipients) {
recipient.sendMessage(message);
if(message instanceof String) {
recipient.sendMessage((String) message);
}else if(message instanceof TextContainer){
recipient.sendMessage((TextContainer) message);
}
}

return recipients.size();
Expand Down
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
23 changes: 23 additions & 0 deletions src/main/java/cn/nukkit/permission/Permission.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cn.nukkit.permission;

import cn.nukkit.Server;
import cn.nukkit.plugin.simple.Children;

import java.util.*;

Expand Down Expand Up @@ -130,6 +131,28 @@ public Permission addParent(String name, boolean value) {
return perm;
}

public static HashMap<String,Object> parsePermission(cn.nukkit.plugin.simple.Permission[] permissions){
HashMap<String,Object> pers = new HashMap<>();
for(cn.nukkit.plugin.simple.Permission p:permissions){
HashMap<String,Object> pers_child = new HashMap<>();
pers_child.put("description",p.description());
pers_child.put("default",p.theDefault());
Children[] children = p.childeren();
if(children.length!=0) {
HashMap<String,Object> map = new HashMap<>();
for (Children c : children) {
HashMap<String,Object> childValue = new HashMap<>();
childValue.put("description",c.description());
childValue.put("default",c.theDefault());
map.put(c.name(),childValue);
}
pers_child.put("children",map);
}
pers.put(p.permission(),pers_child);
}
return pers;
}

public static List<Permission> loadPermissions(Map<String, Object> data) {
return loadPermissions(data, DEFAULT_OP);
}
Expand Down
216 changes: 210 additions & 6 deletions src/main/java/cn/nukkit/plugin/JavaPluginLoader.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
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.*;
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.lang.reflect.Field;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

import static cn.nukkit.permission.Permission.parsePermission;

/**
* Created by Nukkit Team.
*/
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<Class,List<Class>> simplePluginEnableClasses = new HashMap<>();

public JavaPluginLoader(Server server) {
this.server = server;
}
Expand All @@ -46,16 +54,15 @@ 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);

plugin = pluginClass.newInstance();
this.initPlugin(plugin, description, dataFolder, file);

loadEnableRegister(new ArrayList<>(),plugin.getClass(),false,file,classLoader);
return plugin;
} catch (ClassCastException e) {
throw new PluginException("Error whilst initializing main class `" + description.getMain() + "'", e);
Expand All @@ -64,13 +71,177 @@ 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();

List<Class> haveLoaded = new ArrayList<>();
Class<?> mainClass = null;
while (entries.hasMoreElements()){
String name = entries.nextElement().getName();
if(name.endsWith(".class")) {
String mainName = getClassName(name);
Class<?> clz = classLoader.loadClass(mainName);
Main main = clz.getAnnotation(Main.class);
haveLoaded.add(clz);
if(main != null){
classLoaders.put(main.name(),classLoader);
loadedSimplePlugin.put(file,clz);
PluginAssert.isPluginBaseChild(clz,mainName);
mainClass = clz;
}
}
}


if(mainClass!=null) {
loadEnableRegister(haveLoaded,mainClass,true,file,classLoader);
return mainClass;
}
PluginAssert.findMainClass("");
}catch (IOException|ClassNotFoundException e){
throw new PluginException(e.getMessage());
}
return null;
}

private void loadEnableRegister(List<Class> haveLoaded,Class<?> mainClass,boolean hasLoaded,File file,PluginClassLoader loader){
List<Class<? extends Listener>> noRegister = null;
EnableRegister register = mainClass.getAnnotation(EnableRegister.class);
boolean isEnableRegister = register != null;
List<Class> classes = new ArrayList<>();
simplePluginEnableClasses.put(mainClass, classes);
if (register != null) {
noRegister = Arrays.asList(register.noRegister());
}
EnableCommand command = mainClass.getAnnotation(EnableCommand.class);
boolean isEnableCommand = command !=null;
List<Class<? extends cn.nukkit.command.Command>> noCommands = null;
if(command != null){
noCommands = Arrays.asList(command.noRegister());
}
if(isEnableCommand||isEnableRegister){
if(!hasLoaded){
try(JarFile jarFile = new JarFile(file)){
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.endsWith(".class")) {
haveLoaded.add(loader.loadClass(getClassName(name)));
}
}
}catch (IOException|ClassNotFoundException e){
throw new PluginException(e.getMessage());
}
}
}

for (Class<?> loaded : haveLoaded) {
if(isEnableRegister){
if (Arrays.asList(loaded.getInterfaces()).contains(Listener.class)) {

if (!noRegister.contains(loaded)) {
simplePluginEnableClasses.get(mainClass).add(loaded);
}
}
}
if(isEnableCommand){
if (cn.nukkit.command.Command.class.isAssignableFrom(loaded)) {

if (!noCommands.contains(loaded)) {
simplePluginEnableClasses.get(mainClass).add(loaded);
}
}
}


}


}

private String getClassName(String name){
return name.substring(0, name.lastIndexOf(".")).replace("/", ".");
}
/**
* 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();
PluginLoadOrder 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.toString());
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;
}


@Override
public Plugin loadPlugin(String filename) throws Exception {
return this.loadPlugin(new File(filename));
Expand All @@ -83,6 +254,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,10 +291,39 @@ public void enablePlugin(Plugin plugin) {

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

try {
if (simplePluginEnableClasses.get(plugin.getClass())!=null) {
List<Class> assemblies = simplePluginEnableClasses.get(plugin.getClass());
for (Class clz : assemblies) {
Object obj = clz.newInstance();
autoPlugin(obj,plugin);
if(cn.nukkit.command.Command.class.isAssignableFrom(clz)){
server.getCommandMap().register(plugin.getName(),(cn.nukkit.command.Command)obj);
}else{
server.getPluginManager().registerEvents((Listener) obj, plugin);
}

}
}
}catch (InstantiationException|IllegalAccessException e){
throw new PluginException(e.getMessage());
}

this.server.getPluginManager().callEvent(new PluginEnableEvent(plugin));
}
}

private void autoPlugin(Object obj,Plugin plugin) throws IllegalAccessException{
Class clz = obj.getClass();
Field[] fields = clz.getDeclaredFields();
for(Field field:fields){
if(field.getAnnotation(AutoPlugin.class)!=null) {
field.setAccessible(true);
field.set(obj,plugin);
}
}
}

@Override
public void disablePlugin(Plugin plugin) {
if (plugin instanceof PluginBase && plugin.isEnabled()) {
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");
}
}
Loading