Added support for serializing/deserializing WrappedServerPing to JSON.

This commit is contained in:
Kristian S. Stangeland 2014-02-02 16:50:56 +01:00
parent 752807fa5f
commit b59f55e2e5
5 changed files with 146 additions and 3 deletions

View File

@ -12,7 +12,7 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="src/test/resources"/>
<classpathentry including="**/*.java" kind="src" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>

View File

@ -2,4 +2,5 @@ eclipse.preferences.version=1
encoding//src/main/java=cp1252
encoding//src/main/resources=cp1252
encoding//src/test/java=cp1252
encoding//src/test/resources=cp1252
encoding/<project>=cp1252

View File

@ -4,7 +4,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import com.comphenix.protocol.reflect.ExactReflection;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Joiner;
@ -106,6 +105,50 @@ public final class Accessors {
field.setAccessible(true);
return new DefaultFieldAccessor(field);
}
/**
* Retrieve a field accessor that will cache the content of the field.
* <p>
* Note that we don't check if the underlying field has changed after the value has been cached,
* so it's best to use this on final fields.
* @param inner - the accessor.
* @return A cached field accessor.
*/
public static FieldAccessor getCached(final FieldAccessor inner) {
return new FieldAccessor() {
private final Object EMPTY = new Object();
private volatile Object value = EMPTY;
@Override
public void set(Object instance, Object value) {
inner.set(instance, value);
update(value);
}
@Override
public Object get(Object instance) {
Object cache = value;
if (cache != EMPTY)
return cache;
return update(inner.get(instance));
}
/**
* Update the cached value.
* @param value - the value to cache.
* @return The cached value.
*/
private Object update(Object value) {
return this.value = value;
}
@Override
public Field getField() {
return inner.getField();
}
};
}
/**
* Retrieve a field accessor where the write operation is synchronized on the current field value.

View File

@ -78,6 +78,7 @@ import com.google.common.base.Joiner;
public class MinecraftReflection {
public static final ReportType REPORT_CANNOT_FIND_MCPC_REMAPPER = new ReportType("Cannot find MCPC remapper.");
public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC remapper.");
public static final ReportType REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE = new ReportType("Cannot find standard Minecraft library location. Assuming MCPC.");
/**
* Regular expression that matches a Minecraft object.
@ -101,7 +102,12 @@ public class MinecraftReflection {
* The package name of all the classes that belongs to the native code in Minecraft.
*/
private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server";
/**
* The package with all the library classes.
*/
private static String MINECRAFT_LIBRARY_PACKAGE = "net.minecraft.util";
/**
* Represents a regular expression that will match the version string in a package:
* org.bukkit.craftbukkit.v1_6_R2 -> v1_6_R2
@ -114,6 +120,7 @@ public class MinecraftReflection {
// Package private for the purpose of unit testing
static CachedPackage minecraftPackage;
static CachedPackage craftbukkitPackage;
static CachedPackage libraryPackage;
// org.bukkit.craftbukkit
private static Constructor<?> craftNMSConstructor;
@ -199,6 +206,9 @@ public class MinecraftReflection {
// Libigot patch
handleLibigot();
// Minecraft library package
handleLibraryPackage();
// Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package
Class<?> craftEntity = getCraftEntityClass();
Method getHandle = craftEntity.getMethod("getHandle");
@ -244,7 +254,30 @@ public class MinecraftReflection {
throw new IllegalStateException("Could not find Bukkit. Is it running?");
}
}
/**
* Retrieve the Minecraft library package string.
* @return The library package.
*/
private static String getMinecraftLibraryPackage() {
getMinecraftPackage();
return MINECRAFT_LIBRARY_PACKAGE;
}
private static void handleLibraryPackage() {
try {
MINECRAFT_LIBRARY_PACKAGE = "net.minecraft.util";
// Try loading Google GSON
getClassSource().loadClass(CachedPackage.combine(MINECRAFT_LIBRARY_PACKAGE, "com.google.gson.Gson"));
} catch (Exception e) {
// Assume it's MCPC
MINECRAFT_LIBRARY_PACKAGE = "";
ProtocolLibrary.getErrorReporter().reportWarning(MinecraftReflection.class,
Report.newBuilder(REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE));
}
}
/**
* Retrieve the package version of the underlying CraftBukkit server.
* @return The package version, or NULL if not applicable (before 1.4.6).
@ -1552,6 +1585,20 @@ public class MinecraftReflection {
}
}
/**
* Retrieve the google.gson.Gson class used by Minecraft.
* @return The GSON class.
*/
public static Class<?> getMinecraftGsonClass() {
try {
return getMinecraftLibraryClass("com.google.gson.Gson");
} catch (RuntimeException e) {
Class<?> match = FuzzyReflection.fromClass(PacketType.Status.Server.OUT_SERVER_INFO.getPacketClass()).
getFieldByType(".*\\.google\\.gson\\.Gson").getType();
return setMinecraftLibraryClass("com.google.gson.Gson", match);
}
}
/**
* Determine if a given method retrieved by ASM is a constructor.
* @param name - the name of the method.
@ -1766,6 +1813,31 @@ public class MinecraftReflection {
return minecraftPackage.getPackageClass(className);
}
/**
* Retrieve the class object of a specific Minecraft library class.
* @param className - the specific library Minecraft class.
* @return Class object.
* @throws RuntimeException If we are unable to find the given class.
*/
public static Class<?> getMinecraftLibraryClass(String className) {
if (libraryPackage == null)
libraryPackage = new CachedPackage(getMinecraftLibraryPackage(), getClassSource());
return libraryPackage.getPackageClass(className);
}
/**
* Set the class object for the specific library class.
* @param className - name of the Minecraft library class.
* @param clazz - the new class object.
* @return The provided clazz object.
*/
private static Class<?> setMinecraftLibraryClass(String className, Class<?> clazz) {
if (libraryPackage == null)
libraryPackage = new CachedPackage(getMinecraftLibraryPackage(), getClassSource());
libraryPackage.setPackageClass(className, clazz);
return clazz;
}
/**
* Set the class object for the specific Minecraft class.
* @param className - name of the Minecraft class.

View File

@ -20,11 +20,13 @@ import net.minecraft.util.io.netty.buffer.Unpooled;
import net.minecraft.util.io.netty.handler.codec.base64.Base64;
import net.minecraft.util.io.netty.util.IllegalReferenceCountException;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Charsets;
@ -59,6 +61,14 @@ public class WrappedServerPing extends AbstractWrapper {
private static FieldAccessor PLAYERS_MAXIMUM = PLAYERS_INTS[0];
private static FieldAccessor PLAYERS_ONLINE = PLAYERS_INTS[1];
// Server ping serialization
private static Class<?> GSON_CLASS = MinecraftReflection.getMinecraftGsonClass();
private static MethodAccessor GSON_TO_JSON = Accessors.getMethodAccessor(GSON_CLASS, "toJson", Object.class);
private static MethodAccessor GSON_FROM_JSON = Accessors.getMethodAccessor(GSON_CLASS, "fromJson", String.class, Class.class);
private static FieldAccessor PING_GSON = Accessors.getCached(Accessors.getFieldAccessor(
PacketType.Status.Server.OUT_SERVER_INFO.getPacketClass(), GSON_CLASS, true
));
// Server data fields
private static Class<?> VERSION_CLASS = MinecraftReflection.getServerPingServerDataClass();
private static ConstructorAccessor VERSION_CONSTRUCTOR = Accessors.getConstructorAccessor(VERSION_CLASS, String.class, int.class);
@ -117,6 +127,15 @@ public class WrappedServerPing extends AbstractWrapper {
return new WrappedServerPing(handle);
}
/**
* Construct a wrapper server ping from an encoded JSON string.
* @param json - the JSON string.
* @return The wrapped server ping.
*/
public static WrappedServerPing fromJson(String json) {
return fromHandle(GSON_FROM_JSON.invoke(PING_GSON.get(null), json, SERVER_PING));
}
/**
* Retrieve the message of the day.
* @return The messge of the day.
@ -329,6 +348,14 @@ public class WrappedServerPing extends AbstractWrapper {
return copy;
}
/**
* Retrieve the underlying JSON representation of this server ping.
* @return The JSON representation.
*/
public String toJson() {
return (String) GSON_TO_JSON.invoke(PING_GSON.get(null), handle);
}
/**
* Represents a compressed favicon.
* @author Kristian