Add support for the new JSON chat message format. FIXES Ticket-109

In addition, the PacketConstructor can now handle non-wrapped objects,
along with Class types instead of instances in withPacket().
This commit is contained in:
Kristian S. Stangeland 2013-07-19 23:43:39 +02:00
parent 0b56df20d5
commit 6054d559e1
6 changed files with 487 additions and 349 deletions

View File

@ -0,0 +1,43 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.google.common.base.Joiner;
/**
* Represents an error reporter that rethrows every exception instead.
* @author Kristian
*/
public class RethrowErrorReporter implements ErrorReporter {
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
throw new RuntimeException("Minimal error by " + sender + " in " + methodName, error);
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
throw new RuntimeException(
"Minimal error by " + sender + " in " + methodName + " with " + Joiner.on(",").join(parameters), error);
}
@Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder.build());
}
@Override
public void reportWarning(Object sender, Report report) {
throw new RuntimeException("Warning by " + sender + ": " + report);
}
@Override
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportDetailed(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, Report report) {
throw new RuntimeException("Detailed error " + sender + ": " + report, report.getException());
}
}

View File

@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
import com.comphenix.protocol.error.RethrowErrorReporter;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
@ -51,15 +52,19 @@ public class PacketConstructor {
// Used to unwrap Bukkit objects // Used to unwrap Bukkit objects
private List<Unwrapper> unwrappers; private List<Unwrapper> unwrappers;
// Parameters that need to be unwrapped
private boolean[] unwrappable;
private PacketConstructor(Constructor<?> constructorMethod) { private PacketConstructor(Constructor<?> constructorMethod) {
this.constructorMethod = constructorMethod; this.constructorMethod = constructorMethod;
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper()); this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter() ));
} }
private PacketConstructor(int packetID, Constructor<?> constructorMethod, List<Unwrapper> unwrappers) { private PacketConstructor(int packetID, Constructor<?> constructorMethod, List<Unwrapper> unwrappers, boolean[] unwrappable) {
this.packetID = packetID; this.packetID = packetID;
this.constructorMethod = constructorMethod; this.constructorMethod = constructorMethod;
this.unwrappers = unwrappers; this.unwrappers = unwrappers;
this.unwrappable = unwrappable;
} }
public ImmutableList<Unwrapper> getUnwrappers() { public ImmutableList<Unwrapper> getUnwrappers() {
@ -80,31 +85,41 @@ public class PacketConstructor {
* @return A constructor with a different set of unwrappers. * @return A constructor with a different set of unwrappers.
*/ */
public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) { public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) {
return new PacketConstructor(packetID, constructorMethod, unwrappers); return new PacketConstructor(packetID, constructorMethod, unwrappers, unwrappable);
} }
/** /**
* Create a packet constructor that creates packets using the given types. * Create a packet constructor that creates packets using the given types.
* <p>
* Note that if you pass a Class<?> as a value, it will use its type directly.
* @param id - packet ID. * @param id - packet ID.
* @param values - types to create. * @param values - the values that will match each parameter in the desired constructor.
* @return A packet constructor with these types. * @return A packet constructor with these types.
* @throws IllegalArgumentException If no packet constructor could be created with these types. * @throws IllegalArgumentException If no packet constructor could be created with these types.
*/ */
public PacketConstructor withPacket(int id, Object[] values) { public PacketConstructor withPacket(int id, Object[] values) {
Class<?>[] types = new Class<?>[values.length]; Class<?>[] types = new Class<?>[values.length];
Throwable lastException = null;
boolean[] unwrappable = new boolean[values.length];
for (int i = 0; i < types.length; i++) { for (int i = 0; i < types.length; i++) {
// Default type // Default type
if (values[i] != null) { if (values[i] != null) {
types[i] = values[i].getClass(); types[i] = (values[i] instanceof Class) ? (Class<?>)values[i] : values[i].getClass();
for (Unwrapper unwrapper : unwrappers) { for (Unwrapper unwrapper : unwrappers) {
Object result = unwrapper.unwrapItem(values[i]); Object result = null;
try {
result = unwrapper.unwrapItem(values[i]);
} catch (Throwable e) {
lastException = e;
}
// Update type we're searching for // Update type we're searching for
if (result != null) { if (result != null) {
types[i] = result.getClass(); types[i] = result.getClass();
unwrappable[i] = true;
break; break;
} }
} }
@ -126,11 +141,11 @@ public class PacketConstructor {
if (isCompatible(types, params)) { if (isCompatible(types, params)) {
// Right, we've found our type // Right, we've found our type
return new PacketConstructor(id, constructor, unwrappers); return new PacketConstructor(id, constructor, unwrappers, unwrappable);
} }
} }
throw new IllegalArgumentException("No suitable constructor could be found."); throw new IllegalArgumentException("No suitable constructor could be found.", lastException);
} }
/** /**
@ -143,14 +158,16 @@ public class PacketConstructor {
*/ */
public PacketContainer createPacket(Object... values) throws FieldAccessException { public PacketContainer createPacket(Object... values) throws FieldAccessException {
try { try {
// Convert types // Convert types that needs to be converted
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
for (Unwrapper unwrapper : unwrappers) { if (unwrappable[i]) {
Object converted = unwrapper.unwrapItem(values[i]); for (Unwrapper unwrapper : unwrappers) {
Object converted = unwrapper.unwrapItem(values[i]);
if (converted != null) { if (converted != null) {
values[i] = converted; values[i] = converted;
break; break;
}
} }
} }
} }

View File

@ -17,7 +17,11 @@
package com.comphenix.protocol.utility; package com.comphenix.protocol.utility;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -26,8 +30,13 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
/** /**
* Utility methods for sending chat messages. * Utility methods for sending chat messages.
@ -35,11 +44,14 @@ import com.google.common.base.Strings;
* @author Kristian * @author Kristian
*/ */
public class ChatExtensions { public class ChatExtensions {
// Used to sent chat messages // Used to sent chat messages
private PacketConstructor chatConstructor; private PacketConstructor chatConstructor;
private ProtocolManager manager; private ProtocolManager manager;
// Whether or not we have to use the post-1.6.1 chat format
private static Constructor<?> jsonConstructor = getJsonFormatConstructor();
private static Method messageFactory;
public ChatExtensions(ProtocolManager manager) { public ChatExtensions(ProtocolManager manager) {
this.manager = manager; this.manager = manager;
} }
@ -68,10 +80,59 @@ public class ChatExtensions {
* Send a message without invoking the packet listeners. * Send a message without invoking the packet listeners.
* @param player - the player to send it to. * @param player - the player to send it to.
* @param message - the message to send. * @param message - the message to send.
* @return TRUE if the message was sent successfully, FALSE otherwise.
* @throws InvocationTargetException If we were unable to send the message. * @throws InvocationTargetException If we were unable to send the message.
*/ */
private void sendMessageSilently(Player player, String message) throws InvocationTargetException { private void sendMessageSilently(Player player, String message) throws InvocationTargetException {
if (jsonConstructor != null) {
sendMessageAsJson(player, message);
} else {
sendMessageAsString(player, message);
}
}
/**
* Send a message using the new JSON format in 1.6.1.
* @param player - the player to send it to.
* @param message - the message to send.
* @throws InvocationTargetException InvocationTargetException If we were unable to send the message.
*/
private void sendMessageAsJson(Player player, String message) throws InvocationTargetException {
Object messageObject = null;
if (chatConstructor == null) {
Class<?> messageClass = jsonConstructor.getParameterTypes()[0];
chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, messageClass);
// Try one of the string constructors
messageFactory = FuzzyReflection.fromClass(messageClass).getMethod(
FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC).
parameterCount(1).
parameterExactType(String.class).
returnTypeMatches(FuzzyMatchers.matchParent()).
build());
}
try {
messageObject = messageFactory.invoke(null, message);
} catch (Exception e) {
throw new InvocationTargetException(e);
}
try {
manager.sendServerPacket(player, chatConstructor.createPacket(messageObject), false);
} catch (FieldAccessException e) {
throw new InvocationTargetException(e);
}
}
/**
* Send a message as a pure string.
* @param player - the player.
* @param message - the message to send.
* @throws InvocationTargetException If anything went wrong.
*/
private void sendMessageAsString(Player player, String message) throws InvocationTargetException {
if (chatConstructor == null) if (chatConstructor == null)
chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, message); chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, message);
@ -143,4 +204,21 @@ public class ChatExtensions {
} }
return current; return current;
} }
/**
* Retrieve a constructor for post-1.6.1 chat packets.
* @return A constructor for JSON-based packets.
*/
static Constructor<?> getJsonFormatConstructor() {
Class<?> chatPacket = PacketRegistry.getPacketClassFromID(3, true);
List<Constructor<?>> list = FuzzyReflection.fromClass(chatPacket).getConstructorList(
FuzzyMethodContract.newBuilder().
parameterCount(1).
parameterMatches(MinecraftReflection.getMinecraftObjectMatcher()).
build()
);
// First element or NULL
return Iterables.getFirst(list, null);
}
} }