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

@ -1,160 +1,167 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler; import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.MethodUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
/** import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
* /**
* @author Kristian * Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
*/ *
class CleanupStaticMembers { * @author Kristian
*/
private ClassLoader loader; class CleanupStaticMembers {
private ErrorReporter reporter; // Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) { public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
this.loader = loader;
this.reporter = reporter; private ClassLoader loader;
} private ErrorReporter reporter;
/** public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
* Ensure that the previous ClassLoader is not leaking. this.loader = loader;
*/ this.reporter = reporter;
public void resetAll() { }
// This list must always be updated
Class<?>[] publicClasses = { /**
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class, * Ensure that the previous ClassLoader is not leaking.
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, */
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, public void resetAll() {
BackgroundCompiler.class, StructureCompiler.class, // This list must always be updated
ObjectWriter.class, Packets.Server.class, Packets.Client.class, Class<?>[] publicClasses = {
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class, AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class, BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
MinecraftReflection.class, NbtBinarySerializer.class PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
}; BackgroundCompiler.class, StructureCompiler.class,
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
String[] internalClasses = { ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
"com.comphenix.protocol.events.SerializedOfflinePlayer", AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
"com.comphenix.protocol.injector.player.InjectedServerConnection", MinecraftReflection.class, NbtBinarySerializer.class
"com.comphenix.protocol.injector.player.NetworkFieldInjector", };
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector", String[] internalClasses = {
"com.comphenix.protocol.injector.player.PlayerInjector", "com.comphenix.protocol.events.SerializedOfflinePlayer",
"com.comphenix.protocol.injector.EntityUtilities", "com.comphenix.protocol.injector.player.InjectedServerConnection",
"com.comphenix.protocol.injector.packet.PacketRegistry", "com.comphenix.protocol.injector.player.NetworkFieldInjector",
"com.comphenix.protocol.injector.packet.PacketInjector", "com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.packet.ReadPacketModifier", "com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.reflect.compiler.BoxingHelper", "com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor", "com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.wrappers.nbt.WrappedElement", "com.comphenix.protocol.injector.packet.PacketInjector",
}; "com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
resetClasses(publicClasses); "com.comphenix.protocol.reflect.compiler.BoxingHelper",
resetClasses(getClasses(loader, internalClasses)); "com.comphenix.protocol.reflect.compiler.MethodDescriptor",
} "com.comphenix.protocol.wrappers.nbt.WrappedElement",
};
private void resetClasses(Class<?>[] classes) {
// Reset each class one by one resetClasses(publicClasses);
for (Class<?> clazz : classes) { resetClasses(getClasses(loader, internalClasses));
resetClass(clazz); }
}
} private void resetClasses(Class<?>[] classes) {
// Reset each class one by one
private void resetClass(Class<?> clazz) { for (Class<?> clazz : classes) {
for (Field field : clazz.getFields()) { resetClass(clazz);
Class<?> type = field.getType(); }
}
// Only check static non-primitive fields. We also skip strings.
if (Modifier.isStatic(field.getModifiers()) && private void resetClass(Class<?> clazz) {
!type.isPrimitive() && !type.equals(String.class)) { for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
try {
setFinalStatic(field, null); // Only check static non-primitive fields. We also skip strings.
} catch (IllegalAccessException e) { if (Modifier.isStatic(field.getModifiers()) &&
// Just inform the player !type.isPrimitive() && !type.equals(String.class)) {
reporter.reportWarning(this, "Unable to reset field " + field.getName() + ": " + e.getMessage(), e);
} try {
} setFinalStatic(field, null);
} } catch (IllegalAccessException e) {
} // Just inform the player
reporter.reportWarning(this,
// HACK! HAACK! Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException { );
int modifier = field.getModifiers(); }
boolean isFinal = Modifier.isFinal(modifier); }
}
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null; }
// We have to remove the final field first // HACK! HAACK!
if (isFinal) { private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true); int modifier = field.getModifiers();
} boolean isFinal = Modifier.isFinal(modifier);
// Now we can safely modify the field Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
FieldUtils.writeStaticField(field, newValue, true);
// We have to remove the final field first
// Revert modifier if (isFinal) {
if (isFinal) { FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
FieldUtils.writeField(modifiersField, field, modifier, true); }
}
} // Now we can safely modify the field
FieldUtils.writeStaticField(field, newValue, true);
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
List<Class<?>> output = new ArrayList<Class<?>>(); // Revert modifier
if (isFinal) {
for (String name : names) { FieldUtils.writeField(modifiersField, field, modifier, true);
try { }
output.add(loader.loadClass(name)); }
} catch (ClassNotFoundException e) {
// Warn the user private Class<?>[] getClasses(ClassLoader loader, String[] names) {
reporter.reportWarning(this, "Unable to unload class " + name, e); List<Class<?>> output = new ArrayList<Class<?>>();
}
} for (String name : names) {
try {
return output.toArray(new Class<?>[0]); output.add(loader.loadClass(name));
} } catch (ClassNotFoundException e) {
} // Warn the user
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
}
}
return output.toArray(new Class<?>[0]);
}
}

View File

@ -1,111 +1,117 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
/** import com.comphenix.protocol.error.ReportType;
* Base class for all our commands.
* /**
* @author Kristian * Base class for all our commands.
*/ *
abstract class CommandBase implements CommandExecutor { * @author Kristian
*/
public static final String PERMISSION_ADMIN = "protocol.admin"; abstract class CommandBase implements CommandExecutor {
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
private String permission; public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
private String name;
private int minimumArgumentCount; public static final String PERMISSION_ADMIN = "protocol.admin";
protected ErrorReporter reporter; private String permission;
private String name;
public CommandBase(ErrorReporter reporter, String permission, String name) { private int minimumArgumentCount;
this(reporter, permission, name, 0);
} protected ErrorReporter reporter;
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) { public CommandBase(ErrorReporter reporter, String permission, String name) {
this.reporter = reporter; this(reporter, permission, name, 0);
this.name = name; }
this.permission = permission;
this.minimumArgumentCount = minimumArgumentCount; public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
} this.reporter = reporter;
this.name = name;
@Override this.permission = permission;
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) { this.minimumArgumentCount = minimumArgumentCount;
try { }
// Make sure we're dealing with the correct command
if (!command.getName().equalsIgnoreCase(name)) { @Override
reporter.reportWarning(this, "Incorrect command assigned to " + this); public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
return false; try {
} // Make sure we're dealing with the correct command
if (permission != null && !sender.hasPermission(permission)) { if (!command.getName().equalsIgnoreCase(name)) {
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command."); reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
return true; return false;
} }
if (permission != null && !sender.hasPermission(permission)) {
// Check argument length sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
if (args != null && args.length >= minimumArgumentCount) { return true;
return handleCommand(sender, args); }
} else {
sender.sendMessage(ChatColor.RED + "Insufficient commands. You need at least " + minimumArgumentCount); // Check argument length
return false; if (args != null && args.length >= minimumArgumentCount) {
} return handleCommand(sender, args);
} else {
} catch (Exception e) { sender.sendMessage(ChatColor.RED + "Insufficient commands. You need at least " + minimumArgumentCount);
reporter.reportDetailed(this, "Cannot execute command " + name, e, sender, label, args); return false;
return true; }
}
} } catch (Exception e) {
reporter.reportDetailed(this,
/** Report.newBuilder(REPORT_COMMAND_ERROR).error(e).messageParam(name).callerParam(sender, label, args)
* Retrieve the permission necessary to execute this command. );
* @return The permission, or NULL if not needed. return true;
*/ }
public String getPermission() { }
return permission;
} /**
* Retrieve the permission necessary to execute this command.
/** * @return The permission, or NULL if not needed.
* Retrieve the primary name of this command. */
* @return Primary name. public String getPermission() {
*/ return permission;
public String getName() { }
return name;
} /**
* Retrieve the primary name of this command.
/** * @return Primary name.
* Retrieve the error reporter. */
* @return Error reporter. public String getName() {
*/ return name;
protected ErrorReporter getReporter() { }
return reporter;
} /**
* Retrieve the error reporter.
/** * @return Error reporter.
* Main implementation of this command. */
* @param sender - command sender. protected ErrorReporter getReporter() {
* @param args return reporter;
* @return }
*/
protected abstract boolean handleCommand(CommandSender sender, String[] args); /**
} * Main implementation of this command.
* @param sender - command sender.
* @param args
* @return
*/
protected abstract boolean handleCommand(CommandSender sender, String[] args);
}

View File

@ -24,6 +24,8 @@ import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller; import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter; 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.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.DiscreteDomains; import com.google.common.collect.DiscreteDomains;
import com.google.common.collect.Range; import com.google.common.collect.Range;
@ -35,6 +37,12 @@ import com.google.common.collect.Ranges;
* @author Kristian * @author Kristian
*/ */
public class CommandFilter extends CommandBase { 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{ public interface FilterFailedHandler{
/** /**
* Invoked when a given filter has failed. * Invoked when a given filter has failed.
@ -236,7 +244,7 @@ public class CommandFilter extends CommandBase {
printPackageWarning(e1); printPackageWarning(e1);
if (!config.getScriptEngineName().equals("rhino")) { 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.setScriptEngineName("rhino");
config.saveAll(); config.saveAll();
@ -244,7 +252,7 @@ public class CommandFilter extends CommandBase {
initializeEngine(); initializeEngine();
if (!isInitialized()) { 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) { } catch (ScriptException e2) {
// And again .. // And again ..
@ -255,7 +263,7 @@ public class CommandFilter extends CommandBase {
} }
private void printPackageWarning(ScriptException e) { 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 @Override
public boolean handle(PacketEvent event, Filter filter, Exception ex) { public boolean handle(PacketEvent event, Filter filter, Exception ex) {
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event); 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; return false;
} }
}; };
@ -398,7 +408,9 @@ public class CommandFilter extends CommandBase {
whom.sendRawMessage(ChatColor.RED + "Cancelled filter."); whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
} }
} catch (Exception e) { } 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

@ -1,137 +1,147 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol; package com.comphenix.protocol;
import java.io.IOException; import java.io.IOException;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.metrics.Updater.UpdateType; import com.comphenix.protocol.metrics.Updater;
import com.comphenix.protocol.utility.WrappedScheduler; import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.metrics.Updater.UpdateType;
/** import com.comphenix.protocol.utility.WrappedScheduler;
* Handles the "protocol" administration command.
* /**
* @author Kristian * Handles the "protocol" administration command.
*/ *
class CommandProtocol extends CommandBase { * @author Kristian
/** */
* Name of this command. class CommandProtocol extends CommandBase {
*/ /**
public static final String NAME = "protocol"; * Name of this command.
*/
private Plugin plugin; public static final String NAME = "protocol";
private Updater updater;
private ProtocolConfig config; 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 CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) { public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot update ProtocolLib.");
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin; private Plugin plugin;
this.updater = updater; private Updater updater;
this.config = config; private ProtocolConfig config;
}
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
@Override super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
protected boolean handleCommand(CommandSender sender, String[] args) { this.plugin = plugin;
String subCommand = args[0]; this.updater = updater;
this.config = config;
// Only return TRUE if we executed the correct command }
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload"))
reloadConfiguration(sender); @Override
else if (subCommand.equalsIgnoreCase("check")) protected boolean handleCommand(CommandSender sender, String[] args) {
checkVersion(sender); String subCommand = args[0];
else if (subCommand.equalsIgnoreCase("update"))
updateVersion(sender); // Only return TRUE if we executed the correct command
else if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload"))
return false; reloadConfiguration(sender);
return true; else if (subCommand.equalsIgnoreCase("check"))
} checkVersion(sender);
else if (subCommand.equalsIgnoreCase("update"))
public void checkVersion(final CommandSender sender) { updateVersion(sender);
// Perform on an async thread else
WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() { return false;
@Override return true;
public void run() { }
try {
UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true); public void checkVersion(final CommandSender sender) {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); // Perform on an async thread
} catch (Exception e) { WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() {
if (isHttpError(e)) { @Override
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage()); public void run() {
} else { try {
getReporter().reportDetailed(this, "Cannot check updates for ProtocolLib.", e, sender); UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true);
} sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} } catch (Exception e) {
} if (isHttpError(e)) {
}, 0L); getReporter().reportWarning(this,
Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
updateFinished(); );
} } else {
getReporter().reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CHECK_FOR_UPDATES).error(e).callerParam(sender));
public void updateVersion(final CommandSender sender) { }
// Perform on an async thread }
WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() { }
@Override }, 0L);
public void run() {
try { updateFinished();
UpdateResult result = updater.update(UpdateType.DEFAULT, true); }
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) { public void updateVersion(final CommandSender sender) {
if (isHttpError(e)) { // Perform on an async thread
getReporter().reportWarning(this, "Http error: " + e.getCause().getMessage()); WrappedScheduler.runAsynchronouslyOnce(plugin, new Runnable() {
} else { @Override
getReporter().reportDetailed(this, "Cannot update ProtocolLib.", e, sender); public void run() {
} try {
} UpdateResult result = updater.update(UpdateType.DEFAULT, true);
} sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
}, 0L); } catch (Exception e) {
if (isHttpError(e)) {
updateFinished(); getReporter().reportWarning(this,
} Report.newBuilder(REPORT_HTTP_ERROR).messageParam(e.getCause().getMessage())
);
private boolean isHttpError(Exception e) { } else {
Throwable cause = e.getCause(); getReporter().reportDetailed(this,Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e).callerParam(sender));
}
if (cause instanceof IOException) { }
// Thanks for making the message a part of the API ... }
return cause.getMessage().contains("HTTP response"); }, 0L);
} else {
return false; updateFinished();
} }
}
private boolean isHttpError(Exception e) {
/** Throwable cause = e.getCause();
* Prevent further automatic updates until the next delay.
*/ if (cause instanceof IOException) {
public void updateFinished() { // Thanks for making the message a part of the API ...
long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND; return cause.getMessage().contains("HTTP response");
} else {
config.setAutoLastTime(currentTime); return false;
config.saveAll(); }
} }
public void reloadConfiguration(CommandSender sender) { /**
plugin.reloadConfig(); * Prevent further automatic updates until the next delay.
sender.sendMessage(ChatColor.BLUE + "Reloaded configuration!"); */
} public void updateFinished() {
} long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND;
config.setAutoLastTime(currentTime);
config.saveAll();
}
public void reloadConfiguration(CommandSender sender) {
plugin.reloadConfig();
sender.sendMessage(ChatColor.BLUE + "Reloaded configuration!");
}
}

View File

@ -35,6 +35,8 @@ import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.DetailedErrorReporter; import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter; 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.DelayedSingleTask;
import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
@ -51,6 +53,24 @@ import com.comphenix.protocol.utility.MinecraftVersion;
* @author Kristian * @author Kristian
*/ */
public class ProtocolLibrary extends JavaPlugin { 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. * The minimum version ProtocolLib has been tested with.
*/ */
@ -120,13 +140,13 @@ public class ProtocolLibrary extends JavaPlugin {
try { try {
config = new ProtocolConfig(this); config = new ProtocolConfig(this);
} catch (Exception e) { } catch (Exception e) {
detailedReporter.reportWarning(this, "Cannot load configuration", e); detailedReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
// Load it again // Load it again
if (deleteConfig()) { if (deleteConfig()) {
config = new ProtocolConfig(this); config = new ProtocolConfig(this);
} else { } 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); protocolManager.setPlayerHook(hook);
} }
} catch (IllegalArgumentException e) { } 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 // Initialize command handlers
@ -174,7 +194,7 @@ public class ProtocolLibrary extends JavaPlugin {
setupBroadcastUsers(PERMISSION_INFO); setupBroadcastUsers(PERMISSION_INFO);
} catch (Throwable e) { } 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(); disablePlugin();
} }
} }
@ -273,7 +293,7 @@ public class ProtocolLibrary extends JavaPlugin {
createAsyncTask(server); createAsyncTask(server);
} catch (Throwable e) { } catch (Throwable e) {
reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e); reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin(); disablePlugin();
return; return;
} }
@ -284,9 +304,9 @@ public class ProtocolLibrary extends JavaPlugin {
statistisc = new Statistics(this); statistisc = new Statistics(this);
} }
} catch (IOException e) { } 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) { } 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; return current;
} catch (Exception e) { } 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 // Unknown version
@ -345,7 +365,7 @@ public class ProtocolLibrary extends JavaPlugin {
} }
} catch (Exception e) { } 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 // 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."); throw new RuntimeException("plugin.yml might be corrupt.");
} catch (RuntimeException e) { } 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) { } catch (Throwable e) {
if (asyncPacketTask == -1) { 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(); commandProtocol.updateFinished();
} }
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(this, "Cannot perform automatic updates.", e); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
updateDisabled = true; updateDisabled = true;
} }
} }

View File

@ -1,387 +1,431 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.error; package com.comphenix.protocol.error;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle; import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.events.PacketAdapter;
import com.google.common.primitives.Primitives; import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.primitives.Primitives;
/**
* Internal class used to handle exceptions. /**
* * Internal class used to handle exceptions.
* @author Kristian *
*/ * @author Kristian
public class DetailedErrorReporter implements ErrorReporter { */
public class DetailedErrorReporter implements ErrorReporter {
public static final String SECOND_LEVEL_PREFIX = " "; /**
public static final String DEFAULT_PREFIX = " "; * Report format for printing the current exception count.
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/"; */
public static final String PLUGIN_NAME = "ProtocolLib"; public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
// Users that are informed about errors in the chat public static final String SECOND_LEVEL_PREFIX = " ";
public static final String ERROR_PERMISSION = "protocol.info"; public static final String DEFAULT_PREFIX = " ";
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
// We don't want to spam the server
public static final int DEFAULT_MAX_ERROR_COUNT = 20; // Users that are informed about errors in the chat
public static final String ERROR_PERMISSION = "protocol.info";
// Prevent spam per plugin too
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>(); // We don't want to spam the server
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
protected String prefix;
protected String supportURL; // Prevent spam per plugin too
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
protected AtomicInteger internalErrorCount = new AtomicInteger();
protected String prefix;
protected int maxErrorCount; protected String supportURL;
protected Logger logger;
protected AtomicInteger internalErrorCount = new AtomicInteger();
protected WeakReference<Plugin> pluginReference;
protected int maxErrorCount;
// Whether or not Apache Commons is not present protected Logger logger;
protected boolean apacheCommonsMissing;
protected WeakReference<Plugin> pluginReference;
// Map of global objects protected String pluginName;
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
// Whether or not Apache Commons is not present
/** protected boolean apacheCommonsMissing;
* Create a default error reporting system.
*/ // Map of global objects
public DetailedErrorReporter(Plugin plugin) { protected Map<String, Object> globalParameters = new HashMap<String, Object>();
this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
} /**
* Create a default error reporting system.
/** */
* Create a central error reporting system. public DetailedErrorReporter(Plugin plugin) {
* @param plugin - the plugin owner. this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
* @param prefix - default line prefix. }
* @param supportURL - URL to report the error.
*/ /**
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) { * Create a central error reporting system.
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); * @param plugin - the plugin owner.
} * @param prefix - default line prefix.
* @param supportURL - URL to report the error.
// Attempt to get the logger. */
private static Logger getBukkitLogger() { public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
try { this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
return Bukkit.getLogger(); }
} catch (Throwable e) {
return Logger.getLogger("Minecraft"); /**
} * Create a central error reporting system.
} * @param plugin - the plugin owner.
* @param prefix - default line prefix.
/** * @param supportURL - URL to report the error.
* Create a central error reporting system. * @param maxErrorCount - number of errors to print before giving up.
* @param plugin - the plugin owner. * @param logger - current logger.
* @param prefix - default line prefix. */
* @param supportURL - URL to report the error. public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
* @param maxErrorCount - number of errors to print before giving up. if (plugin == null)
* @param logger - current logger. throw new IllegalArgumentException("Plugin cannot be NULL.");
*/
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) { this.pluginReference = new WeakReference<Plugin>(plugin);
if (plugin == null) this.pluginName = plugin.getName();
throw new IllegalArgumentException("Plugin cannot be NULL."); this.prefix = prefix;
this.supportURL = supportURL;
this.pluginReference = new WeakReference<Plugin>(plugin); this.maxErrorCount = maxErrorCount;
this.prefix = prefix; this.logger = logger;
this.supportURL = supportURL; }
this.maxErrorCount = maxErrorCount;
this.logger = logger; // Attempt to get the logger.
} private static Logger getBukkitLogger() {
try {
@Override return Bukkit.getLogger();
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { } catch (Throwable e) {
if (reportMinimalNoSpam(sender, methodName, error)) { return Logger.getLogger("Minecraft");
// Print parameters, if they are given }
if (parameters != null && parameters.length > 0) { }
logger.log(Level.SEVERE, " Parameters:");
@Override
// Print each parameter public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
for (Object parameter : parameters) { if (reportMinimalNoSpam(sender, methodName, error)) {
logger.log(Level.SEVERE, " " + getStringDescription(parameter)); // 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) { @Override
reportMinimalNoSpam(sender, methodName, error); public void reportMinimal(Plugin sender, String methodName, Throwable error) {
} reportMinimalNoSpam(sender, methodName, error);
}
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
String pluginName = PacketAdapter.getPluginName(sender); public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
AtomicInteger counter = warningCount.get(pluginName); String pluginName = PacketAdapter.getPluginName(sender);
AtomicInteger counter = warningCount.get(pluginName);
// Thread safe pattern
if (counter == null) { // Thread safe pattern
AtomicInteger created = new AtomicInteger(); if (counter == null) {
counter = warningCount.putIfAbsent(pluginName, created); AtomicInteger created = new AtomicInteger();
counter = warningCount.putIfAbsent(pluginName, created);
if (counter == null) {
counter = created; if (counter == null) {
} counter = created;
} }
}
final int errorCount = counter.incrementAndGet();
final int errorCount = counter.incrementAndGet();
// See if we should print the full error
if (errorCount < getMaxErrorCount()) { // See if we should print the full error
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + if (errorCount < getMaxErrorCount()) {
methodName + " for " + pluginName, error); logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
return true; methodName + " for " + pluginName, error);
return true;
} else {
// Nope - only print the error count occationally } else {
if (isPowerOfTwo(errorCount)) { // Nope - only print the error count occationally
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception number " + errorCount + " occured in " + if (isPowerOfTwo(errorCount)) {
methodName + " for " + pluginName, error); logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
} methodName + " for " + pluginName, error);
return false; }
} return false;
} }
}
/**
* Determine if a given number is a power of two. /**
* <p> * Determine if a given number is a power of two.
* That is, if there exists an N such that 2^N = number. * <p>
* @param number - the number to check. * That is, if there exists an N such that 2^N = number.
* @return TRUE if the given number is a power of two, FALSE otherwise. * @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; private boolean isPowerOfTwo(int number) {
} return (number & (number - 1)) == 0;
}
@Override
public void reportWarning(Object sender, String message) { @Override
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.");
@Override
public void reportWarning(Object sender, String message, Throwable error) { reportWarning(sender, reportBuilder.build());
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error); }
}
@Override
private String getSenderName(Object sender) { public void reportWarning(Object sender, Report report) {
if (sender != null) String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
return sender.getClass().getSimpleName();
else // Print the main warning
return "NULL"; if (report.getException() != null) {
} logger.log(Level.WARNING, message, report.getException());
} else {
@Override logger.log(Level.WARNING, message);
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) { }
final Plugin plugin = pluginReference.get(); // Parameters?
final int errorCount = internalErrorCount.incrementAndGet(); if (report.hasCallerParameters()) {
// Write it
// Do not overtly spam the server! logger.log(Level.WARNING, printParameters(report.getCallerParameters()));
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, "Internal exception count: " + errorCount + "!"); * Retrieve the name of a sender class.
} else { * @param sender - sender object.
// NEVER SPAM THE CONSOLE * @return The name of the sender's class.
return; */
} private String getSenderName(Object sender) {
} if (sender != null)
return sender.getClass().getSimpleName();
StringWriter text = new StringWriter(); else
PrintWriter writer = new PrintWriter(text); return "NULL";
}
// Helpful message
writer.println("[ProtocolLib] INTERNAL ERROR: " + message); @Override
writer.println("If this problem hasn't already been reported, please open a ticket"); public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
writer.println("at " + supportURL + " with the following data:"); reportDetailed(sender, reportBuilder.build());
}
// Now, let us print important exception information
writer.println(" ===== STACK TRACE ====="); @Override
public void reportDetailed(Object sender, Report report) {
if (error != null) final Plugin plugin = pluginReference.get();
error.printStackTrace(writer); final int errorCount = internalErrorCount.incrementAndGet();
// Data dump! // Do not overtly spam the server!
writer.println(" ===== DUMP ====="); if (errorCount > getMaxErrorCount()) {
// Only allow the error count at rare occations
// Relevant parameters if (isPowerOfTwo(errorCount)) {
if (parameters != null && parameters.length > 0) { // Permit it - but print the number of exceptions first
writer.println("Parameters:"); reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
} else {
// We *really* want to get as much information as possible // NEVER SPAM THE CONSOLE
for (Object param : parameters) { return;
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX)); }
} }
}
StringWriter text = new StringWriter();
// Global parameters PrintWriter writer = new PrintWriter(text);
for (String param : globalParameters()) {
writer.println(SECOND_LEVEL_PREFIX + param + ":"); // Helpful message
writer.println(addPrefix(getStringDescription(getGlobalParameter(param)), writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX)); writer.println("If this problem hasn't already been reported, please open a ticket");
} writer.println("at " + supportURL + " with the following data:");
// Now, for the sender itself // Now, let us print important exception information
writer.println("Sender:"); writer.println(" ===== STACK TRACE =====");
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
if (report.getException() != null) {
// And plugin report.getException().printStackTrace(writer);
if (plugin != null) { }
writer.println("Version:");
writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX)); // Data dump!
} writer.println(" ===== DUMP =====");
// Add the server version too // Relevant parameters
if (Bukkit.getServer() != null) { if (report.hasCallerParameters()) {
writer.println("Server:"); printParameters(writer, report.getCallerParameters());
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX)); }
// Inform of this occurrence // Global parameters
if (ERROR_PERMISSION != null) { for (String param : globalParameters()) {
Bukkit.getServer().broadcast( writer.println(SECOND_LEVEL_PREFIX + param + ":");
String.format("Error %s (%s) occured in %s.", message, error, sender), writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
ERROR_PERMISSION SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
); }
}
} // Now, for the sender itself
writer.println("Sender:");
// Make sure it is reported writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
logger.severe(addPrefix(text.toString(), prefix));
} // And plugin
if (plugin != null) {
/** writer.println("Version:");
* Adds the given prefix to every line in the text. writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
* @param text - text to modify. }
* @param prefix - prefix added to every line in the text.
* @return The modified text. // Add the server version too
*/ if (Bukkit.getServer() != null) {
protected String addPrefix(String text, String prefix) { writer.println("Server:");
return text.replaceAll("(?m)^", prefix); writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
}
// Inform of this occurrence
protected String getStringDescription(Object value) { if (ERROR_PERMISSION != null) {
Bukkit.getServer().broadcast(
// We can't only rely on toString. String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
if (value == null) { ERROR_PERMISSION
return "[NULL]"; );
} if (isSimpleType(value)) { }
return value.toString(); }
} else {
try { // Make sure it is reported
if (!apacheCommonsMissing) logger.severe(addPrefix(text.toString(), prefix));
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null)); }
} catch (Throwable ex) {
// Apache is probably missing private String printParameters(Object... parameters) {
apacheCommonsMissing = true; StringWriter writer = new StringWriter();
}
// Print and retrieve the string buffer
// Use our custom object printer instead printParameters(new PrintWriter(writer), parameters);
try { return writer.toString();
return PrettyPrinter.printObject(value, value.getClass(), Object.class); }
} catch (IllegalAccessException e) {
return "[Error: " + e.getMessage() + "]"; 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));
* 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) { * Adds the given prefix to every line in the text.
return test instanceof String || Primitives.isWrapperType(test.getClass()); * @param text - text to modify.
} * @param prefix - prefix added to every line in the text.
* @return The modified text.
public int getErrorCount() { */
return internalErrorCount.get(); protected String addPrefix(String text, String prefix) {
} return text.replaceAll("(?m)^", prefix);
}
public void setErrorCount(int errorCount) {
internalErrorCount.set(errorCount); /**
} * Retrieve a string representation of the given object.
* @param value - object to convert.
public int getMaxErrorCount() { * @return String representation.
return maxErrorCount; */
} protected String getStringDescription(Object value) {
public void setMaxErrorCount(int maxErrorCount) { // We can't only rely on toString.
this.maxErrorCount = maxErrorCount; if (value == null) {
} return "[NULL]";
} if (isSimpleType(value)) {
/** return value.toString();
* Adds the given global parameter. It will be included in every error report. } else {
* @param key - name of parameter. try {
* @param value - the global parameter itself. if (!apacheCommonsMissing)
*/ return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
public void addGlobalParameter(String key, Object value) { } catch (Throwable ex) {
globalParameters.put(key, value); // Apache is probably missing
} apacheCommonsMissing = true;
}
public Object getGlobalParameter(String key) {
return globalParameters.get(key); // Use our custom object printer instead
} try {
return PrettyPrinter.printObject(value, value.getClass(), Object.class);
public void clearGlobalParameters() { } catch (IllegalAccessException e) {
globalParameters.clear(); return "[Error: " + e.getMessage() + "]";
} }
}
public Set<String> globalParameters() { }
return globalParameters.keySet();
} /**
* Determine if the given object is a wrapper for a primitive/simple type or not.
public String getSupportURL() { * @param test - the object to test.
return supportURL; * @return TRUE if this object is simple enough to simply be printed, FALSE othewise.
} */
protected boolean isSimpleType(Object test) {
public void setSupportURL(String supportURL) { return test instanceof String || Primitives.isWrapperType(test.getClass());
this.supportURL = supportURL; }
}
public int getErrorCount() {
public String getPrefix() { return internalErrorCount.get();
return prefix; }
}
public void setErrorCount(int errorCount) {
public void setPrefix(String prefix) { internalErrorCount.set(errorCount);
this.prefix = prefix; }
}
public int getMaxErrorCount() {
public Logger getLogger() { return maxErrorCount;
return logger; }
}
public void setMaxErrorCount(int maxErrorCount) {
public void setLogger(Logger logger) { this.maxErrorCount = maxErrorCount;
this.logger = logger; }
}
} /**
* Adds the given global parameter. It will be included in every error report.
* @param key - name of parameter.
* @param value - the global parameter itself.
*/
public void addGlobalParameter(String key, Object value) {
globalParameters.put(key, value);
}
public Object getGlobalParameter(String key) {
return globalParameters.get(key);
}
public void clearGlobalParameters() {
globalParameters.clear();
}
public Set<String> globalParameters() {
return globalParameters.keySet();
}
public String getSupportURL() {
return supportURL;
}
public void setSupportURL(String supportURL) {
this.supportURL = supportURL;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}

View File

@ -1,65 +1,69 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.error; package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin; 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. * Prints a small minimal error report about an exception from another plugin.
* @param methodName - name of the caller method. * @param sender - the other plugin.
* @param error - the exception itself. * @param methodName - name of the caller method.
*/ * @param error - the exception itself.
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error); */
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
/**
* Prints a small minimal error report about an exception from another plugin. /**
* @param sender - the other plugin. * Prints a small minimal error report about an exception from another plugin.
* @param methodName - name of the caller method. * @param sender - the other plugin.
* @param error - the exception itself. * @param methodName - name of the caller method.
* @param parameters - any relevant parameters to print. * @param error - the exception itself.
*/ * @param parameters - any relevant parameters to print.
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters); */
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters);
/**
* Prints a warning message from the current plugin. /**
* @param sender - the object containing the caller method. * Prints a warning message from the current plugin.
* @param message - error message. * @param sender - the object containing the caller method.
*/ * @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. * Prints a warning message from the current plugin.
* @param message - error message. * @param sender - the object containing the caller method.
* @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. * Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method. * @param sender - the object containing the caller method.
* @param message - an error message to include. * @param report - an error report to include.
* @param error - the exception that was thrown in the caller method. */
* @param parameters - parameters from the caller method. public abstract void reportDetailed(Object sender, Report report);
*/
public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters); /**
* 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

@ -1,181 +1,213 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.error.ReportType;
import com.google.common.primitives.Primitives; import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.FieldUtils;
/** import com.comphenix.protocol.reflect.instances.DefaultInstances;
* Represents an object capable of converting wrapped Bukkit objects into NMS objects. import com.google.common.primitives.Primitives;
* <p>
* Typical conversions include: /**
* <ul> * Represents an object capable of converting wrapped Bukkit objects into NMS objects.
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li> * <p>
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li> * Typical conversions include:
* </ul> * <ul>
* * <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
* @author Kristian * <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
*/ * </ul>
public class BukkitUnwrapper implements Unwrapper { *
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>(); * @author Kristian
*/
@SuppressWarnings("unchecked") public class BukkitUnwrapper implements Unwrapper {
@Override public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
public Object unwrapItem(Object wrappedObject) { public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
// Special case public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
if (wrappedObject == null)
return null; public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
Class<?> currentClass = wrappedObject.getClass();
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
// Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) { // The current error reporter
return handleCollection((Collection<Object>) wrappedObject); private final ErrorReporter reporter;
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
return null; /**
} * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
*/
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass); public BukkitUnwrapper() {
this(ProtocolLibrary.getErrorReporter());
// Retrieve the handle }
if (specificUnwrapper != null)
return specificUnwrapper.unwrapItem(wrappedObject); /**
else * Construct a new Bukkit unwrapper with the given error reporter.
return null; * @param reporter - the error reporter to use.
} */
public BukkitUnwrapper(ErrorReporter reporter) {
// Handle a collection of items this.reporter = reporter;
private Object handleCollection(Collection<Object> wrappedObject) { }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass()); @Override
public Object unwrapItem(Object wrappedObject) {
if (copy != null) { // Special case
// Unwrap every element if (wrappedObject == null)
for (Object element : wrappedObject) { return null;
copy.add(unwrapItem(element)); Class<?> currentClass = wrappedObject.getClass();
}
return copy; // Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) {
} else { return handleCollection((Collection<Object>) wrappedObject);
// Impossible } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
return null; return null;
} }
}
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
/**
* Retrieve a cached class unwrapper for the given class. // Retrieve the handle
* @param type - the type of the class. if (specificUnwrapper != null)
* @return An unwrapper for the given class. return specificUnwrapper.unwrapItem(wrappedObject);
*/ else
private Unwrapper getSpecificUnwrapper(Class<?> type) { return null;
// See if we're already determined this }
if (unwrapperCache.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe // Handle a collection of items
return unwrapperCache.get(type); private Object handleCollection(Collection<Object> wrappedObject) {
}
@SuppressWarnings("unchecked")
try { Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
final Method find = type.getMethod("getHandle");
if (copy != null) {
// It's thread safe, as getMethod should return the same handle // Unwrap every element
Unwrapper methodUnwrapper = new Unwrapper() { for (Object element : wrappedObject) {
@Override copy.add(unwrapItem(element));
public Object unwrapItem(Object wrappedObject) { }
return copy;
try {
return find.invoke(wrappedObject); } else {
// Impossible
} catch (IllegalArgumentException e) { return null;
ProtocolLibrary.getErrorReporter().reportDetailed( }
this, "Illegal argument.", e, wrappedObject, find); }
} catch (IllegalAccessException e) {
// Should not occur either /**
return null; * Retrieve a cached class unwrapper for the given class.
} catch (InvocationTargetException e) { * @param type - the type of the class.
// This is really bad * @return An unwrapper for the given class.
throw new RuntimeException("Minecraft error.", e); */
} private Unwrapper getSpecificUnwrapper(Class<?> type) {
// See if we're already determined this
return null; if (unwrapperCache.containsKey(type)) {
} // We will never remove from the cache, so this ought to be thread safe
}; return unwrapperCache.get(type);
}
unwrapperCache.put(type, methodUnwrapper);
return methodUnwrapper; try {
final Method find = type.getMethod("getHandle");
} catch (SecurityException e) {
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Security limitation.", e, type.getName()); // It's thread safe, as getMethod should return the same handle
} catch (NoSuchMethodException e) { Unwrapper methodUnwrapper = new Unwrapper() {
// Try getting the field unwrapper too @Override
Unwrapper fieldUnwrapper = getFieldUnwrapper(type); public Object unwrapItem(Object wrappedObject) {
if (fieldUnwrapper != null) try {
return fieldUnwrapper; return find.invoke(wrappedObject);
else
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Cannot find method.", e, type.getName()); } catch (IllegalArgumentException e) {
} reporter.reportDetailed(this,
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
// Default method );
return null; } catch (IllegalAccessException e) {
} // Should not occur either
return null;
/** } catch (InvocationTargetException e) {
* Retrieve a cached unwrapper using the handle field. // This is really bad
* @param type - a cached field unwrapper. throw new RuntimeException("Minecraft error.", e);
* @return The cached field unwrapper. }
*/
private Unwrapper getFieldUnwrapper(Class<?> type) { return null;
final Field find = FieldUtils.getField(type, "handle", true); }
};
// See if we succeeded
if (find != null) { unwrapperCache.put(type, methodUnwrapper);
Unwrapper fieldUnwrapper = new Unwrapper() { return methodUnwrapper;
@Override
public Object unwrapItem(Object wrappedObject) { } catch (SecurityException e) {
try { reporter.reportDetailed(this,
return FieldUtils.readField(find, wrappedObject, true); Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
} catch (IllegalAccessException e) { );
ProtocolLibrary.getErrorReporter().reportDetailed( } catch (NoSuchMethodException e) {
this, "Cannot read field 'handle'.", e, wrappedObject, find.getName()); // Try getting the field unwrapper too
return null; Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
}
} if (fieldUnwrapper != null)
}; return fieldUnwrapper;
else
unwrapperCache.put(type, fieldUnwrapper); reporter.reportDetailed(this,
return fieldUnwrapper; Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
}
} else {
// Inform about this too // Default method
ProtocolLibrary.getErrorReporter().reportDetailed( return null;
this, "Could not find field 'handle'.", }
new Exception("Unable to find 'handle'"), type.getName());
return null; /**
} * Retrieve a cached unwrapper using the handle field.
} * @param type - a cached field unwrapper.
} * @return The cached field unwrapper.
*/
private Unwrapper getFieldUnwrapper(Class<?> type) {
final Field find = FieldUtils.getField(type, "handle", true);
// See if we succeeded
if (find != null) {
Unwrapper fieldUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
);
return null;
}
}
};
unwrapperCache.put(type, fieldUnwrapper);
return fieldUnwrapper;
} else {
// Inform about this too
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.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter; 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.events.*;
import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
@ -67,7 +69,23 @@ import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker { 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. * Sets the inject hook type. Different types allow for maximum compatibility.
* @author Kristian * @author Kristian
@ -234,11 +252,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
knowsServerPackets = PacketRegistry.getServerPackets() != null; knowsServerPackets = PacketRegistry.getServerPackets() != null;
knowsClientPackets = PacketRegistry.getClientPackets() != null; knowsClientPackets = PacketRegistry.getClientPackets() != null;
} catch (FieldAccessException e) { } 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) { } 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) { private void printPluginWarnings(Plugin plugin) {
switch (pluginVerifier.verify(plugin)) { switch (pluginVerifier.verify(plugin)) {
case NO_DEPEND: 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: case VALID:
// Do nothing // Do nothing
break; break;
@ -510,10 +528,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID)) if (!knowsServerPackets || PacketRegistry.getServerPackets().contains(packetID))
playerInjection.addPacketHandler(packetID); playerInjection.addPacketHandler(packetID);
else else
reporter.reportWarning(this, String.format( reporter.reportWarning(this,
"[%s] Unsupported server packet ID in current Minecraft version: %s", Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
PacketAdapter.getPluginName(listener), packetID );
));
} }
// As above, only for client packets // As above, only for client packets
@ -521,10 +538,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID)) if (!knowsClientPackets || PacketRegistry.getClientPackets().contains(packetID))
packetInjector.addPacketHandler(packetID); packetInjector.addPacketHandler(packetID);
else else
reporter.reportWarning(this, String.format( reporter.reportWarning(this,
"[%s] Unsupported client packet ID in current Minecraft version: %s", Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), packetID)
PacketAdapter.getPluginName(listener), packetID );
));
} }
} }
} }
@ -722,7 +738,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
playerInjection.uninjectPlayer(event.getPlayer().getAddress()); playerInjection.uninjectPlayer(event.getPlayer().getAddress());
playerInjection.updatePlayer(event.getPlayer()); playerInjection.updatePlayer(event.getPlayer());
} catch (Exception e) { } 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 // This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE); playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
} catch (Exception e) { } 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.handleDisconnect(player);
playerInjection.uninjectPlayer(player); playerInjection.uninjectPlayer(player);
} catch (Exception e) { } 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()); removePacketListeners(event.getPlugin());
} }
} catch (Exception e) { } 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

@ -1,293 +1,303 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.packet; package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.wrappers.TroveWrapper; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.google.common.base.Objects; import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.ImmutableSet; import com.comphenix.protocol.wrappers.TroveWrapper;
import com.google.common.base.Objects;
/** import com.google.common.collect.ImmutableSet;
* Static packet registry in Minecraft.
* /**
* @author Kristian * Static packet registry in Minecraft.
*/ *
@SuppressWarnings("rawtypes") * @author Kristian
public class PacketRegistry { */
private static final int MIN_SERVER_PACKETS = 5; @SuppressWarnings("rawtypes")
private static final int MIN_CLIENT_PACKETS = 5; public class PacketRegistry {
public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
// Fuzzy reflection
private static FuzzyReflection packetRegistry; 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");
// The packet class to packet ID translator
private static Map<Class, Integer> packetToID; private static final int MIN_SERVER_PACKETS = 5;
private static final int MIN_CLIENT_PACKETS = 5;
// Whether or not certain packets are sent by the client or the server
private static ImmutableSet<Integer> serverPackets; // Fuzzy reflection
private static ImmutableSet<Integer> clientPackets; private static FuzzyReflection packetRegistry;
// The underlying sets // The packet class to packet ID translator
private static Set<Integer> serverPacketsRef; private static Map<Class, Integer> packetToID;
private static Set<Integer> clientPacketsRef;
// Whether or not certain packets are sent by the client or the server
// New proxy values private static ImmutableSet<Integer> serverPackets;
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>(); private static ImmutableSet<Integer> clientPackets;
// Vanilla packets // The underlying sets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>(); private static Set<Integer> serverPacketsRef;
private static Set<Integer> clientPacketsRef;
@SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() { // New proxy values
// Initialize it, if we haven't already private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
if (packetToID == null) {
try { // Vanilla packets
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalArgumentException e) { @SuppressWarnings({ "unchecked" })
// Spigot 1.2.5 MCPC workaround public static Map<Class, Integer> getPacketToID() {
try { // Initialize it, if we haven't already
packetToID = getSpigotWrapper(); if (packetToID == null) {
} catch (Exception e2) { try {
// Very bad indeed Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2); packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} } catch (IllegalArgumentException e) {
// Spigot 1.2.5 MCPC workaround
} catch (IllegalAccessException e) { try {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); packetToID = getSpigotWrapper();
} } catch (Exception e2) {
} // Very bad indeed
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
return packetToID; }
}
} catch (IllegalAccessException e) {
private static Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException { throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
// If it talks like a duck, etc. }
// Perhaps it would be nice to have a proper duck typing library as well }
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
method(FuzzyMethodContract.newBuilder(). return packetToID;
nameExact("size").returnTypeExact(int.class)). }
method(FuzzyMethodContract.newBuilder().
nameExact("put").parameterCount(2)). private static Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
method(FuzzyMethodContract.newBuilder(). // If it talks like a duck, etc.
nameExact("get").parameterCount(1)). // Perhaps it would be nice to have a proper duck typing library as well
build(); FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
method(FuzzyMethodContract.newBuilder().
Field packetsField = getPacketRegistry().getField( nameExact("size").returnTypeExact(int.class)).
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build()); method(FuzzyMethodContract.newBuilder().
Object troveMap = FieldUtils.readStaticField(packetsField, true); nameExact("put").parameterCount(2)).
method(FuzzyMethodContract.newBuilder().
// Check for stupid no_entry_values nameExact("get").parameterCount(1)).
try { build();
Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
Integer value = (Integer) FieldUtils.readField(field, troveMap, true); Field packetsField = getPacketRegistry().getField(
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
if (value >= 0 && value < 256) { Object troveMap = FieldUtils.readStaticField(packetsField, true);
// Someone forgot to set the no entry value. Let's help them.
FieldUtils.writeField(field, troveMap, -1); // Check for stupid no_entry_values
} try {
} catch (IllegalArgumentException e) { Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true);
// Whatever Integer value = (Integer) FieldUtils.readField(field, troveMap, true);
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, "Unable to correct no entry value.", e);
} if (value >= 0 && value < 256) {
// Someone forgot to set the no entry value. Let's help them.
// We'll assume this a Trove map FieldUtils.writeField(field, troveMap, -1);
return TroveWrapper.getDecoratedMap(troveMap); }
} } catch (IllegalArgumentException e) {
// Whatever
/** ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry. Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
* @return Reflected packet registry. }
*/
private static FuzzyReflection getPacketRegistry() { // We'll assume this a Trove map
if (packetRegistry == null) return TroveWrapper.getDecoratedMap(troveMap);
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true); }
return packetRegistry;
} /**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
/** * @return Reflected packet registry.
* Retrieve the injected proxy classes handlig each packet ID. */
* @return Injected classes. private static FuzzyReflection getPacketRegistry() {
*/ if (packetRegistry == null)
public static Map<Integer, Class> getOverwrittenPackets() { packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
return overwrittenPackets; return packetRegistry;
} }
/** /**
* Retrieve the vanilla classes handling each packet ID. * Retrieve the injected proxy classes handlig each packet ID.
* @return Vanilla classes. * @return Injected classes.
*/ */
public static Map<Integer, Class> getPreviousPackets() { public static Map<Integer, Class> getOverwrittenPackets() {
return previousValues; return overwrittenPackets;
} }
/** /**
* Retrieve every known and supported server packet. * Retrieve the vanilla classes handling each packet ID.
* @return An immutable set of every known server packet. * @return Vanilla classes.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. */
*/ public static Map<Integer, Class> getPreviousPackets() {
public static Set<Integer> getServerPackets() throws FieldAccessException { return previousValues;
initializeSets(); }
// Sanity check. This is impossible! /**
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS) * Retrieve every known and supported server packet.
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported"); * @return An immutable set of every known server packet.
return serverPackets; * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
} */
public static Set<Integer> getServerPackets() throws FieldAccessException {
/** initializeSets();
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet. // Sanity check. This is impossible!
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
*/ throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
public static Set<Integer> getClientPackets() throws FieldAccessException { return serverPackets;
initializeSets(); }
// As above /**
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS) * Retrieve every known and supported client packet.
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported"); * @return An immutable set of every known client packet.
return clientPackets; * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
} */
public static Set<Integer> getClientPackets() throws FieldAccessException {
@SuppressWarnings("unchecked") initializeSets();
private static void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) { // As above
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class); if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
try { return clientPackets;
if (sets.size() > 1) { }
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true); @SuppressWarnings("unchecked")
private static void initializeSets() throws FieldAccessException {
// Impossible if (serverPacketsRef == null || clientPacketsRef == null) {
if (serverPacketsRef == null || clientPacketsRef == null) List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
throw new FieldAccessException("Packet sets are in an illegal state.");
try {
// NEVER allow callers to modify the underlying sets if (sets.size() > 1) {
serverPackets = ImmutableSet.copyOf(serverPacketsRef); serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPackets = ImmutableSet.copyOf(clientPacketsRef); clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Check sizes // Impossible
if (serverPackets.size() < MIN_SERVER_PACKETS) if (serverPacketsRef == null || clientPacketsRef == null)
ProtocolLibrary.getErrorReporter().reportWarning( throw new FieldAccessException("Packet sets are in an illegal state.");
PacketRegistry.class, "Too few server packets detected: " + serverPackets.size());
if (clientPackets.size() < MIN_CLIENT_PACKETS) // NEVER allow callers to modify the underlying sets
ProtocolLibrary.getErrorReporter().reportWarning( serverPackets = ImmutableSet.copyOf(serverPacketsRef);
PacketRegistry.class, "Too few client packets detected: " + clientPackets.size()); clientPackets = ImmutableSet.copyOf(clientPacketsRef);
} else { // Check sizes
throw new FieldAccessException("Cannot retrieve packet client/server sets."); if (serverPackets.size() < MIN_SERVER_PACKETS)
} ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size())
} catch (IllegalAccessException e) { );
throw new FieldAccessException("Cannot access field.", e); if (clientPackets.size() < MIN_CLIENT_PACKETS)
} ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size())
} else { );
// Copy over again if it has changed
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size()) } else {
serverPackets = ImmutableSet.copyOf(serverPacketsRef); throw new FieldAccessException("Cannot retrieve packet client/server sets.");
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size()) }
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
} } catch (IllegalAccessException e) {
} throw new FieldAccessException("Cannot access field.", e);
}
/**
* Retrieves the correct packet class from a given packet ID. } else {
* @param packetID - the packet ID. // Copy over again if it has changed
* @return The associated class. if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
*/ serverPackets = ImmutableSet.copyOf(serverPacketsRef);
public static Class getPacketClassFromID(int packetID) { if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
return getPacketClassFromID(packetID, false); clientPackets = ImmutableSet.copyOf(clientPacketsRef);
} }
}
/**
* Retrieves the correct packet class from a given packet ID. /**
* @param packetID - the packet ID. * Retrieves the correct packet class from a given packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes. * @param packetID - the packet ID.
* @return The associated class. * @return The associated class.
*/ */
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) { public static Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets; }
// Optimized lookup /**
if (lookup.containsKey(packetID)) { * Retrieves the correct packet class from a given packet ID.
return removeEnhancer(lookup.get(packetID), forceVanilla); * @param packetID - the packet ID.
} * @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
// Will most likely not be used */
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) { public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
if (Objects.equal(entry.getValue(), packetID)) {
// Attempt to get the vanilla class here too Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey()))
return removeEnhancer(entry.getKey(), forceVanilla); // Optimized lookup
} if (lookup.containsKey(packetID)) {
} return removeEnhancer(lookup.get(packetID), forceVanilla);
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
} // Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
/** if (Objects.equal(entry.getValue(), packetID)) {
* Retrieve the packet ID of a given packet. // Attempt to get the vanilla class here too
* @param packet - the type of packet to check. if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey()))
* @return The ID of the given packet. return removeEnhancer(entry.getKey(), forceVanilla);
* @throws IllegalArgumentException If this is not a valid packet. }
*/ }
public static int getPacketID(Class<?> packet) {
if (packet == null) throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
throw new IllegalArgumentException("Packet type class cannot be NULL."); }
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
throw new IllegalArgumentException("Type must be a packet."); /**
* Retrieve the packet ID of a given packet.
// The registry contains both the overridden and original packets * @param packet - the type of packet to check.
return getPacketToID().get(packet); * @return The ID of the given packet.
} * @throws IllegalArgumentException If this is not a valid packet.
*/
/** public static int getPacketID(Class<?> packet) {
* Find the first superclass that is not a CBLib proxy object. if (packet == null)
* @param clazz - the class whose hierachy we're going to search through. throw new IllegalArgumentException("Packet type class cannot be NULL.");
* @param remove - whether or not to skip enhanced (proxy) classes. if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
* @return If remove is TRUE, the first superclass that is not a proxy. throw new IllegalArgumentException("Type must be a packet.");
*/
private static Class removeEnhancer(Class clazz, boolean remove) { // The registry contains both the overridden and original packets
if (remove) { return getPacketToID().get(packet);
// Get the underlying vanilla class }
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass(); /**
} * Find the first superclass that is not a CBLib proxy object.
} * @param clazz - the class whose hierachy we're going to search through.
* @param remove - whether or not to skip enhanced (proxy) classes.
return clazz; * @return If remove is TRUE, the first superclass that is not a proxy.
} */
} private static Class removeEnhancer(Class clazz, boolean remove) {
if (remove) {
// Get the underlying vanilla class
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass();
}
}
return clazz;
}
}

View File

@ -1,134 +1,140 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.packet; package com.comphenix.protocol.injector.packet;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.error.ReportType;
import com.google.common.collect.MapMaker; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import net.sf.cglib.proxy.MethodInterceptor; import com.google.common.collect.MapMaker;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.MethodInterceptor;
class ReadPacketModifier implements MethodInterceptor { import net.sf.cglib.proxy.MethodProxy;
// A cancel marker
private static final Object CANCEL_MARKER = new Object(); class ReadPacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
// Common for all packets of the same type
private ProxyPacketInjector packetInjector; // A cancel marker
private int packetID; private static final Object CANCEL_MARKER = new Object();
// Report errors // Common for all packets of the same type
private ErrorReporter reporter; private ProxyPacketInjector packetInjector;
private int packetID;
// If this is a read packet data method
private boolean isReadPacketDataMethod; // Report errors
private ErrorReporter reporter;
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap(); // If this is a read packet data method
private boolean isReadPacketDataMethod;
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
this.packetID = packetID; // Whether or not a packet has been cancelled
this.packetInjector = packetInjector; private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
this.reporter = reporter;
this.isReadPacketDataMethod = isReadPacketDataMethod; public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
} this.packetID = packetID;
this.packetInjector = packetInjector;
/** this.reporter = reporter;
* Remove any packet overrides. this.isReadPacketDataMethod = isReadPacketDataMethod;
* @param packet - the packet to rever }
*/
public static void removeOverride(Object packet) { /**
override.remove(packet); * Remove any packet overrides.
} * @param packet - the packet to rever
*/
/** public static void removeOverride(Object packet) {
* Retrieve the packet that overrides the methods of the given packet. override.remove(packet);
* @param packet - the given packet. }
* @return Overriden object.
*/ /**
public static Object getOverride(Object packet) { * Retrieve the packet that overrides the methods of the given packet.
return override.get(packet); * @param packet - the given packet.
} * @return Overriden object.
*/
/** public static Object getOverride(Object packet) {
* Determine if the given packet has been cancelled before. return override.get(packet);
* @param packet - the packet to check. }
* @return TRUE if it has been cancelled, FALSE otherwise.
*/ /**
public static boolean hasCancelled(Object packet) { * Determine if the given packet has been cancelled before.
return getOverride(packet) == CANCEL_MARKER; * @param packet - the packet to check.
} * @return TRUE if it has been cancelled, FALSE otherwise.
*/
@Override public static boolean hasCancelled(Object packet) {
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return getOverride(packet) == CANCEL_MARKER;
// Atomic retrieval }
Object overridenObject = override.get(thisObj);
Object returnValue = null; @Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (overridenObject != null) { // Atomic retrieval
// This packet has been cancelled Object overridenObject = override.get(thisObj);
if (overridenObject == CANCEL_MARKER) { Object returnValue = null;
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE)) if (overridenObject != null) {
return null; // This packet has been cancelled
else // Revert to normal for everything else if (overridenObject == CANCEL_MARKER) {
overridenObject = thisObj; // So, cancel all void methods
} if (method.getReturnType().equals(Void.TYPE))
return null;
returnValue = proxy.invokeSuper(overridenObject, args); else // Revert to normal for everything else
} else { overridenObject = thisObj;
returnValue = proxy.invokeSuper(thisObj, args); }
}
returnValue = proxy.invokeSuper(overridenObject, args);
// Is this a readPacketData method? } else {
if (isReadPacketDataMethod) { returnValue = proxy.invokeSuper(thisObj, args);
try { }
// We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0]; // Is this a readPacketData method?
if (isReadPacketDataMethod) {
// Let the people know try {
PacketContainer container = new PacketContainer(packetID, thisObj); // We need this in order to get the correct player
PacketEvent event = packetInjector.packetRecieved(container, input); DataInputStream input = (DataInputStream) args[0];
// Handle override // Let the people know
if (event != null) { PacketContainer container = new PacketContainer(packetID, thisObj);
Object result = event.getPacket().getHandle(); PacketEvent event = packetInjector.packetRecieved(container, input);
if (event.isCancelled()) { // Handle override
override.put(thisObj, CANCEL_MARKER); if (event != null) {
} else if (!objectEquals(thisObj, result)) { Object result = event.getPacket().getHandle();
override.put(thisObj, result);
} if (event.isCancelled()) {
} override.put(thisObj, CANCEL_MARKER);
} catch (Throwable e) { } else if (!objectEquals(thisObj, result)) {
// Minecraft cannot handle this error override.put(thisObj, result);
reporter.reportDetailed(this, "Cannot handle client packet.", e, args[0]); }
} }
} } catch (Throwable e) {
return returnValue; // Minecraft cannot handle this error
} reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
private boolean objectEquals(Object a, Object b) { );
return System.identityHashCode(a) != System.identityHashCode(b); }
} }
} return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}

View File

@ -1,175 +1,178 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.ListenerInvoker;
import net.sf.cglib.proxy.Callback; import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
/** import net.sf.cglib.proxy.MethodProxy;
* The array list that notifies when packets are sent by the server.
* /**
* @author Kristian * The array list that notifies when packets are sent by the server.
*/ *
class InjectedArrayList extends ArrayList<Object> { * @author Kristian
*/
/** class InjectedArrayList extends ArrayList<Object> {
* Silly Eclipse. public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
*/
private static final long serialVersionUID = -1173865905404280990L; /**
* Silly Eclipse.
private transient PlayerInjector injector; */
private transient Set<Object> ignoredPackets; private static final long serialVersionUID = -1173865905404280990L;
private transient ClassLoader classLoader;
private transient PlayerInjector injector;
private transient InvertedIntegerCallback callback; private transient Set<Object> ignoredPackets;
private transient ClassLoader classLoader;
public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Object> ignoredPackets) {
this.classLoader = classLoader; private transient InvertedIntegerCallback callback;
this.injector = injector;
this.ignoredPackets = ignoredPackets; public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Object> ignoredPackets) {
this.callback = new InvertedIntegerCallback(); this.classLoader = classLoader;
} this.injector = injector;
this.ignoredPackets = ignoredPackets;
@Override this.callback = new InvertedIntegerCallback();
public boolean add(Object packet) { }
Object result = null; @Override
public boolean add(Object packet) {
// Check for fake packets and ignored packets
if (packet instanceof FakePacket) { Object result = null;
return true;
} else if (ignoredPackets.contains(packet)) { // Check for fake packets and ignored packets
// Don't send it to the filters if (packet instanceof FakePacket) {
result = ignoredPackets.remove(packet); return true;
} else { } else if (ignoredPackets.contains(packet)) {
result = injector.handlePacketSending(packet); // Don't send it to the filters
} result = ignoredPackets.remove(packet);
} else {
// A NULL packet indicate cancelling result = injector.handlePacketSending(packet);
try { }
if (result != null) {
super.add(result); // A NULL packet indicate cancelling
} else { try {
// We'll use the FakePacket marker instead of preventing the filters if (result != null) {
injector.sendServerPacket(createNegativePacket(packet), true); super.add(result);
} } else {
// We'll use the FakePacket marker instead of preventing the filters
// Collection.add contract injector.sendServerPacket(createNegativePacket(packet), true);
return true; }
} catch (InvocationTargetException e) { // Collection.add contract
ErrorReporter reporter = ProtocolLibrary.getErrorReporter(); return true;
// Prefer to report this to the user, instead of risking sending it to Minecraft } catch (InvocationTargetException e) {
if (reporter != null) { ErrorReporter reporter = ProtocolLibrary.getErrorReporter();
reporter.reportDetailed(this, "Reverting cancelled packet failed.", e, packet);
} else { // Prefer to report this to the user, instead of risking sending it to Minecraft
System.out.println("[ProtocolLib] Reverting cancelled packet failed."); if (reporter != null) {
e.printStackTrace(); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet));
} } else {
System.out.println("[ProtocolLib] Reverting cancelled packet failed.");
// Failure e.printStackTrace();
return false; }
}
} // Failure
return false;
/** }
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet }
* whereby every int method's return value is inverted (a => -a).
* /**
* @param source - packet to invert. * Used by a hack that reverses the effect of a cancelled packet. Returns a packet
* @return The inverted packet. * whereby every int method's return value is inverted (a => -a).
*/ *
Object createNegativePacket(Object source) { * @param source - packet to invert.
ListenerInvoker invoker = injector.getInvoker(); * @return The inverted packet.
*/
int packetID = invoker.getPacketID(source); Object createNegativePacket(Object source) {
Class<?> type = invoker.getPacketClassFromID(packetID, true); ListenerInvoker invoker = injector.getInvoker();
System.out.println(type.getName()); int packetID = invoker.getPacketID(source);
Class<?> type = invoker.getPacketClassFromID(packetID, true);
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets System.out.println(type.getName());
// might cause a "disconnect.overflow" error.
// // We want to subtract the byte amount that were added to the running
// We do that by constructing a special packet of the same type that returns // total of outstanding packets. Otherwise, cancelling too many packets
// a negative integer for all zero-parameter integer methods. This includes the // might cause a "disconnect.overflow" error.
// size() method, which is used by the queue method to count the number of //
// bytes to add. // We do that by constructing a special packet of the same type that returns
// // a negative integer for all zero-parameter integer methods. This includes the
// Essentially, we have: // size() method, which is used by the queue method to count the number of
// // bytes to add.
// public class NegativePacket extends [a packet] { //
// @Override // Essentially, we have:
// public int size() { //
// return -super.size(); // public class NegativePacket extends [a packet] {
// } // @Override
// ect. // public int size() {
// } // return -super.size();
Enhancer ex = new Enhancer(); // }
ex.setSuperclass(type); // ect.
ex.setInterfaces(new Class[] { FakePacket.class } ); // }
ex.setUseCache(true); Enhancer ex = new Enhancer();
ex.setClassLoader(classLoader); ex.setSuperclass(type);
ex.setCallbackType(InvertedIntegerCallback.class); ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
Class<?> proxyClass = ex.createClass(); ex.setClassLoader(classLoader);
Enhancer.registerCallbacks(proxyClass, new Callback[] { callback }); ex.setCallbackType(InvertedIntegerCallback.class);
try { Class<?> proxyClass = ex.createClass();
// Temporarily associate the fake packet class Enhancer.registerCallbacks(proxyClass, new Callback[] { callback });
invoker.registerPacketClass(proxyClass, packetID);
return proxyClass.newInstance(); try {
// Temporarily associate the fake packet class
} catch (Exception e) { invoker.registerPacketClass(proxyClass, packetID);
// Don't pollute the throws tree return proxyClass.newInstance();
throw new RuntimeException("Cannot create fake class.", e);
} finally { } catch (Exception e) {
// Remove this association // Don't pollute the throws tree
invoker.unregisterPacketClass(proxyClass); throw new RuntimeException("Cannot create fake class.", e);
} } finally {
} // Remove this association
invoker.unregisterPacketClass(proxyClass);
/** }
* Inverts the integer result of every integer method. }
* @author Kristian
*/ /**
private class InvertedIntegerCallback implements MethodInterceptor { * Inverts the integer result of every integer method.
@Override * @author Kristian
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { */
if (method.getReturnType().equals(int.class) && args.length == 0) { private class InvertedIntegerCallback implements MethodInterceptor {
Integer result = (Integer) proxy.invokeSuper(obj, args); @Override
return -result; public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
} else { if (method.getReturnType().equals(int.class) && args.length == 0) {
return proxy.invokeSuper(obj, args); Integer result = (Integer) proxy.invokeSuper(obj, args);
} return -result;
} } else {
} return proxy.invokeSuper(obj, args);
} }
}
}
}

View File

@ -1,318 +1,338 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import org.bukkit.Server; import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
/** import com.comphenix.protocol.utility.MinecraftReflection;
* Used to ensure that the 1.3 server is referencing the correct server handler.
* /**
* @author Kristian * Used to ensure that the 1.3 server is referencing the correct server handler.
*/ *
class InjectedServerConnection { * @author Kristian
*/
private static Field listenerThreadField; class InjectedServerConnection {
private static Field minecraftServerField; // A number of things can go wrong ...
private static Field listField; public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
private static Field dedicatedThreadField; public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
private static Method serverConnectionMethod; 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.");
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists; 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");
// Used to inject net handlers public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
private NetLoginInjector netLoginInjector; public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
// Inject server connections public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
private AbstractInputStreamLookup socketInjector;
private static Field listenerThreadField;
private Server server; private static Field minecraftServerField;
private ErrorReporter reporter; private static Field listField;
private boolean hasAttempted; private static Field dedicatedThreadField;
private boolean hasSuccess;
private static Method serverConnectionMethod;
private Object minecraftServer = null;
private List<VolatileField> listFields;
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) { private List<ReplacedArrayList<Object>> replacedLists;
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>(); // Used to inject net handlers
this.reporter = reporter; private NetLoginInjector netLoginInjector;
this.server = server;
this.socketInjector = socketInjector; // Inject server connections
this.netLoginInjector = netLoginInjector; private AbstractInputStreamLookup socketInjector;
}
private Server server;
public void injectList() { private ErrorReporter reporter;
private boolean hasAttempted;
// Only execute this method once private boolean hasSuccess;
if (!hasAttempted)
hasAttempted = true; private Object minecraftServer = null;
else
return; public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
if (minecraftServerField == null) this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
minecraftServerField = FuzzyReflection.fromObject(server, true). this.reporter = reporter;
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass()); this.server = server;
this.socketInjector = socketInjector;
try { this.netLoginInjector = netLoginInjector;
minecraftServer = FieldUtils.readField(minecraftServerField, server, true); }
} catch (IllegalAccessException e1) {
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit."); public void injectList() {
return; // Only execute this method once
} if (!hasAttempted)
hasAttempted = true;
try { else
if (serverConnectionMethod == null) return;
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection", if (minecraftServerField == null)
MinecraftReflection.getServerConnectionClass(), new Class[] {}); minecraftServerField = FuzzyReflection.fromObject(server, true).
// We're using Minecraft 1.3.1 getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
injectServerConnection();
try {
} catch (IllegalArgumentException e) { minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
// Minecraft 1.2.5 or lower reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
injectListenerThread(); return;
}
} catch (Exception e) {
// Oh damn - inform the player try {
reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e); if (serverConnectionMethod == null)
} serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
} getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {});
private void injectListenerThread() { // We're using Minecraft 1.3.1
try { injectServerConnection();
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer). } catch (IllegalArgumentException e) {
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
} catch (RuntimeException e) { // Minecraft 1.2.5 or lower
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer); injectListenerThread();
return;
} } catch (Exception e) {
// Oh damn - inform the player
Object listenerThread = null; reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
}
// Attempt to get the thread }
try {
listenerThread = listenerThreadField.get(minecraftServer); private void injectListenerThread() {
} catch (Exception e) { try {
reporter.reportWarning(this, "Unable to read the listener thread.", e); if (listenerThreadField == null)
return; listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
} getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
} catch (RuntimeException e) {
// Inject the server socket too reporter.reportDetailed(this,
injectServerSocket(listenerThread); Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
);
// Just inject every list field we can get return;
injectEveryListField(listenerThread, 1); }
hasSuccess = true;
} Object listenerThread = null;
private void injectServerConnection() { // Attempt to get the thread
try {
Object serverConnection = null; listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
// Careful - we might fail reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
try { return;
serverConnection = serverConnectionMethod.invoke(minecraftServer); }
} catch (Exception ex) {
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer); // Inject the server socket too
return; injectServerSocket(listenerThread);
}
// Just inject every list field we can get
if (listField == null) injectEveryListField(listenerThread, 1);
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). hasSuccess = true;
getFieldByType("netServerHandlerList", List.class); }
if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true). private void injectServerConnection() {
getFieldListByType(Thread.class); Object serverConnection = null;
// Verify the field count // Careful - we might fail
if (matches.size() != 1) try {
reporter.reportWarning(this, "Unexpected number of threads in " + serverConnection.getClass().getName()); serverConnection = serverConnectionMethod.invoke(minecraftServer);
else } catch (Exception e) {
dedicatedThreadField = matches.get(0); reporter.reportDetailed(this,
} Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
);
// Next, try to get the dedicated thread return;
try { }
if (dedicatedThreadField != null) {
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true); if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
// Inject server socket and NetServerHandlers. getFieldByType("netServerHandlerList", List.class);
injectServerSocket(dedicatedThread); if (dedicatedThreadField == null) {
injectEveryListField(dedicatedThread, 1); List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
} getFieldListByType(Thread.class);
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e); // Verify the field count
} if (matches.size() != 1)
reporter.reportWarning(this,
injectIntoList(serverConnection, listField); Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
hasSuccess = true; );
} else
dedicatedThreadField = matches.get(0);
private void injectServerSocket(Object container) { }
socketInjector.inject(container);
} // Next, try to get the dedicated thread
try {
/** if (dedicatedThreadField != null) {
* Automatically inject into every List-compatible public or private field of the given object. Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
* @param container - container object with the fields to inject.
* @param minimum - the minimum number of fields we expect exists. // Inject server socket and NetServerHandlers.
*/ injectServerSocket(dedicatedThread);
private void injectEveryListField(Object container, int minimum) { injectEveryListField(dedicatedThread, 1);
// Ok, great. Get every list field }
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class); } catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
for (Field list : lists) { }
injectIntoList(container, list);
} injectIntoList(serverConnection, listField);
hasSuccess = true;
// Warn about unexpected errors }
if (lists.size() < minimum) {
reporter.reportWarning(this, "Unable to inject " + minimum + " lists in " + container.getClass().getName()); private void injectServerSocket(Object container) {
} socketInjector.inject(container);
} }
@SuppressWarnings("unchecked") /**
private void injectIntoList(Object instance, Field field) { * Automatically inject into every List-compatible public or private field of the given object.
VolatileField listFieldRef = new VolatileField(field, instance, true); * @param container - container object with the fields to inject.
List<Object> list = (List<Object>) listFieldRef.getValue(); * @param minimum - the minimum number of fields we expect exists.
*/
// Careful not to inject twice private void injectEveryListField(Object container, int minimum) {
if (list instanceof ReplacedArrayList) { // Ok, great. Get every list field
replacedLists.add((ReplacedArrayList<Object>) list); List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
} else {
ReplacedArrayList<Object> injectedList = createReplacement(list); for (Field list : lists) {
injectIntoList(container, list);
replacedLists.add(injectedList); }
listFieldRef.setValue(injectedList);
listFields.add(listFieldRef); // Warn about unexpected errors
} if (lists.size() < minimum) {
} reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
}
// Hack to avoid the "moved to quickly" error }
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) { @SuppressWarnings("unchecked")
/** private void injectIntoList(Object instance, Field field) {
* Shut up Eclipse! VolatileField listFieldRef = new VolatileField(field, instance, true);
*/ List<Object> list = (List<Object>) listFieldRef.getValue();
private static final long serialVersionUID = 2070481080950500367L;
// Careful not to inject twice
// Object writer we'll use if (list instanceof ReplacedArrayList) {
private final ObjectWriter writer = new ObjectWriter(); replacedLists.add((ReplacedArrayList<Object>) list);
} else {
@Override ReplacedArrayList<Object> injectedList = createReplacement(list);
protected void onReplacing(Object inserting, Object replacement) {
// Is this a normal Minecraft object? replacedLists.add(injectedList);
if (!(inserting instanceof Factory)) { listFieldRef.setValue(injectedList);
// If so, copy the content of the old element to the new listFields.add(listFieldRef);
try { }
writer.copyTo(inserting, replacement, inserting.getClass()); }
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting + // Hack to avoid the "moved to quickly" error
" to new.", e, inserting, replacement); private ReplacedArrayList<Object> createReplacement(List<Object> list) {
} return new ReplacedArrayList<Object>(list) {
} /**
} * Shut up Eclipse!
*/
@Override private static final long serialVersionUID = 2070481080950500367L;
protected void onInserting(Object inserting) {
// Ready for some login handler injection? // Object writer we'll use
if (MinecraftReflection.isLoginHandler(inserting)) { private final ObjectWriter writer = new ObjectWriter();
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
@Override
// Only replace if it has changed protected void onReplacing(Object inserting, Object replacement) {
if (inserting != replaced) // Is this a normal Minecraft object?
addMapping(inserting, replaced, true); if (!(inserting instanceof Factory)) {
} // If so, copy the content of the old element to the new
} try {
writer.copyTo(inserting, replacement, inserting.getClass());
@Override } catch (Throwable e) {
protected void onRemoved(Object removing) { reporter.reportDetailed(InjectedServerConnection.this,
// Clean up? Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
if (MinecraftReflection.isLoginHandler(removing)) { );
netLoginInjector.cleanup(removing); }
} }
} }
};
} @Override
protected void onInserting(Object inserting) {
/** // Ready for some login handler injection?
* Replace the server handler instance kept by the "keep alive" object. if (MinecraftReflection.isLoginHandler(inserting)) {
* @param oldHandler - old server handler. Object replaced = netLoginInjector.onNetLoginCreated(inserting);
* @param newHandler - new, proxied server handler.
*/ // Only replace if it has changed
public void replaceServerHandler(Object oldHandler, Object newHandler) { if (inserting != replaced)
if (!hasAttempted) { addMapping(inserting, replaced, true);
injectList(); }
} }
if (hasSuccess) { @Override
for (ReplacedArrayList<Object> replacedList : replacedLists) { protected void onRemoved(Object removing) {
replacedList.addMapping(oldHandler, newHandler); // Clean up?
} if (MinecraftReflection.isLoginHandler(removing)) {
} netLoginInjector.cleanup(removing);
} }
}
/** };
* Revert to the old vanilla server handler, if it has been replaced. }
* @param oldHandler - old vanilla server handler.
*/ /**
public void revertServerHandler(Object oldHandler) { * Replace the server handler instance kept by the "keep alive" object.
if (hasSuccess) { * @param oldHandler - old server handler.
for (ReplacedArrayList<Object> replacedList : replacedLists) { * @param newHandler - new, proxied server handler.
replacedList.removeMapping(oldHandler); */
} public void replaceServerHandler(Object oldHandler, Object newHandler) {
} if (!hasAttempted) {
} injectList();
}
/**
* Undoes everything. if (hasSuccess) {
*/ for (ReplacedArrayList<Object> replacedList : replacedLists) {
public void cleanupAll() { replacedList.addMapping(oldHandler, newHandler);
if (replacedLists.size() > 0) { }
// Repair the underlying lists }
for (ReplacedArrayList<Object> replacedList : replacedLists) { }
replacedList.revertAll();
} /**
for (VolatileField field : listFields) { * Revert to the old vanilla server handler, if it has been replaced.
field.revertValue(); * @param oldHandler - old vanilla server handler.
} */
public void revertServerHandler(Object oldHandler) {
listFields.clear(); if (hasSuccess) {
replacedLists.clear(); for (ReplacedArrayList<Object> replacedList : replacedLists) {
} replacedList.removeMapping(oldHandler);
} }
} }
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}

View File

@ -1,141 +1,154 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.google.common.collect.Maps; import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
/** import com.google.common.collect.Maps;
* Injects every NetLoginHandler created by the server.
* /**
* @author Kristian * Injects every NetLoginHandler created by the server.
*/ *
class NetLoginInjector { * @author Kristian
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap(); */
class NetLoginInjector {
// Handles every hook public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
private ProxyPlayerInjectionHandler injectionHandler; public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
// Create temporary players private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
// Handles every hook
// The current error reporter private ProxyPlayerInjectionHandler injectionHandler;
private ErrorReporter reporter;
private Server server; // Create temporary players
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
this.reporter = reporter; // The current error reporter
this.server = server; private ErrorReporter reporter;
this.injectionHandler = injectionHandler; private Server server;
}
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
/** this.reporter = reporter;
* Invoked when a NetLoginHandler has been created. this.server = server;
* @param inserting - the new NetLoginHandler. this.injectionHandler = injectionHandler;
* @return An injected NetLoginHandler, or the original object. }
*/
public Object onNetLoginCreated(Object inserting) { /**
try { * Invoked when a NetLoginHandler has been created.
// Make sure we actually need to inject during this phase * @param inserting - the new NetLoginHandler.
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) * @return An injected NetLoginHandler, or the original object.
return inserting; */
public Object onNetLoginCreated(Object inserting) {
Player temporary = playerFactory.createTemporaryPlayer(server); try {
// Note that we bail out if there's an existing player injector // Make sure we actually need to inject during this phase
PlayerInjector injector = injectionHandler.injectPlayer( if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN); return inserting;
if (injector != null) { Player temporary = playerFactory.createTemporaryPlayer(server);
// Update injector as well // Note that we bail out if there's an existing player injector
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector); PlayerInjector injector = injectionHandler.injectPlayer(
injector.updateOnLogin = true; temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
// Save the login if (injector != null) {
injectedLogins.putIfAbsent(inserting, injector); // Update injector as well
} TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
injector.updateOnLogin = true;
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting; // Save the login
injectedLogins.putIfAbsent(inserting, injector);
} catch (Throwable e) { }
// Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this, "Unable to hook " + // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting, injectionHandler); return inserting;
return inserting;
} } catch (Throwable e) {
} // Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this,
/** Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
* Invoked when a NetLoginHandler should be reverted. messageParam(MinecraftReflection.getNetLoginHandlerName()).
* @param inserting - the original NetLoginHandler. callerParam(inserting, injectionHandler).
* @return An injected NetLoginHandler, or the original object. error(e)
*/ );
public synchronized void cleanup(Object removing) { return inserting;
PlayerInjector injected = injectedLogins.get(removing); }
}
if (injected != null) {
try { /**
PlayerInjector newInjector = null; * Invoked when a NetLoginHandler should be reverted.
Player player = injected.getPlayer(); * @param inserting - the original NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
// Clean up list */
injectedLogins.remove(removing); public synchronized void cleanup(Object removing) {
PlayerInjector injected = injectedLogins.get(removing);
// No need to clean up twice
if (injected.isClean()) if (injected != null) {
return; try {
PlayerInjector newInjector = null;
// Hack to clean up other references Player player = injected.getPlayer();
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
injectionHandler.uninjectPlayer(player); // Clean up list
injectedLogins.remove(removing);
// Update NetworkManager
if (newInjector != null) { // No need to clean up twice
if (injected instanceof NetworkObjectInjector) { if (injected.isClean())
newInjector.setNetworkManager(injected.getNetworkManager(), true); return;
}
} // Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
} catch (Throwable e) { injectionHandler.uninjectPlayer(player);
// Don't leak this to Minecraft
reporter.reportDetailed(this, "Cannot cleanup " + // Update NetworkManager
MinecraftReflection.getNetLoginHandlerName() + ".", e, removing); if (newInjector != null) {
} if (injected instanceof NetworkObjectInjector) {
} newInjector.setNetworkManager(injected.getNetworkManager(), true);
} }
}
/**
* Remove all injected hooks. } catch (Throwable e) {
*/ // Don't leak this to Minecraft
public void cleanupAll() { reporter.reportDetailed(this,
for (PlayerInjector injector : injectedLogins.values()) { Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
injector.cleanupAll(); messageParam(MinecraftReflection.getNetLoginHandlerName()).
} callerParam(removing).
error(e)
injectedLogins.clear(); );
} }
} }
}
/**
* Remove all injected hooks.
*/
public void cleanupAll() {
for (PlayerInjector injector : injectedLogins.values()) {
injector.cleanupAll();
}
injectedLogins.clear();
}
}

View File

@ -1,345 +1,352 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector.player; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import net.sf.cglib.proxy.*; import net.sf.cglib.proxy.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.instances.ExistingGenerator; import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.reflect.instances.ExistingGenerator;
import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
/** import com.comphenix.protocol.utility.MinecraftVersion;
* Represents a player hook into the NetServerHandler class.
* /**
* @author Kristian * Represents a player hook into the NetServerHandler class.
*/ *
class NetworkServerInjector extends PlayerInjector { * @author Kristian
private volatile static CallbackFilter callbackFilter; */
private volatile static boolean foundSendPacket; class NetworkServerInjector extends PlayerInjector {
// Disconnected field
private volatile static Field disconnectField; public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
private InjectedServerConnection serverInjection; 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.");
// Determine if we're listening
private IntegerSet sendingFilters; private volatile static CallbackFilter callbackFilter;
private volatile static boolean foundSendPacket;
// Used to create proxy objects
private ClassLoader classLoader; private volatile static Field disconnectField;
private InjectedServerConnection serverInjection;
// Whether or not the player has disconnected
private boolean hasDisconnected; // Determine if we're listening
private IntegerSet sendingFilters;
// Used to copy fields
private final ObjectWriter writer = new ObjectWriter(); // Used to create proxy objects
private ClassLoader classLoader;
public NetworkServerInjector(
ClassLoader classLoader, ErrorReporter reporter, Player player, // Whether or not the player has disconnected
ListenerInvoker invoker, IntegerSet sendingFilters, private boolean hasDisconnected;
InjectedServerConnection serverInjection) throws IllegalAccessException {
// Used to copy fields
super(reporter, player, invoker); private final ObjectWriter writer = new ObjectWriter();
this.classLoader = classLoader;
this.sendingFilters = sendingFilters; public NetworkServerInjector(
this.serverInjection = serverInjection; ClassLoader classLoader, ErrorReporter reporter, Player player,
} ListenerInvoker invoker, IntegerSet sendingFilters,
InjectedServerConnection serverInjection) throws IllegalAccessException {
@Override
protected boolean hasListener(int packetID) { super(reporter, player, invoker);
return sendingFilters.contains(packetID); this.classLoader = classLoader;
} this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
@Override }
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); @Override
protected boolean hasListener(int packetID) {
if (serverDelegate != null) { return sendingFilters.contains(packetID);
try { }
// Note that invocation target exception is a wrapper for a checked exception
MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet); @Override
public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException {
} catch (IllegalArgumentException e) { Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
throw e;
} catch (InvocationTargetException e) { if (serverDelegate != null) {
throw e; try {
} catch (IllegalAccessException e) { // Note that invocation target exception is a wrapper for a checked exception
throw new IllegalStateException("Unable to access send packet method.", e); MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
}
} else { } catch (IllegalArgumentException e) {
throw new IllegalStateException("Unable to load server handler. Cannot send packet."); throw e;
} } catch (InvocationTargetException e) {
} throw e;
} catch (IllegalAccessException e) {
@Override throw new IllegalStateException("Unable to access send packet method.", e);
public void injectManager() { }
} else {
if (serverHandlerRef == null) throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
throw new IllegalStateException("Cannot find server handler."); }
// Don't inject twice }
if (serverHandlerRef.getValue() instanceof Factory)
return; @Override
public void injectManager() {
if (!tryInjectManager()) {
Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass(); if (serverHandlerRef == null)
throw new IllegalStateException("Cannot find server handler.");
// Try to override the proxied object // Don't inject twice
if (proxyServerField != null) { if (serverHandlerRef.getValue() instanceof Factory)
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true); return;
serverHandler = serverHandlerRef.getValue();
if (!tryInjectManager()) {
if (serverHandler == null) Class<?> serverHandlerClass = MinecraftReflection.getNetServerHandlerClass();
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
else // Try to override the proxied object
serverHandlerClass = serverHandler.getClass(); if (proxyServerField != null) {
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
// Try again serverHandler = serverHandlerRef.getValue();
if (tryInjectManager()) {
// It worked - probably if (serverHandler == null)
return; throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
} else
} serverHandlerClass = serverHandler.getClass();
throw new RuntimeException( // Try again
"Cannot hook player: Unable to find a valid constructor for the " if (tryInjectManager()) {
+ serverHandlerClass.getName() + " object."); // It worked - probably
} return;
} }
}
private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass(); throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the "
Enhancer ex = new Enhancer(); + serverHandlerClass.getName() + " object.");
Callback sendPacketCallback = new MethodInterceptor() { }
@Override }
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object packet = args[0]; private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
if (packet != null) {
packet = handlePacketSending(packet); Enhancer ex = new Enhancer();
Callback sendPacketCallback = new MethodInterceptor() {
// A NULL packet indicate cancelling @Override
if (packet != null) public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
args[0] = packet; Object packet = args[0];
else
return null; if (packet != null) {
} packet = handlePacketSending(packet);
// Call the method directly // A NULL packet indicate cancelling
return proxy.invokeSuper(obj, args); if (packet != null)
}; args[0] = packet;
}; else
Callback noOpCallback = NoOp.INSTANCE; return null;
}
// Share callback filter - that way, we avoid generating a new class for
// every logged in player. // Call the method directly
if (callbackFilter == null) { return proxy.invokeSuper(obj, args);
final Method sendPacket = MinecraftMethods.getSendPacketMethod(); };
};
callbackFilter = new CallbackFilter() { Callback noOpCallback = NoOp.INSTANCE;
@Override
public int accept(Method method) { // Share callback filter - that way, we avoid generating a new class for
if (isCallableEqual(sendPacket, method)) { // every logged in player.
foundSendPacket = true; if (callbackFilter == null) {
return 0; final Method sendPacket = MinecraftMethods.getSendPacketMethod();
} else {
return 1; callbackFilter = new CallbackFilter() {
} @Override
} public int accept(Method method) {
}; if (isCallableEqual(sendPacket, method)) {
} foundSendPacket = true;
return 0;
ex.setClassLoader(classLoader); } else {
ex.setSuperclass(serverClass); return 1;
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback }); }
ex.setCallbackFilter(callbackFilter); }
};
// Find the Minecraft NetServerHandler superclass }
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass); ex.setClassLoader(classLoader);
DefaultInstances serverInstances = null; ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
// Maybe the proxy instance can help? ex.setCallbackFilter(callbackFilter);
Object proxyInstance = getProxyServerHandler();
// Find the Minecraft NetServerHandler superclass
// Use the existing server proxy when we create one Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
if (proxyInstance != null && proxyInstance != serverHandler) { ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
serverInstances = DefaultInstances.fromArray(generator, DefaultInstances serverInstances = null;
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else { // Maybe the proxy instance can help?
serverInstances = DefaultInstances.fromArray(generator); Object proxyInstance = getProxyServerHandler();
}
// Use the existing server proxy when we create one
serverInstances.setNonNull(true); if (proxyInstance != null && proxyInstance != serverHandler) {
serverInstances.setMaximumRecursion(1); serverInstances = DefaultInstances.fromArray(generator,
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass); } else {
serverInstances = DefaultInstances.fromArray(generator);
// Inject it now }
if (proxyObject != null) {
// Did we override a sendPacket method? serverInstances.setNonNull(true);
if (!foundSendPacket) { serverInstances.setMaximumRecursion(1);
throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
} Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
serverInjection.replaceServerHandler(serverHandler, proxyObject); // Inject it now
serverHandlerRef.setValue(proxyObject); if (proxyObject != null) {
return true; // Did we override a sendPacket method?
} else { if (!foundSendPacket) {
return false; throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
} }
}
serverInjection.replaceServerHandler(serverHandler, proxyObject);
/** serverHandlerRef.setValue(proxyObject);
* Determine if the two methods are equal in terms of call semantics. return true;
* <p> } else {
* Two methods are equal if they have the same name, parameter types and return type. return false;
* @param first - first method. }
* @param second - second method. }
* @return TRUE if they are, FALSE otherwise.
*/ /**
private boolean isCallableEqual(Method first, Method second) { * Determine if the two methods are equal in terms of call semantics.
return first.getName().equals(second.getName()) && * <p>
first.getReturnType().equals(second.getReturnType()) && * Two methods are equal if they have the same name, parameter types and return type.
Arrays.equals(first.getParameterTypes(), second.getParameterTypes()); * @param first - first method.
} * @param second - second method.
* @return TRUE if they are, FALSE otherwise.
private Object getProxyServerHandler() { */
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) { private boolean isCallableEqual(Method first, Method second) {
try { return first.getName().equals(second.getName()) &&
return FieldUtils.readField(proxyServerField, serverHandler, true); first.getReturnType().equals(second.getReturnType()) &&
} catch (Throwable e) { Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
// Oh well }
}
} private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
return null; try {
} return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (Throwable e) {
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) { // Oh well
if (MinecraftReflection.isMinecraftClass(clazz)) }
return clazz; }
else if (clazz.equals(Object.class))
return clazz; return null;
else }
return getFirstMinecraftSuperClass(clazz.getSuperclass());
} private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (MinecraftReflection.isMinecraftClass(clazz))
@Override return clazz;
protected void cleanHook() { else if (clazz.equals(Object.class))
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) { return clazz;
writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass()); else
serverHandlerRef.revertValue(); return getFirstMinecraftSuperClass(clazz.getSuperclass());
}
try {
if (getNetHandler() != null) { @Override
// Restore packet listener protected void cleanHook() {
try { if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true); writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
} catch (IllegalAccessException e) { serverHandlerRef.revertValue();
// Oh well
e.printStackTrace(); try {
} if (getNetHandler() != null) {
} // Restore packet listener
} catch (IllegalAccessException e) { try {
e.printStackTrace(); FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
} } catch (IllegalAccessException e) {
// Oh well
// Prevent the PlayerQuitEvent from being sent twice e.printStackTrace();
if (hasDisconnected) { }
setDisconnect(serverHandlerRef.getValue(), true); }
} } catch (IllegalAccessException e) {
} e.printStackTrace();
}
serverInjection.revertServerHandler(serverHandler);
} // Prevent the PlayerQuitEvent from being sent twice
if (hasDisconnected) {
@Override setDisconnect(serverHandlerRef.getValue(), true);
public void handleDisconnect() { }
hasDisconnected = true; }
}
serverInjection.revertServerHandler(serverHandler);
/** }
* Set the disconnected field in a NetServerHandler.
* @param handler - the NetServerHandler. @Override
* @param value - the new value. public void handleDisconnect() {
*/ hasDisconnected = true;
private void setDisconnect(Object handler, boolean value) { }
// Set it
try { /**
// Load the field * Set the disconnected field in a NetServerHandler.
if (disconnectField == null) { * @param handler - the NetServerHandler.
disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*"); * @param value - the new value.
} */
FieldUtils.writeField(disconnectField, handler, value); private void setDisconnect(Object handler, boolean value) {
// Set it
} catch (IllegalArgumentException e) { try {
// Assume it's the first ... // Load the field
if (disconnectField == null) { if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class); disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*");
reporter.reportWarning(this, "Unable to find 'disconnected' field. Assuming " + disconnectField); }
FieldUtils.writeField(disconnectField, handler, value);
// Try again
if (disconnectField != null) { } catch (IllegalArgumentException e) {
setDisconnect(handler, value); // Assume it's the first ...
return; if (disconnectField == null) {
} disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
} reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
// This is really bad // Try again
reporter.reportDetailed(this, "Cannot find disconnected field. Is ProtocolLib up to date?", e); if (disconnectField != null) {
setDisconnect(handler, value);
} catch (IllegalAccessException e) { return;
reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice."); }
} }
}
// This is really bad
@Override reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
// We support everything } catch (IllegalAccessException e) {
return null; reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
} }
}
@Override
public boolean canInject(GamePhase phase) { @Override
// Doesn't work when logging in public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
return phase == GamePhase.PLAYING; // We support everything
} return null;
}
@Override
public PlayerInjectHooks getHookType() { @Override
return PlayerInjectHooks.NETWORK_SERVER_OBJECT; public boolean canInject(GamePhase phase) {
} // Doesn't work when logging in
} return phase == GamePhase.PLAYING;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
}

View File

@ -1,365 +1,370 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.reflect.compiler; package com.comphenix.protocol.reflect.compiler;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage; import java.lang.management.MemoryUsage;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey; import com.comphenix.protocol.error.ReportType;
import com.google.common.collect.Lists; import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Maps; import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/** import com.google.common.util.concurrent.ThreadFactoryBuilder;
* Compiles structure modifiers on a background thread.
* <p> /**
* This is necessary as we cannot block the main thread. * Compiles structure modifiers on a background thread.
* * <p>
* @author Kristian * This is necessary as we cannot block the main thread.
*/ *
public class BackgroundCompiler { * @author Kristian
*/
/** public class BackgroundCompiler {
* The default format for the name of new worker threads. 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.");
public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s";
/**
// How long to wait for a shutdown * The default format for the name of new worker threads.
public static final int SHUTDOWN_DELAY_MS = 2000; */
public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s";
/**
* The default fraction of perm gen space after which the background compiler will be disabled. // How long to wait for a shutdown
*/ public static final int SHUTDOWN_DELAY_MS = 2000;
public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65;
/**
// The single background compiler we're using * The default fraction of perm gen space after which the background compiler will be disabled.
private static BackgroundCompiler backgroundCompiler; */
public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65;
// Classes we're currently compiling
private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap(); // The single background compiler we're using
private Object listenerLock = new Object(); private static BackgroundCompiler backgroundCompiler;
private StructureCompiler compiler; // Classes we're currently compiling
private boolean enabled; private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap();
private boolean shuttingDown; private Object listenerLock = new Object();
private ExecutorService executor; private StructureCompiler compiler;
private ErrorReporter reporter; private boolean enabled;
private boolean shuttingDown;
private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
private ExecutorService executor;
/** private ErrorReporter reporter;
* Retrieves the current background compiler.
* @return Current background compiler. private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
*/
public static BackgroundCompiler getInstance() { /**
return backgroundCompiler; * Retrieves the current background compiler.
} * @return Current background compiler.
*/
/** public static BackgroundCompiler getInstance() {
* Sets the single background compiler we're using. return backgroundCompiler;
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded. }
*/
public static void setInstance(BackgroundCompiler backgroundCompiler) { /**
BackgroundCompiler.backgroundCompiler = backgroundCompiler; * Sets the single background compiler we're using.
} * @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
*/
/** public static void setInstance(BackgroundCompiler backgroundCompiler) {
* Initialize a background compiler. BackgroundCompiler.backgroundCompiler = backgroundCompiler;
* <p> }
* Uses the default {@link #THREAD_FORMAT} to name worker threads.
* @param loader - class loader from Bukkit. /**
* @param reporter - current error reporter. * Initialize a background compiler.
*/ * <p>
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) { * Uses the default {@link #THREAD_FORMAT} to name worker threads.
ThreadFactory factory = new ThreadFactoryBuilder(). * @param loader - class loader from Bukkit.
setDaemon(true). * @param reporter - current error reporter.
setNameFormat(THREAD_FORMAT). */
build(); public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) {
initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory)); ThreadFactory factory = new ThreadFactoryBuilder().
} setDaemon(true).
setNameFormat(THREAD_FORMAT).
/** build();
* Initialize a background compiler utilizing the given thread pool. initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory));
* @param loader - class loader from Bukkit. }
* @param reporter - current error reporter.
* @param executor - thread pool we'll use. /**
*/ * Initialize a background compiler utilizing the given thread pool.
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) { * @param loader - class loader from Bukkit.
initializeCompiler(loader, reporter, executor); * @param reporter - current error reporter.
} * @param executor - thread pool we'll use.
*/
// Avoid "Constructor call must be the first statement". public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
private void initializeCompiler(ClassLoader loader, @Nullable ErrorReporter reporter, ExecutorService executor) { initializeCompiler(loader, reporter, executor);
if (loader == null) }
throw new IllegalArgumentException("loader cannot be NULL");
if (executor == null) // Avoid "Constructor call must be the first statement".
throw new IllegalArgumentException("executor cannot be NULL"); private void initializeCompiler(ClassLoader loader, @Nullable ErrorReporter reporter, ExecutorService executor) {
if (loader == null)
this.compiler = new StructureCompiler(loader); throw new IllegalArgumentException("loader cannot be NULL");
this.reporter = reporter; if (executor == null)
this.executor = executor; throw new IllegalArgumentException("executor cannot be NULL");
this.enabled = true;
} this.compiler = new StructureCompiler(loader);
this.reporter = reporter;
/** this.executor = executor;
* Ensure that the indirectly given structure modifier is eventually compiled. this.enabled = true;
* @param cache - store of structure modifiers. }
* @param key - key of the structure modifier to compile.
*/ /**
@SuppressWarnings("rawtypes") * Ensure that the indirectly given structure modifier is eventually compiled.
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) { * @param cache - store of structure modifiers.
* @param key - key of the structure modifier to compile.
@SuppressWarnings("unchecked") */
final StructureModifier<Object> uncompiled = cache.get(key); @SuppressWarnings("rawtypes")
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
if (uncompiled != null) {
scheduleCompilation(uncompiled, new CompileListener<Object>() { @SuppressWarnings("unchecked")
@Override final StructureModifier<Object> uncompiled = cache.get(key);
public void onCompiled(StructureModifier<Object> compiledModifier) {
// Update cache if (uncompiled != null) {
cache.put(key, compiledModifier); scheduleCompilation(uncompiled, new CompileListener<Object>() {
} @Override
}); public void onCompiled(StructureModifier<Object> compiledModifier) {
} // Update cache
} cache.put(key, compiledModifier);
}
/** });
* Ensure that the given structure modifier is eventually compiled. }
* @param uncompiled - structure modifier to compile. }
* @param listener - listener responsible for responding to the compilation.
*/ /**
@SuppressWarnings({"rawtypes", "unchecked"}) * Ensure that the given structure modifier is eventually compiled.
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) { * @param uncompiled - structure modifier to compile.
// Only schedule if we're enabled * @param listener - listener responsible for responding to the compilation.
if (enabled && !shuttingDown) { */
// Check perm gen @SuppressWarnings({"rawtypes", "unchecked"})
if (getPermGenUsage() > disablePermGenFraction) public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
return; // Only schedule if we're enabled
if (enabled && !shuttingDown) {
// Don't try to schedule anything // Check perm gen
if (executor == null || executor.isShutdown()) if (getPermGenUsage() > disablePermGenFraction)
return; return;
// Use to look up structure modifiers // Don't try to schedule anything
final StructureKey key = new StructureKey(uncompiled); if (executor == null || executor.isShutdown())
return;
// Allow others to listen in too
synchronized (listenerLock) { // Use to look up structure modifiers
List list = listeners.get(key); final StructureKey key = new StructureKey(uncompiled);
if (!listeners.containsKey(key)) { // Allow others to listen in too
listeners.put(key, (List) Lists.newArrayList(listener)); synchronized (listenerLock) {
} else { List list = listeners.get(key);
// We're currently compiling
list.add(listener); if (!listeners.containsKey(key)) {
return; listeners.put(key, (List) Lists.newArrayList(listener));
} } else {
} // We're currently compiling
list.add(listener);
// Create the worker that will compile our modifier return;
Callable<?> worker = new Callable<Object>() { }
@Override }
public Object call() throws Exception {
StructureModifier<TKey> modifier = uncompiled; // Create the worker that will compile our modifier
List list = null; Callable<?> worker = new Callable<Object>() {
@Override
// Do our compilation public Object call() throws Exception {
try { StructureModifier<TKey> modifier = uncompiled;
modifier = compiler.compile(modifier); List list = null;
synchronized (listenerLock) { // Do our compilation
list = listeners.get(key); try {
modifier = compiler.compile(modifier);
// Prevent ConcurrentModificationExceptions
if (list != null) { synchronized (listenerLock) {
list = Lists.newArrayList(list); list = listeners.get(key);
}
} // Prevent ConcurrentModificationExceptions
if (list != null) {
// Only execute the listeners if there is a list list = Lists.newArrayList(list);
if (list != null) { }
for (Object compileListener : list) { }
((CompileListener<TKey>) compileListener).onCompiled(modifier);
} // Only execute the listeners if there is a list
if (list != null) {
// Remove it when we're done for (Object compileListener : list) {
synchronized (listenerLock) { ((CompileListener<TKey>) compileListener).onCompiled(modifier);
list = listeners.remove(key); }
}
} // Remove it when we're done
synchronized (listenerLock) {
} catch (Throwable e) { list = listeners.remove(key);
// Disable future compilations! }
setEnabled(false); }
// Inform about this error as best as we can } catch (Throwable e) {
if (reporter != null) { // Disable future compilations!
reporter.reportDetailed(BackgroundCompiler.this, setEnabled(false);
"Cannot compile structure. Disabing compiler.", e, uncompiled);
} else { // Inform about this error as best as we can
System.err.println("Exception occured in structure compiler: "); if (reporter != null) {
e.printStackTrace(); reporter.reportDetailed(BackgroundCompiler.this,
} Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e)
} );
} else {
// We'll also return the new structure modifier System.err.println("Exception occured in structure compiler: ");
return modifier; e.printStackTrace();
}
} }
};
// We'll also return the new structure modifier
try { return modifier;
// Lookup the previous class name on the main thread.
// This is necessary as the Bukkit class loaders are not thread safe }
if (compiler.lookupClassLoader(uncompiled)) { };
try {
worker.call(); try {
} catch (Exception e) { // Lookup the previous class name on the main thread.
// Impossible! // This is necessary as the Bukkit class loaders are not thread safe
e.printStackTrace(); if (compiler.lookupClassLoader(uncompiled)) {
} try {
worker.call();
} else { } catch (Exception e) {
// Impossible!
// Perform the compilation on a seperate thread e.printStackTrace();
executor.submit(worker); }
}
} else {
} catch (RejectedExecutionException e) {
// Occures when the underlying queue is overflowing. Since the compilation // Perform the compilation on a seperate thread
// is only an optmization and not really essential we'll just log this failure executor.submit(worker);
// and move on. }
reporter.reportWarning(this, "Unable to schedule compilation task.", e);
} } catch (RejectedExecutionException e) {
} // 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, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e));
* Add a compile listener if we are still waiting for the structure modifier to be compiled. }
* @param uncompiled - the structure modifier that may get compiled. }
* @param listener - the listener to invoke in that case. }
*/
@SuppressWarnings("unchecked") /**
public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) { * Add a compile listener if we are still waiting for the structure modifier to be compiled.
synchronized (listenerLock) { * @param uncompiled - the structure modifier that may get compiled.
StructureKey key = new StructureKey(uncompiled); * @param listener - the listener to invoke in that case.
*/
@SuppressWarnings("rawtypes") @SuppressWarnings("unchecked")
List list = listeners.get(key); public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
synchronized (listenerLock) {
if (list != null) { StructureKey key = new StructureKey(uncompiled);
list.add(listener);
} @SuppressWarnings("rawtypes")
} List list = listeners.get(key);
}
if (list != null) {
/** list.add(listener);
* Retrieve the current usage of the Perm Gen space in percentage. }
* @return Usage of the perm gen space. }
*/ }
private double getPermGenUsage() {
for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) { /**
if (item.getName().contains("Perm Gen")) { * Retrieve the current usage of the Perm Gen space in percentage.
MemoryUsage usage = item.getUsage(); * @return Usage of the perm gen space.
return usage.getUsed() / (double) usage.getCommitted(); */
} private double getPermGenUsage() {
} for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) {
if (item.getName().contains("Perm Gen")) {
// Unknown MemoryUsage usage = item.getUsage();
return 0; return usage.getUsed() / (double) usage.getCommitted();
} }
}
/**
* Clean up after ourselves using the default timeout. // Unknown
*/ return 0;
public void shutdownAll() { }
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
} /**
* Clean up after ourselves using the default timeout.
/** */
* Clean up after ourselves. public void shutdownAll() {
* @param timeout - the maximum time to wait. shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
* @param unit - the time unit of the timeout argument. }
*/
public void shutdownAll(long timeout, TimeUnit unit) { /**
setEnabled(false); * Clean up after ourselves.
shuttingDown = true; * @param timeout - the maximum time to wait.
executor.shutdown(); * @param unit - the time unit of the timeout argument.
*/
try { public void shutdownAll(long timeout, TimeUnit unit) {
executor.awaitTermination(timeout, unit); setEnabled(false);
} catch (InterruptedException e) { shuttingDown = true;
// Unlikely to ever occur - it's the main thread executor.shutdown();
e.printStackTrace();
} try {
} executor.awaitTermination(timeout, unit);
} catch (InterruptedException e) {
/** // Unlikely to ever occur - it's the main thread
* Retrieve whether or not the background compiler is enabled. e.printStackTrace();
* @return TRUE if it is enabled, FALSE otherwise. }
*/ }
public boolean isEnabled() {
return enabled; /**
} * Retrieve whether or not the background compiler is enabled.
* @return TRUE if it is enabled, FALSE otherwise.
/** */
* Sets whether or not the background compiler is enabled. public boolean isEnabled() {
* @param enabled - TRUE to enable it, FALSE otherwise. return enabled;
*/ }
public void setEnabled(boolean enabled) {
this.enabled = enabled; /**
} * Sets whether or not the background compiler is enabled.
* @param enabled - TRUE to enable it, FALSE otherwise.
/** */
* Retrieve the fraction of perm gen space used after which the background compiler will be disabled. public void setEnabled(boolean enabled) {
* @return The fraction after which the background compiler is disabled. this.enabled = enabled;
*/ }
public double getDisablePermGenFraction() {
return disablePermGenFraction; /**
} * Retrieve the fraction of perm gen space used after which the background compiler will be disabled.
* @return The fraction after which the background compiler is disabled.
/** */
* Set the fraction of perm gen space used after which the background compiler will be disabled. public double getDisablePermGenFraction() {
* @param fraction - the maximum use of perm gen space. return disablePermGenFraction;
*/ }
public void setDisablePermGenFraction(double fraction) {
this.disablePermGenFraction = fraction; /**
} * Set the fraction of perm gen space used after which the background compiler will be disabled.
* @param fraction - the maximum use of perm gen space.
/** */
* Retrieve the current structure compiler. public void setDisablePermGenFraction(double fraction) {
* @return Current structure compiler. this.disablePermGenFraction = fraction;
*/ }
public StructureCompiler getCompiler() {
return compiler; /**
} * Retrieve the current structure compiler.
} * @return Current structure compiler.
*/
public StructureCompiler getCompiler() {
return compiler;
}
}