package org.nwapw.abacus.plugin; import org.nwapw.abacus.Abacus; import org.nwapw.abacus.function.Function; import org.nwapw.abacus.function.Operator; import org.nwapw.abacus.number.NumberInterface; import java.lang.reflect.InvocationTargetException; import java.util.*; /** * A class that controls instances of plugins, allowing for them * to interact with each other and the calculator. */ public class PluginManager { /** * List of classes loaded by this manager. */ private Set> loadedPluginClasses; /** * A list of loaded plugins. */ private Set plugins; /** * List of functions that have been cached, * that is, found in a plugin and returned. */ private Map cachedFunctions; /** * List of operators that have been cached, * that is, found in a plugin and returned. */ private Map cachedOperators; /** * The list of number implementations that have * been cached, that is, found in a plugin and returned. */ private Map cachedNumberImplementations; /** * The list of number implementations that have been * found by their implementation class. */ private Map, NumberImplementation> cachedInterfaceImplementations; /** * The pi values for each implementation class that have already been computer. */ private Map, NumberInterface> cachedPi; /** * List of all functions loaded by the plugins. */ private Set allFunctions; /** * List of all operators loaded by the plugins. */ private Set allOperators; /** * List of all the number implementations loaded by the plugins. */ private Set allNumberImplementations; /** * The list of plugin listeners attached to this instance. */ private Set listeners; /** * The abacus instance used to access other * components of the application. */ private Abacus abacus; /** * Creates a new plugin manager. * * @param abacus the abacus instance. */ public PluginManager(Abacus abacus) { this.abacus = abacus; loadedPluginClasses = new HashSet<>(); plugins = new HashSet<>(); cachedFunctions = new HashMap<>(); cachedOperators = new HashMap<>(); cachedNumberImplementations = new HashMap<>(); cachedInterfaceImplementations = new HashMap<>(); cachedPi = new HashMap<>(); allFunctions = new HashSet<>(); allOperators = new HashSet<>(); allNumberImplementations = new HashSet<>(); listeners = new HashSet<>(); } /** * Searches the plugin list for a certain value, retrieving the Plugin's * list of items of the type using the setFunction and getting the value * of it is available via getFunction. If the value is contained * in the cache, it returns the cached value instead. * * @param plugins the plugin list to search. * @param cache the cache to use * @param setFunction the function to retrieve a set of available T's from the plugin * @param getFunction the function to get the T value under the given name * @param name the name to search for * @param the type of element being search * @param the type of key that the cache is indexed by. * @return the retrieved element, or null if it was not found. */ private static T searchCached(Collection plugins, Map cache, java.util.function.Function> setFunction, java.util.function.BiFunction getFunction, K name) { if (cache.containsKey(name)) return cache.get(name); T loadedValue = null; for (Plugin plugin : plugins) { if (setFunction.apply(plugin).contains(name)) { loadedValue = getFunction.apply(plugin, name); break; } } cache.put(name, loadedValue); return loadedValue; } /** * Gets a function under the given name. * * @param name the name of the function * @return the function under the given name. */ public Function functionFor(String name) { return searchCached(plugins, cachedFunctions, Plugin::providedFunctions, Plugin::getFunction, name); } /** * Gets an operator under the given name. * * @param name the name of the operator. * @return the operator under the given name. */ public Operator operatorFor(String name) { return searchCached(plugins, cachedOperators, Plugin::providedOperators, Plugin::getOperator, name); } /** * Gets the number implementation under the given name. * * @param name the name of the implementation. * @return the implementation. */ public NumberImplementation numberImplementationFor(String name) { return searchCached(plugins, cachedNumberImplementations, Plugin::providedNumberImplementations, Plugin::getNumberImplementation, name); } /** * Gets the number implementation for the given implementation class. * * @param name the class for which to find the implementation. * @return the implementation. */ public NumberImplementation interfaceImplementationFor(Class name) { if (cachedInterfaceImplementations.containsKey(name)) return cachedInterfaceImplementations.get(name); NumberImplementation toReturn = null; outside: for (Plugin plugin : plugins) { for (String implementationName : plugin.providedNumberImplementations()) { NumberImplementation implementation = plugin.getNumberImplementation(implementationName); if (implementation.getImplementation().equals(name)) { toReturn = implementation; break outside; } } } cachedInterfaceImplementations.put(name, toReturn); return toReturn; } /** * Gets the mathematical constant pi for the given implementation class. * * @param forClass the class for which to find pi. * @return pi */ public NumberInterface piFor(Class forClass) { if (cachedPi.containsKey(forClass)) return cachedPi.get(forClass); NumberImplementation implementation = interfaceImplementationFor(forClass); NumberInterface generatedPi = null; if (implementation != null) { generatedPi = implementation.instanceForPi(); } cachedPi.put(forClass, generatedPi); return generatedPi; } /** * Adds an instance of Plugin that already has been instantiated. * * @param plugin the plugin to add. */ public void addInstantiated(Plugin plugin) { if (loadedPluginClasses.contains(plugin.getClass())) return; plugins.add(plugin); loadedPluginClasses.add(plugin.getClass()); } /** * Instantiates a class of plugin, and adds it to this * plugin manager. * * @param newClass the new class to instantiate. */ public void addClass(Class newClass) { if (!Plugin.class.isAssignableFrom(newClass) || newClass == Plugin.class) return; try { addInstantiated((Plugin) newClass.getConstructor(PluginManager.class).newInstance(this)); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } } /** * Loads all the plugins in the PluginManager. */ public void load() { Set disabledPlugins = abacus.getConfiguration().getDisabledPlugins(); for (Plugin plugin : plugins) { if (disabledPlugins.contains(plugin.getClass().getName())) continue; plugin.enable(); } for (Plugin plugin : plugins) { if (disabledPlugins.contains(plugin.getClass().getName())) continue; allFunctions.addAll(plugin.providedFunctions()); allOperators.addAll(plugin.providedOperators()); allNumberImplementations.addAll(plugin.providedNumberImplementations()); } listeners.forEach(e -> e.onLoad(this)); } /** * Unloads all the plugins in the PluginManager. */ public void unload() { listeners.forEach(e -> e.onUnload(this)); Set disabledPlugins = abacus.getConfiguration().getDisabledPlugins(); for (Plugin plugin : plugins) { if (disabledPlugins.contains(plugin.getClass().getName())) continue; plugin.disable(); } cachedFunctions.clear(); cachedOperators.clear(); cachedNumberImplementations.clear(); cachedInterfaceImplementations.clear(); cachedPi.clear(); allFunctions.clear(); allOperators.clear(); allNumberImplementations.clear(); listeners.forEach(e -> e.onUnload(this)); } /** * Reloads all the plugins in the PluginManager. */ public void reload() { unload(); load(); } /** * Gets all the functions loaded by the Plugin Manager. * * @return the set of all functions that were loaded. */ public Set getAllFunctions() { return allFunctions; } /** * Gets all the operators loaded by the Plugin Manager. * * @return the set of all operators that were loaded. */ public Set getAllOperators() { return allOperators; } /** * Gets all the number implementations loaded by the Plugin Manager. * * @return the set of all implementations that were loaded. */ public Set getAllNumberImplementations() { return allNumberImplementations; } /** * Adds a plugin change listener to this plugin manager. * * @param listener the listener to add. */ public void addListener(PluginListener listener) { listeners.add(listener); } /** * Remove the plugin change listener from this plugin manager. * * @param listener the listener to remove. */ public void removeListener(PluginListener listener) { listeners.remove(listener); } /** * Gets a list of all the plugin class files that have been * added to the plugin manager. * * @return the list of all the added plugin classes. */ public Set> getLoadedPluginClasses() { return loadedPluginClasses; } }