Fix bug with annotation processor

This commit is contained in:
fullwall 2012-12-08 14:28:06 +08:00
parent 159037ad69
commit 856fe8c75e
2 changed files with 331 additions and 272 deletions

View File

@ -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;
/**
* <p>
@ -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<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* 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<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* 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;
}
}
/**
* <p>
* 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:
* </p>
* <code>
* StringBuffer data = new StringBuffer();
* data.append(encode("guid")).append('=').append(encode(guid));
* encodeDataPair(data, "version", description.getVersion());
* </code>
*
* @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");
}
/**
* <p>
* 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:
* </p>
* <code>
* StringBuffer data = new StringBuffer();
* data.append(encode("guid")).append('=').append(encode(guid));
* encodeDataPair(data, "version", description.getVersion());
* </code>
*
* @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));
}
}

View File

@ -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<? extends Annotation> annotationClass = annotation.getClass();
Class<? extends Annotation> annotationClass = annotation.annotationType();
if (!annotationProcessors.containsKey(annotationClass))
continue;
Iterator<Annotation> itr = annotations.iterator();