diff --git a/sources/src/main/java/io/akarin/api/internal/Akari.java b/sources/src/main/java/io/akarin/api/internal/Akari.java index 4566c4b48..71c21424b 100644 --- a/sources/src/main/java/io/akarin/api/internal/Akari.java +++ b/sources/src/main/java/io/akarin/api/internal/Akari.java @@ -6,6 +6,8 @@ import java.util.Queue; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,6 +16,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import co.aikar.timings.Timing; import co.aikar.timings.Timings; +import io.akarin.api.internal.utils.ReentrantSpinningLock; import io.akarin.server.core.AkarinGlobalConfig; import net.minecraft.server.MinecraftServer; @@ -91,6 +94,8 @@ public abstract class Akari { return serverVersion + " (MC: " + MinecraftServer.getServer().getVersion() + ")"; } + public static final ReentrantSpinningLock eventLock = new ReentrantSpinningLock(); + /* * Timings */ diff --git a/sources/src/main/java/io/akarin/api/internal/utils/ReentrantSpinningLock.java b/sources/src/main/java/io/akarin/api/internal/utils/ReentrantSpinningLock.java new file mode 100644 index 000000000..98b69594c --- /dev/null +++ b/sources/src/main/java/io/akarin/api/internal/utils/ReentrantSpinningLock.java @@ -0,0 +1,26 @@ +package io.akarin.api.internal.utils; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ReentrantSpinningLock { + private final AtomicBoolean attemptLock = new AtomicBoolean(false); + private volatile long heldThreadId = 0; + + public void lock() { + long currentThreadId = Thread.currentThread().getId(); + attemptLock.getAndSet(true); // In case acquire one lock concurrently + + if (heldThreadId != 0 && heldThreadId != currentThreadId) { + attemptLock.set(false); + while (heldThreadId != 0) ; // The current thread is spinning here + attemptLock.getAndSet(true); + } + + heldThreadId = currentThreadId; + attemptLock.set(false); + } + + public void unlock() { + heldThreadId = 0; + } +} diff --git a/sources/src/main/java/net/minecraft/server/ChunkProviderServer.java b/sources/src/main/java/net/minecraft/server/ChunkProviderServer.java index 847fe7b43..73a1b1c4f 100644 --- a/sources/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/sources/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -1,6 +1,8 @@ package net.minecraft.server; import com.google.common.collect.Lists; + +import io.akarin.api.internal.Akari; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.LongIterator; @@ -14,7 +16,6 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import javax.annotation.Nullable; import com.destroystokyo.paper.exception.ServerInternalException; @@ -26,7 +27,7 @@ import org.bukkit.event.world.ChunkUnloadEvent; /** * Akarin Changes Note - * 1) synchronized block -> reentrant lock (compatibility) + * 1) Lock for event (safety issue) */ public class ChunkProviderServer implements IChunkProvider { @@ -35,7 +36,6 @@ public class ChunkProviderServer implements IChunkProvider { public final ChunkGenerator chunkGenerator; private final IChunkLoader chunkLoader; public final Long2ObjectMap chunks = Long2ObjectMaps.synchronize(new ChunkMap(8192)); - private final ReentrantLock chunksLock = new ReentrantLock(); // Akarin private final ChunkTaskScheduler f; private final SchedulerBatch g; // Paper start - chunk save stats @@ -112,21 +112,14 @@ public class ChunkProviderServer implements IChunkProvider { public Chunk getOrLoadChunkAt(int i, int j) { Long2ObjectMap long2objectmap = this.chunks; - // Akarin start - /* + Akari.eventLock.lock(); // Akarin + try { // Akarin synchronized (this.chunks) { Chunk chunk = world.paperConfig.allowPermaChunkLoaders ? getLoadedChunkAt(i, j) : getChunkIfLoaded(i, j); // Paper - Configurable perma chunk loaders return chunk != null ? chunk : this.loadChunkAt(i, j); } - */ - chunksLock.lock(); - Chunk chunk = world.paperConfig.allowPermaChunkLoaders ? getLoadedChunkAt(i, j) : getChunkIfLoaded(i, j); // Paper - Configurable perma chunk loaders - - chunk = chunk != null ? chunk : this.loadChunkAt(i, j); - chunksLock.unlock(); - return chunk; - // Akarin end + } finally { Akari.eventLock.unlock(); } // Akarin } // CraftBukkit start @@ -154,21 +147,11 @@ public class ChunkProviderServer implements IChunkProvider { public IChunkAccess d(int i, int j) { Long2ObjectMap long2objectmap = this.chunks; - // Akarin start - /* synchronized (this.chunks) { IChunkAccess ichunkaccess = (IChunkAccess) this.chunks.get(ChunkCoordIntPair.a(i, j)); return ichunkaccess != null ? ichunkaccess : (IChunkAccess) this.f.c((new ChunkCoordIntPair(i, j))); // CraftBukkit - decompile error } - */ - chunksLock.lock(); - IChunkAccess ichunkaccess = (IChunkAccess) this.chunks.get(ChunkCoordIntPair.a(i, j)); - - ichunkaccess = ichunkaccess != null ? ichunkaccess : (IChunkAccess) this.f.c((new ChunkCoordIntPair(i, j))); // CraftBukkit - decompile error - chunksLock.unlock(); - return ichunkaccess; - // Akarin end } public CompletableFuture a(Iterable iterable, Consumer consumer) { @@ -226,8 +209,7 @@ public class ChunkProviderServer implements IChunkProvider { Long2ObjectMap long2objectmap = this.chunks; Chunk chunk; - chunksLock.lock(); // Akarin - /*synchronized (this.chunks)*/ { // Akarin + synchronized (this.chunks) { Chunk chunk1 = (Chunk) this.chunks.get(k); if (chunk1 != null) { @@ -246,7 +228,6 @@ public class ChunkProviderServer implements IChunkProvider { this.chunks.put(k, chunk); } - chunksLock.unlock(); // Akarin chunk.addEntities(); return chunk; diff --git a/sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java new file mode 100644 index 000000000..75c480981 --- /dev/null +++ b/sources/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -0,0 +1,795 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import com.destroystokyo.paper.exception.ServerEventException; +import com.destroystokyo.paper.exception.ServerPluginEnableDisableException; +import org.apache.commons.lang.Validate; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.PluginCommandYamlParser; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.util.FileUtil; + +import com.google.common.collect.ImmutableSet; + +import io.akarin.api.internal.Akari; + +/** + * Akarin Changes Note + * 1) Lock for event (safety issue) + */ +/** + * Handles all plugin management from the Server + */ +public final class SimplePluginManager implements PluginManager { + private final Server server; + private final Map fileAssociations = new HashMap(); + private final List plugins = new ArrayList(); + private final Map lookupNames = new HashMap(); + private File updateDirectory; + private final SimpleCommandMap commandMap; + private final Map permissions = new HashMap(); + private final Map> defaultPerms = new LinkedHashMap>(); + private final Map> permSubs = new HashMap>(); + private final Map> defSubs = new HashMap>(); + private boolean useTimings = false; + + public SimplePluginManager(Server instance, SimpleCommandMap commandMap) { + server = instance; + this.commandMap = commandMap; + + defaultPerms.put(true, new HashSet()); + defaultPerms.put(false, new HashSet()); + } + + /** + * Registers the specified plugin loader + * + * @param loader Class name of the PluginLoader to register + * @throws IllegalArgumentException Thrown when the given Class is not a + * valid PluginLoader + */ + public void registerInterface(Class loader) throws IllegalArgumentException { + PluginLoader instance; + + if (PluginLoader.class.isAssignableFrom(loader)) { + Constructor constructor; + + try { + constructor = loader.getConstructor(Server.class); + instance = constructor.newInstance(server); + } catch (NoSuchMethodException ex) { + String className = loader.getName(); + + throw new IllegalArgumentException(String.format("Class %s does not have a public %s(Server) constructor", className, className), ex); + } catch (Exception ex) { + throw new IllegalArgumentException(String.format("Unexpected exception %s while attempting to construct a new instance of %s", ex.getClass().getName(), loader.getName()), ex); + } + } else { + throw new IllegalArgumentException(String.format("Class %s does not implement interface PluginLoader", loader.getName())); + } + + Pattern[] patterns = instance.getPluginFileFilters(); + + synchronized (this) { + for (Pattern pattern : patterns) { + fileAssociations.put(pattern, instance); + } + } + } + + /** + * Loads the plugins contained within the specified directory + * + * @param directory Directory to check for plugins + * @return A list of all plugins loaded + */ + public Plugin[] loadPlugins(File directory) { + Validate.notNull(directory, "Directory cannot be null"); + Validate.isTrue(directory.isDirectory(), "Directory must be a directory"); + + List result = new ArrayList(); + Set filters = fileAssociations.keySet(); + + if (!(server.getUpdateFolder().equals(""))) { + updateDirectory = new File(directory, server.getUpdateFolder()); + } + + Map plugins = new HashMap(); + Set loadedPlugins = new HashSet(); + Map> dependencies = new HashMap>(); + Map> softDependencies = new HashMap>(); + + // This is where it figures out all possible plugins + for (File file : directory.listFiles()) { + PluginLoader loader = null; + for (Pattern filter : filters) { + Matcher match = filter.matcher(file.getName()); + if (match.find()) { + loader = fileAssociations.get(filter); + } + } + + if (loader == null) continue; + + PluginDescriptionFile description = null; + try { + description = loader.getPluginDescription(file); + String name = description.getName(); + if (name.equalsIgnoreCase("bukkit") || name.equalsIgnoreCase("minecraft") || name.equalsIgnoreCase("mojang")) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': Restricted Name"); + continue; + } else if (description.rawName.indexOf(' ') != -1) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': uses the space-character (0x20) in its name"); + continue; + } + } catch (InvalidDescriptionException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + continue; + } + + File replacedFile = plugins.put(description.getName(), file); + if (replacedFile != null) { + server.getLogger().severe(String.format( + "Ambiguous plugin name `%s' for files `%s' and `%s' in `%s'", + description.getName(), + file.getPath(), + replacedFile.getPath(), + directory.getPath() + )); + } + + Collection softDependencySet = description.getSoftDepend(); + if (softDependencySet != null && !softDependencySet.isEmpty()) { + if (softDependencies.containsKey(description.getName())) { + // Duplicates do not matter, they will be removed together if applicable + softDependencies.get(description.getName()).addAll(softDependencySet); + } else { + softDependencies.put(description.getName(), new LinkedList(softDependencySet)); + } + } + + Collection dependencySet = description.getDepend(); + if (dependencySet != null && !dependencySet.isEmpty()) { + dependencies.put(description.getName(), new LinkedList(dependencySet)); + } + + Collection loadBeforeSet = description.getLoadBefore(); + if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) { + for (String loadBeforeTarget : loadBeforeSet) { + if (softDependencies.containsKey(loadBeforeTarget)) { + softDependencies.get(loadBeforeTarget).add(description.getName()); + } else { + // softDependencies is never iterated, so 'ghost' plugins aren't an issue + Collection shortSoftDependency = new LinkedList(); + shortSoftDependency.add(description.getName()); + softDependencies.put(loadBeforeTarget, shortSoftDependency); + } + } + } + } + + while (!plugins.isEmpty()) { + boolean missingDependency = true; + Iterator> pluginIterator = plugins.entrySet().iterator(); + + while (pluginIterator.hasNext()) { + Map.Entry entry = pluginIterator.next(); + String plugin = entry.getKey(); + + if (dependencies.containsKey(plugin)) { + Iterator dependencyIterator = dependencies.get(plugin).iterator(); + + while (dependencyIterator.hasNext()) { + String dependency = dependencyIterator.next(); + + // Dependency loaded + if (loadedPlugins.contains(dependency)) { + dependencyIterator.remove(); + + // We have a dependency not found + } else if (!plugins.containsKey(dependency)) { + missingDependency = false; + pluginIterator.remove(); + softDependencies.remove(plugin); + dependencies.remove(plugin); + + server.getLogger().log( + Level.SEVERE, + "Could not load '" + entry.getValue().getPath() + "' in folder '" + directory.getPath() + "'", + new UnknownDependencyException(dependency)); + break; + } + } + + if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) { + dependencies.remove(plugin); + } + } + if (softDependencies.containsKey(plugin)) { + Iterator softDependencyIterator = softDependencies.get(plugin).iterator(); + + while (softDependencyIterator.hasNext()) { + String softDependency = softDependencyIterator.next(); + + // Soft depend is no longer around + if (!plugins.containsKey(softDependency)) { + softDependencyIterator.remove(); + } + } + + if (softDependencies.get(plugin).isEmpty()) { + softDependencies.remove(plugin); + } + } + if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) { + // We're clear to load, no more soft or hard dependencies left + File file = plugins.get(plugin); + pluginIterator.remove(); + missingDependency = false; + + try { + result.add(loadPlugin(file)); + loadedPlugins.add(plugin); + continue; + } catch (InvalidPluginException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + } + } + } + + if (missingDependency) { + // We now iterate over plugins until something loads + // This loop will ignore soft dependencies + pluginIterator = plugins.entrySet().iterator(); + + while (pluginIterator.hasNext()) { + Map.Entry entry = pluginIterator.next(); + String plugin = entry.getKey(); + + if (!dependencies.containsKey(plugin)) { + softDependencies.remove(plugin); + missingDependency = false; + File file = entry.getValue(); + pluginIterator.remove(); + + try { + result.add(loadPlugin(file)); + loadedPlugins.add(plugin); + break; + } catch (InvalidPluginException ex) { + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); + } + } + } + // We have no plugins left without a depend + if (missingDependency) { + softDependencies.clear(); + dependencies.clear(); + Iterator failedPluginIterator = plugins.values().iterator(); + + while (failedPluginIterator.hasNext()) { + File file = failedPluginIterator.next(); + failedPluginIterator.remove(); + server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': circular dependency detected"); + } + } + } + } + + return result.toArray(new Plugin[result.size()]); + } + + /** + * Loads the plugin in the specified file + *

+ * File must be valid according to the current enabled Plugin interfaces + * + * @param file File containing the plugin to load + * @return The Plugin loaded, or null if it was invalid + * @throws InvalidPluginException Thrown when the specified file is not a + * valid plugin + * @throws UnknownDependencyException If a required dependency could not + * be found + */ + public synchronized Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException { + Validate.notNull(file, "File cannot be null"); + + checkUpdate(file); + + Set filters = fileAssociations.keySet(); + Plugin result = null; + + for (Pattern filter : filters) { + String name = file.getName(); + Matcher match = filter.matcher(name); + + if (match.find()) { + PluginLoader loader = fileAssociations.get(filter); + + result = loader.loadPlugin(file); + } + } + + if (result != null) { + plugins.add(result); + lookupNames.put(result.getDescription().getName().toLowerCase(java.util.Locale.ENGLISH), result); // Spigot + } + + return result; + } + + private void checkUpdate(File file) { + if (updateDirectory == null || !updateDirectory.isDirectory()) { + return; + } + + File updateFile = new File(updateDirectory, file.getName()); + if (updateFile.isFile() && FileUtil.copy(updateFile, file)) { + updateFile.delete(); + } + } + + /** + * Checks if the given plugin is loaded and returns it when applicable + *

+ * Please note that the name of the plugin is case-sensitive + * + * @param name Name of the plugin to check + * @return Plugin if it exists, otherwise null + */ + public synchronized Plugin getPlugin(String name) { + return lookupNames.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); // Spigot + } + + public synchronized Plugin[] getPlugins() { + return plugins.toArray(new Plugin[plugins.size()]); + } + + /** + * Checks if the given plugin is enabled or not + *

+ * Please note that the name of the plugin is case-sensitive. + * + * @param name Name of the plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public boolean isPluginEnabled(String name) { + Plugin plugin = getPlugin(name); + + return isPluginEnabled(plugin); + } + + /** + * Checks if the given plugin is enabled or not + * + * @param plugin Plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public boolean isPluginEnabled(Plugin plugin) { + if ((plugin != null) && (plugins.contains(plugin))) { + return plugin.isEnabled(); + } else { + return false; + } + } + + public void enablePlugin(final Plugin plugin) { + if (!plugin.isEnabled()) { + List pluginCommands = PluginCommandYamlParser.parse(plugin); + + if (!pluginCommands.isEmpty()) { + commandMap.registerAll(plugin.getDescription().getName(), pluginCommands); + } + + try { + plugin.getPluginLoader().enablePlugin(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while enabling " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); + } + + HandlerList.bakeAll(); + } + } + + // Paper start - close Classloader on disable + public void disablePlugins() { + disablePlugins(false); + } + + public void disablePlugins(boolean closeClassloaders) { + // Paper end - close Classloader on disable + Plugin[] plugins = getPlugins(); + for (int i = plugins.length - 1; i >= 0; i--) { + disablePlugin(plugins[i], closeClassloaders); // Paper - close Classloader on disable + } + } + + // Paper start - close Classloader on disable + public void disablePlugin(Plugin plugin) { + disablePlugin(plugin, false); + } + + public void disablePlugin(final Plugin plugin, boolean closeClassloader) { + // Paper end - close Classloader on disable + if (plugin.isEnabled()) { + try { + plugin.getPluginLoader().disablePlugin(plugin, closeClassloader); // Paper - close Classloader on disable + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while disabling " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getScheduler().cancelTasks(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getServicesManager().unregisterAll(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while unregistering services for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + HandlerList.unregisterAll(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while unregistering events for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + + try { + server.getMessenger().unregisterIncomingPluginChannel(plugin); + server.getMessenger().unregisterOutgoingPluginChannel(plugin); + } catch (Throwable ex) { + handlePluginException("Error occurred (in the plugin loader) while unregistering plugin channels for " + + plugin.getDescription().getFullName() + " (Is it up to date?)", ex, plugin); // Paper + } + } + } + + // Paper start + private void handlePluginException(String msg, Throwable ex, Plugin plugin) { + server.getLogger().log(Level.SEVERE, msg, ex); + callEvent(new ServerExceptionEvent(new ServerPluginEnableDisableException(msg, ex, plugin))); + } + // Paper end + + public void clearPlugins() { + synchronized (this) { + disablePlugins(true); // Paper - close Classloader on disable + plugins.clear(); + lookupNames.clear(); + HandlerList.unregisterAll(); + fileAssociations.clear(); + permissions.clear(); + defaultPerms.get(true).clear(); + defaultPerms.get(false).clear(); + } + } + + /** + * Calls an event with the given details. + *

+ * This method only synchronizes when the event is not asynchronous. + * + * @param event Event details + */ + public void callEvent(Event event) { + if (event.isAsynchronous()) { + if (Thread.holdsLock(this)) { + throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); + } + if (server.isPrimaryThread()) { + throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread."); + } + fireEvent(event); + } else { + Akari.eventLock.lock(); // Akarin + synchronized (this) { + fireEvent(event); + } + Akari.eventLock.unlock(); // Akarin + } + } + + private void fireEvent(Event event) { + HandlerList handlers = event.getHandlers(); + RegisteredListener[] listeners = handlers.getRegisteredListeners(); + + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { + continue; + } + + try { + registration.callEvent(event); + } catch (AuthorNagException ex) { + Plugin plugin = registration.getPlugin(); + + if (plugin.isNaggable()) { + plugin.setNaggable(false); + + server.getLogger().log(Level.SEVERE, String.format( + "Nag author(s): '%s' of '%s' about the following: %s", + plugin.getDescription().getAuthors(), + plugin.getDescription().getFullName(), + ex.getMessage() + )); + } + } catch (Throwable ex) { + // Paper start - error reporting + String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(); + server.getLogger().log(Level.SEVERE, msg, ex); + if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop + callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); + } + // Paper end + } + } + } + + public void registerEvents(Listener listener, Plugin plugin) { + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled"); + } + + for (Map.Entry, Set> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) { + getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue()); + } + + } + + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) { + registerEvent(event, listener, priority, executor, plugin, false); + } + + /** + * Registers the given event to the specified listener using a directly + * passed EventExecutor + * + * @param event Event class to register + * @param listener PlayerListener to register + * @param priority Priority of this event + * @param executor EventExecutor to register + * @param plugin Plugin to register + * @param ignoreCancelled Do not call executor if event was already + * cancelled + */ + public void registerEvent(Class event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin, boolean ignoreCancelled) { + Validate.notNull(listener, "Listener cannot be null"); + Validate.notNull(priority, "Priority cannot be null"); + Validate.notNull(executor, "Executor cannot be null"); + Validate.notNull(plugin, "Plugin cannot be null"); + + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); + } + + executor = new co.aikar.timings.TimedEventExecutor(executor, plugin, null, event); // Spigot + if (false) { // Spigot - RL handles useTimings check now + getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } else { + getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); + } + } + + private HandlerList getEventListeners(Class type) { + try { + Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList"); + method.setAccessible(true); + return (HandlerList) method.invoke(null); + } catch (Exception e) { + throw new IllegalPluginAccessException(e.toString()); + } + } + + private Class getRegistrationClass(Class clazz) { + try { + clazz.getDeclaredMethod("getHandlerList"); + return clazz; + } catch (NoSuchMethodException e) { + if (clazz.getSuperclass() != null + && !clazz.getSuperclass().equals(Event.class) + && Event.class.isAssignableFrom(clazz.getSuperclass())) { + return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); + } else { + throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!"); + } + } + } + + public Permission getPermission(String name) { + return permissions.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public void addPermission(Permission perm) { + addPermission(perm, true); + } + + @Deprecated + public void addPermission(Permission perm, boolean dirty) { + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + + if (permissions.containsKey(name)) { + throw new IllegalArgumentException("The permission " + name + " is already defined!"); + } + + permissions.put(name, perm); + calculatePermissionDefault(perm, dirty); + } + + public Set getDefaultPermissions(boolean op) { + return ImmutableSet.copyOf(defaultPerms.get(op)); + } + + public void removePermission(Permission perm) { + removePermission(perm.getName()); + } + + public void removePermission(String name) { + permissions.remove(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public void recalculatePermissionDefaults(Permission perm) { + if (perm != null && permissions.containsKey(perm.getName().toLowerCase(java.util.Locale.ENGLISH))) { + defaultPerms.get(true).remove(perm); + defaultPerms.get(false).remove(perm); + + calculatePermissionDefault(perm, true); + } + } + + private void calculatePermissionDefault(Permission perm, boolean dirty) { + if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + defaultPerms.get(true).add(perm); + if (dirty) { + dirtyPermissibles(true); + } + } + if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + defaultPerms.get(false).add(perm); + if (dirty) { + dirtyPermissibles(false); + } + } + } + + @Deprecated + public void dirtyPermissibles() { + dirtyPermissibles(true); + dirtyPermissibles(false); + } + + private void dirtyPermissibles(boolean op) { + Set permissibles = getDefaultPermSubscriptions(op); + + for (Permissible p : permissibles) { + p.recalculatePermissions(); + } + } + + public void subscribeToPermission(String permission, Permissible permissible) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map == null) { + map = new WeakHashMap(); + permSubs.put(name, map); + } + + map.put(permissible, true); + } + + public void unsubscribeFromPermission(String permission, Permissible permissible) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map != null) { + map.remove(permissible); + + if (map.isEmpty()) { + permSubs.remove(name); + } + } + } + + public Set getPermissionSubscriptions(String permission) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = permSubs.get(name); + + if (map == null) { + return ImmutableSet.of(); + } else { + return ImmutableSet.copyOf(map.keySet()); + } + } + + public void subscribeToDefaultPerms(boolean op, Permissible permissible) { + Map map = defSubs.get(op); + + if (map == null) { + map = new WeakHashMap(); + defSubs.put(op, map); + } + + map.put(permissible, true); + } + + public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible) { + Map map = defSubs.get(op); + + if (map != null) { + map.remove(permissible); + + if (map.isEmpty()) { + defSubs.remove(op); + } + } + } + + public Set getDefaultPermSubscriptions(boolean op) { + Map map = defSubs.get(op); + + if (map == null) { + return ImmutableSet.of(); + } else { + return ImmutableSet.copyOf(map.keySet()); + } + } + + public Set getPermissions() { + return new HashSet(permissions.values()); + } + + public boolean useTimings() { + return co.aikar.timings.Timings.isTimingsEnabled(); // Spigot + } + + /** + * Sets whether or not per event timing code should be used + * + * @param use True if per event timing code should be used + */ + public void useTimings(boolean use) { + co.aikar.timings.Timings.setTimingsEnabled(use); // Spigot + } + + // Paper start + public void clearPermissions() { + permissions.clear(); + defaultPerms.get(true).clear(); + defaultPerms.get(false).clear(); + } + // Paper end + +}