Allow packet constructor to properly unwrap other Bukkit wrappers.

This commit is contained in:
Kristian S. Stangeland 2013-11-19 00:50:55 +01:00
parent ba6c6e5abf
commit 86f04f53b5
6 changed files with 199 additions and 28 deletions

View File

@ -77,7 +77,11 @@ public class BukkitUnwrapper implements Unwrapper {
// Special case
if (wrappedObject == null)
return null;
Class<?> currentClass = wrappedObject.getClass();
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
// No need to unwrap primitives
if (currentClass.isPrimitive() || currentClass.equals(String.class))
return null;
// Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) {
@ -119,7 +123,7 @@ public class BukkitUnwrapper implements Unwrapper {
* @param type - the type of the class.
* @return An unwrapper for the given class.
*/
private Unwrapper getSpecificUnwrapper(Class<?> type) {
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
// 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
@ -133,8 +137,9 @@ public class BukkitUnwrapper implements Unwrapper {
Unwrapper methodUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
return find.invoke(wrappedObject);
} catch (IllegalArgumentException e) {
@ -180,7 +185,7 @@ public class BukkitUnwrapper implements Unwrapper {
* @param type - a cached field unwrapper.
* @return The cached field unwrapper.
*/
private Unwrapper getFieldUnwrapper(Class<?> type) {
private Unwrapper getFieldUnwrapper(final Class<?> type) {
final Field find = FieldUtils.getField(type, "handle", true);
// See if we succeeded
@ -189,6 +194,8 @@ public class BukkitUnwrapper implements Unwrapper {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
return checkClass((Class<?>) wrappedObject, type, find.getType());
return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) {
reporter.reportDetailed(this,
@ -210,4 +217,11 @@ public class BukkitUnwrapper implements Unwrapper {
return null;
}
}
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
if (expected.isAssignableFrom(input)) {
return result;
}
return null;
}
}

View File

@ -25,6 +25,7 @@ import com.comphenix.protocol.error.RethrowErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
@ -53,18 +54,19 @@ public class PacketConstructor {
private List<Unwrapper> unwrappers;
// Parameters that need to be unwrapped
private boolean[] unwrappable;
private Unwrapper[] paramUnwrapper;
private PacketConstructor(Constructor<?> constructorMethod) {
this.constructorMethod = constructorMethod;
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter() ));
this.unwrappers.addAll(BukkitConverters.getUnwrappers());
}
private PacketConstructor(int packetID, Constructor<?> constructorMethod, List<Unwrapper> unwrappers, boolean[] unwrappable) {
private PacketConstructor(int packetID, Constructor<?> constructorMethod, List<Unwrapper> unwrappers, Unwrapper[] paramUnwrapper) {
this.packetID = packetID;
this.constructorMethod = constructorMethod;
this.unwrappers = unwrappers;
this.unwrappable = unwrappable;
this.paramUnwrapper = paramUnwrapper;
}
public ImmutableList<Unwrapper> getUnwrappers() {
@ -85,7 +87,7 @@ public class PacketConstructor {
* @return A constructor with a different set of unwrappers.
*/
public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) {
return new PacketConstructor(packetID, constructorMethod, unwrappers, unwrappable);
return new PacketConstructor(packetID, constructorMethod, unwrappers, paramUnwrapper);
}
/**
@ -100,12 +102,12 @@ public class PacketConstructor {
public PacketConstructor withPacket(int id, Object[] values) {
Class<?>[] types = new Class<?>[values.length];
Throwable lastException = null;
boolean[] unwrappable = new boolean[values.length];
Unwrapper[] paramUnwrapper = new Unwrapper[values.length];
for (int i = 0; i < types.length; i++) {
// Default type
if (values[i] != null) {
types[i] = (values[i] instanceof Class) ? (Class<?>)values[i] : values[i].getClass();
types[i] = PacketConstructor.getClass(values[i]);
for (Unwrapper unwrapper : unwrappers) {
Object result = null;
@ -118,8 +120,8 @@ public class PacketConstructor {
// Update type we're searching for
if (result != null) {
types[i] = result.getClass();
unwrappable[i] = true;
types[i] = PacketConstructor.getClass(result);
paramUnwrapper[i] = unwrapper;
break;
}
}
@ -141,7 +143,7 @@ public class PacketConstructor {
if (isCompatible(types, params)) {
// Right, we've found our type
return new PacketConstructor(id, constructor, unwrappers, unwrappable);
return new PacketConstructor(id, constructor, unwrappers, paramUnwrapper);
}
}
@ -160,15 +162,8 @@ public class PacketConstructor {
try {
// Convert types that needs to be converted
for (int i = 0; i < values.length; i++) {
if (unwrappable[i]) {
for (Unwrapper unwrapper : unwrappers) {
Object converted = unwrapper.unwrapItem(values[i]);
if (converted != null) {
values[i] = converted;
break;
}
}
if (paramUnwrapper[i] != null) {
values[i] = paramUnwrapper[i].unwrapItem(values[i]);
}
}
@ -196,7 +191,7 @@ public class PacketConstructor {
Class<?> paramType = params[i];
// The input type is always wrapped
if (paramType.isPrimitive()) {
if (!inputType.isPrimitive() && paramType.isPrimitive()) {
// Wrap it
paramType = Primitives.wrap(paramType);
}
@ -213,6 +208,17 @@ public class PacketConstructor {
// Parameter count must match
return false;
}
/**
* Retrieve the class of an object, or just the class if it already is a class object.
* @param obj - the object.
* @return The class of an object.
*/
public static Class<?> getClass(Object obj) {
if (obj instanceof Class)
return (Class<?>) obj;
return obj.getClass();
}
/**
* Represents a unwrapper for a constructor parameter.
@ -222,8 +228,11 @@ public class PacketConstructor {
public static interface Unwrapper {
/**
* Convert the given wrapped object to the equivalent net.minecraft.server object.
* @param wrappedObject - wrapped object.
* @return The net.minecraft.server object.
* <p>
* Note that we may pass in a class instead of object - in that case, the unwrapper should
* return the equivalent NMS class.
* @param wrappedObject - wrapped object or class.
* @return The equivalent net.minecraft.server object or class.
*/
public Object unwrapItem(Object wrappedObject);
}

View File

@ -26,6 +26,8 @@ package com.comphenix.protocol.reflect;
public interface EquivalentConverter<TType> {
/**
* Retrieve a copy of the specific type using an instance of the generic type.
* <p>
* This is usually a wrapper type in the Bukkit API.
* @param generic - the generic type.
* @return The new specific type.
*/
@ -33,6 +35,8 @@ public interface EquivalentConverter<TType> {
/**
* Retrieve a copy of the generic type from a specific type.
* <p>
* This is usually a native net.minecraft.server type in Minecraft.
* @param genericType - class or super class of the generic type.
* @param specific - the specific type we need to copy.
* @return A copy of the specific type.

View File

@ -43,6 +43,7 @@ import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
@ -1120,6 +1121,27 @@ public class MinecraftReflection {
}
}
/**
* Retrieve the net.minecraft.server.MobEffect class.
* @return The mob effect class.
*/
public static Class<?> getMobEffectClass() {
try {
return getMinecraftClass("MobEffect");
} catch (RuntimeException e) {
// It is the second parameter in Packet41MobEffect
Class<?> packet = PacketRegistry.getPacketClassFromID(Packets.Server.MOB_EFFECT);
Constructor<?> constructor = FuzzyReflection.fromClass(packet).getConstructor(
FuzzyMethodContract.newBuilder().
parameterCount(2).
parameterExactType(int.class, 0).
parameterMatches(getMinecraftObjectMatcher(), 1).
build()
);
return setMinecraftClass("MobEffect", constructor.getParameterTypes()[1]);
}
}
/**
* Determine if a given method retrieved by ASM is a constructor.
* @param name - the name of the method.

View File

@ -18,6 +18,7 @@
package com.comphenix.protocol.wrappers;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
@ -28,18 +29,24 @@ import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
@ -55,11 +62,16 @@ public class BukkitConverters {
// The static maps
private static Map<Class<?>, EquivalentConverter<Object>> specificConverters;
private static Map<Class<?>, EquivalentConverter<Object>> genericConverters;
private static List<Unwrapper> unwrappers;
// Used to access the world type
private static Method worldTypeName;
private static Method worldTypeGetType;
// Used for potion effect conversion
private static volatile Constructor<?> mobEffectConstructor;
private static volatile StructureModifier<Object> mobEffectModifier;
static {
try {
MinecraftReflection.getWorldTypeClass();
@ -449,6 +461,57 @@ public class BukkitConverters {
}
/**
* Retrieve the converter used to convert between a PotionEffect and the equivalent NMS Mobeffect.
* @return The potion effect converter.
*/
public static EquivalentConverter<PotionEffect> getPotionEffectConverter() {
return new IgnoreNullConverter<PotionEffect>() {
@Override
protected Object getGenericValue(Class<?> genericType, PotionEffect specific) {
// Locate the constructor
if (mobEffectConstructor == null) {
try {
mobEffectConstructor = MinecraftReflection.getMobEffectClass().
getConstructor(int.class, int.class, int.class, boolean.class);
} catch (Exception e) {
throw new RuntimeException("Cannot find mob effect constructor (int, int, int, boolean).", e);
}
}
// Create the generic value
try {
return mobEffectConstructor.newInstance(
specific.getType().getId(), specific.getDuration(),
specific.getAmplifier(), specific.isAmbient());
} catch (Exception e) {
throw new RuntimeException("Cannot construct MobEffect.", e);
}
}
@Override
protected PotionEffect getSpecificValue(Object generic) {
if (mobEffectModifier == null) {
mobEffectModifier = new StructureModifier<Object>(MinecraftReflection.getMobEffectClass(), false);
}
StructureModifier<Integer> ints = mobEffectModifier.withTarget(generic).withType(int.class);
StructureModifier<Boolean> bools = mobEffectModifier.withTarget(generic).withType(boolean.class);
return new PotionEffect(
PotionEffectType.getById(ints.read(0)), /* effectId */
ints.read(1), /* duration */
ints.read(2), /* amplification */
bools.read(1) /* ambient */
);
}
@Override
public Class<PotionEffect> getSpecificType() {
return PotionEffect.class;
}
};
}
/**
* Wraps a given equivalent converter in NULL checks, ensuring that such values are ignored.
* @param delegate - the underlying equivalent converter.
* @return A equivalent converter that ignores NULL values.
@ -473,6 +536,31 @@ public class BukkitConverters {
};
}
/**
* Retrieve an equivalent unwrapper for the converter.
* @param nativeType - the native NMS type the converter produces.
* @param converter - the converter.
* @return The equivalent unwrapper.
*/
public static Unwrapper asUnwrapper(final Class<?> nativeType, final EquivalentConverter<Object> converter) {
return new Unwrapper() {
@SuppressWarnings("rawtypes")
@Override
public Object unwrapItem(Object wrappedObject) {
Class<?> type = PacketConstructor.getClass(wrappedObject);
// Ensure the type is correct before we test
if (converter.getSpecificType().isAssignableFrom(type)) {
if (wrappedObject instanceof Class)
return nativeType;
else
return converter.getGeneric((Class) nativeType, wrappedObject);
}
return null;
}
};
}
/**
* Retrieve every converter that is associated with a specific class.
* @return Every converter with a unique specific class.
@ -487,7 +575,8 @@ public class BukkitConverters {
put(ItemStack.class, (EquivalentConverter) getItemStackConverter()).
put(NbtBase.class, (EquivalentConverter) getNbtConverter()).
put(NbtCompound.class, (EquivalentConverter) getNbtConverter()).
put(WrappedWatchableObject.class, (EquivalentConverter) getWatchableObjectConverter());
put(WrappedWatchableObject.class, (EquivalentConverter) getWatchableObjectConverter()).
put(PotionEffect.class, (EquivalentConverter) getPotionEffectConverter());
if (hasWorldType)
builder.put(WorldType.class, (EquivalentConverter) getWorldTypeConverter());
@ -512,7 +601,8 @@ public class BukkitConverters {
put(MinecraftReflection.getItemStackClass(), (EquivalentConverter) getItemStackConverter()).
put(MinecraftReflection.getNBTBaseClass(), (EquivalentConverter) getNbtConverter()).
put(MinecraftReflection.getNBTCompoundClass(), (EquivalentConverter) getNbtConverter()).
put(MinecraftReflection.getWatchableObjectClass(), (EquivalentConverter) getWatchableObjectConverter());
put(MinecraftReflection.getWatchableObjectClass(), (EquivalentConverter) getWatchableObjectConverter()).
put(MinecraftReflection.getMobEffectClass(), (EquivalentConverter) getPotionEffectConverter());
if (hasWorldType)
builder.put(MinecraftReflection.getWorldTypeClass(), (EquivalentConverter) getWorldTypeConverter());
@ -522,4 +612,20 @@ public class BukkitConverters {
}
return genericConverters;
}
/**
* Retrieve every NMS <-> Bukkit converter as unwrappers.
* @return Every unwrapper.
*/
public static List<Unwrapper> getUnwrappers() {
if (unwrappers == null) {
ImmutableList.Builder<Unwrapper> builder = ImmutableList.builder();
for (Map.Entry<Class<?>, EquivalentConverter<Object>> entry : getConvertersForGeneric().entrySet()) {
builder.add(asUnwrapper(entry.getKey(), entry.getValue()));
}
unwrappers = builder.build();
}
return unwrappers;
}
}

View File

@ -35,6 +35,8 @@ import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory;
import org.bukkit.Material;
import org.bukkit.WorldType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -42,6 +44,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
@ -345,7 +348,20 @@ public class PacketContainerTest {
ToStringBuilder.reflectionToString(clonedSnapshot, ToStringStyle.SHORT_PREFIX_STYLE));
}
@Test
public void testPotionEffect() {
PotionEffect effect = new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 20 * 60, 1);
// The constructor we want to call
PacketConstructor creator = PacketConstructor.DEFAULT.withPacket(
Packets.Server.MOB_EFFECT, new Class<?>[] { int.class, PotionEffect.class });
PacketContainer packet = creator.createPacket(1, effect);
assertEquals(1, (int) packet.getIntegers().read(0));
assertEquals(effect.getType().getId(), (byte) packet.getBytes().read(0));
assertEquals(effect.getAmplifier(), (byte) packet.getBytes().read(1));
assertEquals(effect.getDuration(), (short) packet.getShorts().read(0));
}
@Test
public void testDeepClone() {