org.starnub.starnubserver.pluggable.PluggableManager.java Source code

Java tutorial

Introduction

Here is the source code for org.starnub.starnubserver.pluggable.PluggableManager.java

Source

/*
* Copyright (C) 2014 www.StarNub.org - Underbalanced
*
* This utilities.file is part of org.starnub a Java Wrapper for Starbound.
*
* This above mentioned StarNub software is free software:
* you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free
* Software Foundation, either version  3 of the License, or
* any later version. This above mentioned CodeHome software
* is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
* the GNU General Public License for more details. You should
* have received a copy of the GNU General Public License in
* this StarNub Software.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.starnub.starnubserver.pluggable;

import org.apache.commons.io.FileUtils;
import org.starnub.starnubserver.StarNub;
import org.starnub.starnubserver.events.events.StarNubEvent;
import org.starnub.starnubserver.pluggable.generic.IPluggable;
import org.starnub.starnubserver.pluggable.generic.PluggableDetails;
import org.starnub.starnubserver.pluggable.generic.PluggableReturn;
import org.starnub.utilities.classloaders.CustomURLClassLoader;
import org.starnub.utilities.collections.list.ReturnableList;
import org.starnub.utilities.exceptions.DependencyError;
import org.starnub.utilities.exceptions.DirectoryCreationFailed;
import org.starnub.utilities.exceptions.MissingData;
import org.starnub.utilities.strings.StringUtilities;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
* Represents the PluginManager Enum singleton
* <p>
* This enum singleton holds and runs all things important
* to Plugins and Commands
* <p>
* @author Daniel (Underbalanced) (www.StarNub.org)
* @since 1.0
*/
public class PluggableManager {

    /**
     * Represents the only instance of this class - Singleton Pattern
     */
    private static final PluggableManager instance = new PluggableManager();

    /**
     * This constructor is private - Singleton Pattern
     */
    private PluggableManager() {
    }

    public static PluggableManager getInstance() {
        return instance;
    }

    private final String PLUGIN_DIRECTORY_STRING = "StarNub/Plugins/";
    private final String COMMAND_DIRECTORY_STRING = "StarNub/Commands/";
    private final ConcurrentHashMap<String, Plugin> PLUGINS = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Command> COMMANDS = new ConcurrentHashMap<>();
    private final CustomURLClassLoader<StarNub> PLUGGABLE_CLASS_LOADER = new CustomURLClassLoader<>(StarNub.class);

    public String getPLUGIN_DIRECTORY_STRING() {
        return PLUGIN_DIRECTORY_STRING;
    }

    public String getCOMMAND_DIRECTORY_STRING() {
        return COMMAND_DIRECTORY_STRING;
    }

    public ConcurrentHashMap<String, Plugin> getPLUGINS() {
        return PLUGINS;
    }

    public ConcurrentHashMap<String, Command> getCOMMANDS() {
        return COMMANDS;
    }

    public CustomURLClassLoader getPLUGGABLE_CLASS_LOADER() {
        return PLUGGABLE_CLASS_LOADER;
    }

    @SuppressWarnings("unchecked")
    public NavigableSet<UnloadedPluggable> pluginScan(boolean updating) {
        ConcurrentHashMap<String, Pluggable> pluggablePluggins = new ConcurrentHashMap<>((Map) PLUGINS);
        return pluggableScan(PLUGIN_DIRECTORY_STRING, pluggablePluggins, updating);
    }

    @SuppressWarnings("unchecked")
    public NavigableSet<UnloadedPluggable> commandScan(boolean updating) {
        ConcurrentHashMap<String, Pluggable> pluggableCommands = new ConcurrentHashMap<>((Map) COMMANDS);
        return pluggableScan(COMMAND_DIRECTORY_STRING, pluggableCommands, updating);
    }

    public NavigableSet<UnloadedPluggable> pluggableScan(String directory,
            ConcurrentHashMap<String, Pluggable> loadedPluggables, boolean updating) {
        TreeSet<UnloadedPluggable> unloadedPluggables = getFiles(directory, "jar", "py");
        /* Remove unloaded pluggables that are older then currently loaded*/
        Iterator<UnloadedPluggable> iterator = unloadedPluggables.iterator();
        while (iterator.hasNext()) {
            UnloadedPluggable up = iterator.next();
            String upName = up.getDetails().getNAME().toLowerCase();
            Pluggable p = loadedPluggables.get(upName);
            if (p != null) {
                boolean canUpdate = canUpdate(up, p, updating);
                if (!canUpdate) {
                    iterator.remove();
                }
            }
        }
        return unloadedPluggables.descendingSet();
    }

    private TreeSet<UnloadedPluggable> getFiles(String directoryString, String... extensions) {
        File directoryFile = new File(directoryString);
        Collection<File> fileCollection = FileUtils.listFiles(directoryFile, extensions, false);
        ArrayList<File> files = new ArrayList<>(fileCollection);
        TreeSet<UnloadedPluggable> unloadedPluggables = new TreeSet<>();
        for (File file : files) {
            try {
                UnloadedPluggable futureUnloadedPluggable = new UnloadedPluggable(file);
                if (futureUnloadedPluggable.getDetails() != null) {
                    String futureUnloadedPluggableName = futureUnloadedPluggable.getDetails().getNAME();
                    double futureUnloadedPluggableVersion = futureUnloadedPluggable.getDetails().getVERSION();
                    Iterator<UnloadedPluggable> iterator = unloadedPluggables.iterator();
                    boolean removed = false;
                    boolean exist = false;
                    while (iterator.hasNext()) {
                        UnloadedPluggable alreadyUnloadedPluggable = iterator.next();
                        String alreadyUnloadedPluggableName = alreadyUnloadedPluggable.getDetails().getNAME();
                        if (futureUnloadedPluggableName.equalsIgnoreCase(alreadyUnloadedPluggableName)) {
                            exist = true;
                            double versionTest = alreadyUnloadedPluggable.getDetails().getVERSION();
                            if (futureUnloadedPluggableVersion > versionTest) {
                                iterator.remove();
                                removed = true;
                            }
                        }
                    }
                    if (removed || !exist) {
                        unloadedPluggables.add(futureUnloadedPluggable);
                    }
                }
            } catch (DirectoryCreationFailed | MissingData | IOException e) {
                e.printStackTrace();
            }
        }
        for (UnloadedPluggable unloadedPluggable : unloadedPluggables) {
            new StarNubEvent("Unloaded_Pluggable_File_Loaded", unloadedPluggable);
        }
        return unloadedPluggables;
    }

    private boolean canUpdate(UnloadedPluggable unloadedPluggable, Pluggable pluggable, boolean updating) {
        if (!updating) {
            return false;
        } else {
            double upVersion = unloadedPluggable.getDetails().getVERSION();
            double pVersion = pluggable.getDetails().getVERSION();
            if (upVersion > pVersion) {
                unloadedPluggable.setUpdating();
                return true;
            } else {
                return false;
            }
        }
    }

    public ReturnableList<PluggableReturn> loadAllCommands(boolean enable) {
        NavigableSet<UnloadedPluggable> upSet = commandScan(true);
        ReturnableList<PluggableReturn> cSuccess = new ReturnableList<>();
        upSet.stream().forEach(up -> {
            String upName = up.getDetails().getNAME();
            PluggableReturn pls = loadPluggable(upName, up, enable);
            cSuccess.add(pls);
        });
        return cSuccess;
    }

    public ReturnableList<PluggableReturn> loadAllPlugins(boolean enable) {
        NavigableSet<UnloadedPluggable> upSet = pluginScan(true);
        ReturnableList<PluggableReturn> pSuccess = new ReturnableList<>();
        upSet.stream().forEach(up -> {
            String upName = up.getDetails().getNAME();
            PluggableReturn pls = loadPluggable(upName, up, enable);
            pSuccess.add(pls);
        });
        return pSuccess;
    }

    public PluggableReturn loadSpecificPlugin(String pName, boolean enable, boolean updating) {
        NavigableSet<UnloadedPluggable> pScan = pluginScan(updating);
        for (UnloadedPluggable up : pScan) {
            String upName = up.getDetails().getNAME();
            if (upName.equalsIgnoreCase(pName)) {
                return loadPluggable(upName, up, enable);
            }
        }
        return null;
    }

    public PluggableReturn loadSpecificCommand(String cName, boolean enable, boolean updating) {
        NavigableSet<UnloadedPluggable> cScan = commandScan(updating);
        for (UnloadedPluggable up : cScan) {
            String upName = up.getDetails().getNAME();
            if (upName.equalsIgnoreCase(cName)) {
                return loadPluggable(upName, up, enable);
            }
        }
        return null;
    }

    private PluggableReturn loadPluggable(String unloadedPluggableName, UnloadedPluggable unloadedPluggable,
            boolean enable) {
        Pluggable pluggable;
        PluggableType pluggableType = unloadedPluggable.getDetails().getTYPE();
        String type = unloadedPluggable.getDetails().getTypeString();
        try {
            if (pluggableType == PluggableType.PLUGIN) {
                pluggable = unloadedPluggable.instantiatePluggable(PluggableType.PLUGIN);
            } else {
                pluggable = unloadedPluggable.instantiatePluggable(PluggableType.COMMAND);
            }
        } catch (ClassCastException e) {
            return classCastFailure(type, e, unloadedPluggable);
        } catch (IOException e) {
            return pluggableIOError(type, e, unloadedPluggable);
        } catch (ClassNotFoundException e) {
            return classNotFoundError(type, e, unloadedPluggable);
        } catch (DirectoryCreationFailed e) {
            return directoryCreationError(type, e, unloadedPluggable);
        } catch (InstantiationException e) {
            return instantiateError(type, e, unloadedPluggable);
        } catch (MissingData e) {
            return missingDataError(type, e, unloadedPluggable);
        } catch (IllegalAccessException e) {
            return illegalAccessError(type, e, unloadedPluggable);
        }
        PluggableReturn pluggableReturn;
        String lowerCaseName = unloadedPluggableName.toLowerCase();
        if (enable) {
            try {
                pluggable.enable();
            } catch (Exception e) {
                e.printStackTrace();
                return pluggableEnableError(type, unloadedPluggable);
            }
        }
        boolean containsKey;
        if (pluggableType == PluggableType.PLUGIN) {
            containsKey = PLUGINS.containsKey(lowerCaseName);
        } else {
            containsKey = COMMANDS.containsKey(lowerCaseName);
        }

        if (!unloadedPluggable.isUpdating() && containsKey) {
            return failNewerVersion(type, unloadedPluggable);
        } else if (unloadedPluggable.isUpdating()) {
            Pluggable remove;
            if (pluggableType == PluggableType.PLUGIN) {
                remove = PLUGINS.remove(lowerCaseName);
            } else {
                remove = COMMANDS.remove(lowerCaseName);
            }
            remove.disable();
            pluggableReturn = pluggableUpdated(type, pluggable);
        } else {
            pluggableReturn = pluggableLoaded(type, pluggable);
        }
        if (pluggableType == PluggableType.PLUGIN) {
            PLUGINS.put(lowerCaseName, (Plugin) pluggable);
        } else {
            COMMANDS.put(lowerCaseName, (Command) pluggable);
        }
        return pluggableReturn;
    }

    public ReturnableList<PluggableReturn> enableSpecificPlugin(String pluginName) {
        ReturnableList<PluggableReturn> rl = getSpecificLoadedPluginOrNearMatchs(pluginName);
        if (rl.size() > 1) {
            return rl;
        } else {
            PluggableReturn pr = rl.get(0);
            IPluggable pluggable = pr.getPLUGGABLE();
            if (pluggable instanceof Pluggable) {
                try {
                    ((Pluggable) pluggable).enable();
                } catch (DependencyError de) {
                    pr = dependencyError("Plugin", de, pluggable);
                }
                rl.clear();
                rl.add(pr);
            }
        }
        return rl;
    }

    public ReturnableList<PluggableReturn> enableSpecificCommand(String commandName) {
        ReturnableList<PluggableReturn> rl = getSpecificLoadedCommandOrNearMatchs(commandName);
        if (rl.size() > 1) {
            return rl;
        } else {
            PluggableReturn pr = rl.get(0);
            IPluggable pluggable = pr.getPLUGGABLE();
            if (pluggable instanceof Pluggable) {
                try {
                    ((Pluggable) pluggable).enable();
                } catch (DependencyError de) {
                    pr = dependencyError("Command", de, pluggable);
                }
                rl.clear();
                rl.add(pr);
            }
        }
        return rl;
    }

    public ReturnableList<PluggableReturn> enableAllPlugins() {
        ReturnableList<PluggableReturn> enableSuccess = new ReturnableList<>();
        PLUGINS.values().stream().forEach(p -> {
            if (!p.isEnabled()) {
                try {
                    p.enable();
                } catch (DependencyError de) {
                    PluggableReturn pLoadSuccess = dependencyError("Plugin", de, p);
                    enableSuccess.add(pLoadSuccess);
                }
            }
        });
        return enableSuccess;
    }

    public ReturnableList<PluggableReturn> enableAllCommands() {
        ReturnableList<PluggableReturn> enableSuccess = new ReturnableList<>();
        COMMANDS.values().stream().forEach(c -> {
            if (!c.isEnabled()) {
                try {
                    c.enable();
                } catch (DependencyError de) {
                    PluggableReturn pLoadSuccess = dependencyError("Commands", de, c);
                    enableSuccess.add(pLoadSuccess);
                }
            }
        });
        return enableSuccess;
    }

    public ReturnableList<Pluggable> disableUnloadAllPlugins(boolean unload) {
        ReturnableList<Pluggable> rl = new ReturnableList<>();
        PLUGINS.values().forEach(p -> {
            ReturnableList<Pluggable> rl2 = disableUnloadPluggable(p, unload);
            rl.addAll(rl2.stream().collect(Collectors.toList()));
        });
        return rl;
    }

    public ReturnableList<Pluggable> disableUnloadAllCommands(boolean unload) {
        ReturnableList<Pluggable> rl = new ReturnableList<>();
        COMMANDS.values().forEach(p -> {
            ReturnableList<Pluggable> rl2 = disableUnloadPluggable(p, unload);
            rl.addAll(rl2.stream().collect(Collectors.toList()));
        });
        return rl;
    }

    public ReturnableList<Pluggable> disableUnloadSpecificPlugin(String pluginName, boolean unload) {
        Plugin plugin = getSpecificLoadedPlugin(pluginName);
        return disableUnloadPluggable(plugin, unload);
    }

    public ReturnableList<Pluggable> disableUnloadSpecificCommand(String commandName, boolean unload) {
        Command command = getSpecificLoadedCommand(commandName);
        return disableUnloadPluggable(command, unload);
    }

    public ReturnableList<Pluggable> disableUnloadPluggable(Pluggable p, boolean unload) {
        ReturnableList<Pluggable> rl = new ReturnableList<>();
        rl.add(p);
        String lowerCaseName = p.getDetails().getNAME().toLowerCase();
        if (p.getDetails().isUNLOADABLE()) {
            for (Plugin plugin : PLUGINS.values()) {
                PluggableDetails details = plugin.getDetails();
                String pluginName = details.getNAME();
                if (details.hasDependency(lowerCaseName)) {
                    ReturnableList<Pluggable> rl2 = disableUnloadSpecificPlugin(pluginName, false);
                    rl.addAll(rl2.stream().collect(Collectors.toList()));
                }
            }
            for (Command command : COMMANDS.values()) {
                PluggableDetails details = command.getDetails();
                String pluginName = details.getNAME();
                if (details.hasDependency(lowerCaseName)) {
                    ReturnableList<Pluggable> rl3 = disableUnloadSpecificCommand(pluginName, false);
                    rl.addAll(rl3.stream().collect(Collectors.toList()));
                }
            }
            if (rl.size() == 0) {
                rl.setExactMatch();
            }
            p.disable();
            if (unload) {
                if (p.getDetails().getTYPE() == PluggableType.PLUGIN) {
                    PLUGINS.remove(lowerCaseName);
                } else {
                    COMMANDS.remove(lowerCaseName);
                }
            }
        }
        return rl;
    }

    public Plugin getSpecificLoadedPlugin(String pluginName) {
        return PLUGINS.get(pluginName.toLowerCase());
    }

    public Command getSpecificLoadedCommand(String pluginName) {
        return COMMANDS.get(pluginName.toLowerCase());
    }

    public ReturnableList<PluggableReturn> getSpecificLoadedPluginOrNearMatchs(String pluginName) {
        Plugin slp = getSpecificLoadedPlugin(pluginName);
        ReturnableList<PluggableReturn> sp = new ReturnableList<>();
        if (slp == null) {
            for (Map.Entry<String, Plugin> entrySet : PLUGINS.entrySet()) {
                String name = entrySet.getKey();
                Plugin plugin = entrySet.getValue();
                double distance = StringUtilities.similarityCalculationCaseInsensitive(name, pluginName);
                if (distance > 90) {
                    PluggableReturn pr = new PluggableReturn(plugin, false, "Similar Matches");
                    sp.add(pr);
                }
            }
        } else {
            sp.setExactMatch();
            PluggableReturn pr = new PluggableReturn(slp, true, "Exact Match");
            sp.add(pr);
        }
        return sp;
    }

    public ReturnableList<PluggableReturn> getSpecificLoadedCommandOrNearMatchs(String commandName) {
        Command slc = getSpecificLoadedCommand(commandName);
        ReturnableList<PluggableReturn> sc = new ReturnableList<>();
        if (slc == null) {
            for (Map.Entry<String, Command> entrySet : COMMANDS.entrySet()) {
                String name = entrySet.getKey();
                Command command = entrySet.getValue();
                double distance = StringUtilities.similarityCalculationCaseInsensitive(name, commandName);
                if (distance > 90) {
                    PluggableReturn pr = new PluggableReturn(command, false, "Similar Matches");
                    sc.add(pr);
                }
            }
        } else {
            sc.setExactMatch();
            PluggableReturn pr = new PluggableReturn(slc, true, "Exact Match");
            sc.add(pr);
        }
        return sc;
    }

    private PluggableReturn classCastFailure(String type, ClassCastException e, IPluggable iPluggable) {
        return failureMethod(e, type, iPluggable, "_Class_Cast_Exception", "Class Cast Failure " + type);
    }

    private PluggableReturn pluggableIOError(String type, IOException e, IPluggable iPluggable) {
        return failureMethod(e, type, iPluggable, "_IO_Error", "IO Error");
    }

    private PluggableReturn classNotFoundError(String type, ClassNotFoundException e, IPluggable iPluggable) {
        return failureMethod(e, type, iPluggable, "_Class_Not_Found", "Class Not Found");
    }

    private PluggableReturn directoryCreationError(String type, DirectoryCreationFailed e, IPluggable iPluggable) {
        return failureMethod(e, type, iPluggable, "_Directory_Creation_Failure", "Directory Creation Error");
    }

    private PluggableReturn instantiateError(String type, InstantiationException e, IPluggable iPluggable) {
        return failureMethod(e, type, iPluggable, "_Instantiation_Failure", "Instantiation Failure");
    }

    private PluggableReturn missingDataError(String type, MissingData e, IPluggable iPluggable) {
        return failureMethod(e, type, iPluggable, "_Manifest_Missing_Data", e.getMessage());
    }

    private PluggableReturn illegalAccessError(String type, IllegalAccessException e, IPluggable iPluggable) {
        return failureMethod(e, type, iPluggable, "_Class_Access_Error", "Class Access Error");
    }

    private PluggableReturn dependencyError(String type, DependencyError e, IPluggable iPluggable) {
        return failureMethod(null, type, iPluggable, "_Missing_Dependancies", e.getMessage());
    }

    private PluggableReturn pluggableEnableError(String type, IPluggable iPluggable) {
        return failureMethod(null, type, iPluggable, "_Enable_Method_Exception", "Enable Method Exception");
    }

    private PluggableReturn failNewerVersion(String type, IPluggable iPluggable) {
        return failureMethod(null, type, iPluggable, "_Newer_Version_Loaded", "Newer Version Already Loaded");
    }

    private PluggableReturn failureMethod(Exception e, String type, IPluggable iPluggable, String event,
            String error) {
        if (e != null) {
            e.printStackTrace();
        }
        String nameVersion = iPluggable.getDetails().getNameVersion();
        String failedString = nameVersion + " could not successfully loadData. Reason: " + error + ".";
        StarNub.getLogger().cErrPrint("StarNub", failedString);
        new StarNubEvent(type + event, iPluggable);
        return new PluggableReturn(iPluggable, false, failedString);
    }

    private PluggableReturn pluggableLoaded(String type, Pluggable pluggable) {
        String nameVersion = pluggable.getDetails().getNameVersion();
        String success = nameVersion + " was successfully loaded as a " + type;
        return successMethod(type, pluggable, success, "_Loaded");
    }

    private PluggableReturn pluggableUpdated(String type, Pluggable pluggable) {
        String nameVersion = pluggable.getDetails().getNameVersion();
        String success = type + " " + nameVersion + " updated.";
        return successMethod(type, pluggable, success, "_Updated");
    }

    private PluggableReturn successMethod(String type, Pluggable pluggable, String success, String event) {
        StarNub.getLogger().cInfoPrint("StarNub", success);
        new StarNubEvent(type + event, pluggable);
        return new PluggableReturn(pluggable, true, success);
    }
}