[Breaking] Update data watchers for 1.9

Basically I had to rewrite the classes since Mojang decided to muck the
whole thing up. If there are any methods necessary, I'll try to readd
them.
This commit is contained in:
Dan Mulloy 2016-03-02 18:08:01 -05:00
parent 4987cd188a
commit d359f7455f
6 changed files with 246 additions and 1273 deletions

View File

@ -63,7 +63,6 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException; import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException;
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException.Reason; import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavaibleException.Reason;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.comphenix.protocol.wrappers.nbt.NbtType; import com.comphenix.protocol.wrappers.nbt.NbtType;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
@ -1247,11 +1246,8 @@ public class MinecraftReflection {
* @return The ChunkPosition class. * @return The ChunkPosition class.
*/ */
public static Class<?> getChunkCoordinatesClass() { public static Class<?> getChunkCoordinatesClass() {
try { // TODO Figure out a fallback
return getMinecraftClass("ChunkCoordinates"); return getMinecraftClass("ChunkCoordinates");
} catch (RuntimeException e) {
return setMinecraftClass("ChunkCoordinates", WrappedDataWatcher.getTypeClass(6));
}
} }
/** /**
@ -1288,6 +1284,10 @@ public class MinecraftReflection {
* @return The WatchableObject class. * @return The WatchableObject class.
*/ */
public static Class<?> getWatchableObjectClass() { public static Class<?> getWatchableObjectClass() {
if (dataWatcherItemExists()) {
return getDataWatcherItemClass();
}
try { try {
return getMinecraftClass("WatchableObject", "DataWatcher$WatchableObject"); return getMinecraftClass("WatchableObject", "DataWatcher$WatchableObject");
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -1303,6 +1303,30 @@ public class MinecraftReflection {
} }
} }
public static Class<?> getDataWatcherItemClass() {
// TODO Implement a fallback
return getMinecraftClass("DataWatcher$Item");
}
public static Class<?> getDataWatcherObjectClass() {
// TODO Implement a fallback
return getMinecraftClass("DataWatcherObject");
}
public static Class<?> getDataWatcherSerializerClass() {
// TODO Implement a fallback
return getMinecraftClass("DataWatcherSerializer");
}
public static boolean dataWatcherItemExists() {
try {
getDataWatcherItemClass();
return true;
} catch (RuntimeException e) {
return false;
}
}
/** /**
* Retrieve the ServerConnection abstract class. * Retrieve the ServerConnection abstract class.
* @return The ServerConnection class. * @return The ServerConnection class.

View File

@ -16,345 +16,92 @@
*/ */
package com.comphenix.protocol.wrappers; package com.comphenix.protocol.wrappers;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nullable;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; 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.reflect.accessors.ReadOnlyFieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.collection.ConvertedMap;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
/**
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
*
* @author Kristian
*/
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> { public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> {
/** private static ConstructorAccessor dataWatcherObjectConstructor = null;
* Every custom watchable type in the Spigot protocol hack. private static MethodAccessor getter = null;
* @author Kristian private static MethodAccessor setter = null;
*/
public enum CustomType {
BYTE_SHORT("org.spigotmc.ProtocolData$ByteShort", 0, short.class),
DUAL_BYTE("org.spigotmc.ProtocolData$DualByte", 0, byte.class, byte.class),
HIDDEN_BYTE("org.spigotmc.ProtocolData$HiddenByte", 0, byte.class),
INT_BYTE("org.spigotmc.ProtocolData$IntByte", 2, int.class, byte.class),
DUAL_INT("org.spigotmc.ProtocolData$DualInt", 2, int.class, int.class);
private Class<?> spigotClass; @Deprecated
private ConstructorAccessor constructor;
private FieldAccessor secondaryValue;
private int typeId;
private CustomType(String className, int typeId, Class<?>... parameters) {
try {
this.spigotClass = Class.forName(className);
this.constructor = Accessors.getConstructorAccessor(spigotClass, parameters);
this.secondaryValue = parameters.length > 1 ? Accessors.getFieldAccessor(spigotClass, "value2", true) : null;
} catch (ClassNotFoundException e) {
// ProtocolLibrary.log(Level.WARNING, "Unable to find " + className);
this.spigotClass = null;
}
this.typeId = typeId;
}
/**
* Construct a new instance of this Spigot type.
* @param value - the value. Cannot be NULL.
* @return The instance to construct.
*/
Object newInstance(Object value) {
return newInstance(value, null);
}
/**
* Construct a new instance of this Spigot type.
* <p>
* The secondary value may be NULL if this custom type does not contain a secondary value.
* @param value - the value.
* @param secondary - optional secondary value.
* @return
*/
Object newInstance(Object value, Object secondary) {
Preconditions.checkNotNull(value, "value cannot be NULL.");
if (hasSecondary()) {
return constructor.invoke(value, secondary);
} else {
if (secondary != null) {
throw new IllegalArgumentException("Cannot construct " + this + " with a secondary value");
}
return constructor.invoke(value);
}
}
/**
* Set the secondary value of a given type.
* @param instance - the instance.
* @param secondary - the secondary value.
*/
void setSecondary(Object instance, Object secondary) {
if (!hasSecondary()) {
throw new IllegalArgumentException(this + " does not have a secondary value.");
}
secondaryValue.set(instance, secondary);
}
/**
* Get the secondary value of a type.
* @param instance - the instance.
* @return The secondary value.
*/
Object getSecondary(Object instance) {
if (!hasSecondary()) {
throw new IllegalArgumentException(this + " does not have a secondary value.");
}
return secondaryValue.get(instance);
}
/**
* Determine if this type has a secondary value.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasSecondary() {
return secondaryValue != null;
}
/**
* Underlying Spigot class.
* @return The class.
*/
public Class<?> getSpigotClass() {
return spigotClass;
}
/**
* The equivalent type ID.
* @return The equivalent ID.
*/
public int getTypeId() {
return typeId;
}
/**
* Retrieve the custom Spigot type of a value.
* @param value - the value.
* @return The Spigot type, or NULL if not found.
*/
public static CustomType fromValue(Object value) {
for (CustomType type : CustomType.values()) {
if (type.getSpigotClass().isInstance(value)) {
return type;
}
}
return null;
}
}
/**
* Used to assign integer IDs to given types.
*/
private static Map<Class<?>, Integer> TYPE_MAP;
/**
* Custom types in the bountiful update.
*/
private static Map<Class<?>, Integer> CUSTOM_MAP;
// Accessors
private static FieldAccessor TYPE_MAP_ACCESSOR;
private static FieldAccessor VALUE_MAP_ACCESSOR;
// Fields
private static Field READ_WRITE_LOCK_FIELD;
private static Field ENTITY_FIELD;
// Methods
private static Method CREATE_KEY_VALUE_METHOD;
private static Method UPDATE_KEY_VALUE_METHOD;
private static Method GET_KEY_VALUE_METHOD;
// Constructors
private static Constructor<?> CREATE_DATA_WATCHER_CONSTRUCTOR;
// Entity methods
private volatile static Field ENTITY_DATA_FIELD;
/**
* Whether or not this class has already been initialized.
*/
private static boolean HAS_INITIALIZED;
// Lock
private ReadWriteLock readWriteLock;
// Map of watchable objects
private Map<Integer, Object> watchableObjects;
// A map view of all the watchable objects
private Map<Integer, WrappedWatchableObject> mapView;
/**
* Initialize a new data watcher.
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
*/
public WrappedDataWatcher() { public WrappedDataWatcher() {
super(MinecraftReflection.getDataWatcherClass()); super(MinecraftReflection.getDataWatcherClass());
// Just create a new watcher
try {
if (MinecraftReflection.isUsingNetty()) {
setHandle(newEntityHandle(null));
} else {
setHandle(getHandleType().newInstance());
}
initialize();
} catch (Exception e) {
throw new RuntimeException("Unable to construct DataWatcher.", e);
}
} }
/**
* Create a wrapper for a given data watcher.
* @param handle - the data watcher to wrap.
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
*/
public WrappedDataWatcher(Object handle) { public WrappedDataWatcher(Object handle) {
super(MinecraftReflection.getDataWatcherClass());
setHandle(handle);
initialize();
}
/**
* Construct a new data watcher with the given entity.
* <p>
* In 1.6.4 and ealier, this will fall back to using {@link #WrappedDataWatcher()}.
* @param entity - the entity.
* @return The wrapped data watcher.
*/
public static WrappedDataWatcher newWithEntity(Entity entity) {
// Use the old constructor
if (!MinecraftReflection.isUsingNetty())
return new WrappedDataWatcher();
return new WrappedDataWatcher(newEntityHandle(entity));
}
/**
* Construct a new native DataWatcher with the given entity.
* <p>
* Warning: This is only supported in 1.7.2 and above.
* @param entity - the entity, or NULL.
* @return The data watcher.
*/
private static Object newEntityHandle(Entity entity) {
Class<?> dataWatcher = MinecraftReflection.getDataWatcherClass();
try {
if (CREATE_DATA_WATCHER_CONSTRUCTOR == null)
CREATE_DATA_WATCHER_CONSTRUCTOR = dataWatcher.getConstructor(MinecraftReflection.getEntityClass());
return CREATE_DATA_WATCHER_CONSTRUCTOR.newInstance(
BukkitUnwrapper.getInstance().unwrapItem(entity)
);
} catch (Exception e) {
throw new RuntimeException("Cannot construct data watcher.", e);
}
}
/**
* Create a new data watcher for a list of watchable objects.
* <p>
* Note that the watchable objects are not cloned, and will be modified in place. Use "deepClone" if
* that is not desirable.
* <p>
* The {@link #removeObject(int)} method will not modify the given list, however.
*
* @param watchableObjects - list of watchable objects that will be copied.
* @throws FieldAccessException Unable to read watchable objects.
*/
public WrappedDataWatcher(List<WrappedWatchableObject> watchableObjects) throws FieldAccessException {
this(); this();
setHandle(handle);
}
Lock writeLock = getReadWriteLock().writeLock(); @Override
Map<Integer, Object> map = getWatchableObjectMap(); public Iterator<WrappedWatchableObject> iterator() {
return getWatchableObjects().iterator();
}
writeLock.lock(); public List<WrappedWatchableObject> getWatchableObjects() {
return new ArrayList<>(asMap().values());
}
@SuppressWarnings("unchecked")
public Map<Integer, WrappedWatchableObject> asMap() {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
List<Field> candidates = fuzzy.getFieldListByType(Map.class);
Field match = null;
for (Field candidate : candidates) {
if (Modifier.isStatic(candidate.getModifiers())) {
// This is the entity class to current index map, which we really don't have a use for
} else {
// This is the map we're looking for
match = candidate;
}
}
if (match == null) {
throw new FieldAccessException("Could not find index -> object map.");
}
Map<Integer, ?> map = null;
try { try {
// Add the watchable objects by reference match.setAccessible(true);
for (WrappedWatchableObject watched : watchableObjects) { map = (Map<Integer, ?>) match.get(handle);
map.put(watched.getIndex(), watched.handle); } catch (IllegalArgumentException e) {
} throw new FieldAccessException(e);
} finally { } catch (IllegalAccessException e) {
writeLock.unlock(); throw new FieldAccessException(e);
}
} }
/** Map<Integer, WrappedWatchableObject> ret = new HashMap<>();
* Retrieve the ID of a given type, if it's allowed to be watched. for (Entry<Integer, ?> entry : map.entrySet()) {
* @param clazz - Class to check for. ret.put(entry.getKey(), new WrappedWatchableObject(entry.getValue()));
* @return The ID, or NULL if it cannot be watched.
* @throws FieldAccessException If we cannot initialize the reflection machinery.
*/
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
initialize();
Integer result = TYPE_MAP.get(WrappedWatchableObject.getUnwrappedType(clazz));
if (result == null) {
result = CUSTOM_MAP.get(clazz);
}
return result;
} }
/** return ret;
* Retrieve the type of a given ID, if it's allowed to be watched.
* @param id - id to get type for
* @return The type using a given ID, or NULL if it cannot be watched.
* @throws FieldAccessException If we cannot initialize the reflection machinery.
*/
public static Class<?> getTypeClass(int id) throws FieldAccessException {
initialize();
for (Map.Entry<Class<?>, Integer> entry : TYPE_MAP.entrySet()) {
if (Objects.equal(entry.getValue(), id)) {
return entry.getKey();
}
} }
// Unknown class type public int size() {
return null; return asMap().size();
} }
// ---- Object Getters
/** /**
* Get a watched byte. * Get a watched byte.
* @param index - index of the watched byte. * @param index - index of the watched byte.
@ -432,49 +179,60 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
* @throws FieldAccessException Cannot read underlying field. * @throws FieldAccessException Cannot read underlying field.
*/ */
public Object getObject(int index) throws FieldAccessException { public Object getObject(int index) throws FieldAccessException {
Object watchable = getWatchedObject(index); return getWatchedObject(index);
}
if (watchable != null) { private Object getWatchedObject(int index) {
return new WrappedWatchableObject(watchable).getValue(); if (dataWatcherObjectConstructor == null) {
} else { dataWatcherObjectConstructor = Accessors.getConstructorAccessor(MinecraftReflection.getDataWatcherObjectClass().getConstructors()[0]);
}
Object object = dataWatcherObjectConstructor.invoke(index, null);
if (getter == null) {
getter = Accessors.getMethodAccessor(handleType, "get", object.getClass());
}
return getter.invoke(handle, object);
}
// ---- Object Setters
public void setObject(int index, Object value) {
if (dataWatcherObjectConstructor == null) {
dataWatcherObjectConstructor = Accessors.getConstructorAccessor(MinecraftReflection.getDataWatcherObjectClass().getConstructors()[0]);
}
Object object = dataWatcherObjectConstructor.invoke(index, null);
if (setter == null) {
setter = Accessors.getMethodAccessor(handleType, "set", object.getClass(), Object.class);
}
setter.invoke(handle, object, value);
}
public WrappedDataWatcher deepClone() {
// TODO This
return null; return null;
} }
@Deprecated
@SuppressWarnings("unused")
public static Integer getTypeID(Class<?> clazz) {
return null;
} }
/** @Deprecated
* Retrieve every watchable object in this watcher. @SuppressWarnings("unused")
* @return Every watchable object. public static Class<?> getTypeClass(int typeID) {
* @throws FieldAccessException If reflection failed. return null;
*/
public List<WrappedWatchableObject> getWatchableObjects() throws FieldAccessException {
Lock readLock = getReadWriteLock().readLock();
readLock.lock();
try {
List<WrappedWatchableObject> result = new ArrayList<WrappedWatchableObject>();
// Add each watchable object to the list
for (Object watchable : getWatchableObjectMap().values()) {
if (watchable != null) {
result.add(new WrappedWatchableObject(watchable));
} else {
result.add(null);
}
}
return result;
} finally {
readLock.unlock();
}
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
// Quick checks if (obj == this) return true;
if (obj == this) if (obj == null) return false;
return true;
if (obj == null)
return false;
if (obj instanceof WrappedDataWatcher) { if (obj instanceof WrappedDataWatcher) {
WrappedDataWatcher other = (WrappedDataWatcher) obj; WrappedDataWatcher other = (WrappedDataWatcher) obj;
@ -484,13 +242,15 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
if (size() != other.size()) if (size() != other.size())
return false; return false;
for (; first.hasNext() && second.hasNext(); ) { for (; first.hasNext() && second.hasNext();) {
// See if the two elements are equal // See if the two elements are equal
if (!first.next().equals(second.next())) if (!first.next().equals(second.next()))
return false; return false;
} }
return true; return true;
} }
return false; return false;
} }
@ -498,427 +258,4 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
public int hashCode() { public int hashCode() {
return getWatchableObjects().hashCode(); return getWatchableObjects().hashCode();
} }
/**
* Retrieve a copy of every index associated with a watched object.
* @return Every watched object index.
* @throws FieldAccessException If we're unable to read the underlying object.
*/
public Set<Integer> indexSet() throws FieldAccessException {
Lock readLock = getReadWriteLock().readLock();
readLock.lock();
try {
return new HashSet<Integer>(getWatchableObjectMap().keySet());
} finally {
readLock.unlock();
}
}
/**
* Clone the content of the current DataWatcher.
* @return A cloned data watcher.
*/
public WrappedDataWatcher deepClone() {
WrappedDataWatcher clone = new WrappedDataWatcher();
// Make a new copy instead
for (WrappedWatchableObject watchable : this) {
clone.setObject(watchable.getIndex(), watchable.getClonedValue());
}
return clone;
}
/**
* Retrieve the number of watched objects.
* @return Watched object count.
* @throws FieldAccessException If we're unable to read the underlying object.
*/
public int size() throws FieldAccessException {
Lock readLock = getReadWriteLock().readLock();
readLock.lock();
try {
return getWatchableObjectMap().size();
} finally {
readLock.unlock();
}
}
/**
* Remove a given object from the underlying DataWatcher.
* @param index - index of the object to remove.
* @return The watchable object that was removed, or NULL If none could be found.
*/
public WrappedWatchableObject removeObject(int index) {
Lock writeLock = getReadWriteLock().writeLock();
writeLock.lock();
try {
Object removed = getWatchableObjectMap().remove(index);
return removed != null ? new WrappedWatchableObject(removed) : null;
} finally {
writeLock.unlock();
}
}
/**
* Set a watched byte.
* @param index - index of the watched byte.
* @param newValue - the new watched value.
* @throws FieldAccessException Cannot read underlying field.
*/
public void setObject(int index, Object newValue) throws FieldAccessException {
setObject(index, newValue, true);
}
/**
* Set a watched byte.
* @param index - index of the watched byte.
* @param newValue - the new watched value.
* @param update - whether or not to refresh every listening client.
* @throws FieldAccessException Cannot read underlying field.
*/
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
// Aquire write lock
Lock writeLock = getReadWriteLock().writeLock();
writeLock.lock();
try {
Object watchable = getWatchedObject(index);
if (watchable != null) {
new WrappedWatchableObject(watchable).setValue(newValue, update);
} else {
CREATE_KEY_VALUE_METHOD.invoke(handle, index, WrappedWatchableObject.getUnwrapped(newValue));
}
// Handle invoking the method
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Cannot convert arguments.", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Illegal access.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Checked exception in Minecraft.", e);
} finally {
writeLock.unlock();
}
}
/**
* Set a watched byte with an optional secondary value.
* @param index - index of the watched byte.
* @param newValue - the new watched value.
* @param secondary - optional secondary value.
* @param update - whether or not to refresh every listening client.
* @param type - custom type.
* @throws FieldAccessException If we cannot read the underlying field.
*/
public void setObject(int index, Object newValue, Object secondary, boolean update, CustomType type) throws FieldAccessException {
Object created = type.newInstance(newValue, secondary);
// Now update the watcher
setObject(index, created, update);
}
private Object getWatchedObject(int index) throws FieldAccessException {
// We use the get-method first and foremost
if (GET_KEY_VALUE_METHOD != null) {
try {
return GET_KEY_VALUE_METHOD.invoke(handle, index);
} catch (Exception e) {
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
}
} else {
try {
getReadWriteLock().readLock().lock();
return getWatchableObjectMap().get(index);
} finally {
getReadWriteLock().readLock().unlock();
}
}
}
/**
* Retrieve the current read write lock.
* @return Current read write lock.
* @throws FieldAccessException If we're unable to read the underlying field.
*/
protected ReadWriteLock getReadWriteLock() throws FieldAccessException {
try {
// Cache the read write lock
if (readWriteLock != null)
return readWriteLock;
else if (READ_WRITE_LOCK_FIELD != null)
return readWriteLock = (ReadWriteLock) FieldUtils.readField(READ_WRITE_LOCK_FIELD, handle, true);
else
return readWriteLock = new ReentrantReadWriteLock();
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to read lock field.", e);
}
}
/**
* Retrieve the underlying map of key values that stores watchable objects.
* @return A map of watchable objects.
* @throws FieldAccessException If we don't have permission to perform reflection.
*/
@SuppressWarnings("unchecked")
protected Map<Integer, Object> getWatchableObjectMap() throws FieldAccessException {
if (watchableObjects == null)
watchableObjects = (Map<Integer, Object>) VALUE_MAP_ACCESSOR.get(handle);
return watchableObjects;
}
/**
* Retrieve the data watcher associated with an entity.
* @param entity - the entity to read from.
* @return Associated data watcher.
* @throws FieldAccessException Reflection failed.
*/
public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException {
if (ENTITY_DATA_FIELD == null)
ENTITY_DATA_FIELD = FuzzyReflection.fromClass(MinecraftReflection.getEntityClass(), true).
getFieldByType("datawatcher", MinecraftReflection.getDataWatcherClass());
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
try {
Object nsmWatcher = FieldUtils.readField(ENTITY_DATA_FIELD, unwrapper.unwrapItem(entity), true);
if (nsmWatcher != null)
return new WrappedDataWatcher(nsmWatcher);
else
return null;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access DataWatcher field.", e);
}
}
/**
* Invoked when a data watcher is first used.
*/
@SuppressWarnings("unchecked")
private static void initialize() throws FieldAccessException {
// This method should only be run once, even if an exception is thrown
if (!HAS_INITIALIZED)
HAS_INITIALIZED = true;
else
return;
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherClass(), true);
for (Field lookup : fuzzy.getFieldListByType(Map.class)) {
if (Modifier.isStatic(lookup.getModifiers())) {
// This must be the type map
TYPE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true);
} else {
// If not, then we're probably dealing with the value map
VALUE_MAP_ACCESSOR = Accessors.getFieldAccessor(lookup, true);
}
}
// Spigot workaround (not necessary
initializeSpigot(fuzzy);
// Any custom types
CUSTOM_MAP = initializeCustom();
// Initialize static type type
TYPE_MAP = (Map<Class<?>, Integer>) TYPE_MAP_ACCESSOR.get(null);
try {
READ_WRITE_LOCK_FIELD = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
} catch (IllegalArgumentException e) {
// It's not a big deal
}
// Check for the entity field as well
if (MinecraftReflection.isUsingNetty()) {
ENTITY_FIELD = fuzzy.getFieldByType("entity", MinecraftReflection.getEntityClass());
ENTITY_FIELD.setAccessible(true);
}
initializeMethods(fuzzy);
}
// For Spigot's bountiful update patch
private static Map<Class<?>, Integer> initializeCustom() {
Map<Class<?>, Integer> map = Maps.newHashMap();
for (CustomType type : CustomType.values()) {
if (type.getSpigotClass() != null) {
map.put(type.getSpigotClass(), type.getTypeId());
}
}
return map;
}
// TODO: Remove, as this was fixed in build #1189 of Spigot
private static void initializeSpigot(FuzzyReflection fuzzy) {
// See if the workaround is needed
if (TYPE_MAP_ACCESSOR != null && VALUE_MAP_ACCESSOR != null)
return;
for (Field lookup : fuzzy.getFields()) {
final Class<?> type = lookup.getType();
if (TroveWrapper.isTroveClass(type)) {
// Create a wrapper accessor
final ReadOnlyFieldAccessor accessor = TroveWrapper.wrapMapField(
Accessors.getFieldAccessor(lookup, true), new Function<Integer, Integer>() {
@Override
public Integer apply(@Nullable Integer value) {
// Do not use zero for no entry value
if (value == 0)
return -1;
return value;
}
});
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<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
new Class<?>[] { int.class, Object.class});
// Load the get-method
try {
GET_KEY_VALUE_METHOD = fuzzy.getMethodByParameters(
"getWatchableObject", MinecraftReflection.getWatchableObjectClass(), new Class[] { int.class });
GET_KEY_VALUE_METHOD.setAccessible(true);
} catch (IllegalArgumentException e) {
// Use the fallback method
}
for (Method method : candidates) {
if (!method.getName().startsWith("watch")) {
CREATE_KEY_VALUE_METHOD = method;
} else {
UPDATE_KEY_VALUE_METHOD = method;
}
}
// Did we succeed?
if (UPDATE_KEY_VALUE_METHOD == null || CREATE_KEY_VALUE_METHOD == null) {
// Go by index instead
if (candidates.size() > 1) {
CREATE_KEY_VALUE_METHOD = candidates.get(0);
UPDATE_KEY_VALUE_METHOD = candidates.get(1);
} else {
//throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
}
// Be a little scientist - see if this in fact IS the right way around
try {
WrappedDataWatcher watcher = new WrappedDataWatcher();
watcher.setObject(0, 0);
watcher.setObject(0, 1);
if (watcher.getInteger(0) != 1) {
throw new IllegalStateException("This cannot be!");
}
} catch (Exception e) {
// Nope
UPDATE_KEY_VALUE_METHOD = candidates.get(0);
CREATE_KEY_VALUE_METHOD = candidates.get(1);
}
}
}
@Override
public Iterator<WrappedWatchableObject> iterator() {
// We'll wrap the iterator instead of creating a new list every time
return Iterators.transform(getWatchableObjectMap().values().iterator(),
new Function<Object, WrappedWatchableObject>() {
@Override
public WrappedWatchableObject apply(@Nullable Object item) {
if (item != null)
return new WrappedWatchableObject(item);
else
return null;
}
});
}
/**
* Retrieve a view of this DataWatcher as a map.
* <p>
* Any changes to the map will be reflected in this DataWatcher, and vice versa.
* @return A view of the data watcher as a map.
*/
public Map<Integer, WrappedWatchableObject> asMap() {
// Construct corresponding map
if (mapView == null) {
mapView = new ConvertedMap<Integer, Object, WrappedWatchableObject>(getWatchableObjectMap()) {
@Override
protected Object toInner(WrappedWatchableObject outer) {
if (outer == null)
return null;
return outer.getHandle();
}
@Override
protected WrappedWatchableObject toOuter(Object inner) {
if (inner == null)
return null;
return new WrappedWatchableObject(inner);
}
};
}
return mapView;
}
@Override
public String toString() {
return asMap().toString();
}
/**
* Retrieve the entity associated with this data watcher.
* <p>
* <b>Warning:</b> This is only supported on 1.7.2 and above.
* @return The entity, or NULL.
*/
public Entity getEntity() {
if (!MinecraftReflection.isUsingNetty())
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
try {
return (Entity) MinecraftReflection.getBukkitEntity(ENTITY_FIELD.get(handle));
} catch (Exception e) {
throw new RuntimeException("Unable to retrieve entity.", e);
}
}
/**
* Set the entity associated with this data watcher.
* <p>
* <b>Warning:</b> This is only supported on 1.7.2 and above.
* @param entity - the new entity.
*/
public void setEntity(Entity entity) {
if (!MinecraftReflection.isUsingNetty())
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
try {
ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity));
} catch (Exception e) {
throw new RuntimeException("Unable to set entity.", e);
}
}
} }

View File

@ -1,467 +1,42 @@
/* /**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * (c) 2016 dmulloy2
* Copyright (C) 2012 Kristian S. Stangeland
*
* 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
* 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;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* 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;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/ */
package com.comphenix.protocol.wrappers; package com.comphenix.protocol.wrappers;
import java.lang.reflect.Constructor;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Objects;
/** /**
* Represents a watchable object. * @author dmulloy2
*
* @author Kristian
*/ */
public class WrappedWatchableObject extends AbstractWrapper { public class WrappedWatchableObject extends AbstractWrapper {
// Whether or not the reflection machinery has been initialized
private static boolean hasInitialized;
// The field containing the value itself private WrappedWatchableObject() {
private static StructureModifier<Object> baseModifier; super(MinecraftReflection.getDataWatcherItemClass());
}
// Used to create new watchable objects
private static Constructor<?> watchableConstructor;
// The watchable object class type
private static Class<?> watchableObjectClass;
protected StructureModifier<Object> modifier;
// Type of the stored value
private Class<?> typeClass;
/**
* Wrap a given raw Minecraft watchable object.
* @param handle - the raw watchable object to wrap.
*/
public WrappedWatchableObject(Object handle) { public WrappedWatchableObject(Object handle) {
super(MinecraftReflection.getWatchableObjectClass()); this();
load(handle); setHandle(handle);
} }
/** public Object getValue() {
* Construct a watchable object from an index and a given value. return new StructureModifier<Object>(handleType).withTarget(this).read(1);
* @param index - the index.
* @param value - non-null value of specific types.
*/
public WrappedWatchableObject(int index, Object value) {
super(MinecraftReflection.getWatchableObjectClass());
if (value == null)
throw new IllegalArgumentException("Value cannot be NULL.");
// Get the correct type ID
Integer typeID = WrappedDataWatcher.getTypeID(value.getClass());
if (typeID != null) {
if (watchableConstructor == null) {
try {
watchableConstructor = MinecraftReflection.getWatchableObjectClass().
getConstructor(int.class, int.class, Object.class);
} catch (Exception e) {
throw new RuntimeException("Cannot get the WatchableObject(int, int, Object) constructor.", e);
}
}
// Create the object
try {
load(watchableConstructor.newInstance(typeID, index, getUnwrapped(value)));
} catch (Exception e) {
throw new RuntimeException("Cannot construct underlying WatchableObject.", e);
}
} else {
throw new IllegalArgumentException("Cannot watch the type " + value.getClass());
}
}
// /**
// * Construct a custom watchable object from an index, value and custom type.
// * @param index - the index.
// * @param primary - non-null value of specific types.
// * @param secondary - optional secondary value, if the type can store it.
// * @param type - the custom Spigot type.
// */
// public WrappedWatchableObject(int index, Object value, Object secondary, CustomType type) {
// this(index, type.newInstance(value, secondary));
// }
// Wrap a NMS object
private void load(Object handle) {
initialize();
this.handle = handle;
this.modifier = baseModifier.withTarget(handle);
// Make sure the type is correct
if (!watchableObjectClass.isAssignableFrom(handle.getClass())) {
throw new ClassCastException("Cannot cast the class " + handle.getClass().getName() +
" to " + watchableObjectClass.getName());
}
}
/**
* Initialize reflection machinery.
*/
private static void initialize() {
if (!hasInitialized) {
hasInitialized = true;
watchableObjectClass = MinecraftReflection.getWatchableObjectClass();
baseModifier = new StructureModifier<Object>(watchableObjectClass, null, false);
}
}
// /**
// * Retrieve the custom type of this object.
// * @return The custom type, or NULL if not applicable.
// */
// public CustomType getCustomType() {
// return CustomType.fromValue(getValue());
// }
/**
* Retrieve the correct super type of the current value.
* @return Super type.
* @throws FieldAccessException Unable to read values.
*/
public Class<?> getType() throws FieldAccessException {
return getWrappedType(getTypeRaw());
}
/**
* Retrieve the correct super type of the current value, given the raw NMS object.
* @return Super type.
* @throws FieldAccessException Unable to read values.
*/
private Class<?> getTypeRaw() throws FieldAccessException {
if (typeClass == null) {
typeClass = WrappedDataWatcher.getTypeClass(getTypeID());
if (typeClass == null) {
throw new IllegalStateException("Unrecognized data type: " + getTypeID());
}
}
return typeClass;
}
/**
* Retrieve the index of this watchable object. This is used to identify a value.
* @return Object index.
* @throws FieldAccessException Reflection failed.
*/
public int getIndex() throws FieldAccessException {
return modifier.<Integer>withType(int.class).read(1);
}
/**
* Set the the index of this watchable object.
* @param index - the new object index.
* @throws FieldAccessException Reflection failed.
*/
public void setIndex(int index) throws FieldAccessException {
modifier.<Integer>withType(int.class).write(1, index);
}
/**
* Retrieve the type ID of a watchable object.
* <table border=1>
* <caption>Type to Value</caption>
* <tbody>
* <tr>
* <th>Type ID</th>
* <th>Data Type</th>
* </tr>
* <tr>
* <td>0</td>
* <td>Byte</td>
* </tr>
* <tr>
* <td>1</td>
* <td>Short</td>
* </tr>
* <tr>
* <td>2</td>
* <td>Int</td>
* </tr>
* <tr>
* <td>3</td>
* <td>Float</td>
* </tr>
* <tr>
* <td>4</td>
* <td>{@link String}</td>
* </tr>
* <tr>
* <td>5</td>
* <td>{@link org.bukkit.inventory.ItemStack ItemStack}</td>
* </tr>
* <tr>
* <td>6<sup>*</sup></td>
* <td>{@link com.comphenix.protocol.wrappers.ChunkPosition ChunkPosition}</td>
* </tr>
* </tbody>
* </table>
* @return Type ID that identifies the type of the value.
* @throws FieldAccessException Reflection failed.
*/
public int getTypeID() throws FieldAccessException {
return modifier.<Integer>withType(int.class).read(0);
}
/**
* Set the type ID of a watchable object.
* @param id - the new ID.
* @throws FieldAccessException Reflection failed.
* @see #getTypeID()
*/
public void setTypeID(int id) throws FieldAccessException {
modifier.<Integer>withType(int.class).write(0, id);
}
/**
* Update the value field.
* @param newValue - new value.
* @throws FieldAccessException Unable to use reflection.
*/
public void setValue(Object newValue) throws FieldAccessException {
setValue(newValue, true);
}
/**
* Update the value field.
* @param newValue - new value.
* @param updateClient - whether or not to update listening clients.
* @throws FieldAccessException Unable to use reflection.
*/
public void setValue(Object newValue, boolean updateClient) throws FieldAccessException {
// Verify a few quick things
if (newValue == null)
throw new IllegalArgumentException("Cannot watch a NULL value.");
if (!getType().isAssignableFrom(newValue.getClass()))
throw new IllegalArgumentException("Object " + newValue + " must be of type " + getType().getName());
// See if we should update the client to
if (updateClient)
setDirtyState(true);
// Use the modifier to set the value
modifier.withType(Object.class).write(0, getUnwrapped(newValue));
}
/**
* Read the underlying value field.
* @return The underlying value.
*/
private Object getNMSValue() {
return modifier.withType(Object.class).read(0);
}
/**
* Read the value field.
* @return The watched value.
* @throws FieldAccessException Unable to use reflection.
*/
public Object getValue() throws FieldAccessException {
return getWrapped(modifier.withType(Object.class).read(0));
}
// /**
// * Retrieve the secondary value associated with this watchable object.
// * <p>
// * This is only applicable for certain {@link CustomType}.
// * @return The secondary value, or NULL if not found.
// */
// public Object getSecondaryValue() {
// CustomType type = getCustomType();
// return type != null ? type.getSecondary(getValue()) : null;
// }
//
// /**
// * Set the secondary value.
// * @param secondary - the secondary value.
// * @throws IllegalStateException If this watchable object does not have a secondary value.
// */
// public void setSecondaryValue(Object secondary) {
// CustomType type = getCustomType();
//
// if (type == null) {
// throw new IllegalStateException(this + " does not have a custom type.");
// }
// type.setSecondary(getValue(), secondary);
// }
/**
* Set whether or not the value must be synchronized with the client.
* @param dirty - TRUE if the value should be synchronized, FALSE otherwise.
* @throws FieldAccessException Unable to use reflection.
*/
public void setDirtyState(boolean dirty) throws FieldAccessException {
modifier.<Boolean>withType(boolean.class).write(0, dirty);
}
/**
* Retrieve whether or not the value must be synchronized with the client.
* @return TRUE if the value should be synchronized, FALSE otherwise.
* @throws FieldAccessException Unable to use reflection.
*/
public boolean getDirtyState() throws FieldAccessException {
return modifier.<Boolean>withType(boolean.class).read(0);
}
/**
* Retrieve the wrapped object value, if needed.
* @param value - the raw NMS object to wrap.
* @return The wrapped object.
*/
@SuppressWarnings("rawtypes")
static Object getWrapped(Object value) {
// Handle the special cases
if (MinecraftReflection.isItemStack(value)) {
return BukkitConverters.getItemStackConverter().getSpecific(value);
} else if (MinecraftReflection.isChunkCoordinates(value)) {
return new WrappedChunkCoordinate((Comparable) value);
} else {
return value;
}
}
/**
* Retrieve the wrapped type, if needed.
* @param wrapped - the wrapped class type.
* @return The wrapped class type.
*/
static Class<?> getWrappedType(Class<?> unwrapped) {
if (unwrapped.equals(MinecraftReflection.getChunkPositionClass()))
return ChunkPosition.class;
else if (unwrapped.equals(MinecraftReflection.getBlockPositionClass()))
return BlockPosition.class;
else if (unwrapped.equals(MinecraftReflection.getChunkCoordinatesClass()))
return WrappedChunkCoordinate.class;
else if (unwrapped.equals(MinecraftReflection.getItemStackClass()))
return ItemStack.class;
else
return unwrapped;
}
/**
* Retrieve the raw NMS value.
* @param wrapped - the wrapped position.
* @return The raw NMS object.
*/
static Object getUnwrapped(Object wrapped) {
// Convert special cases
if (wrapped instanceof ChunkPosition)
return ChunkPosition.getConverter().getGeneric(MinecraftReflection.getChunkPositionClass(), (ChunkPosition) wrapped);
else if (wrapped instanceof BlockPosition)
return BlockPosition.getConverter().getGeneric(MinecraftReflection.getBlockPositionClass(), (BlockPosition) wrapped);
else if (wrapped instanceof WrappedChunkCoordinate)
return ((WrappedChunkCoordinate) wrapped).getHandle();
else if (wrapped instanceof ItemStack)
return BukkitConverters.getItemStackConverter().getGeneric(MinecraftReflection.getItemStackClass(), (ItemStack) wrapped);
else
return wrapped;
}
/**
* Retrieve the unwrapped type, if needed.
* @param wrapped - the unwrapped class type.
* @return The unwrapped class type.
*/
static Class<?> getUnwrappedType(Class<?> wrapped) {
if (wrapped.equals(ChunkPosition.class))
return MinecraftReflection.getChunkPositionClass();
else if (wrapped.equals(BlockPosition.class))
return MinecraftReflection.getBlockPositionClass();
else if (wrapped.equals(WrappedChunkCoordinate.class))
return MinecraftReflection.getChunkCoordinatesClass();
else if (ItemStack.class.isAssignableFrom(wrapped))
return MinecraftReflection.getItemStackClass();
else
return wrapped;
}
/**
* Clone the current wrapped watchable object, along with any contained objects.
* @return A deep clone of the current watchable object.
* @throws FieldAccessException If we're unable to use reflection.
*/
public WrappedWatchableObject deepClone() throws FieldAccessException {
WrappedWatchableObject clone = new WrappedWatchableObject(
DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass()));
clone.setDirtyState(getDirtyState());
clone.setIndex(getIndex());
clone.setTypeID(getTypeID());
clone.setValue(getClonedValue(), false);
return clone;
}
// Helper
Object getClonedValue() throws FieldAccessException {
Object value = getNMSValue();
// Only a limited set of references types are supported
if (MinecraftReflection.isBlockPosition(value)) {
EquivalentConverter<BlockPosition> converter = BlockPosition.getConverter();
return converter.getGeneric(MinecraftReflection.getBlockPositionClass(), converter.getSpecific(value));
} else if (MinecraftReflection.isChunkPosition(value)) {
EquivalentConverter<ChunkPosition> converter = ChunkPosition.getConverter();
return converter.getGeneric(MinecraftReflection.getChunkPositionClass(), converter.getSpecific(value));
} else if (MinecraftReflection.isItemStack(value)) {
return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(value).clone());
} else {
// A string or primitive wrapper, which are all immutable.
return value;
}
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
// Quick checks if (obj == this) return true;
if (obj == this) if (obj == null) return false;
return true;
if (obj == null)
return false;
if (obj instanceof WrappedWatchableObject) { if (obj instanceof WrappedWatchableObject) {
WrappedWatchableObject other = (WrappedWatchableObject) obj; WrappedWatchableObject other = (WrappedWatchableObject) obj;
return other.handle.equals(handle);
return Objects.equal(getIndex(), other.getIndex()) &&
Objects.equal(getTypeID(), other.getTypeID()) &&
Objects.equal(getValue(), other.getValue());
} }
// No, this is not equivalent
return false; return false;
} }
@Override // TODO Flesh out this class
public int hashCode() {
return Objects.hashCode(getIndex(), getTypeID(), getValue());
}
@Override
public String toString() {
return String.format("[%s: %s (%s)]", getIndex(), getValue(), getType().getSimpleName());
}
} }

View File

@ -26,6 +26,9 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import net.minecraft.server.v1_9_R1.AttributeModifier; import net.minecraft.server.v1_9_R1.AttributeModifier;
import net.minecraft.server.v1_9_R1.DataWatcher;
import net.minecraft.server.v1_9_R1.Entity;
import net.minecraft.server.v1_9_R1.EntityLightning;
import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes; import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes;
import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes.AttributeSnapshot; import net.minecraft.server.v1_9_R1.PacketPlayOutUpdateAttributes.AttributeSnapshot;
@ -50,7 +53,9 @@ import com.comphenix.protocol.utility.Util;
import com.comphenix.protocol.wrappers.BlockPosition; import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.WrappedBlockData; import com.comphenix.protocol.wrappers.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -61,8 +66,8 @@ import com.google.common.collect.Lists;
//@PrepareForTest(CraftItemFactory.class) //@PrepareForTest(CraftItemFactory.class)
public class PacketContainerTest { public class PacketContainerTest {
// Helper converters // Helper converters
//private EquivalentConverter<WrappedDataWatcher> watchConvert = BukkitConverters.getDataWatcherConverter(); // private EquivalentConverter<WrappedDataWatcher> watchConvert = BukkitConverters.getDataWatcherConverter();
//private EquivalentConverter<ItemStack> itemConvert = BukkitConverters.getItemStackConverter(); // private EquivalentConverter<ItemStack> itemConvert = BukkitConverters.getItemStackConverter();
@BeforeClass @BeforeClass
public static void initializeBukkit() throws IllegalAccessException { public static void initializeBukkit() throws IllegalAccessException {
@ -167,6 +172,7 @@ public class PacketContainerTest {
} }
// TODO Find a packet with integer arrays // TODO Find a packet with integer arrays
/*@Test /*@Test
public void testGetIntegerArrays() { public void testGetIntegerArrays() {
// Contains a byte array we will test // Contains a byte array we will test
@ -278,21 +284,26 @@ public class PacketContainerTest {
assertEquals(compound.getList("ages"), result.getList("ages")); assertEquals(compound.getList("ages"), result.getList("ages"));
} }
/*@Test @Test
public void testGetDataWatcherModifier() { public void testGetDataWatcherModifier() {
PacketContainer mobSpawnPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY_LIVING); PacketContainer mobSpawnPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY_LIVING);
StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier(); StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier();
WrappedDataWatcher dataWatcher = new WrappedDataWatcher(); Entity entity = new EntityLightning(null, 0, 0, 0, true);
dataWatcher.setObject(1, 100); DataWatcher watcher = entity.getDataWatcher();
dataWatcher.setObject(2, 125);
WrappedDataWatcher dataWatcher = new WrappedDataWatcher(watcher);
dataWatcher.setObject(1, (byte) 1);
dataWatcher.setObject(2, 301);
dataWatcher.setObject(3, true);
dataWatcher.setObject(4, "Lorem");
assertNull(watcherAccessor.read(0)); assertNull(watcherAccessor.read(0));
// Insert and read back // Insert and read back
watcherAccessor.write(0, dataWatcher); watcherAccessor.write(0, dataWatcher);
assertEquals(dataWatcher, watcherAccessor.read(0)); assertEquals(dataWatcher, watcherAccessor.read(0));
}*/ }
// Unfortunately, it might be too difficult to mock this one // Unfortunately, it might be too difficult to mock this one
// //
@ -322,7 +333,7 @@ public class PacketContainerTest {
assertEquals(positions, cloned); assertEquals(positions, cloned);
} }
/*@Test @Test
public void testGetWatchableCollectionModifier() { public void testGetWatchableCollectionModifier() {
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
StructureModifier<List<WrappedWatchableObject>> watchableAccessor = StructureModifier<List<WrappedWatchableObject>> watchableAccessor =
@ -330,16 +341,16 @@ public class PacketContainerTest {
assertNull(watchableAccessor.read(0)); assertNull(watchableAccessor.read(0));
WrappedDataWatcher watcher = new WrappedDataWatcher(); Entity entity = new EntityLightning(null, 0, 0, 0, true);
watcher.setObject(1, 10); DataWatcher watcher = entity.getDataWatcher();
watcher.setObject(8, 10);
List<WrappedWatchableObject> list = watcher.getWatchableObjects(); WrappedDataWatcher wrapper = new WrappedDataWatcher(watcher);
List<WrappedWatchableObject> list = wrapper.getWatchableObjects();
// Insert and read back // Insert and read back
watchableAccessor.write(0, list); watchableAccessor.write(0, list);
assertEquals(list, watchableAccessor.read(0)); assertEquals(list, watchableAccessor.read(0));
}*/ }
@Test @Test
public void testGameProfiles() { public void testGameProfiles() {
@ -350,19 +361,18 @@ public class PacketContainerTest {
assertEquals(profile, spawnEntity.getGameProfiles().read(0)); assertEquals(profile, spawnEntity.getGameProfiles().read(0));
} }
/*@Test @Test
public void testChatComponents() { public void testChatComponents() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Server.CHAT); PacketContainer chatPacket = new PacketContainer(PacketType.Play.Server.CHAT);
chatPacket.getChatComponents().write(0, chatPacket.getChatComponents().write(0,
WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]); WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]);
assertEquals("{\"extra\":[\"You shall not \",{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}", assertEquals("{\"extra\":[{\"text\":\"You shall not \"},{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}",
chatPacket.getChatComponents().read(0).getJson()); chatPacket.getChatComponents().read(0).getJson());
}*/ }
@Test @Test
public void testSerialization() { public void testSerialization() {
try {
PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT); PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT);
chat.getStrings().write(0, "Test"); chat.getStrings().write(0, "Test");
@ -370,12 +380,6 @@ public class PacketContainerTest {
assertEquals(PacketType.Play.Client.CHAT, copy.getType()); assertEquals(PacketType.Play.Client.CHAT, copy.getType());
assertEquals("Test", copy.getStrings().read(0)); assertEquals("Test", copy.getStrings().read(0));
} catch (Throwable ex) {
// This occurs intermittently on Java 6, for the time being just log it and move on
// TODO: Possibly find a solution to this
System.err.println("Failed to serialize packet:");
ex.printStackTrace();
}
} }
@Test @Test

View File

@ -0,0 +1,37 @@
/**
* (c) 2016 dmulloy2
*/
package com.comphenix.protocol.wrappers;
import static org.junit.Assert.assertTrue;
import net.minecraft.server.v1_9_R1.DataWatcher;
import net.minecraft.server.v1_9_R1.Entity;
import net.minecraft.server.v1_9_R1.EntityLightning;
import org.junit.BeforeClass;
import org.junit.Test;
import com.comphenix.protocol.BukkitInitialization;
/**
* @author dmulloy2
*/
public class WrappedDataWatcherTest {
@BeforeClass
public static void prepare() {
BukkitInitialization.initializePackage();
}
@Test
public void test() {
Entity entity = new EntityLightning(null, 0, 0, 0, true);
DataWatcher handle = entity.getDataWatcher();
WrappedDataWatcher wrapper = new WrappedDataWatcher(handle);
wrapper.setObject(0, (byte) 1);
assertTrue(wrapper.getByte(0) == 1);
}
}

View File

@ -1,9 +1,5 @@
package com.comphenix.protocol.wrappers; package com.comphenix.protocol.wrappers;
import static org.junit.Assert.assertEquals;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PowerMockIgnore;
@ -19,11 +15,11 @@ public class WrappedWatchableObjectTest {
} }
//@Test //@Test
public void testItemStack() { /* public void testItemStack() {
final ItemStack stack = new ItemStack(Material.GOLD_AXE); final ItemStack stack = new ItemStack(Material.GOLD_AXE);
final WrappedWatchableObject test = new WrappedWatchableObject(0, stack); final WrappedWatchableObject test = new WrappedWatchableObject(0, stack);
ItemStack value = (ItemStack) test.getValue(); ItemStack value = (ItemStack) test.getValue();
assertEquals(value.getType(), stack.getType()); assertEquals(value.getType(), stack.getType());
} } */
} }