Refactor the report system. Allow identification of report messages.

All warnings and error messages will now be identified using fields in 
the sender classes, to avoid depending on the format of the error or 
warning messages directly. This decoupling will make it possible to 
filter out certain irrelevant messages.
This commit is contained in:
Kristian 2013-04-26 20:59:28 +02:00
parent 7e18207a2b
commit 40a3abf5b9
22 changed files with 5748 additions and 5255 deletions

View File

@ -24,6 +24,8 @@ import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper;
@ -51,6 +53,9 @@ import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
* @author Kristian
*/
class CleanupStaticMembers {
// Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
private ClassLoader loader;
private ErrorReporter reporter;
@ -116,7 +121,9 @@ class CleanupStaticMembers {
setFinalStatic(field, null);
} catch (IllegalAccessException e) {
// Just inform the player
reporter.reportWarning(this, "Unable to reset field " + field.getName() + ": " + e.getMessage(), e);
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
);
}
}
}
@ -151,7 +158,7 @@ class CleanupStaticMembers {
output.add(loader.loadClass(name));
} catch (ClassNotFoundException e) {
// Warn the user
reporter.reportWarning(this, "Unable to unload class " + name, e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
}
}

View File

@ -23,6 +23,8 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
/**
* Base class for all our commands.
@ -30,6 +32,8 @@ import com.comphenix.protocol.error.ErrorReporter;
* @author Kristian
*/
abstract class CommandBase implements CommandExecutor {
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
public static final String PERMISSION_ADMIN = "protocol.admin";
@ -55,7 +59,7 @@ abstract class CommandBase implements CommandExecutor {
try {
// Make sure we're dealing with the correct command
if (!command.getName().equalsIgnoreCase(name)) {
reporter.reportWarning(this, "Incorrect command assigned to " + this);
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
return false;
}
if (permission != null && !sender.hasPermission(permission)) {
@ -72,7 +76,9 @@ abstract class CommandBase implements CommandExecutor {
}
} catch (Exception e) {
reporter.reportDetailed(this, "Cannot execute command " + name, e, sender, label, args);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_COMMAND_ERROR).error(e).messageParam(name).callerParam(sender, label, args)
);
return true;
}
}

View File

@ -24,6 +24,8 @@ import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.DiscreteDomains;
import com.google.common.collect.Range;
@ -35,6 +37,12 @@ import com.google.common.collect.Ranges;
* @author Kristian
*/
public class CommandFilter extends CommandBase {
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
public interface FilterFailedHandler{
/**
* Invoked when a given filter has failed.
@ -236,7 +244,7 @@ public class CommandFilter extends CommandBase {
printPackageWarning(e1);
if (!config.getScriptEngineName().equals("rhino")) {
reporter.reportWarning(this, "Falling back to the Rhino engine.");
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
config.setScriptEngineName("rhino");
config.saveAll();
@ -244,7 +252,7 @@ public class CommandFilter extends CommandBase {
initializeEngine();
if (!isInitialized()) {
reporter.reportWarning(this, "Could not load Rhino either. Please upgrade your JVM or OS.");
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
}
} catch (ScriptException e2) {
// And again ..
@ -255,7 +263,7 @@ public class CommandFilter extends CommandBase {
}
private void printPackageWarning(ScriptException e) {
reporter.reportWarning(this, "Unable to initialize packages for JavaScript engine.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
}
/**
@ -288,7 +296,9 @@ public class CommandFilter extends CommandBase {
@Override
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
reporter.reportWarning(this, "Removing filter " + filter.getName() + " for causing an exception.");
reporter.reportWarning(this,
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
);
return false;
}
};
@ -398,7 +408,9 @@ public class CommandFilter extends CommandBase {
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
}
} catch (Exception e) {
reporter.reportDetailed(this, "Cannot handle conversation.", e, event);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
);
}
}
}).

View File

@ -36,6 +36,8 @@ import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.concurrency.AbstractIntervalTree;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
@ -57,6 +59,7 @@ import com.google.common.collect.Sets;
* @author Kristian
*/
class CommandPacket extends CommandBase {
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
private interface DetailedPacketListener extends PacketListener {
/**
@ -166,7 +169,9 @@ class CommandPacket extends CommandBase {
try {
chatter.sendMessageSilently(receiver, message);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this, "Cannot send chat message.", e, receiver, message);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
);
}
}
@ -179,7 +184,9 @@ class CommandPacket extends CommandBase {
try {
chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this, "Cannot send chat message.", e, message, permission);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
);
}
}

View File

@ -24,6 +24,8 @@ import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.metrics.Updater;
import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.metrics.Updater.UpdateType;
@ -40,6 +42,10 @@ class CommandProtocol extends CommandBase {
*/
public static final String NAME = "protocol";
public static final ReportType REPORT_HTTP_ERROR = new ReportType("Http error: %s");
public static final ReportType REPORT_CANNOT_CHECK_FOR_UPDATES = new ReportType("Cannot check updates for ProtocolLib.");
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot update ProtocolLib.");
private Plugin plugin;
private Updater updater;
private ProtocolConfig config;
@ -77,9 +83,11 @@ class CommandProtocol extends CommandBase {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) {
if (isHttpError(e)) {
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage());
getReporter().reportWarning(this,
Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
);
} else {
getReporter().reportDetailed(this, "Cannot check updates for ProtocolLib.", e, sender);
getReporter().reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CHECK_FOR_UPDATES).error(e).callerParam(sender));
}
}
}
@ -98,9 +106,11 @@ class CommandProtocol extends CommandBase {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) {
if (isHttpError(e)) {
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage());
getReporter().reportWarning(this,
Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
);
} else {
getReporter().reportDetailed(this, "Cannot update ProtocolLib.", e, sender);
getReporter().reportDetailed(this,Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e).callerParam(sender));
}
}
}

View File

@ -35,6 +35,8 @@ import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
@ -51,6 +53,24 @@ import com.comphenix.protocol.utility.MinecraftVersion;
* @author Kristian
*/
public class ProtocolLibrary extends JavaPlugin {
// Every possible error or warning report type
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version.");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
/**
* The minimum version ProtocolLib has been tested with.
*/
@ -120,13 +140,13 @@ public class ProtocolLibrary extends JavaPlugin {
try {
config = new ProtocolConfig(this);
} catch (Exception e) {
detailedReporter.reportWarning(this, "Cannot load configuration", e);
detailedReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
// Load it again
if (deleteConfig()) {
config = new ProtocolConfig(this);
} else {
reporter.reportWarning(this, "Cannot delete old ProtocolLib configuration.");
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
}
}
@ -162,7 +182,7 @@ public class ProtocolLibrary extends JavaPlugin {
protocolManager.setPlayerHook(hook);
}
} catch (IllegalArgumentException e) {
detailedReporter.reportWarning(config, "Cannot parse injection method. Using default.", e);
detailedReporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
}
// Initialize command handlers
@ -174,7 +194,7 @@ public class ProtocolLibrary extends JavaPlugin {
setupBroadcastUsers(PERMISSION_INFO);
} catch (Throwable e) {
detailedReporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager);
detailedReporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin();
}
}
@ -273,7 +293,7 @@ public class ProtocolLibrary extends JavaPlugin {
createAsyncTask(server);
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e);
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin();
return;
}
@ -284,9 +304,9 @@ public class ProtocolLibrary extends JavaPlugin {
statistisc = new Statistics(this);
}
} catch (IOException e) {
reporter.reportDetailed(this, "Unable to enable metrics.", e, statistisc);
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistisc));
} catch (Throwable e) {
reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc);
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistisc));
}
}
@ -308,7 +328,7 @@ public class ProtocolLibrary extends JavaPlugin {
return current;
} catch (Exception e) {
reporter.reportWarning(this, "Unable to retrieve current Minecraft version.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e));
}
// Unknown version
@ -345,7 +365,7 @@ public class ProtocolLibrary extends JavaPlugin {
}
} catch (Exception e) {
reporter.reportWarning(this, "Unable to detect conflicting plugin versions.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS).error(e));
}
// See if the newest version is actually higher
@ -374,7 +394,9 @@ public class ProtocolLibrary extends JavaPlugin {
throw new RuntimeException("plugin.yml might be corrupt.");
} catch (RuntimeException e) {
reporter.reportWarning(this, "Cannot register command " + name + ": " + e.getMessage());
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e)
);
}
}
@ -408,7 +430,7 @@ public class ProtocolLibrary extends JavaPlugin {
} catch (Throwable e) {
if (asyncPacketTask == -1) {
reporter.reportDetailed(this, "Unable to create packet timeout task.", e);
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
}
}
}
@ -431,7 +453,7 @@ public class ProtocolLibrary extends JavaPlugin {
commandProtocol.updateFinished();
}
} catch (Exception e) {
reporter.reportDetailed(this, "Cannot perform automatic updates.", e);
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
updateDisabled = true;
}
}

View File

@ -34,6 +34,7 @@ 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;
@ -44,11 +45,14 @@ import com.google.common.primitives.Primitives;
* @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/";
public static final String PLUGIN_NAME = "ProtocolLib";
// Users that are informed about errors in the chat
public static final String ERROR_PERMISSION = "protocol.info";
@ -68,6 +72,7 @@ public class DetailedErrorReporter implements ErrorReporter {
protected Logger logger;
protected WeakReference<Plugin> pluginReference;
protected String pluginName;
// Whether or not Apache Commons is not present
protected boolean apacheCommonsMissing;
@ -92,15 +97,6 @@ public class DetailedErrorReporter implements ErrorReporter {
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
}
// Attempt to get the logger.
private static Logger getBukkitLogger() {
try {
return Bukkit.getLogger();
} catch (Throwable e) {
return Logger.getLogger("Minecraft");
}
}
/**
* Create a central error reporting system.
* @param plugin - the plugin owner.
@ -114,23 +110,28 @@ public class DetailedErrorReporter implements ErrorReporter {
throw new IllegalArgumentException("Plugin cannot be NULL.");
this.pluginReference = new WeakReference<Plugin>(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, " Parameters:");
// Print each parameter
for (Object parameter : parameters) {
logger.log(Level.SEVERE, " " + getStringDescription(parameter));
}
logger.log(Level.SEVERE, printParameters(parameters));
}
}
}
@ -158,14 +159,14 @@ public class DetailedErrorReporter implements ErrorReporter {
// See if we should print the full error
if (errorCount < getMaxErrorCount()) {
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " +
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, "[" + PLUGIN_NAME + "] Unhandled exception number " + errorCount + " occured in " +
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
methodName + " for " + pluginName, error);
}
return false;
@ -184,15 +185,36 @@ public class DetailedErrorReporter implements ErrorReporter {
}
@Override
public void reportWarning(Object sender, String message) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message);
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, String message, Throwable error) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error);
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();
@ -201,8 +223,12 @@ public class DetailedErrorReporter implements ErrorReporter {
}
@Override
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) {
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();
@ -211,7 +237,7 @@ public class DetailedErrorReporter implements ErrorReporter {
// Only allow the error count at rare occations
if (isPowerOfTwo(errorCount)) {
// Permit it - but print the number of exceptions first
reportWarning(this, "Internal exception count: " + errorCount + "!");
reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
} else {
// NEVER SPAM THE CONSOLE
return;
@ -222,27 +248,23 @@ public class DetailedErrorReporter implements ErrorReporter {
PrintWriter writer = new PrintWriter(text);
// Helpful message
writer.println("[ProtocolLib] INTERNAL ERROR: " + 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 (error != null)
error.printStackTrace(writer);
if (report.getException() != null) {
report.getException().printStackTrace(writer);
}
// Data dump!
writer.println(" ===== DUMP =====");
// Relevant parameters
if (parameters != null && parameters.length > 0) {
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));
}
if (report.hasCallerParameters()) {
printParameters(writer, report.getCallerParameters());
}
// Global parameters
@ -270,7 +292,7 @@ public class DetailedErrorReporter implements ErrorReporter {
// Inform of this occurrence
if (ERROR_PERMISSION != null) {
Bukkit.getServer().broadcast(
String.format("Error %s (%s) occured in %s.", message, error, sender),
String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
ERROR_PERMISSION
);
}
@ -280,6 +302,23 @@ public class DetailedErrorReporter implements ErrorReporter {
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.
@ -290,6 +329,11 @@ public class DetailedErrorReporter implements ErrorReporter {
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.

View File

@ -19,8 +19,9 @@ package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
public interface ErrorReporter {
import com.comphenix.protocol.error.Report.ReportBuilder;
public interface ErrorReporter {
/**
* Prints a small minimal error report about an exception from another plugin.
* @param sender - the other plugin.
@ -41,25 +42,28 @@ public interface ErrorReporter {
/**
* Prints a warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param message - error message.
* @param report - an error report to include.
*/
public abstract void reportWarning(Object sender, String message);
public abstract void reportWarning(Object sender, Report report);
/**
* Prints a warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param message - error message.
* @param error - the exception that was thrown.
* @param reportBuilder - an error report builder that will be used to get the report.
*/
public abstract void reportWarning(Object sender, String message, Throwable error);
public abstract void reportWarning(Object sender, ReportBuilder reportBuilder);
/**
* Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method.
* @param message - an error message to include.
* @param error - the exception that was thrown in the caller method.
* @param parameters - parameters from the caller method.
* @param report - an error report to include.
*/
public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters);
public abstract void reportDetailed(Object sender, Report report);
/**
* Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method.
* @param reportBuilder - an error report builder that will be used to get the report.
*/
public abstract void reportDetailed(Object sender, ReportBuilder reportBuilder);
}

View File

@ -0,0 +1,162 @@
package com.comphenix.protocol.error;
import javax.annotation.Nullable;
/**
* Represents a error or warning report.
*
* @author Kristian
*/
public class Report {
private final ReportType type;
private final Throwable exception;
private final Object[] messageParameters;
private final Object[] callerParameters;
/**
* Must be constructed through the factory method in Report.
*/
public static class ReportBuilder {
private ReportType type;
private Throwable exception;
private Object[] messageParameters;
private Object[] callerParameters;
private ReportBuilder() {
// Don't allow
}
/**
* Set the current report type. Cannot be NULL.
* @param type - report type.
* @return This builder, for chaining.
*/
public ReportBuilder type(ReportType type) {
if (type == null)
throw new IllegalArgumentException("Report type cannot be set to NULL.");
this.type = type;
return this;
}
/**
* Set the current exception that occured.
* @param exception - exception that occured.
* @return This builder, for chaining.
*/
public ReportBuilder error(@Nullable Throwable exception) {
this.exception = exception;
return this;
}
/**
* Set the message parameters that are used to construct a message text.
* @param messageParameters - parameters for the report type.
* @return This builder, for chaining.
*/
public ReportBuilder messageParam(@Nullable Object... messageParameters) {
this.messageParameters = messageParameters;
return this;
}
/**
* Set the parameters in the caller method. This is optional.
* @param callerParameters - parameters of the caller method.
* @return This builder, for chaining.
*/
public ReportBuilder callerParam(@Nullable Object... callerParameters) {
this.callerParameters = callerParameters;
return this;
}
/**
* Construct a new report with the provided input.
* @return A new report.
*/
public Report build() {
return new Report(type, exception, messageParameters, callerParameters);
}
}
/**
* Construct a new report builder.
* @param type - the initial report type.
* @return Report builder.
*/
public static ReportBuilder newBuilder(ReportType type) {
return new ReportBuilder().type(type);
}
/**
* Construct a new report with the given type and parameters.
* @param exception - exception that occured in the caller method.
* @param type - the report type that will be used to construct the message.
* @param messageParameters - parameters used to construct the report message.
* @param callerParameters - parameters from the caller method.
*/
protected Report(ReportType type, @Nullable Throwable exception, @Nullable Object[] messageParameters, @Nullable Object[] callerParameters) {
if (type == null)
throw new IllegalArgumentException("type cannot be NULL.");
this.type = type;
this.exception = exception;
this.messageParameters = messageParameters;
this.callerParameters = callerParameters;
}
/**
* Format the current report type with the provided message parameters.
* @return The formated report message.
*/
public String getReportMessage() {
return type.getMessage(messageParameters);
}
/**
* Retrieve the message parameters that will be used to construc the report message.
* <p<
* This should not be confused with the method parameters of the caller method.
* @return Message parameters.
*/
public Object[] getMessageParameters() {
return messageParameters;
}
/**
* Retrieve the parameters of the caller method. Optional - may be NULL.
* @return Parameters or the caller method.
*/
public Object[] getCallerParameters() {
return callerParameters;
}
/**
* Retrieve the report type.
* @return Report type.
*/
public ReportType getType() {
return type;
}
/**
* Retrieve the associated exception, or NULL if not found.
* @return Associated exception, or NULL.
*/
public Throwable getException() {
return exception;
}
/**
* Determine if we have any message parameters.
* @return TRUE if there are any message parameters, FALSE otherwise.
*/
public boolean hasMessageParameters() {
return messageParameters != null && messageParameters.length > 0;
}
/**
* Determine if we have any caller parameters.
* @return TRUE if there are any caller parameters, FALSE otherwise.
*/
public boolean hasCallerParameters() {
return callerParameters != null && callerParameters.length > 0;
}
}

View File

@ -0,0 +1,66 @@
package com.comphenix.protocol.error;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Represents a strongly-typed report. Subclasses should be immutable.
* <p>
* By convention, a report must be declared as a static field publicly accessible from the sender class.
* @author Kristian
*/
public class ReportType {
private final String errorFormat;
/**
* Construct a new report type.
* @param errorFormat - string used to format the underlying report.
*/
public ReportType(String errorFormat) {
this.errorFormat = errorFormat;
}
/**
* Convert the given report to a string, using the provided parameters.
* @param parameters - parameters to insert, or NULL to insert nothing.
* @return The full report in string format.
*/
public String getMessage(Object[] parameters) {
if (parameters == null || parameters.length == 0)
return toString();
else
return String.format(errorFormat, parameters);
}
@Override
public String toString() {
return errorFormat;
}
/**
* Retrieve all publicly associated reports.
* @param clazz - sender class.
* @return All associated reports.
*/
public static ReportType[] getReports(Class<?> clazz) {
if (clazz == null)
throw new IllegalArgumentException("clazz cannot be NULL.");
List<ReportType> result = new ArrayList<ReportType>();
for (Field field : clazz.getFields()) {
if (Modifier.isStatic(field.getModifiers()) &&
ReportType.class.isAssignableFrom(field.getDeclaringClass())) {
try {
result.add((ReportType) field.get(null));
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to access field.", e);
}
}
}
return result.toArray(new ReportType[0]);
}
}

View File

@ -25,6 +25,9 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
@ -42,8 +45,32 @@ import com.google.common.primitives.Primitives;
* @author Kristian
*/
public class BukkitUnwrapper implements Unwrapper {
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
// The current error reporter
private final ErrorReporter reporter;
/**
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
*/
public BukkitUnwrapper() {
this(ProtocolLibrary.getErrorReporter());
}
/**
* Construct a new Bukkit unwrapper with the given error reporter.
* @param reporter - the error reporter to use.
*/
public BukkitUnwrapper(ErrorReporter reporter) {
this.reporter = reporter;
}
@SuppressWarnings("unchecked")
@Override
public Object unwrapItem(Object wrappedObject) {
@ -111,8 +138,9 @@ public class BukkitUnwrapper implements Unwrapper {
return find.invoke(wrappedObject);
} catch (IllegalArgumentException e) {
ProtocolLibrary.getErrorReporter().reportDetailed(
this, "Illegal argument.", e, wrappedObject, find);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
);
} catch (IllegalAccessException e) {
// Should not occur either
return null;
@ -129,7 +157,9 @@ public class BukkitUnwrapper implements Unwrapper {
return methodUnwrapper;
} catch (SecurityException e) {
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Security limitation.", e, type.getName());
reporter.reportDetailed(this,
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
);
} catch (NoSuchMethodException e) {
// Try getting the field unwrapper too
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
@ -137,7 +167,8 @@ public class BukkitUnwrapper implements Unwrapper {
if (fieldUnwrapper != null)
return fieldUnwrapper;
else
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Cannot find method.", e, type.getName());
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
}
// Default method
@ -160,8 +191,9 @@ public class BukkitUnwrapper implements Unwrapper {
try {
return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) {
ProtocolLibrary.getErrorReporter().reportDetailed(
this, "Cannot read field 'handle'.", e, wrappedObject, find.getName());
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
);
return null;
}
}
@ -172,9 +204,9 @@ public class BukkitUnwrapper implements Unwrapper {
} else {
// Inform about this too
ProtocolLibrary.getErrorReporter().reportDetailed(
this, "Could not find field 'handle'.",
new Exception("Unable to find 'handle'"), type.getName());
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
);
return null;
}
}

View File

@ -50,6 +50,8 @@ import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
@ -67,6 +69,22 @@ import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector");
public static final ReportType REPORT_PLUGIN_DEPEND_MISSING =
new ReportType("%s doesn't depend on ProtocolLib. Check that its plugin.yml has a 'depend' directive.");
// Registering packet IDs that are not supported
public static final ReportType REPORT_UNSUPPORTED_SERVER_PACKET_ID = new ReportType("[%s] Unsupported server packet ID in current Minecraft version: %s");
public static final ReportType REPORT_UNSUPPORTED_CLIENT_PACKET_ID = new ReportType("[%s] Unsupported client packet ID in current Minecraft version: %s");
// Problems injecting and uninjecting players
public static final ReportType REPORT_CANNOT_UNINJECT_PLAYER = new ReportType("Unable to uninject net handler for player.");
public static final ReportType REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER = new ReportType("Unable to uninject logged off player.");
public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player.");
public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin.");
/**
* Sets the inject hook type. Different types allow for maximum compatibility.
@ -234,11 +252,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
knowsServerPackets = PacketRegistry.getServerPackets() != null;
knowsClientPackets = PacketRegistry.getClientPackets() != null;
} catch (FieldAccessException e) {
reporter.reportWarning(this, "Cannot load server and client packet list.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
}
} catch (FieldAccessException e) {
reporter.reportWarning(this, "Unable to initialize packet injector.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR).error(e));
}
}
@ -282,7 +300,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
private void printPluginWarnings(Plugin plugin) {
switch (pluginVerifier.verify(plugin)) {
case NO_DEPEND:
reporter.reportWarning(this, plugin + " doesn't depend on ProtocolLib. Check that its plugin.yml has a 'depend' directive.");
reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName()));
case VALID:
// Do nothing
break;
@ -510,10 +528,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID))
playerInjection.addPacketHandler(packetID);
else
reporter.reportWarning(this, String.format(
"[%s] Unsupported server packet ID in current Minecraft version: %s",
PacketAdapter.getPluginName(listener), packetID
));
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
);
}
// As above, only for client packets
@ -521,10 +538,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID))
packetInjector.addPacketHandler(packetID);
else
reporter.reportWarning(this, String.format(
"[%s] Unsupported client packet ID in current Minecraft version: %s",
PacketAdapter.getPluginName(listener), packetID
));
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
);
}
}
}
@ -722,7 +738,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
playerInjection.updatePlayer(event.getPlayer());
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event);
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNINJECT_PLAYER).callerParam(event).error(e)
);
}
}
@ -731,7 +749,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_INJECT_PLAYER).callerParam(event).error(e)
);
}
}
@ -743,7 +763,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
playerInjection.handleDisconnect(player);
playerInjection.uninjectPlayer(player);
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event);
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER).callerParam(event).error(e)
);
}
}
@ -754,7 +776,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
removePacketListeners(event.getPlugin());
}
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event);
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNREGISTER_PLUGIN).callerParam(event).error(e)
);
}
}

View File

@ -26,6 +26,8 @@ import java.util.Set;
import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
@ -44,6 +46,11 @@ import com.google.common.collect.ImmutableSet;
*/
@SuppressWarnings("rawtypes")
public class PacketRegistry {
public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
private static final int MIN_SERVER_PACKETS = 5;
private static final int MIN_CLIENT_PACKETS = 5;
@ -118,7 +125,8 @@ public class PacketRegistry {
}
} catch (IllegalArgumentException e) {
// Whatever
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, "Unable to correct no entry value.", e);
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
}
// We'll assume this a Trove map
@ -200,10 +208,12 @@ public class PacketRegistry {
// Check sizes
if (serverPackets.size() < MIN_SERVER_PACKETS)
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, "Too few server packets detected: " + serverPackets.size());
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size())
);
if (clientPackets.size() < MIN_CLIENT_PACKETS)
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, "Too few client packets detected: " + clientPackets.size());
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size())
);
} else {
throw new FieldAccessException("Cannot retrieve packet client/server sets.");

View File

@ -22,6 +22,8 @@ import java.lang.reflect.Method;
import java.util.Map;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.MapMaker;
@ -30,6 +32,8 @@ import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class ReadPacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
// A cancel marker
private static final Object CANCEL_MARKER = new Object();
@ -122,7 +126,9 @@ class ReadPacketModifier implements MethodInterceptor {
}
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
);
}
}
return returnValue;

View File

@ -24,6 +24,8 @@ import java.util.Set;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
@ -38,6 +40,7 @@ import net.sf.cglib.proxy.MethodProxy;
* @author Kristian
*/
class InjectedArrayList extends ArrayList<Object> {
public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
/**
* Silly Eclipse.
@ -89,7 +92,7 @@ class InjectedArrayList extends ArrayList<Object> {
// Prefer to report this to the user, instead of risking sending it to Minecraft
if (reporter != null) {
reporter.reportDetailed(this, "Reverting cancelled packet failed.", e, packet);
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet));
} else {
System.out.println("[ProtocolLib] Reverting cancelled packet failed.");
e.printStackTrace();

View File

@ -27,6 +27,8 @@ import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
@ -40,6 +42,19 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* @author Kristian
*/
class InjectedServerConnection {
// A number of things can go wrong ...
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
private static Field listenerThreadField;
private static Field minecraftServerField;
@ -74,7 +89,6 @@ class InjectedServerConnection {
}
public void injectList() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
@ -88,7 +102,7 @@ class InjectedServerConnection {
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit.");
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
return;
}
@ -107,7 +121,7 @@ class InjectedServerConnection {
} catch (Exception e) {
// Oh damn - inform the player
reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e);
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
}
}
@ -117,7 +131,9 @@ class InjectedServerConnection {
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
} catch (RuntimeException e) {
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
);
return;
}
@ -127,7 +143,7 @@ class InjectedServerConnection {
try {
listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
reporter.reportWarning(this, "Unable to read the listener thread.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
return;
}
@ -140,14 +156,15 @@ class InjectedServerConnection {
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = serverConnectionMethod.invoke(minecraftServer);
} catch (Exception ex) {
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer);
} catch (Exception e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
);
return;
}
@ -160,7 +177,9 @@ class InjectedServerConnection {
// Verify the field count
if (matches.size() != 1)
reporter.reportWarning(this, "Unexpected number of threads in " + serverConnection.getClass().getName());
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
);
else
dedicatedThreadField = matches.get(0);
}
@ -175,7 +194,7 @@ class InjectedServerConnection {
injectEveryListField(dedicatedThread, 1);
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
}
injectIntoList(serverConnection, listField);
@ -201,7 +220,7 @@ class InjectedServerConnection {
// Warn about unexpected errors
if (lists.size() < minimum) {
reporter.reportWarning(this, "Unable to inject " + minimum + " lists in " + container.getClass().getName());
reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
}
}
@ -241,8 +260,9 @@ class InjectedServerConnection {
try {
writer.copyTo(inserting, replacement, inserting.getClass());
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting +
" to new.", e, inserting, replacement);
reporter.reportDetailed(InjectedServerConnection.this,
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
);
}
}
}

View File

@ -23,6 +23,8 @@ import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
@ -35,6 +37,9 @@ import com.google.common.collect.Maps;
* @author Kristian
*/
class NetLoginInjector {
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook
@ -83,8 +88,12 @@ class NetLoginInjector {
} catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this, "Unable to hook " +
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(inserting, injectionHandler).
error(e)
);
return inserting;
}
}
@ -122,8 +131,12 @@ class NetLoginInjector {
} catch (Throwable e) {
// Don't leak this to Minecraft
reporter.reportDetailed(this, "Cannot cleanup " +
MinecraftReflection.getNetLoginHandlerName() + ".", e, removing);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(removing).
error(e)
);
}
}
}

View File

@ -28,6 +28,8 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
@ -48,6 +50,11 @@ import com.comphenix.protocol.utility.MinecraftVersion;
* @author Kristian
*/
class NetworkServerInjector extends PlayerInjector {
// Disconnected field
public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
private volatile static CallbackFilter callbackFilter;
private volatile static boolean foundSendPacket;
@ -309,7 +316,7 @@ class NetworkServerInjector extends PlayerInjector {
// Assume it's the first ...
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
reporter.reportWarning(this, "Unable to find 'disconnected' field. Assuming " + disconnectField);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
// Try again
if (disconnectField != null) {
@ -319,10 +326,10 @@ class NetworkServerInjector extends PlayerInjector {
}
// This is really bad
reporter.reportDetailed(this, "Cannot find disconnected field. Is ProtocolLib up to date?", e);
reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice.");
reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
}
}

View File

@ -30,6 +30,8 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
@ -45,7 +47,21 @@ import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
abstract class PlayerInjector implements SocketInjector {
public abstract class PlayerInjector implements SocketInjector {
// Disconnect method related reports
public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s.");
public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s");
public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method.");
public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket.");
public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket.");
public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER =
new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!");
public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type.");
public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent.");
public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet.");
// Net login handler stuff
private static Field netLoginNetworkField;
@ -305,7 +321,7 @@ abstract class PlayerInjector implements SocketInjector {
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class);
reporter.reportWarning(this, "Cannot find disconnect method by name. Assuming " + disconnect);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect));
}
// Save the method for later
@ -319,9 +335,9 @@ abstract class PlayerInjector implements SocketInjector {
disconnect.invoke(handler, message);
return;
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this, "Invalid argument passed to disconnect method: " + message, e, handler);
reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to access disconnect method.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e));
}
}
@ -332,16 +348,15 @@ abstract class PlayerInjector implements SocketInjector {
try {
socket.close();
} catch (IOException e) {
reporter.reportDetailed(this, "Unable to close socket.", e, socket);
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket));
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Insufficient permissions. Cannot close socket.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e));
}
}
private Field getProxyField(Object notchEntity, Field serverField) {
try {
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
@ -353,7 +368,7 @@ abstract class PlayerInjector implements SocketInjector {
return null;
hasProxyType = true;
reporter.reportWarning(this, "Detected server handler proxy type by another plugin. Conflict may occur!");
reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(notchEntity, serverField));
// No? Is it a Proxy type?
try {
@ -368,7 +383,7 @@ abstract class PlayerInjector implements SocketInjector {
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to load server handler from proxy type.");
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField));
}
// Nope, just go with it
@ -534,7 +549,7 @@ abstract class PlayerInjector implements SocketInjector {
try {
updatedPlayer = (Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(getNetHandler()));
} catch (IllegalAccessException e) {
reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet);
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet));
}
}
@ -559,7 +574,7 @@ abstract class PlayerInjector implements SocketInjector {
}
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot handle server packet.", e, packet);
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet));
}
return packet;

View File

@ -34,6 +34,8 @@ import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
@ -59,6 +61,16 @@ import com.google.common.collect.Maps;
* @author Kristian
*/
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// Warnings and errors
public static final ReportType REPORT_UNSUPPPORTED_LISTENER = new ReportType("Cannot fully register listener for %s: %s");
// Fallback to older player hook types
public static final ReportType REPORT_PLAYER_HOOK_FAILED = new ReportType("Player hook %s failed.");
public static final ReportType REPORT_SWITCHED_PLAYER_HOOK = new ReportType("Switching to %s instead.");
public static final ReportType REPORT_HOOK_CLEANUP_FAILED = new ReportType("Cleaing up after player hook failed.");
public static final ReportType REPORT_CANNOT_REVERT_HOOK = new ReportType("Unable to fully revert old injector. May cause conflicts.");
// Server connection injection
private InjectedServerConnection serverInjection;
@ -355,8 +367,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} catch (Exception e) {
// Mark this injection attempt as a failure
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
e, player, injectionPoint, phase);
reporter.reportDetailed(this,
Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e)
);
hookFailed = true;
}
@ -364,7 +377,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead.");
reporter.reportWarning(this, Report.newBuilder(REPORT_SWITCHED_PLAYER_HOOK).messageParam(tempHook));
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
@ -400,7 +413,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
if (injector != null)
injector.cleanupAll();
} catch (Exception ex) {
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector);
reporter.reportDetailed(this, Report.newBuilder(REPORT_HOOK_CLEANUP_FAILED).callerParam(injector).error(ex));
}
}
@ -462,7 +475,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
} catch (IllegalAccessException e) {
// Let the user know
reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REVERT_HOOK).error(e));
}
}
@ -646,8 +659,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// We won't prevent the listener, as it may still have valid packets
if (result != null) {
reporter.reportWarning(this, "Cannot fully register listener for " +
PacketAdapter.getPluginName(listener) + ": " + result.toString());
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result)
);
// These are illegal
for (int packetID : result.getPackets())

View File

@ -32,6 +32,8 @@ import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey;
import com.google.common.collect.Lists;
@ -46,6 +48,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
* @author Kristian
*/
public class BackgroundCompiler {
public static final ReportType REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER = new ReportType("Cannot compile structure. Disabing compiler.");
public static final ReportType REPORT_CANNOT_SCHEDULE_COMPILATION = new ReportType("Unable to schedule compilation task.");
/**
* The default format for the name of new worker threads.
@ -224,7 +228,8 @@ public class BackgroundCompiler {
// Inform about this error as best as we can
if (reporter != null) {
reporter.reportDetailed(BackgroundCompiler.this,
"Cannot compile structure. Disabing compiler.", e, uncompiled);
Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e)
);
} else {
System.err.println("Exception occured in structure compiler: ");
e.printStackTrace();
@ -258,7 +263,7 @@ public class BackgroundCompiler {
// Occures when the underlying queue is overflowing. Since the compilation
// is only an optmization and not really essential we'll just log this failure
// and move on.
reporter.reportWarning(this, "Unable to schedule compilation task.", e);
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e));
}
}
}

View File

@ -26,6 +26,8 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects;
import com.google.common.primitives.Primitives;
@ -92,6 +94,7 @@ import net.sf.cglib.asm.*;
* @author Kristian
*/
public final class StructureCompiler {
public static final ReportType REPORT_TOO_MANY_GENERATED_CLASSES = new ReportType("Generated too many classes (count: %s)");
// Used to store generated classes of different types
@SuppressWarnings("rawtypes")
@ -210,7 +213,8 @@ public final class StructureCompiler {
} catch (OutOfMemoryError e) {
// Print the number of generated classes by the current instances
ProtocolLibrary.getErrorReporter().reportWarning(
this, "May have generated too many classes (count: " + compiledCache.size() + ")");
this, Report.newBuilder(REPORT_TOO_MANY_GENERATED_CLASSES).messageParam(compiledCache.size())
);
throw e;
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Used invalid parameters in instance creation", e);