package org.sunflow.system; import org.codehaus.janino.ClassBodyEvaluator; import org.codehaus.commons.compiler.CompileException; import org.sunflow.system.UI.Module; import org.sunflow.util.FastHashMap; /** * This class represents a list of plugins which implement a certain interface * or extend a certain class. Many plugins may be registered and created at a * later time by recalling their unique name only. * * @param Default constructible type or interface all plugins will derive * from or implement */ public final class Plugins { private final FastHashMap> pluginClasses; private final Class baseClass; /** * Create an empty plugin list. You must specify T.class as an * argument. * * @param baseClass */ public Plugins(Class baseClass) { pluginClasses = new FastHashMap<>(); this.baseClass = baseClass; } /** * Create an object from the specified type name. If this type name is * unknown or invalid, null is returned. * * @param name plugin type name * @return an instance of the specified plugin type, or null if * not found or invalid */ public T createObject(String name) { if (name == null || name.equals("none")) { return null; } Class c = pluginClasses.get(name); if (c == null) { // don't print an error, this will be handled by the caller return null; } try { return c.newInstance(); } catch (InstantiationException | IllegalAccessException e) { UI.printError(Module.API, "Cannot create object of type \"%s\" - %s", name, e.getLocalizedMessage()); return null; } } /** * Check this plugin list for the presence of the specified type name * * @param name plugin type name * @return true if this name has been registered, * false otherwise */ public boolean hasType(String name) { return pluginClasses.get(name) != null; } /** * Generate a unique plugin type name which has not yet been registered. * This is meant to be used when the actual type name is not crucial, but * succesfully registration is. * * @param prefix a prefix to be used in generating the unique name * @return a unique plugin type name not yet in use */ public String generateUniqueName(String prefix) { String type; for (int i = 1; hasType(type = String.format("%s_%d", prefix, i)); i++) { } return type; } /** * Define a new plugin type from java source code. The code string contains * import declarations and a class body only. The implemented type is * implicitly the one of the plugin list being registered against.If the * plugin type name was previously associated with a different class, it * will be overriden. This allows the behavior core classes to be modified * at runtime. * * @param name plugin type name * @param sourceCode Java source code definition for the plugin * @return true if the code compiled and registered * successfully, false otherwise */ // @SuppressWarnings("unchecked") public boolean registerPlugin(String name, String sourceCode) { try { ClassBodyEvaluator cbe = new ClassBodyEvaluator(); cbe.setClassName(name); cbe.setExtendedClass(baseClass); cbe.cook(sourceCode); return registerPlugin(name, cbe.getClazz()); } catch (CompileException e) { UI.printError(Module.API, "Plugin \"%s\" could not be declared - %s", name, e.getLocalizedMessage()); return false; } } /** * Define a new plugin type from an existing class. This checks to make sure * the provided class is default constructible (ie: has a constructor with * no parameters). If the plugin type name was previously associated with a * different class, it will be overriden. This allows the behavior core * classes to be modified at runtime. * * @param name plugin type name * @param pluginClass class object for the plugin class * @return true if the plugin registered successfully, * false otherwise */ public boolean registerPlugin(String name, Class pluginClass) { // check that the given class is compatible with the base class try { if (pluginClass.getConstructor() == null) { UI.printError(Module.API, "Plugin \"%s\" could not be declared - default constructor was not found", name); return false; } } catch (SecurityException e) { UI.printError(Module.API, "Plugin \"%s\" could not be declared - default constructor is not visible (%s)", name, e.getLocalizedMessage()); return false; } catch (NoSuchMethodException e) { UI.printError(Module.API, "Plugin \"%s\" could not be declared - default constructor was not found (%s)", name, e.getLocalizedMessage()); return false; } if (pluginClasses.get(name) != null) { UI.printWarning(Module.API, "Plugin \"%s\" was already defined - overwriting previous definition", name); } pluginClasses.put(name, (Class) pluginClass); return true; } }