diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 4677a5f8..07330826 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -217,7 +217,7 @@ public class ProtocolLibrary extends JavaPlugin { @Override protected Report filterReport(Object sender, Report report, boolean detailed) { - String canonicalName = ReportType.getReportName(sender.getClass(), report.getType()); + String canonicalName = ReportType.getReportName(sender, report.getType()); String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase(); if (config != null && config.getModificationCount() != lastModCount) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java index 7584f5d0..6d855320 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java @@ -1,80 +1,80 @@ -package com.comphenix.protocol.error; - -import org.bukkit.plugin.Plugin; - -import com.comphenix.protocol.error.Report.ReportBuilder; - -/** - * Construct an error reporter that delegates to another error reporter. - * @author Kristian - */ -public class DelegatedErrorReporter implements ErrorReporter { - private final ErrorReporter delegated; - - /** - * Construct a new error reporter that forwards all reports to a given reporter. - * @param delegated - the delegated reporter. - */ - public DelegatedErrorReporter(ErrorReporter delegated) { - this.delegated = delegated; - } - - /** - * Retrieve the underlying error reporter. - * @return Underlying error reporter. - */ - public ErrorReporter getDelegated() { - return delegated; - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error) { - delegated.reportMinimal(sender, methodName, error); - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { - delegated.reportMinimal(sender, methodName, error, parameters); - } - - @Override - public void reportWarning(Object sender, Report report) { - Report transformed = filterReport(sender, report, false); - - if (transformed != null) { - delegated.reportWarning(sender, transformed); - } - } - - @Override - public void reportDetailed(Object sender, Report report) { - Report transformed = filterReport(sender, report, true); - - if (transformed != null) { - delegated.reportDetailed(sender, transformed); - } - } - - /** - * Invoked before an error report is passed on to the underlying error reporter. - *

- * To cancel a report, return NULL. - * @param sender - the sender component. - * @param report - the error report. - * @param detailed - whether or not the report will be displayed in detail. - * @return The report to pass on, or NULL to cancel it. - */ - protected Report filterReport(Object sender, Report report, boolean detailed) { - return report; - } - - @Override - public void reportWarning(Object sender, ReportBuilder reportBuilder) { - reportWarning(sender, reportBuilder.build()); - } - - @Override - public void reportDetailed(Object sender, ReportBuilder reportBuilder) { - reportDetailed(sender, reportBuilder.build()); - } -} +package com.comphenix.protocol.error; + +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.error.Report.ReportBuilder; + +/** + * Construct an error reporter that delegates to another error reporter. + * @author Kristian + */ +public class DelegatedErrorReporter implements ErrorReporter { + private final ErrorReporter delegated; + + /** + * Construct a new error reporter that forwards all reports to a given reporter. + * @param delegated - the delegated reporter. + */ + public DelegatedErrorReporter(ErrorReporter delegated) { + this.delegated = delegated; + } + + /** + * Retrieve the underlying error reporter. + * @return Underlying error reporter. + */ + public ErrorReporter getDelegated() { + return delegated; + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error) { + delegated.reportMinimal(sender, methodName, error); + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { + delegated.reportMinimal(sender, methodName, error, parameters); + } + + @Override + public void reportWarning(Object sender, Report report) { + Report transformed = filterReport(sender, report, false); + + if (transformed != null) { + delegated.reportWarning(sender, transformed); + } + } + + @Override + public void reportDetailed(Object sender, Report report) { + Report transformed = filterReport(sender, report, true); + + if (transformed != null) { + delegated.reportDetailed(sender, transformed); + } + } + + /** + * Invoked before an error report is passed on to the underlying error reporter. + *

+ * To cancel a report, return NULL. + * @param sender - the sender instance or class. + * @param report - the error report. + * @param detailed - whether or not the report will be displayed in detail. + * @return The report to pass on, or NULL to cancel it. + */ + protected Report filterReport(Object sender, Report report, boolean detailed) { + return report; + } + + @Override + public void reportWarning(Object sender, ReportBuilder reportBuilder) { + reportWarning(sender, reportBuilder.build()); + } + + @Override + public void reportDetailed(Object sender, ReportBuilder reportBuilder) { + reportDetailed(sender, reportBuilder.build()); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java index 6953d437..9020da58 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java @@ -1,499 +1,499 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.error; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.apache.commons.lang.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; - -import com.comphenix.protocol.error.Report.ReportBuilder; -import com.comphenix.protocol.events.PacketAdapter; -import com.comphenix.protocol.reflect.PrettyPrinter; -import com.google.common.primitives.Primitives; - -/** - * Internal class used to handle exceptions. - * - * @author Kristian - */ -public class DetailedErrorReporter implements ErrorReporter { - /** - * Report format for printing the current exception count. - */ - public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!"); - - public static final String SECOND_LEVEL_PREFIX = " "; - public static final String DEFAULT_PREFIX = " "; - public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/"; - - // Users that are informed about errors in the chat - public static final String ERROR_PERMISSION = "protocol.info"; - - // We don't want to spam the server - public static final int DEFAULT_MAX_ERROR_COUNT = 20; - - // Prevent spam per plugin too - private ConcurrentMap warningCount = new ConcurrentHashMap(); - - protected String prefix; - protected String supportURL; - - protected AtomicInteger internalErrorCount = new AtomicInteger(); - - protected int maxErrorCount; - protected Logger logger; - - protected WeakReference pluginReference; - protected String pluginName; - - // Whether or not Apache Commons is not present - protected boolean apacheCommonsMissing; - - // Map of global objects - protected Map globalParameters = new HashMap(); - - /** - * Create a default error reporting system. - */ - public DetailedErrorReporter(Plugin plugin) { - this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL); - } - - /** - * Create a central error reporting system. - * @param plugin - the plugin owner. - * @param prefix - default line prefix. - * @param supportURL - URL to report the error. - */ - public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) { - this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); - } - - /** - * Create a central error reporting system. - * @param plugin - the plugin owner. - * @param prefix - default line prefix. - * @param supportURL - URL to report the error. - * @param maxErrorCount - number of errors to print before giving up. - * @param logger - current logger. - */ - public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) { - if (plugin == null) - throw new IllegalArgumentException("Plugin cannot be NULL."); - - this.pluginReference = new WeakReference(plugin); - this.pluginName = plugin.getName(); - this.prefix = prefix; - this.supportURL = supportURL; - this.maxErrorCount = maxErrorCount; - this.logger = logger; - } - - // Attempt to get the logger. - private static Logger getBukkitLogger() { - try { - return Bukkit.getLogger(); - } catch (Throwable e) { - return Logger.getLogger("Minecraft"); - } - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { - if (reportMinimalNoSpam(sender, methodName, error)) { - // Print parameters, if they are given - if (parameters != null && parameters.length > 0) { - logger.log(Level.SEVERE, printParameters(parameters)); - } - } - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error) { - reportMinimalNoSpam(sender, methodName, error); - } - - /** - * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports. - * @param sender - the component that observed this exception. - * @param methodName - the method name. - * @param error - the error itself. - * @return TRUE if the error was printed, FALSE if it was suppressed. - */ - public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) { - String pluginName = PacketAdapter.getPluginName(sender); - AtomicInteger counter = warningCount.get(pluginName); - - // Thread safe pattern - if (counter == null) { - AtomicInteger created = new AtomicInteger(); - counter = warningCount.putIfAbsent(pluginName, created); - - if (counter == null) { - counter = created; - } - } - - final int errorCount = counter.incrementAndGet(); - - // See if we should print the full error - if (errorCount < getMaxErrorCount()) { - logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " + - methodName + " for " + pluginName, error); - return true; - - } else { - // Nope - only print the error count occationally - if (isPowerOfTwo(errorCount)) { - logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " + - methodName + " for " + pluginName, error); - } - return false; - } - } - - /** - * Determine if a given number is a power of two. - *

- * That is, if there exists an N such that 2^N = number. - * @param number - the number to check. - * @return TRUE if the given number is a power of two, FALSE otherwise. - */ - private boolean isPowerOfTwo(int number) { - return (number & (number - 1)) == 0; - } - - @Override - public void reportWarning(Object sender, ReportBuilder reportBuilder) { - if (reportBuilder == null) - throw new IllegalArgumentException("reportBuilder cannot be NULL."); - - reportWarning(sender, reportBuilder.build()); - } - - @Override - public void reportWarning(Object sender, Report report) { - String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage(); - - // Print the main warning - if (report.getException() != null) { - logger.log(Level.WARNING, message, report.getException()); - } else { - logger.log(Level.WARNING, message); - } - - // Parameters? - if (report.hasCallerParameters()) { - // Write it - logger.log(Level.WARNING, printParameters(report.getCallerParameters())); - } - } - - /** - * Retrieve the name of a sender class. - * @param sender - sender object. - * @return The name of the sender's class. - */ - private String getSenderName(Object sender) { - if (sender != null) - return sender.getClass().getSimpleName(); - else - return "NULL"; - } - - @Override - public void reportDetailed(Object sender, ReportBuilder reportBuilder) { - reportDetailed(sender, reportBuilder.build()); - } - - @Override - public void reportDetailed(Object sender, Report report) { - final Plugin plugin = pluginReference.get(); - final int errorCount = internalErrorCount.incrementAndGet(); - - // Do not overtly spam the server! - if (errorCount > getMaxErrorCount()) { - // Only allow the error count at rare occations - if (isPowerOfTwo(errorCount)) { - // Permit it - but print the number of exceptions first - reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build()); - } else { - // NEVER SPAM THE CONSOLE - return; - } - } - - StringWriter text = new StringWriter(); - PrintWriter writer = new PrintWriter(text); - - // Helpful message - writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage()); - writer.println("If this problem hasn't already been reported, please open a ticket"); - writer.println("at " + supportURL + " with the following data:"); - - // Now, let us print important exception information - writer.println(" ===== STACK TRACE ====="); - - if (report.getException() != null) { - report.getException().printStackTrace(writer); - } - - // Data dump! - writer.println(" ===== DUMP ====="); - - // Relevant parameters - if (report.hasCallerParameters()) { - printParameters(writer, report.getCallerParameters()); - } - - // Global parameters - for (String param : globalParameters()) { - writer.println(SECOND_LEVEL_PREFIX + param + ":"); - writer.println(addPrefix(getStringDescription(getGlobalParameter(param)), - SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX)); - } - - // Now, for the sender itself - writer.println("Sender:"); - writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX)); - - // And plugin - if (plugin != null) { - writer.println("Version:"); - writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX)); - } - - // Add the server version too - if (Bukkit.getServer() != null) { - writer.println("Server:"); - writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX)); - - // Inform of this occurrence - if (ERROR_PERMISSION != null) { - Bukkit.getServer().broadcast( - String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender), - ERROR_PERMISSION - ); - } - } - - // Make sure it is reported - logger.severe(addPrefix(text.toString(), prefix)); - } - - private String printParameters(Object... parameters) { - StringWriter writer = new StringWriter(); - - // Print and retrieve the string buffer - printParameters(new PrintWriter(writer), parameters); - return writer.toString(); - } - - private void printParameters(PrintWriter writer, Object[] parameters) { - writer.println("Parameters: "); - - // We *really* want to get as much information as possible - for (Object param : parameters) { - writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX)); - } - } - - /** - * Adds the given prefix to every line in the text. - * @param text - text to modify. - * @param prefix - prefix added to every line in the text. - * @return The modified text. - */ - protected String addPrefix(String text, String prefix) { - return text.replaceAll("(?m)^", prefix); - } - - /** - * Retrieve a string representation of the given object. - * @param value - object to convert. - * @return String representation. - */ - protected String getStringDescription(Object value) { - // We can't only rely on toString. - if (value == null) { - return "[NULL]"; - } if (isSimpleType(value)) { - return value.toString(); - } else { - try { - if (!apacheCommonsMissing) - return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null)); - } catch (Throwable ex) { - // Apache is probably missing - apacheCommonsMissing = true; - } - - // Use our custom object printer instead - try { - return PrettyPrinter.printObject(value, value.getClass(), Object.class); - } catch (IllegalAccessException e) { - return "[Error: " + e.getMessage() + "]"; - } - } - } - - /** - * Determine if the given object is a wrapper for a primitive/simple type or not. - * @param test - the object to test. - * @return TRUE if this object is simple enough to simply be printed, FALSE othewise. - */ - protected boolean isSimpleType(Object test) { - return test instanceof String || Primitives.isWrapperType(test.getClass()); - } - - /** - * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}. - * @return Number of errors printed. - */ - public int getErrorCount() { - return internalErrorCount.get(); - } - - /** - * Set the number of errors printed. - * @param errorCount - new number of errors printed. - */ - public void setErrorCount(int errorCount) { - internalErrorCount.set(errorCount); - } - - /** - * Retrieve the maximum number of errors we can print before we begin suppressing errors. - * @return Maximum number of errors. - */ - public int getMaxErrorCount() { - return maxErrorCount; - } - - /** - * Set the maximum number of errors we can print before we begin suppressing errors. - * @param maxErrorCount - new max count. - */ - public void setMaxErrorCount(int maxErrorCount) { - this.maxErrorCount = maxErrorCount; - } - - /** - * Adds the given global parameter. It will be included in every error report. - *

- * Both key and value must be non-null. - * @param key - name of parameter. - * @param value - the global parameter itself. - */ - public void addGlobalParameter(String key, Object value) { - if (key == null) - throw new IllegalArgumentException("key cannot be NULL."); - if (value == null) - throw new IllegalArgumentException("value cannot be NULL."); - - globalParameters.put(key, value); - } - - /** - * Retrieve a global parameter by its key. - * @param key - key of the parameter to retrieve. - * @return The value of the global parameter, or NULL if not found. - */ - public Object getGlobalParameter(String key) { - if (key == null) - throw new IllegalArgumentException("key cannot be NULL."); - - return globalParameters.get(key); - } - - /** - * Reset all global parameters. - */ - public void clearGlobalParameters() { - globalParameters.clear(); - } - - /** - * Retrieve a set of every registered global parameter. - * @return Set of all registered global parameters. - */ - public Set globalParameters() { - return globalParameters.keySet(); - } - - /** - * Retrieve the support URL that will be added to all detailed reports. - * @return Support URL. - */ - public String getSupportURL() { - return supportURL; - } - - /** - * Set the support URL that will be added to all detailed reports. - * @param supportURL - the new support URL. - */ - public void setSupportURL(String supportURL) { - this.supportURL = supportURL; - } - - /** - * Retrieve the prefix to apply to every line in the error reports. - * @return Error report prefix. - */ - public String getPrefix() { - return prefix; - } - - /** - * Set the prefix to apply to every line in the error reports. - * @param prefix - new prefix. - */ - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - /** - * Retrieve the current logger that is used to print all reports. - * @return The current logger. - */ - public Logger getLogger() { - return logger; - } - - /** - * Set the current logger that is used to print all reports. - * @param logger - new logger. - */ - public void setLogger(Logger logger) { - this.logger = logger; - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.error; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.error.Report.ReportBuilder; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.reflect.PrettyPrinter; +import com.google.common.primitives.Primitives; + +/** + * Internal class used to handle exceptions. + * + * @author Kristian + */ +public class DetailedErrorReporter implements ErrorReporter { + /** + * Report format for printing the current exception count. + */ + public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!"); + + public static final String SECOND_LEVEL_PREFIX = " "; + public static final String DEFAULT_PREFIX = " "; + public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/"; + + // Users that are informed about errors in the chat + public static final String ERROR_PERMISSION = "protocol.info"; + + // We don't want to spam the server + public static final int DEFAULT_MAX_ERROR_COUNT = 20; + + // Prevent spam per plugin too + private ConcurrentMap warningCount = new ConcurrentHashMap(); + + protected String prefix; + protected String supportURL; + + protected AtomicInteger internalErrorCount = new AtomicInteger(); + + protected int maxErrorCount; + protected Logger logger; + + protected WeakReference pluginReference; + protected String pluginName; + + // Whether or not Apache Commons is not present + protected boolean apacheCommonsMissing; + + // Map of global objects + protected Map globalParameters = new HashMap(); + + /** + * Create a default error reporting system. + */ + public DetailedErrorReporter(Plugin plugin) { + this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL); + } + + /** + * Create a central error reporting system. + * @param plugin - the plugin owner. + * @param prefix - default line prefix. + * @param supportURL - URL to report the error. + */ + public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) { + this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); + } + + /** + * Create a central error reporting system. + * @param plugin - the plugin owner. + * @param prefix - default line prefix. + * @param supportURL - URL to report the error. + * @param maxErrorCount - number of errors to print before giving up. + * @param logger - current logger. + */ + public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) { + if (plugin == null) + throw new IllegalArgumentException("Plugin cannot be NULL."); + + this.pluginReference = new WeakReference(plugin); + this.pluginName = plugin.getName(); + this.prefix = prefix; + this.supportURL = supportURL; + this.maxErrorCount = maxErrorCount; + this.logger = logger; + } + + // Attempt to get the logger. + private static Logger getBukkitLogger() { + try { + return Bukkit.getLogger(); + } catch (Throwable e) { + return Logger.getLogger("Minecraft"); + } + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { + if (reportMinimalNoSpam(sender, methodName, error)) { + // Print parameters, if they are given + if (parameters != null && parameters.length > 0) { + logger.log(Level.SEVERE, printParameters(parameters)); + } + } + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error) { + reportMinimalNoSpam(sender, methodName, error); + } + + /** + * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports. + * @param sender - the component that observed this exception. + * @param methodName - the method name. + * @param error - the error itself. + * @return TRUE if the error was printed, FALSE if it was suppressed. + */ + public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) { + String pluginName = PacketAdapter.getPluginName(sender); + AtomicInteger counter = warningCount.get(pluginName); + + // Thread safe pattern + if (counter == null) { + AtomicInteger created = new AtomicInteger(); + counter = warningCount.putIfAbsent(pluginName, created); + + if (counter == null) { + counter = created; + } + } + + final int errorCount = counter.incrementAndGet(); + + // See if we should print the full error + if (errorCount < getMaxErrorCount()) { + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " + + methodName + " for " + pluginName, error); + return true; + + } else { + // Nope - only print the error count occationally + if (isPowerOfTwo(errorCount)) { + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " + + methodName + " for " + pluginName, error); + } + return false; + } + } + + /** + * Determine if a given number is a power of two. + *

+ * That is, if there exists an N such that 2^N = number. + * @param number - the number to check. + * @return TRUE if the given number is a power of two, FALSE otherwise. + */ + private boolean isPowerOfTwo(int number) { + return (number & (number - 1)) == 0; + } + + @Override + public void reportWarning(Object sender, ReportBuilder reportBuilder) { + if (reportBuilder == null) + throw new IllegalArgumentException("reportBuilder cannot be NULL."); + + reportWarning(sender, reportBuilder.build()); + } + + @Override + public void reportWarning(Object sender, Report report) { + String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage(); + + // Print the main warning + if (report.getException() != null) { + logger.log(Level.WARNING, message, report.getException()); + } else { + logger.log(Level.WARNING, message); + } + + // Parameters? + if (report.hasCallerParameters()) { + // Write it + logger.log(Level.WARNING, printParameters(report.getCallerParameters())); + } + } + + /** + * Retrieve the name of a sender class. + * @param sender - sender object. + * @return The name of the sender's class. + */ + private String getSenderName(Object sender) { + if (sender != null) + return ReportType.getSenderClass(sender).getSimpleName(); + else + return "NULL"; + } + + @Override + public void reportDetailed(Object sender, ReportBuilder reportBuilder) { + reportDetailed(sender, reportBuilder.build()); + } + + @Override + public void reportDetailed(Object sender, Report report) { + final Plugin plugin = pluginReference.get(); + final int errorCount = internalErrorCount.incrementAndGet(); + + // Do not overtly spam the server! + if (errorCount > getMaxErrorCount()) { + // Only allow the error count at rare occations + if (isPowerOfTwo(errorCount)) { + // Permit it - but print the number of exceptions first + reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build()); + } else { + // NEVER SPAM THE CONSOLE + return; + } + } + + StringWriter text = new StringWriter(); + PrintWriter writer = new PrintWriter(text); + + // Helpful message + writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage()); + writer.println("If this problem hasn't already been reported, please open a ticket"); + writer.println("at " + supportURL + " with the following data:"); + + // Now, let us print important exception information + writer.println(" ===== STACK TRACE ====="); + + if (report.getException() != null) { + report.getException().printStackTrace(writer); + } + + // Data dump! + writer.println(" ===== DUMP ====="); + + // Relevant parameters + if (report.hasCallerParameters()) { + printParameters(writer, report.getCallerParameters()); + } + + // Global parameters + for (String param : globalParameters()) { + writer.println(SECOND_LEVEL_PREFIX + param + ":"); + writer.println(addPrefix(getStringDescription(getGlobalParameter(param)), + SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX)); + } + + // Now, for the sender itself + writer.println("Sender:"); + writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX)); + + // And plugin + if (plugin != null) { + writer.println("Version:"); + writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX)); + } + + // Add the server version too + if (Bukkit.getServer() != null) { + writer.println("Server:"); + writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX)); + + // Inform of this occurrence + if (ERROR_PERMISSION != null) { + Bukkit.getServer().broadcast( + String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender), + ERROR_PERMISSION + ); + } + } + + // Make sure it is reported + logger.severe(addPrefix(text.toString(), prefix)); + } + + private String printParameters(Object... parameters) { + StringWriter writer = new StringWriter(); + + // Print and retrieve the string buffer + printParameters(new PrintWriter(writer), parameters); + return writer.toString(); + } + + private void printParameters(PrintWriter writer, Object[] parameters) { + writer.println("Parameters: "); + + // We *really* want to get as much information as possible + for (Object param : parameters) { + writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX)); + } + } + + /** + * Adds the given prefix to every line in the text. + * @param text - text to modify. + * @param prefix - prefix added to every line in the text. + * @return The modified text. + */ + protected String addPrefix(String text, String prefix) { + return text.replaceAll("(?m)^", prefix); + } + + /** + * Retrieve a string representation of the given object. + * @param value - object to convert. + * @return String representation. + */ + protected String getStringDescription(Object value) { + // We can't only rely on toString. + if (value == null) { + return "[NULL]"; + } if (isSimpleType(value) || value instanceof Class) { + return value.toString(); + } else { + try { + if (!apacheCommonsMissing) + return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null)); + } catch (Throwable ex) { + // Apache is probably missing + apacheCommonsMissing = true; + } + + // Use our custom object printer instead + try { + return PrettyPrinter.printObject(value, value.getClass(), Object.class); + } catch (IllegalAccessException e) { + return "[Error: " + e.getMessage() + "]"; + } + } + } + + /** + * Determine if the given object is a wrapper for a primitive/simple type or not. + * @param test - the object to test. + * @return TRUE if this object is simple enough to simply be printed, FALSE othewise. + */ + protected boolean isSimpleType(Object test) { + return test instanceof String || Primitives.isWrapperType(test.getClass()); + } + + /** + * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}. + * @return Number of errors printed. + */ + public int getErrorCount() { + return internalErrorCount.get(); + } + + /** + * Set the number of errors printed. + * @param errorCount - new number of errors printed. + */ + public void setErrorCount(int errorCount) { + internalErrorCount.set(errorCount); + } + + /** + * Retrieve the maximum number of errors we can print before we begin suppressing errors. + * @return Maximum number of errors. + */ + public int getMaxErrorCount() { + return maxErrorCount; + } + + /** + * Set the maximum number of errors we can print before we begin suppressing errors. + * @param maxErrorCount - new max count. + */ + public void setMaxErrorCount(int maxErrorCount) { + this.maxErrorCount = maxErrorCount; + } + + /** + * Adds the given global parameter. It will be included in every error report. + *

+ * Both key and value must be non-null. + * @param key - name of parameter. + * @param value - the global parameter itself. + */ + public void addGlobalParameter(String key, Object value) { + if (key == null) + throw new IllegalArgumentException("key cannot be NULL."); + if (value == null) + throw new IllegalArgumentException("value cannot be NULL."); + + globalParameters.put(key, value); + } + + /** + * Retrieve a global parameter by its key. + * @param key - key of the parameter to retrieve. + * @return The value of the global parameter, or NULL if not found. + */ + public Object getGlobalParameter(String key) { + if (key == null) + throw new IllegalArgumentException("key cannot be NULL."); + + return globalParameters.get(key); + } + + /** + * Reset all global parameters. + */ + public void clearGlobalParameters() { + globalParameters.clear(); + } + + /** + * Retrieve a set of every registered global parameter. + * @return Set of all registered global parameters. + */ + public Set globalParameters() { + return globalParameters.keySet(); + } + + /** + * Retrieve the support URL that will be added to all detailed reports. + * @return Support URL. + */ + public String getSupportURL() { + return supportURL; + } + + /** + * Set the support URL that will be added to all detailed reports. + * @param supportURL - the new support URL. + */ + public void setSupportURL(String supportURL) { + this.supportURL = supportURL; + } + + /** + * Retrieve the prefix to apply to every line in the error reports. + * @return Error report prefix. + */ + public String getPrefix() { + return prefix; + } + + /** + * Set the prefix to apply to every line in the error reports. + * @param prefix - new prefix. + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + /** + * Retrieve the current logger that is used to print all reports. + * @return The current logger. + */ + public Logger getLogger() { + return logger; + } + + /** + * Set the current logger that is used to print all reports. + * @param logger - new logger. + */ + public void setLogger(Logger logger) { + this.logger = logger; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java index e795677c..8e98a5a9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java @@ -46,6 +46,39 @@ public class ReportType { return errorFormat; } + /** + * Retrieve the class of the given sender. + *

+ * If the sender is already a Class, we return it. + * @param sender - the sender to look up. + * @return The class of the sender. + */ + public static Class getSenderClass(Object sender) { + if (sender == null) + throw new IllegalArgumentException("sender cannot be NUll."); + else if (sender instanceof Class) + return (Class) sender; + else + return sender.getClass(); + } + + /** + * Retrieve the full canonical name of a given report type. + *

+ * Note that the sender may be a class (for static callers), in which + * case it will be used directly instead of its getClass() method. + *

+ * It is thus not advisable for class classes to report reports. + * @param sender - the sender, or its class. + * @param type - the report type. + * @return The full canonical name. + */ + public static String getReportName(Object sender, ReportType type) { + if (sender == null) + throw new IllegalArgumentException("sender cannot be NUll."); + return getReportName(getSenderClass(sender), type); + } + /** * Retrieve the full canonical name of a given report type. *

@@ -54,7 +87,7 @@ public class ReportType { * @param type - the report instance. * @return The full canonical name. */ - public static String getReportName(Class sender, ReportType type) { + private static String getReportName(Class sender, ReportType type) { if (sender == null) throw new IllegalArgumentException("sender cannot be NUll."); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java index eaef213f..006f650b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java @@ -362,6 +362,10 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { return asyncManager; } + /** + * Update the asynchronous manager. This must be set. + * @param asyncManager - the asynchronous manager. + */ public void setAsynchronousManager(AsynchronousManager asyncManager) { this.asyncManager = asyncManager; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index c40fd81d..b0267334 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -268,6 +268,10 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // We need to delay this until we know if Netty is enabled final DelayedPacketManager delayed = new DelayedPacketManager(reporter); + // They must reference each other + delayed.setAsynchronousManager(asyncManager); + asyncManager.setManager(delayed); + Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback() { @Override public void onSuccess(WorldInitEvent event) { @@ -297,7 +301,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @Override public void onFailure(Throwable error) { - reporter.reportWarning(this, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); + reporter.reportWarning(PacketFilterManager.class, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); } });