From 4473b2e57eb7a4c0f729302563b2ec04b69ed3f1 Mon Sep 17 00:00:00 2001 From: fullwall Date: Sat, 8 Dec 2012 14:28:06 +0800 Subject: [PATCH] Fix bug with annotation processor --- src/main/java/net/citizensnpcs/Metrics.java | 585 ++++++++++-------- .../citizensnpcs/command/CommandManager.java | 18 +- 2 files changed, 331 insertions(+), 272 deletions(-) diff --git a/src/main/java/net/citizensnpcs/Metrics.java b/src/main/java/net/citizensnpcs/Metrics.java index 936aceb00..f912a8548 100644 --- a/src/main/java/net/citizensnpcs/Metrics.java +++ b/src/main/java/net/citizensnpcs/Metrics.java @@ -51,6 +51,7 @@ import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.scheduler.BukkitTask; /** *

@@ -68,6 +69,48 @@ import org.bukkit.plugin.PluginDescriptionFile; */ public class Metrics { + /** + * The current revision number + */ + private final static int REVISION = 6; + + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://mcstats.org"; + + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/report/%s"; + + /** + * The separator to use for custom data. This MUST NOT change unless you are + * hosting your own version of metrics and want to change it. + */ + private static final String CUSTOM_DATA_SEPARATOR = "~~"; + + /** + * Interval of time to ping (in minutes) + */ + private static final int PING_INTERVAL = 10; + + /** + * The plugin this metrics submits for + */ + private final Plugin plugin; + + /** + * All of the custom graphs to submit to metrics + */ + private final Set graphs = Collections.synchronizedSet(new HashSet()); + + /** + * The default graph, used for addCustomData when you don't want a specific + * graph + */ + private final Graph defaultGraph = new Graph("Default"); + /** * The plugin configuration file */ @@ -78,36 +121,25 @@ public class Metrics { */ private final File configurationFile; - /** - * The default graph, used for addCustomData when you don't want a specific - * graph - */ - private final Graph defaultGraph = new Graph("Default"); - - /** - * All of the custom graphs to submit to metrics - */ - private final Set graphs = Collections.synchronizedSet(new HashSet()); - /** * Unique server id */ private final String guid; + /** + * Debug mode + */ + private final boolean debug; + /** * Lock for synchronization */ private final Object optOutLock = new Object(); /** - * The plugin this metrics submits for + * The scheduled task */ - private final Plugin plugin; - - /** - * Id of the scheduled task - */ - private volatile int taskId = -1; + private volatile BukkitTask task = null; public Metrics(final Plugin plugin) throws IOException { if (plugin == null) { @@ -123,6 +155,7 @@ public class Metrics { // add some defaults configuration.addDefault("opt-out", false); configuration.addDefault("guid", UUID.randomUUID().toString()); + configuration.addDefault("debug", false); // Do we need to create the file? if (configuration.get("guid", null) == null) { @@ -132,39 +165,7 @@ public class Metrics { // Load the guid then guid = configuration.getString("guid"); - } - - /** - * Adds a custom data plotter to the default graph - * - * @param plotter - * The plotter to use to plot custom data - */ - public void addCustomData(final Plotter plotter) { - if (plotter == null) { - throw new IllegalArgumentException("Plotter cannot be null"); - } - - // Add the plotter to the graph o/ - defaultGraph.addPlotter(plotter); - - // Ensure the default graph is included in the submitted graphs - graphs.add(defaultGraph); - } - - /** - * Add a Graph object to Metrics that represents data for the plugin that - * should be sent to the backend - * - * @param graph - * The name of the graph - */ - public void addGraph(final Graph graph) { - if (graph == null) { - throw new IllegalArgumentException("Graph cannot be null"); - } - - graphs.add(graph); + debug = configuration.getBoolean("debug", false); } /** @@ -193,27 +194,126 @@ public class Metrics { } /** - * Disables metrics for the server by setting "opt-out" to true in the - * config file and canceling the metrics task. + * Add a Graph object to Metrics that represents data for the plugin that + * should be sent to the backend * - * @throws IOException + * @param graph + * The name of the graph */ - public void disable() throws IOException { - // This has to be synchronized or it can collide with the check in the - // task. + public void addGraph(final Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + + graphs.add(graph); + } + + /** + * Adds a custom data plotter to the default graph + * + * @param plotter + * The plotter to use to plot custom data + */ + public void addCustomData(final Plotter plotter) { + if (plotter == null) { + throw new IllegalArgumentException("Plotter cannot be null"); + } + + // Add the plotter to the graph o/ + defaultGraph.addPlotter(plotter); + + // Ensure the default graph is included in the submitted graphs + graphs.add(defaultGraph); + } + + /** + * Start measuring statistics. This will immediately create an async + * repeating task as the plugin and send the initial data to the metrics + * backend, and then after that it will post in increments of PING_INTERVAL + * * 1200 ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { synchronized (optOutLock) { - // Check if the server owner has already set opt-out, if not, set - // it. - if (!isOptOut()) { - configuration.set("opt-out", true); - configuration.save(configurationFile); + // Did we opt out? + if (isOptOut()) { + return false; } - // Disable Task, if it is running - if (taskId > 0) { - this.plugin.getServer().getScheduler().cancelTask(taskId); - taskId = -1; + // Is metrics already running? + if (task != null) { + return true; } + + // Begin hitting the server with glorious data + task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, new Runnable() { + + private boolean firstPost = true; + + @Override + public void run() { + try { + // This has to be synchronized or it can collide with + // the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the server + // owner decided to opt-out + if (isOptOut() && task != null) { + task.cancel(); + task = null; + // Tell all plotters to stop gathering + // information. + for (Graph graph : graphs) { + graph.onOptOut(); + } + } + } + + // We use the inverse of firstPost because if it is the + // first time we are posting, + // it is not a interval ping, so it evaluates to FALSE + // Each time thereafter it will evaluate to TRUE, i.e + // PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to false + // Each post thereafter will be a ping + firstPost = false; + } catch (IOException e) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + } + } + } + }, 0, PING_INTERVAL * 1200); + + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + synchronized (optOutLock) { + try { + // Reload the metrics file + configuration.load(getConfigFile()); + } catch (IOException ex) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } + return true; + } catch (InvalidConfigurationException ex) { + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } + return true; + } + return configuration.getBoolean("opt-out", false); } } @@ -235,12 +335,37 @@ public class Metrics { } // Enable Task, if it is not running - if (taskId < 0) { + if (task == null) { start(); } } } + /** + * Disables metrics for the server by setting "opt-out" to true in the + * config file and canceling the metrics task. + * + * @throws IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the + // task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set + // it. + if (!isOptOut()) { + configuration.set("opt-out", true); + configuration.save(configurationFile); + } + + // Disable Task, if it is running + if (task != null) { + task.cancel(); + task = null; + } + } + } + /** * Gets the File object of the config file that should be used to store data * such as the GUID and opt-out status @@ -260,58 +385,53 @@ public class Metrics { return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml"); } - /** - * Check if mineshafter is present. If it is, we need to bypass it to send - * POST requests - * - * @return true if mineshafter is installed on the server - */ - private boolean isMineshafterPresent() { - try { - Class.forName("mineshafter.MineServer"); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * Has the server owner denied plugin metrics? - * - * @return true if metrics should be opted out of it - */ - public boolean isOptOut() { - synchronized (optOutLock) { - try { - // Reload the metrics file - configuration.load(getConfigFile()); - } catch (IOException ex) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); - return true; - } catch (InvalidConfigurationException ex) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); - return true; - } - return configuration.getBoolean("opt-out", false); - } - } - /** * Generic method that posts a plugin to the metrics website */ private void postPlugin(final boolean isPing) throws IOException { - // The plugin's description file containg all of the plugin data such as - // name, version, author, etc - final PluginDescriptionFile description = plugin.getDescription(); + // Server software specific section + PluginDescriptionFile description = plugin.getDescription(); + String pluginName = description.getName(); + boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if + // online mode + // is enabled + String pluginVersion = description.getVersion(); + String serverVersion = Bukkit.getVersion(); + int playersOnline = Bukkit.getServer().getOnlinePlayers().length; + + // END server software specific section -- all code below does not use + // any code outside of this class / Java // Construct the post data final StringBuilder data = new StringBuilder(); + + // The plugin's description file containg all of the plugin data such as + // name, version, author, etc data.append(encode("guid")).append('=').append(encode(guid)); - encodeDataPair(data, "version", description.getVersion().replaceAll("\\(.*$", "")); - encodeDataPair(data, "server", Bukkit.getVersion()); - encodeDataPair(data, "players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length)); + encodeDataPair(data, "version", pluginVersion); + encodeDataPair(data, "server", serverVersion); + encodeDataPair(data, "players", Integer.toString(playersOnline)); encodeDataPair(data, "revision", String.valueOf(REVISION)); + // New data as of R6 + String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + String osversion = System.getProperty("os.version"); + String java_version = System.getProperty("java.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + // normalize os arch .. amd64 -> x86_64 + if (osarch.equals("amd64")) { + osarch = "x86_64"; + } + + encodeDataPair(data, "osname", osname); + encodeDataPair(data, "osarch", osarch); + encodeDataPair(data, "osversion", osversion); + encodeDataPair(data, "cores", Integer.toString(coreCount)); + encodeDataPair(data, "online-mode", Boolean.toString(onlineMode)); + encodeDataPair(data, "java_version", java_version); + // If we're pinging, append it if (isPing) { encodeDataPair(data, "ping", "true"); @@ -347,7 +467,7 @@ public class Metrics { } // Create the url - URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(plugin.getDescription().getName()))); + URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(pluginName))); // Connect to the website URLConnection connection; @@ -396,69 +516,54 @@ public class Metrics { } /** - * Start measuring statistics. This will immediately create an async - * repeating task as the plugin and send the initial data to the metrics - * backend, and then after that it will post in increments of PING_INTERVAL - * * 1200 ticks. + * Check if mineshafter is present. If it is, we need to bypass it to send + * POST requests * - * @return True if statistics measuring is running, otherwise false. + * @return true if mineshafter is installed on the server */ - public boolean start() { - synchronized (optOutLock) { - // Did we opt out? - if (isOptOut()) { - return false; - } - - // Is metrics already running? - if (taskId >= 0) { - return true; - } - - // Begin hitting the server with glorious data - taskId = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, new Runnable() { - - private boolean firstPost = true; - - @Override - public void run() { - try { - // This has to be synchronized or it can collide with - // the disable method. - synchronized (optOutLock) { - // Disable Task, if it is running and the server - // owner decided to opt-out - if (isOptOut() && taskId > 0) { - plugin.getServer().getScheduler().cancelTask(taskId); - taskId = -1; - // Tell all plotters to stop gathering - // information. - for (Graph graph : graphs) { - graph.onOptOut(); - } - } - } - - // We use the inverse of firstPost because if it is the - // first time we are posting, - // it is not a interval ping, so it evaluates to FALSE - // Each time thereafter it will evaluate to TRUE, i.e - // PING! - postPlugin(!firstPost); - - // After the first post we set firstPost to false - // Each post thereafter will be a ping - firstPost = false; - } catch (IOException e) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); - } - } - }, 0, PING_INTERVAL * 1200).getTaskId(); - + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); return true; + } catch (Exception e) { + return false; } } + /** + *

+ * Encode a key/value data pair to be used in a HTTP post request. This + * INCLUDES a & so the first key/value pair MUST be included manually, e.g: + *

+ * + * StringBuffer data = new StringBuffer(); + * data.append(encode("guid")).append('=').append(encode(guid)); + * encodeDataPair(data, "version", description.getVersion()); + * + * + * @param buffer + * the stringbuilder to append the data pair onto + * @param key + * the key value + * @param value + * the value + */ + private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) + throws UnsupportedEncodingException { + buffer.append('&').append(encode(key)).append('=').append(encode(value)); + } + + /** + * Encode text as UTF-8 + * + * @param text + * the text to encode + * @return the encoded text, as UTF-8 + */ + private static String encode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + /** * Represents a custom graph on the website */ @@ -479,6 +584,15 @@ public class Metrics { this.name = name; } + /** + * Gets the graph's name + * + * @return the Graph's name + */ + public String getName() { + return name; + } + /** * Add a plotter to the graph, which will be used to plot entries * @@ -489,23 +603,14 @@ public class Metrics { plotters.add(plotter); } - @Override - public boolean equals(final Object object) { - if (!(object instanceof Graph)) { - return false; - } - - final Graph graph = (Graph) object; - return graph.name.equals(name); - } - /** - * Gets the graph's name + * Remove a plotter from the graph * - * @return the Graph's name + * @param plotter + * the plotter to remove from the graph */ - public String getName() { - return name; + public void removePlotter(final Plotter plotter) { + plotters.remove(plotter); } /** @@ -522,6 +627,16 @@ public class Metrics { return name.hashCode(); } + @Override + public boolean equals(final Object object) { + if (!(object instanceof Graph)) { + return false; + } + + final Graph graph = (Graph) object; + return graph.name.equals(name); + } + /** * Called when the server owner decides to opt-out of Metrics while the * server is running. @@ -529,16 +644,6 @@ public class Metrics { protected void onOptOut() { } - /** - * Remove a plotter from the graph - * - * @param plotter - * the plotter to remove from the graph - */ - public void removePlotter(final Plotter plotter) { - plotters.remove(plotter); - } - } /** @@ -569,25 +674,6 @@ public class Metrics { this.name = name; } - @Override - public boolean equals(final Object object) { - if (!(object instanceof Plotter)) { - return false; - } - - final Plotter plotter = (Plotter) object; - return plotter.name.equals(name) && plotter.getValue() == getValue(); - } - - /** - * Get the column name for the plotted point - * - * @return the plotted point's column name - */ - public String getColumnName() { - return name; - } - /** * Get the current value for the plotted point. Since this function * defers to an external function it may or may not return immediately @@ -599,9 +685,13 @@ public class Metrics { */ public abstract int getValue(); - @Override - public int hashCode() { - return getColumnName().hashCode(); + /** + * Get the column name for the plotted point + * + * @return the plotted point's column name + */ + public String getColumnName() { + return name; } /** @@ -610,66 +700,21 @@ public class Metrics { public void reset() { } - } + @Override + public int hashCode() { + return getColumnName().hashCode(); + } - /** - * The base url of the metrics domain - */ - private static final String BASE_URL = "http://mcstats.org"; + @Override + public boolean equals(final Object object) { + if (!(object instanceof Plotter)) { + return false; + } - /** - * The separator to use for custom data. This MUST NOT change unless you are - * hosting your own version of metrics and want to change it. - */ - private static final String CUSTOM_DATA_SEPARATOR = "~~"; + final Plotter plotter = (Plotter) object; + return plotter.name.equals(name) && plotter.getValue() == getValue(); + } - /** - * Interval of time to ping (in minutes) - */ - private static final int PING_INTERVAL = 10; - - /** - * The url used to report a server's status - */ - private static final String REPORT_URL = "/report/%s"; - - /** - * The current revision number - */ - private final static int REVISION = 5; - - /** - * Encode text as UTF-8 - * - * @param text - * the text to encode - * @return the encoded text, as UTF-8 - */ - private static String encode(final String text) throws UnsupportedEncodingException { - return URLEncoder.encode(text, "UTF-8"); - } - - /** - *

- * Encode a key/value data pair to be used in a HTTP post request. This - * INCLUDES a & so the first key/value pair MUST be included manually, e.g: - *

- * - * StringBuffer data = new StringBuffer(); - * data.append(encode("guid")).append('=').append(encode(guid)); - * encodeDataPair(data, "version", description.getVersion()); - * - * - * @param buffer - * the stringbuilder to append the data pair onto - * @param key - * the key value - * @param value - * the value - */ - private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) - throws UnsupportedEncodingException { - buffer.append('&').append(encode(key)).append('=').append(encode(value)); } } \ No newline at end of file diff --git a/src/main/java/net/citizensnpcs/command/CommandManager.java b/src/main/java/net/citizensnpcs/command/CommandManager.java index 550b95432..9f16288e1 100644 --- a/src/main/java/net/citizensnpcs/command/CommandManager.java +++ b/src/main/java/net/citizensnpcs/command/CommandManager.java @@ -311,6 +311,20 @@ public class CommandManager { registerMethods(clazz, null); } + /** + * Registers an {@link CommandAnnotationProcessor} that can process + * annotations before a command is executed. + * + * Methods with the {@link Command} annotation will have the rest of their + * annotations scanned and stored if there is a matching + * {@link CommandAnnotationProcessor}. Annotations that do not have a + * processor are discarded. The scanning method uses annotations from the + * declaring class as a base before narrowing using the method's + * annotations. + * + * @param processor + * The annotation processor + */ public void registerAnnotationProcessor(CommandAnnotationProcessor processor) { annotationProcessors.put(processor.getAnnotationClass(), processor); } @@ -320,7 +334,7 @@ public class CommandManager { * instances as necessary. */ private void registerMethods(Class clazz, Method parent) { - Object obj = injector.getInstance(clazz); + Object obj = injector != null ? injector.getInstance(clazz) : null; registerMethods(clazz, parent, obj); } @@ -347,7 +361,7 @@ public class CommandManager { annotations.add(annotation); } for (Annotation annotation : method.getAnnotations()) { - Class annotationClass = annotation.getClass(); + Class annotationClass = annotation.annotationType(); if (!annotationProcessors.containsKey(annotationClass)) continue; Iterator itr = annotations.iterator();