requestingReport = Lists.newArrayList();
-+ private static final int MAX_HISTORY_FRAMES = 12;
-+ public static final Timing NULL_HANDLER = new NullTimingHandler();
-+ static boolean timingsEnabled = false;
-+ static boolean verboseEnabled = false;
-+ private static int historyInterval = -1;
-+ private static int historyLength = -1;
-+
-+ private Timings() {}
-+
-+ /**
-+ * Returns a Timing for a plugin corresponding to a name.
-+ *
-+ * @param plugin Plugin to own the Timing
-+ * @param name Name of Timing
-+ * @return Handler
-+ */
-+ @NotNull
-+ public static Timing of(@NotNull Plugin plugin, @NotNull String name) {
-+ Timing pluginHandler = null;
-+ if (plugin != null) {
-+ pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER);
-+ }
-+ return of(plugin, name, pluginHandler);
-+ }
-+
-+ /**
-+ * Returns a handler that has a groupHandler timer handler. Parent timers should not have their
-+ * start/stop methods called directly, as the children will call it for you.
-+ *
-+ * Parent Timers are used to group multiple subsections together and get a summary of them combined
-+ * Parent Handler can not be changed after first call
-+ *
-+ * @param plugin Plugin to own the Timing
-+ * @param name Name of Timing
-+ * @param groupHandler Parent handler to mirror .start/stop calls to
-+ * @return Timing Handler
-+ */
-+ @NotNull
-+ public static Timing of(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) {
-+ Preconditions.checkNotNull(plugin, "Plugin can not be null");
-+ return TimingsManager.getHandler(plugin.getName(), name, groupHandler);
-+ }
-+
-+ /**
-+ * Returns a Timing object after starting it, useful for Java7 try-with-resources.
-+ *
-+ * try (Timing ignored = Timings.ofStart(plugin, someName)) {
-+ * // timed section
-+ * }
-+ *
-+ * @param plugin Plugin to own the Timing
-+ * @param name Name of Timing
-+ * @return Timing Handler
-+ */
-+ @NotNull
-+ public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name) {
-+ return ofStart(plugin, name, null);
-+ }
-+
-+ /**
-+ * Returns a Timing object after starting it, useful for Java7 try-with-resources.
-+ *
-+ * try (Timing ignored = Timings.ofStart(plugin, someName, groupHandler)) {
-+ * // timed section
-+ * }
-+ *
-+ * @param plugin Plugin to own the Timing
-+ * @param name Name of Timing
-+ * @param groupHandler Parent handler to mirror .start/stop calls to
-+ * @return Timing Handler
-+ */
-+ @NotNull
-+ public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) {
-+ Timing timing = of(plugin, name, groupHandler);
-+ timing.startTiming();
-+ return timing;
-+ }
-+
-+ /**
-+ * Gets whether or not the Spigot Timings system is enabled
-+ *
-+ * @return Enabled or not
-+ */
-+ public static boolean isTimingsEnabled() {
-+ return timingsEnabled;
-+ }
-+
-+ /**
-+ * Sets whether or not the Spigot Timings system should be enabled
-+ *
-+ * Calling this will reset timing data.
-+ *
-+ * @param enabled Should timings be reported
-+ */
-+ public static void setTimingsEnabled(boolean enabled) {
-+ timingsEnabled = enabled;
-+ reset();
-+ }
-+
-+ /**
-+ * Sets whether or not the Timings should monitor at Verbose level.
-+ *
-+ * When Verbose is disabled, high-frequency timings will not be available.
-+ *
-+ * @return Enabled or not
-+ */
-+ public static boolean isVerboseTimingsEnabled() {
-+ return verboseEnabled;
-+ }
-+
-+ /**
-+ * Sets whether or not the Timings should monitor at Verbose level.
-+ *
-+ * When Verbose is disabled, high-frequency timings will not be available.
-+ * Calling this will reset timing data.
-+ *
-+ * @param enabled Should high-frequency timings be reported
-+ */
-+ public static void setVerboseTimingsEnabled(boolean enabled) {
-+ verboseEnabled = enabled;
-+ TimingsManager.needsRecheckEnabled = true;
-+ }
-+
-+ /**
-+ * Gets the interval between Timing History report generation.
-+ *
-+ * Defaults to 5 minutes (6000 ticks)
-+ *
-+ * @return Interval in ticks
-+ */
-+ public static int getHistoryInterval() {
-+ return historyInterval;
-+ }
-+
-+ /**
-+ * Sets the interval between Timing History report generations.
-+ *
-+ * Defaults to 5 minutes (6000 ticks)
-+ *
-+ * This will recheck your history length, so lowering this value will lower your
-+ * history length if you need more than 60 history windows.
-+ *
-+ * @param interval Interval in ticks
-+ */
-+ public static void setHistoryInterval(int interval) {
-+ historyInterval = Math.max(20*60, interval);
-+ // Recheck the history length with the new Interval
-+ if (historyLength != -1) {
-+ setHistoryLength(historyLength);
-+ }
-+ }
-+
-+ /**
-+ * Gets how long in ticks Timings history is kept for the server.
-+ *
-+ * Defaults to 1 hour (72000 ticks)
-+ *
-+ * @return Duration in Ticks
-+ */
-+ public static int getHistoryLength() {
-+ return historyLength;
-+ }
-+
-+ /**
-+ * Sets how long Timing History reports are kept for the server.
-+ *
-+ * Defaults to 1 hours(72000 ticks)
-+ *
-+ * This value is capped at a maximum of getHistoryInterval() * MAX_HISTORY_FRAMES (12)
-+ *
-+ * Will not reset Timing Data but may truncate old history if the new length is less than old length.
-+ *
-+ * @param length Duration in ticks
-+ */
-+ public static void setHistoryLength(int length) {
-+ // Cap at 12 History Frames, 1 hour at 5 minute frames.
-+ int maxLength = historyInterval * MAX_HISTORY_FRAMES;
-+ // For special cases of servers with special permission to bypass the max.
-+ // This max helps keep data file sizes reasonable for processing on Aikar's Timing parser side.
-+ // Setting this will not help you bypass the max unless Aikar has added an exception on the API side.
-+ if (System.getProperty("timings.bypassMax") != null) {
-+ maxLength = Integer.MAX_VALUE;
-+ }
-+ historyLength = Math.max(Math.min(maxLength, length), historyInterval);
-+ Queue oldQueue = TimingsManager.HISTORY;
-+ int frames = (getHistoryLength() / getHistoryInterval());
-+ if (length > maxLength) {
-+ Bukkit.getLogger().log(Level.WARNING, "Timings Length too high. Requested " + length + ", max is " + maxLength + ". To get longer history, you must increase your interval. Set Interval to " + Math.ceil(length / MAX_HISTORY_FRAMES) + " to achieve this length.");
-+ }
-+ TimingsManager.HISTORY = EvictingQueue.create(frames);
-+ TimingsManager.HISTORY.addAll(oldQueue);
-+ }
-+
-+ /**
-+ * Resets all Timing Data
-+ */
-+ public static void reset() {
-+ TimingsManager.reset();
-+ }
-+
-+ /**
-+ * Generates a report and sends it to the specified command sender.
-+ *
-+ * If sender is null, ConsoleCommandSender will be used.
-+ * @param sender The sender to send to, or null to use the ConsoleCommandSender
-+ */
-+ public static void generateReport(@Nullable CommandSender sender) {
-+ if (sender == null) {
-+ sender = Bukkit.getConsoleSender();
-+ }
-+ requestingReport.add(sender);
-+ }
-+
-+ /**
-+ * Generates a report and sends it to the specified listener.
-+ * Use with {@link org.bukkit.command.BufferedCommandSender} to get full response when done!
-+ * @param sender The listener to send responses too.
-+ */
-+ public static void generateReport(@NotNull TimingsReportListener sender) {
-+ Validate.notNull(sender);
-+ requestingReport.add(sender);
-+ }
-+
-+ /*
-+ =================
-+ Protected API: These are for internal use only in Bukkit/CraftBukkit
-+ These do not have isPrimaryThread() checks in the startTiming/stopTiming
-+ =================
-+ */
-+ @NotNull
-+ static TimingHandler ofSafe(@NotNull String name) {
-+ return ofSafe(null, name, null);
-+ }
-+
-+ @NotNull
-+ static Timing ofSafe(@Nullable Plugin plugin, @NotNull String name) {
-+ Timing pluginHandler = null;
-+ if (plugin != null) {
-+ pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER);
-+ }
-+ return ofSafe(plugin != null ? plugin.getName() : "Minecraft - Invalid Plugin", name, pluginHandler);
-+ }
-+
-+ @NotNull
-+ static TimingHandler ofSafe(@NotNull String name, @Nullable Timing groupHandler) {
-+ return ofSafe(null, name, groupHandler);
-+ }
-+
-+ @NotNull
-+ static TimingHandler ofSafe(@Nullable String groupName, @NotNull String name, @Nullable Timing groupHandler) {
-+ return TimingsManager.getHandler(groupName, name, groupHandler);
-+ }
-+}
-diff --git a/src/main/java/co/aikar/timings/TimingsCommand.java b/src/main/java/co/aikar/timings/TimingsCommand.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..f7c2245a310a084367ff25db539b3c967d5cb141
---- /dev/null
-+++ b/src/main/java/co/aikar/timings/TimingsCommand.java
-@@ -0,0 +1,119 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2014 Daniel Ennis
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
-+package co.aikar.timings;
-+
-+import com.google.common.collect.ImmutableList;
-+import org.apache.commons.lang.Validate;
-+import org.bukkit.ChatColor;
-+import org.bukkit.command.CommandSender;
-+import org.bukkit.command.defaults.BukkitCommand;
-+import org.bukkit.util.StringUtil;
-+
-+import java.util.ArrayList;
-+import java.util.List;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+
-+public class TimingsCommand extends BukkitCommand {
-+ private static final List TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste", "verbon", "verboff");
-+ private long lastResetAttempt = 0;
-+
-+ public TimingsCommand(@NotNull String name) {
-+ super(name);
-+ this.description = "Manages Spigot Timings data to see performance of the server.";
-+ this.usageMessage = "/timings ";
-+ this.setPermission("bukkit.command.timings");
-+ }
-+
-+ @Override
-+ public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) {
-+ if (!testPermission(sender)) {
-+ return true;
-+ }
-+ if (args.length < 1) {
-+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
-+ return true;
-+ }
-+ final String arg = args[0];
-+ if ("on".equalsIgnoreCase(arg)) {
-+ Timings.setTimingsEnabled(true);
-+ sender.sendMessage("Enabled Timings & Reset");
-+ return true;
-+ } else if ("off".equalsIgnoreCase(arg)) {
-+ Timings.setTimingsEnabled(false);
-+ sender.sendMessage("Disabled Timings");
-+ return true;
-+ }
-+
-+ if (!Timings.isTimingsEnabled()) {
-+ sender.sendMessage("Please enable timings by typing /timings on");
-+ return true;
-+ }
-+
-+ long now = System.currentTimeMillis();
-+ if ("verbon".equalsIgnoreCase(arg)) {
-+ Timings.setVerboseTimingsEnabled(true);
-+ sender.sendMessage("Enabled Verbose Timings");
-+ return true;
-+ } else if ("verboff".equalsIgnoreCase(arg)) {
-+ Timings.setVerboseTimingsEnabled(false);
-+ sender.sendMessage("Disabled Verbose Timings");
-+ return true;
-+ } else if ("reset".equalsIgnoreCase(arg)) {
-+ if (now - lastResetAttempt < 30000) {
-+ TimingsManager.reset();
-+ sender.sendMessage(ChatColor.RED + "Timings reset. Please wait 5-10 minutes before using /timings report.");
-+ } else {
-+ lastResetAttempt = now;
-+ sender.sendMessage(ChatColor.RED + "WARNING: Timings v2 should not be reset. If you are encountering lag, please wait 3 minutes and then issue a report. The best timings will include 10+ minutes, with data before and after your lag period. If you really want to reset, run this command again within 30 seconds.");
-+ }
-+ } else if (
-+ "paste".equalsIgnoreCase(arg) ||
-+ "report".equalsIgnoreCase(arg) ||
-+ "get".equalsIgnoreCase(arg) ||
-+ "merged".equalsIgnoreCase(arg) ||
-+ "separate".equalsIgnoreCase(arg)
-+ ) {
-+ Timings.generateReport(sender);
-+ } else {
-+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
-+ }
-+ return true;
-+ }
-+
-+ @NotNull
-+ @Override
-+ public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) {
-+ Validate.notNull(sender, "Sender cannot be null");
-+ Validate.notNull(args, "Arguments cannot be null");
-+ Validate.notNull(alias, "Alias cannot be null");
-+
-+ if (args.length == 1) {
-+ return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS,
-+ new ArrayList(TIMINGS_SUBCOMMANDS.size()));
-+ }
-+ return ImmutableList.of();
-+ }
-+}
-diff --git a/src/main/java/co/aikar/timings/TimingsManager.java b/src/main/java/co/aikar/timings/TimingsManager.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..ef824d701c97cad8b31e76ad98c94fc4367a7eda
---- /dev/null
-+++ b/src/main/java/co/aikar/timings/TimingsManager.java
-@@ -0,0 +1,188 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2014 Daniel Ennis
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
-+package co.aikar.timings;
-+
-+import co.aikar.util.LoadingMap;
-+import com.google.common.collect.EvictingQueue;
-+import org.bukkit.Bukkit;
-+import org.bukkit.Server;
-+import org.bukkit.command.Command;
-+import org.bukkit.plugin.Plugin;
-+import org.bukkit.plugin.java.PluginClassLoader;
-+
-+import java.util.ArrayList;
-+import java.util.Collections;
-+import java.util.List;
-+import java.util.Map;
-+import java.util.concurrent.ConcurrentHashMap;
-+import java.util.logging.Level;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+public final class TimingsManager {
-+ static final Map TIMING_MAP = LoadingMap.of(
-+ new ConcurrentHashMap<>(4096, .5F), TimingHandler::new
-+ );
-+ public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler();
-+ public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK);
-+ public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins");
-+ public static List hiddenConfigs = new ArrayList();
-+ public static boolean privacy = false;
-+
-+ static final List HANDLERS = new ArrayList<>(1024);
-+ static final List MINUTE_REPORTS = new ArrayList<>(64);
-+
-+ static EvictingQueue HISTORY = EvictingQueue.create(12);
-+ static long timingStart = 0;
-+ static long historyStart = 0;
-+ static boolean needsFullReset = false;
-+ static boolean needsRecheckEnabled = false;
-+
-+ private TimingsManager() {}
-+
-+ /**
-+ * Resets all timing data on the next tick
-+ */
-+ static void reset() {
-+ needsFullReset = true;
-+ }
-+
-+ /**
-+ * Ticked every tick by CraftBukkit to count the number of times a timer
-+ * caused TPS loss.
-+ */
-+ static void tick() {
-+ if (Timings.timingsEnabled) {
-+ boolean violated = FULL_SERVER_TICK.isViolated();
-+
-+ for (TimingHandler handler : HANDLERS) {
-+ if (handler.isSpecial()) {
-+ // We manually call this
-+ continue;
-+ }
-+ handler.processTick(violated);
-+ }
-+
-+ TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size();
-+ TimingHistory.timedTicks++;
-+ // Generate TPS/Ping/Tick reports every minute
-+ }
-+ }
-+ static void stopServer() {
-+ Timings.timingsEnabled = false;
-+ recheckEnabled();
-+ }
-+ static void recheckEnabled() {
-+ synchronized (TIMING_MAP) {
-+ for (TimingHandler timings : TIMING_MAP.values()) {
-+ timings.checkEnabled();
-+ }
-+ }
-+ needsRecheckEnabled = false;
-+ }
-+ static void resetTimings() {
-+ if (needsFullReset) {
-+ // Full resets need to re-check every handlers enabled state
-+ // Timing map can be modified from async so we must sync on it.
-+ synchronized (TIMING_MAP) {
-+ for (TimingHandler timings : TIMING_MAP.values()) {
-+ timings.reset(true);
-+ }
-+ }
-+ Bukkit.getLogger().log(Level.INFO, "Timings Reset");
-+ HISTORY.clear();
-+ needsFullReset = false;
-+ needsRecheckEnabled = false;
-+ timingStart = System.currentTimeMillis();
-+ } else {
-+ // Soft resets only need to act on timings that have done something
-+ // Handlers can only be modified on main thread.
-+ for (TimingHandler timings : HANDLERS) {
-+ timings.reset(false);
-+ }
-+ }
-+
-+ HANDLERS.clear();
-+ MINUTE_REPORTS.clear();
-+
-+ TimingHistory.resetTicks(true);
-+ historyStart = System.currentTimeMillis();
-+ }
-+
-+ @NotNull
-+ static TimingHandler getHandler(@Nullable String group, @NotNull String name, @Nullable Timing parent) {
-+ return TIMING_MAP.get(new TimingIdentifier(group, name, parent));
-+ }
-+
-+
-+ /**
-+ * Due to access restrictions, we need a helper method to get a Command TimingHandler with String group
-+ *
-+ * Plugins should never call this
-+ *
-+ * @param pluginName Plugin this command is associated with
-+ * @param command Command to get timings for
-+ * @return TimingHandler
-+ */
-+ @NotNull
-+ public static Timing getCommandTiming(@Nullable String pluginName, @NotNull Command command) {
-+ Plugin plugin = null;
-+ final Server server = Bukkit.getServer();
-+ if (!( server == null || pluginName == null ||
-+ "minecraft".equals(pluginName) || "bukkit".equals(pluginName) ||
-+ "spigot".equalsIgnoreCase(pluginName) || "paper".equals(pluginName)
-+ )) {
-+ plugin = server.getPluginManager().getPlugin(pluginName);
-+ }
-+ if (plugin == null) {
-+ // Plugin is passing custom fallback prefix, try to look up by class loader
-+ plugin = getPluginByClassloader(command.getClass());
-+ }
-+ if (plugin == null) {
-+ return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName());
-+ }
-+
-+ return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName());
-+ }
-+
-+ /**
-+ * Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the
-+ * Plugin that created this class.
-+ *
-+ * @param clazz Class to check
-+ * @return Plugin if created by a plugin
-+ */
-+ @Nullable
-+ public static Plugin getPluginByClassloader(@Nullable Class> clazz) {
-+ if (clazz == null) {
-+ return null;
-+ }
-+ final ClassLoader classLoader = clazz.getClassLoader();
-+ if (classLoader instanceof PluginClassLoader) {
-+ PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader;
-+ return pluginClassLoader.getPlugin();
-+ }
-+ return null;
-+ }
-+}
-diff --git a/src/main/java/co/aikar/timings/TimingsReportListener.java b/src/main/java/co/aikar/timings/TimingsReportListener.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..ef58a6c00f444bd498a2d8fc4e457236f393954f
---- /dev/null
-+++ b/src/main/java/co/aikar/timings/TimingsReportListener.java
-@@ -0,0 +1,77 @@
-+package co.aikar.timings;
-+
-+import com.google.common.collect.Lists;
-+import org.apache.commons.lang.Validate;
-+import org.bukkit.Bukkit;
-+import org.bukkit.command.CommandSender;
-+import org.bukkit.command.ConsoleCommandSender;
-+import org.bukkit.command.MessageCommandSender;
-+import org.bukkit.command.RemoteConsoleCommandSender;
-+
-+import java.util.List;
-+import java.util.UUID;
-+
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+@SuppressWarnings("WeakerAccess")
-+public class TimingsReportListener implements MessageCommandSender {
-+ private final List senders;
-+ private final Runnable onDone;
-+ private String timingsURL;
-+
-+ public TimingsReportListener(@NotNull CommandSender senders) {
-+ this(senders, null);
-+ }
-+ public TimingsReportListener(@NotNull CommandSender sender, @Nullable Runnable onDone) {
-+ this(Lists.newArrayList(sender), onDone);
-+ }
-+ public TimingsReportListener(@NotNull List senders) {
-+ this(senders, null);
-+ }
-+ public TimingsReportListener(@NotNull List senders, @Nullable Runnable onDone) {
-+ Validate.notNull(senders);
-+ Validate.notEmpty(senders);
-+
-+ this.senders = Lists.newArrayList(senders);
-+ this.onDone = onDone;
-+ }
-+
-+ @Nullable
-+ public String getTimingsURL() {
-+ return timingsURL;
-+ }
-+
-+ public void done() {
-+ done(null);
-+ }
-+
-+ public void done(@Nullable String url) {
-+ this.timingsURL = url;
-+ if (onDone != null) {
-+ onDone.run();
-+ }
-+ for (CommandSender sender : senders) {
-+ if (sender instanceof TimingsReportListener) {
-+ ((TimingsReportListener) sender).done();
-+ }
-+ }
-+ }
-+
-+ @Override
-+ public void sendMessage(@NotNull String message) {
-+ senders.forEach((sender) -> sender.sendMessage(message));
-+ }
-+
-+ public void addConsoleIfNeeded() {
-+ boolean hasConsole = false;
-+ for (CommandSender sender : this.senders) {
-+ if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
-+ hasConsole = true;
-+ }
-+ }
-+ if (!hasConsole) {
-+ this.senders.add(Bukkit.getConsoleSender());
-+ }
-+ }
-+}
-diff --git a/src/main/java/co/aikar/timings/UnsafeTimingHandler.java b/src/main/java/co/aikar/timings/UnsafeTimingHandler.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..632c4961515f5052551f841cfa840e60bba7a257
---- /dev/null
-+++ b/src/main/java/co/aikar/timings/UnsafeTimingHandler.java
-@@ -0,0 +1,53 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2014 Daniel Ennis
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
-+package co.aikar.timings;
-+
-+import org.bukkit.Bukkit;
-+import org.jetbrains.annotations.NotNull;
-+
-+class UnsafeTimingHandler extends TimingHandler {
-+
-+ UnsafeTimingHandler(@NotNull TimingIdentifier id) {
-+ super(id);
-+ }
-+
-+ private static void checkThread() {
-+ if (!Bukkit.isPrimaryThread()) {
-+ throw new IllegalStateException("Calling Timings from Async Operation");
-+ }
-+ }
-+
-+ @NotNull
-+ @Override
-+ public Timing startTiming() {
-+ checkThread();
-+ return super.startTiming();
-+ }
-+
-+ @Override
-+ public void stopTiming() {
-+ checkThread();
-+ super.stopTiming();
-+ }
-+}
-diff --git a/src/main/java/co/aikar/util/Counter.java b/src/main/java/co/aikar/util/Counter.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..80155072d1004e34e04342d434cf7d75f0b7e29d
---- /dev/null
-+++ b/src/main/java/co/aikar/util/Counter.java
-@@ -0,0 +1,38 @@
-+package co.aikar.util;
-+
-+import com.google.common.collect.ForwardingMap;
-+
-+import java.util.HashMap;
-+import java.util.Map;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+public class Counter extends ForwardingMap {
-+ private final Map counts = new HashMap<>();
-+
-+ public long decrement(@Nullable T key) {
-+ return increment(key, -1);
-+ }
-+ public long increment(@Nullable T key) {
-+ return increment(key, 1);
-+ }
-+ public long decrement(@Nullable T key, long amount) {
-+ return decrement(key, -amount);
-+ }
-+ public long increment(@Nullable T key, long amount) {
-+ Long count = this.getCount(key);
-+ count += amount;
-+ this.counts.put(key, count);
-+ return count;
-+ }
-+
-+ public long getCount(@Nullable T key) {
-+ return this.counts.getOrDefault(key, 0L);
-+ }
-+
-+ @NotNull
-+ @Override
-+ protected Map delegate() {
-+ return this.counts;
-+ }
-+}
-diff --git a/src/main/java/co/aikar/util/JSONUtil.java b/src/main/java/co/aikar/util/JSONUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..190bf0598442c89c2a1c93ad7c8c1a29797304ae
---- /dev/null
-+++ b/src/main/java/co/aikar/util/JSONUtil.java
-@@ -0,0 +1,140 @@
-+package co.aikar.util;
-+
-+import com.google.common.base.Function;
-+import com.google.common.collect.Lists;
-+import com.google.common.collect.Maps;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+import org.json.simple.JSONArray;
-+import org.json.simple.JSONObject;
-+
-+import java.util.ArrayList;
-+import java.util.LinkedHashMap;
-+import java.util.List;
-+import java.util.Map;
-+
-+/**
-+ * Provides Utility methods that assist with generating JSON Objects
-+ */
-+@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
-+public final class JSONUtil {
-+ private JSONUtil() {}
-+
-+ /**
-+ * Creates a key/value "JSONPair" object
-+ *
-+ * @param key Key to use
-+ * @param obj Value to use
-+ * @return JSONPair
-+ */
-+ @NotNull
-+ public static JSONPair pair(@NotNull String key, @Nullable Object obj) {
-+ return new JSONPair(key, obj);
-+ }
-+
-+ @NotNull
-+ public static JSONPair pair(long key, @Nullable Object obj) {
-+ return new JSONPair(String.valueOf(key), obj);
-+ }
-+
-+ /**
-+ * Creates a new JSON object from multiple JSONPair key/value pairs
-+ *
-+ * @param data JSONPairs
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map createObject(@NotNull JSONPair... data) {
-+ return appendObjectData(new LinkedHashMap(), data);
-+ }
-+
-+ /**
-+ * This appends multiple key/value Obj pairs into a JSON Object
-+ *
-+ * @param parent Map to be appended to
-+ * @param data Data to append
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map appendObjectData(@NotNull Map parent, @NotNull JSONPair... data) {
-+ for (JSONPair JSONPair : data) {
-+ parent.put(JSONPair.key, JSONPair.val);
-+ }
-+ return parent;
-+ }
-+
-+ /**
-+ * This builds a JSON array from a set of data
-+ *
-+ * @param data Data to build JSON array from
-+ * @return List
-+ */
-+ @NotNull
-+ public static List toArray(@NotNull Object... data) {
-+ return Lists.newArrayList(data);
-+ }
-+
-+ /**
-+ * These help build a single JSON array using a mapper function
-+ *
-+ * @param collection Collection to apply to
-+ * @param mapper Mapper to apply
-+ * @param Element Type
-+ * @return List
-+ */
-+ @NotNull
-+ public static List toArrayMapper(@NotNull E[] collection, @NotNull Function mapper) {
-+ return toArrayMapper(Lists.newArrayList(collection), mapper);
-+ }
-+
-+ @NotNull
-+ public static List toArrayMapper(@NotNull Iterable collection, @NotNull Function mapper) {
-+ List array = Lists.newArrayList();
-+ for (E e : collection) {
-+ Object object = mapper.apply(e);
-+ if (object != null) {
-+ array.add(object);
-+ }
-+ }
-+ return array;
-+ }
-+
-+ /**
-+ * These help build a single JSON Object from a collection, using a mapper function
-+ *
-+ * @param collection Collection to apply to
-+ * @param mapper Mapper to apply
-+ * @param Element Type
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map toObjectMapper(@NotNull E[] collection, @NotNull Function mapper) {
-+ return toObjectMapper(Lists.newArrayList(collection), mapper);
-+ }
-+
-+ @NotNull
-+ public static Map toObjectMapper(@NotNull Iterable collection, @NotNull Function mapper) {
-+ Map object = Maps.newLinkedHashMap();
-+ for (E e : collection) {
-+ JSONPair JSONPair = mapper.apply(e);
-+ if (JSONPair != null) {
-+ object.put(JSONPair.key, JSONPair.val);
-+ }
-+ }
-+ return object;
-+ }
-+
-+ /**
-+ * Simply stores a key and a value, used internally by many methods below.
-+ */
-+ @SuppressWarnings("PublicInnerClass")
-+ public static class JSONPair {
-+ final String key;
-+ final Object val;
-+
-+ JSONPair(@NotNull String key, @NotNull Object val) {
-+ this.key = key;
-+ this.val = val;
-+ }
-+ }
-+}
-diff --git a/src/main/java/co/aikar/util/LoadingIntMap.java b/src/main/java/co/aikar/util/LoadingIntMap.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..63a899c7dbdb69daa4876a2ce2a7dfb734b5af9d
---- /dev/null
-+++ b/src/main/java/co/aikar/util/LoadingIntMap.java
-@@ -0,0 +1,76 @@
-+/*
-+ * Copyright (c) 2015. Starlis LLC / dba Empire Minecraft
-+ *
-+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
-+ *
-+ */
-+package co.aikar.util;
-+
-+
-+import com.google.common.base.Function;
-+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Allows you to pass a Loader function that when a key is accessed that doesn't exist,
-+ * automatically loads the entry into the map by calling the loader Function.
-+ *
-+ * .get() Will only return null if the Loader can return null.
-+ *
-+ * You may pass any backing Map to use.
-+ *
-+ * This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed.
-+ *
-+ * Do not wrap the backing map with Collections.synchronizedMap.
-+ *
-+ * @param Value
-+ */
-+public class LoadingIntMap extends Int2ObjectOpenHashMap {
-+ private final Function loader;
-+
-+ public LoadingIntMap(@NotNull Function loader) {
-+ super();
-+ this.loader = loader;
-+ }
-+
-+ public LoadingIntMap(int expectedSize, @NotNull Function loader) {
-+ super(expectedSize);
-+ this.loader = loader;
-+ }
-+
-+ public LoadingIntMap(int expectedSize, float loadFactor, @NotNull Function loader) {
-+ super(expectedSize, loadFactor);
-+ this.loader = loader;
-+ }
-+
-+
-+ @Nullable
-+ @Override
-+ public V get(int key) {
-+ V res = super.get(key);
-+ if (res == null) {
-+ res = loader.apply(key);
-+ if (res != null) {
-+ put(key, res);
-+ }
-+ }
-+ return res;
-+ }
-+
-+ /**
-+ * Due to java stuff, you will need to cast it to (Function) for some cases
-+ *
-+ * @param Type
-+ */
-+ public abstract static class Feeder implements Function {
-+ @Nullable
-+ @Override
-+ public T apply(@Nullable Object input) {
-+ return apply();
-+ }
-+
-+ @Nullable
-+ public abstract T apply();
-+ }
-+}
-diff --git a/src/main/java/co/aikar/util/LoadingMap.java b/src/main/java/co/aikar/util/LoadingMap.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..aedbb03321886cb267879d7994653e447b485f6a
---- /dev/null
-+++ b/src/main/java/co/aikar/util/LoadingMap.java
-@@ -0,0 +1,368 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2014 Daniel Ennis
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
-+package co.aikar.util;
-+
-+import com.google.common.base.Preconditions;
-+import java.lang.reflect.Constructor;
-+import java.util.AbstractMap;
-+import java.util.Collection;
-+import java.util.HashMap;
-+import java.util.IdentityHashMap;
-+import java.util.Map;
-+import java.util.Set;
-+import java.util.function.Function;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Allows you to pass a Loader function that when a key is accessed that doesn't exists,
-+ * automatically loads the entry into the map by calling the loader Function.
-+ *
-+ * .get() Will only return null if the Loader can return null.
-+ *
-+ * You may pass any backing Map to use.
-+ *
-+ * This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed.
-+ *
-+ * Do not wrap the backing map with Collections.synchronizedMap.
-+ *
-+ * @param Key
-+ * @param Value
-+ */
-+public class LoadingMap extends AbstractMap {
-+ private final Map backingMap;
-+ private final java.util.function.Function loader;
-+
-+ /**
-+ * Initializes an auto loading map using specified loader and backing map
-+ * @param backingMap Map to wrap
-+ * @param loader Loader
-+ */
-+ public LoadingMap(@NotNull Map backingMap, @NotNull java.util.function.Function loader) {
-+ this.backingMap = backingMap;
-+ this.loader = loader;
-+ }
-+
-+ /**
-+ * Creates a new LoadingMap with the specified map and loader
-+ *
-+ * @param backingMap Actual map being used.
-+ * @param loader Loader to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map of(@NotNull Map backingMap, @NotNull Function loader) {
-+ return new LoadingMap<>(backingMap, loader);
-+ }
-+
-+ /**
-+ * Creates a LoadingMap with an auto instantiating loader.
-+ *
-+ * Will auto construct class of of Value when not found
-+ *
-+ * Since this uses Reflection, It is more effecient to define your own static loader
-+ * than using this helper, but if performance is not critical, this is easier.
-+ *
-+ * @param backingMap Actual map being used.
-+ * @param keyClass Class used for the K generic
-+ * @param valueClass Class used for the V generic
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map that auto instantiates on .get()
-+ */
-+ @NotNull
-+ public static Map newAutoMap(@NotNull Map backingMap, @Nullable final Class extends K> keyClass,
-+ @NotNull final Class extends V> valueClass) {
-+ return new LoadingMap<>(backingMap, new AutoInstantiatingLoader<>(keyClass, valueClass));
-+ }
-+ /**
-+ * Creates a LoadingMap with an auto instantiating loader.
-+ *
-+ * Will auto construct class of of Value when not found
-+ *
-+ * Since this uses Reflection, It is more effecient to define your own static loader
-+ * than using this helper, but if performance is not critical, this is easier.
-+ *
-+ * @param backingMap Actual map being used.
-+ * @param valueClass Class used for the V generic
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map that auto instantiates on .get()
-+ */
-+ @NotNull
-+ public static Map newAutoMap(@NotNull Map backingMap,
-+ @NotNull final Class extends V> valueClass) {
-+ return newAutoMap(backingMap, null, valueClass);
-+ }
-+
-+ /**
-+ * @see #newAutoMap
-+ *
-+ * new Auto initializing map using a HashMap.
-+ *
-+ * @param keyClass Class used for the K generic
-+ * @param valueClass Class used for the V generic
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map that auto instantiates on .get()
-+ */
-+ @NotNull
-+ public static Map newHashAutoMap(@Nullable final Class extends K> keyClass, @NotNull final Class extends V> valueClass) {
-+ return newAutoMap(new HashMap<>(), keyClass, valueClass);
-+ }
-+
-+ /**
-+ * @see #newAutoMap
-+ *
-+ * new Auto initializing map using a HashMap.
-+ *
-+ * @param valueClass Class used for the V generic
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map that auto instantiates on .get()
-+ */
-+ @NotNull
-+ public static Map newHashAutoMap(@NotNull final Class extends V> valueClass) {
-+ return newHashAutoMap(null, valueClass);
-+ }
-+
-+ /**
-+ * @see #newAutoMap
-+ *
-+ * new Auto initializing map using a HashMap.
-+ *
-+ * @param keyClass Class used for the K generic
-+ * @param valueClass Class used for the V generic
-+ * @param initialCapacity Initial capacity to use
-+ * @param loadFactor Load factor to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map that auto instantiates on .get()
-+ */
-+ @NotNull
-+ public static Map newHashAutoMap(@Nullable final Class extends K> keyClass, @NotNull final Class extends V> valueClass, int initialCapacity, float loadFactor) {
-+ return newAutoMap(new HashMap<>(initialCapacity, loadFactor), keyClass, valueClass);
-+ }
-+
-+ /**
-+ * @see #newAutoMap
-+ *
-+ * new Auto initializing map using a HashMap.
-+ *
-+ * @param valueClass Class used for the V generic
-+ * @param initialCapacity Initial capacity to use
-+ * @param loadFactor Load factor to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map that auto instantiates on .get()
-+ */
-+ @NotNull
-+ public static Map newHashAutoMap(@NotNull final Class extends V> valueClass, int initialCapacity, float loadFactor) {
-+ return newHashAutoMap(null, valueClass, initialCapacity, loadFactor);
-+ }
-+
-+ /**
-+ * Initializes an auto loading map using a HashMap
-+ *
-+ * @param loader Loader to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map newHashMap(@NotNull Function loader) {
-+ return new LoadingMap<>(new HashMap<>(), loader);
-+ }
-+
-+ /**
-+ * Initializes an auto loading map using a HashMap
-+ *
-+ * @param loader Loader to use
-+ * @param initialCapacity Initial capacity to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map newHashMap(@NotNull Function loader, int initialCapacity) {
-+ return new LoadingMap<>(new HashMap<>(initialCapacity), loader);
-+ }
-+ /**
-+ * Initializes an auto loading map using a HashMap
-+ *
-+ * @param loader Loader to use
-+ * @param initialCapacity Initial capacity to use
-+ * @param loadFactor Load factor to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map newHashMap(@NotNull Function loader, int initialCapacity, float loadFactor) {
-+ return new LoadingMap<>(new HashMap<>(initialCapacity, loadFactor), loader);
-+ }
-+
-+ /**
-+ * Initializes an auto loading map using an Identity HashMap
-+ *
-+ * @param loader Loader to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map newIdentityHashMap(@NotNull Function loader) {
-+ return new LoadingMap<>(new IdentityHashMap<>(), loader);
-+ }
-+
-+ /**
-+ * Initializes an auto loading map using an Identity HashMap
-+ *
-+ * @param loader Loader to use
-+ * @param initialCapacity Initial capacity to use
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map newIdentityHashMap(@NotNull Function loader, int initialCapacity) {
-+ return new LoadingMap<>(new IdentityHashMap<>(initialCapacity), loader);
-+ }
-+
-+ @Override
-+ public int size() {return backingMap.size();}
-+
-+ @Override
-+ public boolean isEmpty() {return backingMap.isEmpty();}
-+
-+ @Override
-+ public boolean containsKey(@Nullable Object key) {return backingMap.containsKey(key);}
-+
-+ @Override
-+ public boolean containsValue(@Nullable Object value) {return backingMap.containsValue(value);}
-+
-+ @Nullable
-+ @Override
-+ public V get(@Nullable Object key) {
-+ V v = backingMap.get(key);
-+ if (v != null) {
-+ return v;
-+ }
-+ return backingMap.computeIfAbsent((K) key, loader);
-+ }
-+
-+ @Nullable
-+ public V put(@Nullable K key, @Nullable V value) {return backingMap.put(key, value);}
-+
-+ @Nullable
-+ @Override
-+ public V remove(@Nullable Object key) {return backingMap.remove(key);}
-+
-+ public void putAll(@NotNull Map extends K, ? extends V> m) {backingMap.putAll(m);}
-+
-+ @Override
-+ public void clear() {backingMap.clear();}
-+
-+ @NotNull
-+ @Override
-+ public Set keySet() {return backingMap.keySet();}
-+
-+ @NotNull
-+ @Override
-+ public Collection values() {return backingMap.values();}
-+
-+ @Override
-+ public boolean equals(@Nullable Object o) {return backingMap.equals(o);}
-+
-+ @Override
-+ public int hashCode() {return backingMap.hashCode();}
-+
-+ @NotNull
-+ @Override
-+ public Set> entrySet() {
-+ return backingMap.entrySet();
-+ }
-+
-+ @NotNull
-+ public LoadingMap clone() {
-+ return new LoadingMap<>(backingMap, loader);
-+ }
-+
-+ private static class AutoInstantiatingLoader implements Function {
-+ final Constructor extends V> constructor;
-+ private final Class extends V> valueClass;
-+
-+ AutoInstantiatingLoader(@Nullable Class extends K> keyClass, @NotNull Class extends V> valueClass) {
-+ try {
-+ this.valueClass = valueClass;
-+ if (keyClass != null) {
-+ constructor = valueClass.getConstructor(keyClass);
-+ } else {
-+ constructor = null;
-+ }
-+ } catch (NoSuchMethodException e) {
-+ throw new IllegalStateException(
-+ valueClass.getName() + " does not have a constructor for " + (keyClass != null ? keyClass.getName() : null));
-+ }
-+ }
-+
-+ @NotNull
-+ @Override
-+ public V apply(@Nullable K input) {
-+ try {
-+ return (constructor != null ? constructor.newInstance(input) : valueClass.newInstance());
-+ } catch (Exception e) {
-+ throw new ExceptionInInitializerError(e);
-+ }
-+ }
-+
-+ @Override
-+ public int hashCode() {
-+ return super.hashCode();
-+ }
-+
-+ @Override
-+ public boolean equals(Object object) {
-+ return false;
-+ }
-+ }
-+
-+ /**
-+ * Due to java stuff, you will need to cast it to (Function) for some cases
-+ *
-+ * @param Type
-+ */
-+ public abstract static class Feeder implements Function {
-+ @Nullable
-+ @Override
-+ public T apply(@Nullable Object input) {
-+ return apply();
-+ }
-+
-+ @Nullable
-+ public abstract T apply();
-+ }
-+}
-diff --git a/src/main/java/co/aikar/util/MRUMapCache.java b/src/main/java/co/aikar/util/MRUMapCache.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..5989ee21297935651b0edd44b8239e655eaef1d9
---- /dev/null
-+++ b/src/main/java/co/aikar/util/MRUMapCache.java
-@@ -0,0 +1,111 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2014 Daniel Ennis
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
-+package co.aikar.util;
-+
-+import java.util.AbstractMap;
-+import java.util.Collection;
-+import java.util.Map;
-+import java.util.Set;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Implements a Most Recently Used cache in front of a backing map, to quickly access the last accessed result.
-+ *
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ */
-+public class MRUMapCache extends AbstractMap {
-+ final Map backingMap;
-+ Object cacheKey;
-+ V cacheValue;
-+ public MRUMapCache(@NotNull final Map backingMap) {
-+ this.backingMap = backingMap;
-+ }
-+
-+ public int size() {return backingMap.size();}
-+
-+ public boolean isEmpty() {return backingMap.isEmpty();}
-+
-+ public boolean containsKey(@Nullable Object key) {
-+ return key != null && key.equals(cacheKey) || backingMap.containsKey(key);
-+ }
-+
-+ public boolean containsValue(@Nullable Object value) {
-+ return value != null && value == cacheValue || backingMap.containsValue(value);
-+ }
-+
-+ @Nullable
-+ public V get(@Nullable Object key) {
-+ if (cacheKey != null && cacheKey.equals(key)) {
-+ return cacheValue;
-+ }
-+ cacheKey = key;
-+ return cacheValue = backingMap.get(key);
-+ }
-+
-+ @Nullable
-+ public V put(@Nullable K key, @Nullable V value) {
-+ cacheKey = key;
-+ return cacheValue = backingMap.put(key, value);
-+ }
-+
-+ @Nullable
-+ public V remove(@Nullable Object key) {
-+ if (key != null && key.equals(cacheKey)) {
-+ cacheKey = null;
-+ }
-+ return backingMap.remove(key);
-+ }
-+
-+ public void putAll(@NotNull Map extends K, ? extends V> m) {backingMap.putAll(m);}
-+
-+ public void clear() {
-+ cacheKey = null;
-+ cacheValue = null;
-+ backingMap.clear();
-+ }
-+
-+ @NotNull
-+ public Set keySet() {return backingMap.keySet();}
-+
-+ @NotNull
-+ public Collection values() {return backingMap.values();}
-+
-+ @NotNull
-+ public Set> entrySet() {return backingMap.entrySet();}
-+
-+ /**
-+ * Wraps the specified map with a most recently used cache
-+ *
-+ * @param map Map to be wrapped
-+ * @param Key Type of the Map
-+ * @param Value Type of the Map
-+ * @return Map
-+ */
-+ @NotNull
-+ public static Map of(@NotNull Map map) {
-+ return new MRUMapCache(map);
-+ }
-+}
-diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
-index 7168dd083ee30a47b104ab32cabb3215815f7470..7c715fdc11ab7837552b1fe3ffd08b31cec0a63b 100644
---- a/src/main/java/org/bukkit/Bukkit.java
-+++ b/src/main/java/org/bukkit/Bukkit.java
-@@ -649,7 +649,6 @@ public final class Bukkit {
- */
- public static void reload() {
- server.reload();
-- org.spigotmc.CustomTimingsHandler.reload(); // Spigot
- }
-
- /**
-diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
-index 4ba8572f1beb3b9ad46620946eb4ee89ac91818e..a6b9e4f158583e5932bf8ca210d531857e9f5360 100644
---- a/src/main/java/org/bukkit/Server.java
-+++ b/src/main/java/org/bukkit/Server.java
-@@ -1344,6 +1344,26 @@ public interface Server extends PluginMessageRecipient {
- throw new UnsupportedOperationException("Not supported yet.");
- }
-
-+ // Paper start
-+ @NotNull
-+ public org.bukkit.configuration.file.YamlConfiguration getBukkitConfig()
-+ {
-+ throw new UnsupportedOperationException( "Not supported yet." );
-+ }
-+
-+ @NotNull
-+ public org.bukkit.configuration.file.YamlConfiguration getSpigotConfig()
-+ {
-+ throw new UnsupportedOperationException("Not supported yet.");
-+ }
-+
-+ @NotNull
-+ public org.bukkit.configuration.file.YamlConfiguration getPaperConfig()
-+ {
-+ throw new UnsupportedOperationException("Not supported yet.");
-+ }
-+ // Paper end
-+
- /**
- * Sends the component to the player
- *
-diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
-index 247d194f86c00db11acbc58e7d163b2606db4f07..945b8b030d1b2a13afc0c4efad76997eb7bf00ba 100644
---- a/src/main/java/org/bukkit/UnsafeValues.java
-+++ b/src/main/java/org/bukkit/UnsafeValues.java
-@@ -18,6 +18,7 @@ import org.bukkit.plugin.PluginDescriptionFile;
- @Deprecated
- public interface UnsafeValues {
-
-+ void reportTimings(); // Paper
- Material toLegacy(Material material);
-
- Material fromLegacy(Material material);
-@@ -69,4 +70,12 @@ public interface UnsafeValues {
- * @return true if a file matching this key was found and deleted
- */
- boolean removeAdvancement(NamespacedKey key);
-+
-+ // Paper start
-+ /**
-+ * Server name to report to timings v2
-+ * @return name
-+ */
-+ String getTimingsServerName();
-+ // Paper end
- }
-diff --git a/src/main/java/org/bukkit/command/BufferedCommandSender.java b/src/main/java/org/bukkit/command/BufferedCommandSender.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..f9a00aecca5ec41b460bf41dfe1c69694768cf98
---- /dev/null
-+++ b/src/main/java/org/bukkit/command/BufferedCommandSender.java
-@@ -0,0 +1,21 @@
-+package org.bukkit.command;
-+
-+import org.jetbrains.annotations.NotNull;
-+
-+public class BufferedCommandSender implements MessageCommandSender {
-+ private final StringBuffer buffer = new StringBuffer();
-+ @Override
-+ public void sendMessage(@NotNull String message) {
-+ buffer.append(message);
-+ buffer.append("\n");
-+ }
-+
-+ @NotNull
-+ public String getBuffer() {
-+ return buffer.toString();
-+ }
-+
-+ public void reset() {
-+ this.buffer.setLength(0);
-+ }
-+}
-diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java
-index 4bfc214685164a38ba4261b2bae7faa8a3bd297e..03bdc1622791e1206406c87065978688d602e39e 100644
---- a/src/main/java/org/bukkit/command/Command.java
-+++ b/src/main/java/org/bukkit/command/Command.java
-@@ -33,7 +33,8 @@ public abstract class Command {
- protected String usageMessage;
- private String permission;
- private String permissionMessage;
-- public org.spigotmc.CustomTimingsHandler timings; // Spigot
-+ public co.aikar.timings.Timing timings; // Paper
-+ @NotNull public String getTimingName() {return getName();} // Paper
-
- protected Command(@NotNull String name) {
- this(name, "", "/" + name, new ArrayList());
-@@ -47,7 +48,6 @@ public abstract class Command {
- this.usageMessage = (usageMessage == null) ? "/" + name : usageMessage;
- this.aliases = aliases;
- this.activeAliases = new ArrayList(aliases);
-- this.timings = new org.spigotmc.CustomTimingsHandler("** Command: " + name); // Spigot
- }
-
- /**
-@@ -245,7 +245,6 @@ public abstract class Command {
- }
- this.nextLabel = name;
- if (!isRegistered()) {
-- this.timings = new org.spigotmc.CustomTimingsHandler("** Command: " + name); // Spigot
- this.label = name;
- return true;
- }
-diff --git a/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/src/main/java/org/bukkit/command/FormattedCommandAlias.java
-index d6c8938b1e13b63116b7b0e074ea8ef5997f8dc3..a6ad94ef98a1df1d2842635d850bc990b0137849 100644
---- a/src/main/java/org/bukkit/command/FormattedCommandAlias.java
-+++ b/src/main/java/org/bukkit/command/FormattedCommandAlias.java
-@@ -9,6 +9,7 @@ public class FormattedCommandAlias extends Command {
-
- public FormattedCommandAlias(@NotNull String alias, @NotNull String[] formatStrings) {
- super(alias);
-+ timings = co.aikar.timings.TimingsManager.getCommandTiming("minecraft", this); // Spigot
- this.formatStrings = formatStrings;
- }
-
-@@ -113,6 +114,10 @@ public class FormattedCommandAlias extends Command {
- return formatString;
- }
-
-+ @NotNull
-+ @Override // Paper
-+ public String getTimingName() {return "Command Forwarder - " + super.getTimingName();} // Paper
-+
- private static boolean inRange(int i, int j, int k) {
- return i >= j && i <= k;
- }
-diff --git a/src/main/java/org/bukkit/command/MessageCommandSender.java b/src/main/java/org/bukkit/command/MessageCommandSender.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..a7ef1f51c2b96617a32e6e7b1723e8770ba8a6a8
---- /dev/null
-+++ b/src/main/java/org/bukkit/command/MessageCommandSender.java
-@@ -0,0 +1,129 @@
-+package org.bukkit.command;
-+
-+import org.apache.commons.lang.NotImplementedException;
-+import org.bukkit.Bukkit;
-+import org.bukkit.Server;
-+import org.bukkit.permissions.Permission;
-+import org.bukkit.permissions.PermissionAttachment;
-+import org.bukkit.permissions.PermissionAttachmentInfo;
-+import org.bukkit.plugin.Plugin;
-+
-+import java.util.Set;
-+import java.util.UUID;
-+
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * For when all you care about is just messaging
-+ */
-+public interface MessageCommandSender extends CommandSender {
-+
-+ @Override
-+ default void sendMessage(@NotNull String[] messages) {
-+ for (String message : messages) {
-+ sendMessage(message);
-+ }
-+ }
-+
-+ @Override
-+ default void sendMessage(@Nullable UUID sender, @NotNull String message) {
-+ sendMessage(message);
-+ }
-+
-+ @Override
-+ default void sendMessage(@Nullable UUID sender, @NotNull String[] messages) {
-+ for (String message : messages) {
-+ sendMessage(message);
-+ }
-+ }
-+
-+ @NotNull
-+ @Override
-+ default Server getServer() {
-+ return Bukkit.getServer();
-+ }
-+
-+ @NotNull
-+ @Override
-+ default String getName() {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default boolean isOp() {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default void setOp(boolean value) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default boolean isPermissionSet(@NotNull String name) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default boolean isPermissionSet(@NotNull Permission perm) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default boolean hasPermission(@NotNull String name) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default boolean hasPermission(@NotNull Permission perm) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @NotNull
-+ @Override
-+ default PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @NotNull
-+ @Override
-+ default PermissionAttachment addAttachment(@NotNull Plugin plugin) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @NotNull
-+ @Override
-+ default PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @NotNull
-+ @Override
-+ default PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default void removeAttachment(@NotNull PermissionAttachment attachment) {
-+ throw new NotImplementedException();
-+ }
-+
-+ @Override
-+ default void recalculatePermissions() {
-+ throw new NotImplementedException();
-+ }
-+
-+ @NotNull
-+ @Override
-+ default Set getEffectivePermissions() {
-+ throw new NotImplementedException();
-+ }
-+
-+ @NotNull
-+ @Override
-+ default Spigot spigot() {
-+ throw new NotImplementedException();
-+ }
-+
-+}
-diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-index 81e4fa57337f5a40c4b673136dd5eb595cce4629..f020cb04eba27a2e70fc7cf799ebbfb434b9d974 100644
---- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
-+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-@@ -15,7 +15,6 @@ import org.bukkit.command.defaults.BukkitCommand;
- import org.bukkit.command.defaults.HelpCommand;
- import org.bukkit.command.defaults.PluginsCommand;
- import org.bukkit.command.defaults.ReloadCommand;
--import org.bukkit.command.defaults.TimingsCommand;
- import org.bukkit.command.defaults.VersionCommand;
- import org.bukkit.entity.Player;
- import org.bukkit.util.StringUtil;
-@@ -35,7 +34,7 @@ public class SimpleCommandMap implements CommandMap {
- register("bukkit", new VersionCommand("version"));
- register("bukkit", new ReloadCommand("reload"));
- register("bukkit", new PluginsCommand("plugins"));
-- register("bukkit", new TimingsCommand("timings"));
-+ register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Paper
- }
-
- public void setFallbackCommands() {
-@@ -67,6 +66,7 @@ public class SimpleCommandMap implements CommandMap {
- */
- @Override
- public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) {
-+ command.timings = co.aikar.timings.TimingsManager.getCommandTiming(fallbackPrefix, command); // Paper
- label = label.toLowerCase(java.util.Locale.ENGLISH).trim();
- fallbackPrefix = fallbackPrefix.toLowerCase(java.util.Locale.ENGLISH).trim();
- boolean registered = register(label, command, false, fallbackPrefix);
-@@ -143,16 +143,22 @@ public class SimpleCommandMap implements CommandMap {
- return false;
- }
-
-+ // Paper start - Plugins do weird things to workaround normal registration
-+ if (target.timings == null) {
-+ target.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, target);
-+ }
-+ // Paper end
-+
- try {
-- target.timings.startTiming(); // Spigot
-+ try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources
- // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false)
- target.execute(sender, sentCommandLabel, Arrays.copyOfRange(args, 1, args.length));
-- target.timings.stopTiming(); // Spigot
-+ } // target.timings.stopTiming(); // Spigot // Paper
- } catch (CommandException ex) {
-- target.timings.stopTiming(); // Spigot
-+ //target.timings.stopTiming(); // Spigot // Paper
- throw ex;
- } catch (Throwable ex) {
-- target.timings.stopTiming(); // Spigot
-+ //target.timings.stopTiming(); // Spigot // Paper
- throw new CommandException("Unhandled exception executing '" + commandLine + "' in " + target, ex);
- }
-
-diff --git a/src/main/java/org/bukkit/command/defaults/TimingsCommand.java b/src/main/java/org/bukkit/command/defaults/TimingsCommand.java
-deleted file mode 100644
-index 2a145d851ce30360aa39549745bd87590c034584..0000000000000000000000000000000000000000
---- a/src/main/java/org/bukkit/command/defaults/TimingsCommand.java
-+++ /dev/null
-@@ -1,250 +0,0 @@
--package org.bukkit.command.defaults;
--
--import com.google.common.collect.ImmutableList;
--import java.io.File;
--import java.io.IOException;
--import java.io.PrintStream;
--import java.util.ArrayList;
--import java.util.List;
--import org.apache.commons.lang.Validate;
--import org.bukkit.Bukkit;
--import org.bukkit.ChatColor;
--import org.bukkit.command.CommandSender;
--import org.bukkit.event.Event;
--import org.bukkit.event.HandlerList;
--import org.bukkit.plugin.Plugin;
--import org.bukkit.plugin.RegisteredListener;
--import org.bukkit.plugin.TimedRegisteredListener;
--import org.bukkit.util.StringUtil;
--import org.jetbrains.annotations.NotNull;
--
--// Spigot start
--// CHECKSTYLE:OFF
--import java.io.ByteArrayOutputStream;
--import java.io.OutputStream;
--import java.net.HttpURLConnection;
--import java.net.URL;
--import java.util.logging.Level;
--import org.bukkit.command.RemoteConsoleCommandSender;
--import org.bukkit.plugin.SimplePluginManager;
--import org.spigotmc.CustomTimingsHandler;
--// CHECKSTYLE:ON
--// Spigot end
--
--public class TimingsCommand extends BukkitCommand {
-- private static final List TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste"); // Spigot
-- public static long timingStart = 0; // Spigot
--
-- public TimingsCommand(@NotNull String name) {
-- super(name);
-- this.description = "Manages Spigot Timings data to see performance of the server."; // Spigot
-- this.usageMessage = "/timings "; // Spigot
-- this.setPermission("bukkit.command.timings");
-- }
--
-- // Spigot start - redesigned Timings Command
-- public void executeSpigotTimings(@NotNull CommandSender sender, @NotNull String[] args) {
-- if ("on".equals(args[0])) {
-- ((SimplePluginManager) Bukkit.getPluginManager()).useTimings(true);
-- CustomTimingsHandler.reload();
-- sender.sendMessage("Enabled Timings & Reset");
-- return;
-- } else if ("off".equals(args[0])) {
-- ((SimplePluginManager) Bukkit.getPluginManager()).useTimings(false);
-- sender.sendMessage("Disabled Timings");
-- return;
-- }
--
-- if (!Bukkit.getPluginManager().useTimings()) {
-- sender.sendMessage("Please enable timings by typing /timings on");
-- return;
-- }
--
-- boolean paste = "paste".equals(args[0]);
-- if ("reset".equals(args[0])) {
-- CustomTimingsHandler.reload();
-- sender.sendMessage("Timings reset");
-- } else if ("merged".equals(args[0]) || "report".equals(args[0]) || paste) {
-- long sampleTime = System.nanoTime() - timingStart;
-- int index = 0;
-- File timingFolder = new File("timings");
-- timingFolder.mkdirs();
-- File timings = new File(timingFolder, "timings.txt");
-- ByteArrayOutputStream bout = (paste) ? new ByteArrayOutputStream() : null;
-- while (timings.exists()) timings = new File(timingFolder, "timings" + (++index) + ".txt");
-- PrintStream fileTimings = null;
-- try {
-- fileTimings = (paste) ? new PrintStream(bout) : new PrintStream(timings);
--
-- CustomTimingsHandler.printTimings(fileTimings);
-- fileTimings.println("Sample time " + sampleTime + " (" + sampleTime / 1E9 + "s)");
--
-- fileTimings.println("");
-- fileTimings.println(Bukkit.spigot().getConfig().saveToString());
-- fileTimings.println("");
--
-- if (paste) {
-- new PasteThread(sender, bout).start();
-- return;
-- }
--
-- sender.sendMessage("Timings written to " + timings.getPath());
-- sender.sendMessage("Paste contents of file into form at http://www.spigotmc.org/go/timings to read results.");
--
-- } catch (IOException e) {
-- } finally {
-- if (fileTimings != null) {
-- fileTimings.close();
-- }
-- }
-- }
-- }
-- // Spigot end
--
-- @Override
-- public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) {
-- if (!testPermission(sender)) return true;
-- if (args.length < 1) { // Spigot
-- sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
-- return false;
-- }
-- // Spigot start
-- if (true) {
-- executeSpigotTimings(sender, args);
-- return true;
-- }
-- // Spigot end
-- if (!sender.getServer().getPluginManager().useTimings()) {
-- sender.sendMessage("Please enable timings by setting \"settings.plugin-profiling\" to true in bukkit.yml");
-- return true;
-- }
--
-- boolean separate = "separate".equalsIgnoreCase(args[0]);
-- if ("reset".equalsIgnoreCase(args[0])) {
-- for (HandlerList handlerList : HandlerList.getHandlerLists()) {
-- for (RegisteredListener listener : handlerList.getRegisteredListeners()) {
-- if (listener instanceof TimedRegisteredListener) {
-- ((TimedRegisteredListener) listener).reset();
-- }
-- }
-- }
-- sender.sendMessage("Timings reset");
-- } else if ("merged".equalsIgnoreCase(args[0]) || separate) {
--
-- int index = 0;
-- int pluginIdx = 0;
-- File timingFolder = new File("timings");
-- timingFolder.mkdirs();
-- File timings = new File(timingFolder, "timings.txt");
-- File names = null;
-- while (timings.exists()) timings = new File(timingFolder, "timings" + (++index) + ".txt");
-- PrintStream fileTimings = null;
-- PrintStream fileNames = null;
-- try {
-- fileTimings = new PrintStream(timings);
-- if (separate) {
-- names = new File(timingFolder, "names" + index + ".txt");
-- fileNames = new PrintStream(names);
-- }
-- for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
-- pluginIdx++;
-- long totalTime = 0;
-- if (separate) {
-- fileNames.println(pluginIdx + " " + plugin.getDescription().getFullName());
-- fileTimings.println("Plugin " + pluginIdx);
-- } else {
-- fileTimings.println(plugin.getDescription().getFullName());
-- }
-- for (RegisteredListener listener : HandlerList.getRegisteredListeners(plugin)) {
-- if (listener instanceof TimedRegisteredListener) {
-- TimedRegisteredListener trl = (TimedRegisteredListener) listener;
-- long time = trl.getTotalTime();
-- int count = trl.getCount();
-- if (count == 0) continue;
-- long avg = time / count;
-- totalTime += time;
-- Class extends Event> eventClass = trl.getEventClass();
-- if (count > 0 && eventClass != null) {
-- fileTimings.println(" " + eventClass.getSimpleName() + (trl.hasMultiple() ? " (and sub-classes)" : "") + " Time: " + time + " Count: " + count + " Avg: " + avg);
-- }
-- }
-- }
-- fileTimings.println(" Total time " + totalTime + " (" + totalTime / 1000000000 + "s)");
-- }
-- sender.sendMessage("Timings written to " + timings.getPath());
-- if (separate) sender.sendMessage("Names written to " + names.getPath());
-- } catch (IOException e) {
-- } finally {
-- if (fileTimings != null) {
-- fileTimings.close();
-- }
-- if (fileNames != null) {
-- fileNames.close();
-- }
-- }
-- } else {
-- sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
-- return false;
-- }
-- return true;
-- }
--
-- @NotNull
-- @Override
-- public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) {
-- Validate.notNull(sender, "Sender cannot be null");
-- Validate.notNull(args, "Arguments cannot be null");
-- Validate.notNull(alias, "Alias cannot be null");
--
-- if (args.length == 1) {
-- return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS, new ArrayList(TIMINGS_SUBCOMMANDS.size()));
-- }
-- return ImmutableList.of();
-- }
--
-- // Spigot start
-- private static class PasteThread extends Thread {
--
-- private final CommandSender sender;
-- private final ByteArrayOutputStream bout;
--
-- public PasteThread(@NotNull CommandSender sender, @NotNull ByteArrayOutputStream bout) {
-- super("Timings paste thread");
-- this.sender = sender;
-- this.bout = bout;
-- }
--
-- @Override
-- public synchronized void start() {
-- if (sender instanceof RemoteConsoleCommandSender) {
-- run();
-- } else {
-- super.start();
-- }
-- }
--
-- @Override
-- public void run() {
-- try {
-- HttpURLConnection con = (HttpURLConnection) new URL("https://timings.spigotmc.org/paste").openConnection();
-- con.setDoOutput(true);
-- con.setRequestMethod("POST");
-- con.setInstanceFollowRedirects(false);
--
-- OutputStream out = con.getOutputStream();
-- out.write(bout.toByteArray());
-- out.close();
--
-- com.google.gson.JsonObject location = new com.google.gson.Gson().fromJson(new java.io.InputStreamReader(con.getInputStream()), com.google.gson.JsonObject.class);
-- con.getInputStream().close();
--
-- String pasteID = location.get("key").getAsString();
-- sender.sendMessage(ChatColor.GREEN + "Timings results can be viewed at https://www.spigotmc.org/go/timings?url=" + pasteID);
-- } catch (IOException ex) {
-- sender.sendMessage(ChatColor.RED + "Error pasting timings, check your console for more information");
-- Bukkit.getServer().getLogger().log(Level.WARNING, "Could not paste timings", ex);
-- }
-- }
-- }
-- // Spigot end
--}
-diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index da1b5b5253c0ac0abe1019096166e6c76a50e699..586fd9ebd02039ebd2c071cbbbf60f24879f96b9 100644
---- a/src/main/java/org/bukkit/entity/Player.java
-+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -1368,7 +1368,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
- */
- public void sendMessage(@NotNull net.md_5.bungee.api.ChatMessageType position, @Nullable UUID sender, @NotNull net.md_5.bungee.api.chat.BaseComponent... components) {
- throw new UnsupportedOperationException("Not supported yet.");
-+
-+ }
-+
-+ // Paper start
-+ public int getPing() {
-+ throw new UnsupportedOperationException( "Not supported yet." );
- }
-+ // Paper end
- }
-
- @NotNull
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index 62d0017362204070465c8ff72e5c2ca07501f558..745eaa8f2f2ff83536301db8ca47a8af30df7a73 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -358,7 +358,6 @@ public final class SimplePluginManager implements PluginManager {
- }
- }
-
-- org.bukkit.command.defaults.TimingsCommand.timingStart = System.nanoTime(); // Spigot
- return result.toArray(new Plugin[result.size()]);
- }
-
-@@ -397,9 +396,9 @@ public final class SimplePluginManager implements PluginManager {
-
- if (result != null) {
- plugins.add(result);
-- lookupNames.put(result.getDescription().getName(), result);
-+ lookupNames.put(result.getDescription().getName().toLowerCase(java.util.Locale.ENGLISH), result); // Paper
- for (String provided : result.getDescription().getProvides()) {
-- lookupNames.putIfAbsent(provided, result);
-+ lookupNames.putIfAbsent(provided.toLowerCase(java.util.Locale.ENGLISH), result); // Paper
- }
- }
-
-@@ -428,7 +427,7 @@ public final class SimplePluginManager implements PluginManager {
- @Override
- @Nullable
- public synchronized Plugin getPlugin(@NotNull String name) {
-- return lookupNames.get(name.replace(' ', '_'));
-+ return lookupNames.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); // Paper
- }
-
- @Override
-@@ -646,7 +645,8 @@ public final class SimplePluginManager implements PluginManager {
- throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled");
- }
-
-- if (useTimings) {
-+ executor = new co.aikar.timings.TimedEventExecutor(executor, plugin, null, event); // Paper
-+ if (false) { // Spigot - RL handles useTimings check now // Paper
- getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
- } else {
- getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
-@@ -860,7 +860,7 @@ public final class SimplePluginManager implements PluginManager {
-
- @Override
- public boolean useTimings() {
-- return useTimings;
-+ return co.aikar.timings.Timings.isTimingsEnabled(); // Spigot
- }
-
- /**
-@@ -869,6 +869,6 @@ public final class SimplePluginManager implements PluginManager {
- * @param use True if per event timing code should be used
- */
- public void useTimings(boolean use) {
-- useTimings = use;
-+ co.aikar.timings.Timings.setTimingsEnabled(use); // Paper
- }
- }
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index a09c3f71ca563b6f40a118ce1344d0eb273bed40..cf2f517765d8f2a23cc4a17d9ee2dcd81f841b1b 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -54,7 +54,6 @@ public final class JavaPluginLoader implements PluginLoader {
- private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")};
- private final List loaders = new CopyOnWriteArrayList();
- private final LibraryLoader libraryLoader;
-- public static final CustomTimingsHandler pluginParentTimer = new CustomTimingsHandler("** Plugins"); // Spigot
-
- /**
- * This class was not meant to be constructed explicitly
-@@ -292,27 +291,21 @@ public final class JavaPluginLoader implements PluginLoader {
- }
- }
-
-- final CustomTimingsHandler timings = new CustomTimingsHandler("Plugin: " + plugin.getDescription().getFullName() + " Event: " + listener.getClass().getName() + "::" + method.getName() + "(" + eventClass.getSimpleName() + ")", pluginParentTimer); // Spigot
-- EventExecutor executor = new EventExecutor() {
-+ EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Paper
- @Override
-- public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException {
-+ public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { // Paper
- try {
- if (!eventClass.isAssignableFrom(event.getClass())) {
- return;
- }
-- // Spigot start
-- boolean isAsync = event.isAsynchronous();
-- if (!isAsync) timings.startTiming();
- method.invoke(listener, event);
-- if (!isAsync) timings.stopTiming();
-- // Spigot end
- } catch (InvocationTargetException ex) {
- throw new EventException(ex.getCause());
- } catch (Throwable t) {
- throw new EventException(t);
- }
- }
-- };
-+ }, plugin, method, eventClass); // Paper
- if (false) { // Spigot - RL handles useTimings check now
- eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
- } else {
-diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-index 6843e32438492f380e2e72bb40dd49d45fe675cb..5ffa98bb9c76d802a9d0ea6c572a704a2732c67c 100644
---- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-@@ -29,7 +29,8 @@ import org.jetbrains.annotations.Nullable;
- /**
- * A ClassLoader for plugins, to allow shared classes across multiple plugins
- */
--final class PluginClassLoader extends URLClassLoader {
-+public final class PluginClassLoader extends URLClassLoader { // Spigot
-+ public JavaPlugin getPlugin() { return plugin; } // Spigot
- private final JavaPluginLoader loader;
- private final Map> classes = new ConcurrentHashMap>();
- private final PluginDescriptionFile description;
-diff --git a/src/main/java/org/bukkit/util/CachedServerIcon.java b/src/main/java/org/bukkit/util/CachedServerIcon.java
-index 5ca863b3692b2e1b58e7fb4d82f554a92cc4f01e..612958a331575d1da2715531ebdf6b1168f2e860 100644
---- a/src/main/java/org/bukkit/util/CachedServerIcon.java
-+++ b/src/main/java/org/bukkit/util/CachedServerIcon.java
-@@ -2,6 +2,7 @@ package org.bukkit.util;
-
- import org.bukkit.Server;
- import org.bukkit.event.server.ServerListPingEvent;
-+import org.jetbrains.annotations.Nullable;
-
- /**
- * This is a cached version of a server-icon. It's internal representation
-@@ -12,4 +13,9 @@ import org.bukkit.event.server.ServerListPingEvent;
- * @see Server#loadServerIcon(java.io.File)
- * @see ServerListPingEvent#setServerIcon(CachedServerIcon)
- */
--public interface CachedServerIcon {}
-+public interface CachedServerIcon {
-+
-+ @Nullable
-+ public String getData(); // Paper
-+
-+}
-diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java
-index 44badfedcc3fdc26bdc293b85d8c781d6f659faa..3cbe5c2bb55dead7968a6f165ef267e3e2931061 100644
---- a/src/main/java/org/spigotmc/CustomTimingsHandler.java
-+++ b/src/main/java/org/spigotmc/CustomTimingsHandler.java
-@@ -1,3 +1,26 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2014 Daniel Ennis
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
- package org.spigotmc;
-
- import java.io.PrintStream;
-@@ -5,133 +28,84 @@ import java.util.Queue;
- import java.util.concurrent.ConcurrentLinkedQueue;
- import org.bukkit.Bukkit;
- import org.bukkit.World;
--import org.bukkit.command.defaults.TimingsCommand;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
-+import org.bukkit.plugin.AuthorNagException;
-+import org.bukkit.plugin.Plugin;
-+import co.aikar.timings.Timing;
-+import co.aikar.timings.Timings;
-+import co.aikar.timings.TimingsManager;
-+
-+import java.lang.reflect.InvocationTargetException;
-+import java.lang.reflect.Method;
-+import java.util.logging.Level;
-
- /**
-- * Provides custom timing sections for /timings merged.
-+ * This is here for legacy purposes incase any plugin used it.
-+ *
-+ * If you use this, migrate ASAP as this will be removed in the future!
-+ *
-+ * @deprecated
-+ * @see co.aikar.timings.Timings#of
- */
--public class CustomTimingsHandler {
--
-- private static Queue HANDLERS = new ConcurrentLinkedQueue