Java tutorial
/* Copyright 2014 Lisovik Denis (? ?) ckyberlis@gmail.com This file is part of PPLoader. PPLoader 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 (at your option) any later version. PPLoader 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 along with PPLoader. If not, see <http://www.gnu.org/licenses/> */ package org.cyberlis.pyloader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.regex.Pattern; import org.apache.commons.lang.Validate; import org.bukkit.Server; import org.bukkit.event.Event; import org.bukkit.event.EventException; import org.bukkit.event.Listener; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.event.server.PluginEnableEvent; import org.bukkit.plugin.EventExecutor; import org.bukkit.plugin.InvalidDescriptionException; import org.bukkit.plugin.InvalidPluginException; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginLoader; import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.TimedRegisteredListener; import org.bukkit.plugin.UnknownDependencyException; import org.cyberlis.dataloaders.PluginDataFile; import org.cyberlis.dataloaders.PluginPythonDirectory; import org.cyberlis.dataloaders.PluginPythonZip; import org.python.core.PyDictionary; import org.python.core.PyList; import org.python.core.PyObject; import org.python.core.PyString; import org.python.core.PySystemState; import org.python.util.PythonInterpreter; import org.yaml.snakeyaml.error.YAMLException; /** * A jython plugin loader. depends on JavaPluginLoader and SimplePluginManager. */ public class PythonPluginLoader implements PluginLoader { private final Server server; /** * Filter - matches all of the following, for the regex illiterate: * <pre> * plugin_py_dir * plugin.py.dir * plugin.py.zip * plugin.pyp * </pre> */ public static final Pattern[] fileFilters = new Pattern[] { Pattern.compile("^(.*)\\.py\\.dir$"), Pattern.compile("^(.*)_py_dir$"), Pattern.compile("^(.*)\\.py\\.zip$"), Pattern.compile("^(.*)\\.pyp$"), }; private HashSet<String> loadedplugins = new HashSet<String>(); /** * @param server server to initialize with */ public PythonPluginLoader(Server server) { this.server = server; } public Plugin loadPlugin(File file) throws InvalidPluginException/*, UnknownDependencyException*/ { return loadPlugin(file, false); } public Plugin loadPlugin(File file, boolean ignoreSoftDependencies) throws InvalidPluginException/*, InvalidDescriptionException, UnknownDependencyException*/ { if (!file.exists()) { throw new InvalidPluginException( new FileNotFoundException(String.format("%s does not exist", file.getPath()))); } PluginDataFile data = null; if (file.getName().endsWith(".dir") || file.getName().endsWith("_dir")) { if (!file.isDirectory()) throw new InvalidPluginException( new Exception("python directories cannot be normal files! try .py or .py.zip instead.")); data = new PluginPythonDirectory(file); } else if (file.getName().endsWith(".zip") || file.getName().endsWith(".pyp")) { if (file.isDirectory()) throw new InvalidPluginException( new Exception("python zips cannot be directories! try .py.dir instead.")); data = new PluginPythonZip(file); } else { throw new InvalidPluginException(new Exception("filename '" + file.getName() + "' does not end in py, dir, zip, or pyp! did you add a regex without altering loadPlugin()?")); } try { return loadPlugin(file, ignoreSoftDependencies, data); } finally { try { data.close(); } catch (IOException e) { e.printStackTrace(); } } } private Properties setDefaultPythonPath(Properties props, String file_path) { String pythonPathProp = props.getProperty("python.path"); String new_value; if (pythonPathProp == null) { new_value = file_path; } else { new_value = pythonPathProp + java.io.File.pathSeparator + file_path + java.io.File.pathSeparator; } props.setProperty("python.path", new_value); return props; } private Plugin loadPlugin(File file, boolean ignoreSoftDependencies, PluginDataFile data) throws InvalidPluginException/*, InvalidDescriptionException, UnknownDependencyException*/ { Properties props; System.out.println("[PPLoader] Loading Plugin " + file.getName()); PythonPlugin result = null; PluginDescriptionFile description = null; try { InputStream stream = data.getStream("plugin.yml"); if (stream == null) { throw new InvalidPluginException(new Exception("You must include plugin.yml!")); } description = new PluginDescriptionFile(stream); if (stream != null) stream.close(); } catch (IOException ex) { throw new InvalidPluginException(ex); } catch (YAMLException ex) { throw new InvalidPluginException(ex); } catch (InvalidDescriptionException ex) { throw new InvalidPluginException(ex); } File dataFolder = new File(file.getParentFile(), description.getName()); if (dataFolder.getAbsolutePath().equals(file.getAbsolutePath())) { throw new InvalidPluginException(new Exception( String.format("Projected datafolder: '%s' for %s is the same file as the plugin itself (%s)", dataFolder, description.getName(), file))); } if (dataFolder.exists() && !dataFolder.isDirectory()) { throw new InvalidPluginException(new Exception( String.format("Projected datafolder: '%s' for %s (%s) exists and is not a directory", dataFolder, description.getName(), file))); } List<String> depend; try { depend = description.getDepend(); if (depend == null) { depend = new ArrayList<>(); } } catch (ClassCastException ex) { throw new InvalidPluginException(ex); } for (String pluginName : depend) { if (!isPluginLoaded(pluginName)) { throw new UnknownDependencyException(pluginName); } } props = PySystemState.getBaseProperties(); props = setDefaultPythonPath(props, file.getAbsolutePath()); PySystemState state = new PySystemState(); state.initialize(System.getProperties(), props, null); PyList pythonpath = state.path; PyString filepath = new PyString(file.getAbsolutePath()); pythonpath.append(filepath); String mainfile = "plugin.py"; InputStream instream = null; try { instream = data.getStream(mainfile); if (instream == null) { mainfile = "main.py"; instream = data.getStream(mainfile); } } catch (IOException e) { throw new InvalidPluginException(e); } if (instream == null) { throw new InvalidPluginException(new FileNotFoundException("Can not find plugin.py or main.py")); } try { PyDictionary table = new PyDictionary(); PythonInterpreter interp = new PythonInterpreter(table, state); String[] pre_plugin_scripts = { "preload.py" }; String[] post_plugin_scripts = { "postload.py" }; // Run scripts designed to be run before plugin creation for (String script : pre_plugin_scripts) { InputStream metastream = this.getClass().getClassLoader().getResourceAsStream("scripts/" + script); interp.execfile(metastream); metastream.close(); } interp.execfile(instream); instream.close(); String mainclass = description.getMain(); PyObject pyClass = interp.get(mainclass); if (pyClass == null) pyClass = interp.get("Plugin"); if (pyClass == null) { throw new InvalidPluginException(new Exception("Can not find Mainclass.")); } else result = (PythonPlugin) pyClass.__call__().__tojava__(PythonPlugin.class); interp.set("PYPLUGIN", result); result.interp = interp; // Run scripts designed to be run after plugin creation for (String script : post_plugin_scripts) { InputStream metastream = this.getClass().getClassLoader().getResourceAsStream("scripts/" + script); interp.execfile(metastream); metastream.close(); } result.initialize(this, server, description, dataFolder, file); result.setDataFile(data); } catch (Throwable t) { throw new InvalidPluginException(t); } if (!loadedplugins.contains(description.getName())) loadedplugins.add(description.getName()); return result; } private boolean isPluginLoaded(String name) { if (loadedplugins.contains(name)) return true; if (ReflectionHelper.isJavaPluginLoaded(server.getPluginManager(), name)) return true; return false; } public Pattern[] getPluginFileFilters() { return fileFilters; } public void disablePlugin(Plugin plugin) { if (!(plugin instanceof PythonPlugin)) { throw new IllegalArgumentException("Plugin is not associated with this PluginLoader"); } if (plugin.isEnabled()) { PythonPlugin pyPlugin = (PythonPlugin) plugin; try { pyPlugin.setEnabled(false); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?): " + ex.getMessage(), ex); } server.getPluginManager().callEvent(new PluginDisableEvent(plugin)); String pluginName = pyPlugin.getDescription().getName(); if (loadedplugins.contains(pluginName)) loadedplugins.remove(pluginName); } } public void enablePlugin(Plugin plugin) { if (!(plugin instanceof PythonPlugin)) { throw new IllegalArgumentException("Plugin is not associated with this PluginLoader"); } if (!plugin.isEnabled()) { PythonPlugin pyPlugin = (PythonPlugin) plugin; String pluginName = pyPlugin.getDescription().getName(); if (!loadedplugins.contains(pluginName)) loadedplugins.add(pluginName); try { pyPlugin.setEnabled(true); } catch (Throwable ex) { server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?): " + ex.getMessage(), ex); } // Perhaps abort here, rather than continue going, but as it stands, // an abort is not possible the way it's currently written server.getPluginManager().callEvent(new PluginEnableEvent(plugin)); } } @Override public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(Listener listener, Plugin plugin) { boolean useTimings = server.getPluginManager().useTimings(); Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<Class<? extends Event>, Set<RegisteredListener>>(); PythonListener pyListener = (PythonListener) listener; for (Map.Entry<Class<? extends Event>, Set<PythonEventHandler>> entry : pyListener.handlers.entrySet()) { Set<RegisteredListener> eventSet = new HashSet<RegisteredListener>(); for (final PythonEventHandler handler : entry.getValue()) { EventExecutor executor = new EventExecutor() { @Override public void execute(Listener listener, Event event) throws EventException { ((PythonListener) listener).fireEvent(event, handler); } }; if (useTimings) { eventSet.add( new TimedRegisteredListener(pyListener, executor, handler.priority, plugin, false)); } else { eventSet.add(new RegisteredListener(pyListener, executor, handler.priority, plugin, false)); } } ret.put(entry.getKey(), eventSet); } return ret; } @Override public PluginDescriptionFile getPluginDescription(File file) throws InvalidDescriptionException { Validate.notNull(file, "File cannot be null"); InputStream stream = null; PluginDataFile data = null; if (file.getName().endsWith(".dir") || file.getName().endsWith("_dir")) { if (!file.isDirectory()) throw new InvalidDescriptionException(new InvalidPluginException( new Exception("python directories cannot be normal files! .pyp or .py.zip instead."))); data = new PluginPythonDirectory(file); } else if (file.getName().endsWith(".zip") || file.getName().endsWith(".pyp")) { if (file.isDirectory()) throw new InvalidDescriptionException(new InvalidPluginException( new Exception("python zips cannot be directories! try .py.dir instead."))); try { data = new PluginPythonZip(file); } catch (InvalidPluginException ex) { throw new InvalidDescriptionException(ex); } } else { throw new InvalidDescriptionException(new InvalidPluginException(new Exception("filename '" + file.getName() + "' does not end in dir, zip, or pyp! did you add a regex without altering loadPlugin()?"))); } try { stream = data.getStream("plugin.yml"); if (stream == null) { //TODO Does this cause serious problems with plugins which have no plugin.yml file? throw new InvalidDescriptionException(new InvalidPluginException( new FileNotFoundException("Plugin does not contain plugin.yml"))); } return new PluginDescriptionFile(stream); } catch (IOException e) { e.printStackTrace(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { } } } return null; } }