From 74d83b3ed68d95eb5c362507234d128921207c9e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 13 Dec 2013 20:35:00 +0100 Subject: [PATCH] Add support for the latest build of Spigot. See commit 8b2b731ea5deda5607058849f2ca9ec2e3bf003f in SpigotMC/ Spigot-Server. --- .../accessors/ReadOnlyFieldAccessor.java | 8 ++ .../protocol/utility/ClassSource.java | 68 +++++++++++- .../protocol/utility/MinecraftMethods.java | 2 - .../protocol/wrappers/TroveWrapper.java | 104 ++++++++++++++++-- .../protocol/wrappers/WrappedDataWatcher.java | 57 +++++++--- 5 files changed, 210 insertions(+), 29 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java new file mode 100644 index 00000000..1254d7a3 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java @@ -0,0 +1,8 @@ +package com.comphenix.protocol.reflect.accessors; + +public abstract class ReadOnlyFieldAccessor implements FieldAccessor { + @Override + public final void set(Object instance, Object value) { + throw new UnsupportedOperationException("Cannot update the content of a read-only field accessor."); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java index ad0b0750..f2277c77 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ClassSource.java @@ -6,19 +6,28 @@ import java.util.Map; * Represents an abstract class loader that can only retrieve classes by their canonical name. * @author Kristian */ -abstract class ClassSource { +public abstract class ClassSource { /** - * Construct a class source from the current class loader. - * @return A package source. + * Construct a class source from the default class loader. + * @return A class source. */ public static ClassSource fromClassLoader() { return fromClassLoader(ClassSource.class.getClassLoader()); } + /** + * Construct a class source from the default class loader and package. + * @param packageName - the package that is prepended to every lookup. + * @return A package source. + */ + public static ClassSource fromPackage(String packageName) { + return fromClassLoader().usingPackage(packageName); + } + /** * Construct a class source from the given class loader. * @param loader - the class loader. - * @return The corresponding package source. + * @return The corresponding class source. */ public static ClassSource fromClassLoader(final ClassLoader loader) { return new ClassSource() { @@ -43,6 +52,57 @@ abstract class ClassSource { }; } + /** + * Retrieve a class source that will retry failed lookups in the given source. + * @param other - the other class source. + * @return A new class source. + */ + public ClassSource retry(final ClassSource other) { + return new ClassSource() { + @Override + public Class loadClass(String canonicalName) throws ClassNotFoundException { + try { + return ClassSource.this.loadClass(canonicalName); + } catch (ClassNotFoundException e) { + return other.getClass(); + } + } + }; + } + + /** + * Retrieve a class source that prepends a specific package name to every lookup. + * @param packageName - the package name to prepend. + * @return The class source. + */ + public ClassSource usingPackage(final String packageName) { + return new ClassSource() { + @Override + public Class loadClass(String canonicalName) throws ClassNotFoundException { + return ClassSource.this.loadClass(append(packageName, canonicalName)); + } + }; + } + + /** + * Append to canonical names together. + * @param a - the name to the left. + * @param b - the name to the right. + * @return The full canonical name, with a dot seperator. + */ + protected static String append(String a, String b) { + boolean left = a.endsWith("."); + boolean right = b.endsWith("."); + + // Only add a dot if necessary + if (left && right) + return a.substring(0, a.length() - 1) + b; + else if (left != right) + return a + b; + else + return a + "." + b; + } + /** * Retrieve a class by name. * @param canonicalName - the full canonical name of the class. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java index e57e79c6..c8caaa50 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java @@ -5,8 +5,6 @@ import java.lang.reflect.Method; import java.util.List; import java.util.Map; -import org.bukkit.plugin.Plugin; - import net.minecraft.util.io.netty.buffer.ByteBuf; import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator; import net.minecraft.util.io.netty.channel.ChannelHandlerContext; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java index bef4b369..52284f97 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java @@ -1,5 +1,6 @@ package com.comphenix.protocol.wrappers; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; @@ -9,15 +10,75 @@ import java.util.Set; import javax.annotation.Nonnull; import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor; import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; +import com.comphenix.protocol.utility.ClassSource; /** * Wrap a GNU Trove Collection class with an equivalent Java Collection class. * @author Kristian */ public class TroveWrapper { - private volatile static Class decorators; + private static final String[] TROVE_LOCATIONS = new String[] { + "net.minecraft.util.gnu.trove", // For Minecraft 1.7.2 + "gnu.trove" // For an old version of Spigot + }; + + // The different Trove versions + private static final ClassSource[] TROVE_SOURCES = { + ClassSource.fromPackage(TROVE_LOCATIONS[0]), + ClassSource.fromPackage(TROVE_LOCATIONS[1]) + }; + + /** + * Retrieve a read-only field accessor that automatically wraps the underlying Trove instance. + * @param accessor - the accessor. + * @return The read only accessor. + */ + public static ReadOnlyFieldAccessor wrapMapField(final FieldAccessor accessor) { + return new ReadOnlyFieldAccessor() { + public Object get(Object instance) { + return getDecoratedMap(accessor.get(instance)); + } + public Field getField() { + return accessor.getField(); + } + }; + } + + /** + * Retrieve a read-only field accessor that automatically wraps the underlying Trove instance. + * @param accessor - the accessor. + * @return The read only accessor. + */ + public static ReadOnlyFieldAccessor wrapSetField(final FieldAccessor accessor) { + return new ReadOnlyFieldAccessor() { + public Object get(Object instance) { + return getDecoratedSet(accessor.get(instance)); + } + public Field getField() { + return accessor.getField(); + } + }; + } + + /** + * Retrieve a read-only field accessor that automatically wraps the underlying Trove instance. + * @param accessor - the accessor. + * @return The read only accessor. + */ + public static ReadOnlyFieldAccessor wrapListField(final FieldAccessor accessor) { + return new ReadOnlyFieldAccessor() { + public Object get(Object instance) { + return getDecoratedList(accessor.get(instance)); + } + public Field getField() { + return accessor.getField(); + } + }; + } /** * Retrieve a Java wrapper for the corresponding Trove map. @@ -61,19 +122,46 @@ public class TroveWrapper { return result; } + /** + * Determine if the given class is found within gnu.trove. + * @param clazz - the clazz. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isTroveClass(Class clazz) { + return getClassSource(clazz) != null; + } + + /** + * Retrieve the correct class source from the given class. + * @param clazz - the class source. + * @return The class source, or NULL if not found. + */ + private static ClassSource getClassSource(Class clazz) { + for (int i = 0; i < TROVE_LOCATIONS.length; i++) { + if (clazz.getCanonicalName().startsWith(TROVE_LOCATIONS[i])) { + return TROVE_SOURCES[i]; + } + } + return null; + } + + /** + * Retrieve the corresponding decorator. + * @param trove - the trove class. + * @return The wrapped trove class. + */ private static Object getDecorated(@Nonnull Object trove) { if (trove == null) throw new IllegalArgumentException("trove instance cannot be non-null."); AbstractFuzzyMatcher> match = FuzzyMatchers.matchSuper(trove.getClass()); + Class decorators = null; - if (decorators == null) { - try { - // Attempt to get decorator class - decorators = TroveWrapper.class.getClassLoader().loadClass("gnu.trove.TDecorators"); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Cannot find TDecorators in Gnu Trove.", e); - } + try { + // Attempt to get decorator class + decorators = getClassSource(trove.getClass()).loadClass("TDecorators"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); } // Find an appropriate wrapper method in TDecorators diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index c8a71274..4d403a57 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -41,6 +41,9 @@ import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.collection.ConvertedMap; import com.google.common.base.Function; @@ -58,8 +61,11 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable, Integer> TYPE_MAP; + // Accessors + private static FieldAccessor TYPE_MAP_ACCESSOR; + private static FieldAccessor VALUE_MAP_ACCESSOR; + // Fields - private static Field VALUE_MAP_FIELD; private static Field READ_WRITE_LOCK_FIELD; private static Field ENTITY_FIELD; @@ -509,13 +515,8 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable getWatchableObjectMap() throws FieldAccessException { - if (watchableObjects == null) { - try { - watchableObjects = (Map) FieldUtils.readField(VALUE_MAP_FIELD, handle, true); - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot read watchable object field.", e); - } - } + if (watchableObjects == null) + watchableObjects = (Map) VALUE_MAP_ACCESSOR.get(handle); return watchableObjects; } @@ -561,17 +562,17 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable, Integer>) FieldUtils.readStaticField(lookup, true); - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot access type map field.", e); - } - + TYPE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true); } else { // If not, then we're probably dealing with the value map - VALUE_MAP_FIELD = lookup; + VALUE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true); } } + // Spigot workaround + initializeSpigot(fuzzy); + + // Initialize static type type + TYPE_MAP = (Map, Integer>) TYPE_MAP_ACCESSOR.get(null); try { READ_WRITE_LOCK_FIELD = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class); @@ -587,6 +588,32 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable type = lookup.getType(); + + if (TroveWrapper.isTroveClass(type)) { + // Create a wrapper accessor + final ReadOnlyFieldAccessor accessor = TroveWrapper.wrapMapField(Accessors.getFieldAccessor(lookup, true)); + + if (Modifier.isStatic(lookup.getModifiers())) { + TYPE_MAP_ACCESSOR = accessor; + } else { + VALUE_MAP_ACCESSOR = accessor; + } + } + } + + if (TYPE_MAP_ACCESSOR == null) + throw new IllegalArgumentException("Unable to find static type map."); + if (VALUE_MAP_ACCESSOR == null) + throw new IllegalArgumentException("Unable to find static value map."); + } + private static void initializeMethods(FuzzyReflection fuzzy) { List candidates = fuzzy.getMethodListByParameters(Void.TYPE, new Class[] { int.class, Object.class});