Added the ability to read data watchers and watchable object lists.

This commit is contained in:
Kristian S. Stangeland 2012-11-13 14:34:07 +01:00
parent a567721114
commit ad69b0caac
10 changed files with 1141 additions and 231 deletions

View File

@ -19,6 +19,8 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
/**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
@ -45,7 +47,8 @@ class CleanupStaticMembers {
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class,
ObjectCloner.class, Packets.Server.class, Packets.Client.class
ObjectCloner.class, Packets.Server.class, Packets.Client.class,
ChunkPosition.class, WrappedDataWatcher.class
};
String[] internalClasses = {
@ -62,7 +65,8 @@ class CleanupStaticMembers {
"com.comphenix.protocol.injector.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor"
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
"com.comphenix.protocol.wrappers.WrappedWatchableObject"
};
resetClasses(publicClasses);

View File

@ -25,26 +25,22 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import net.minecraft.server.Packet;
@ -66,24 +62,10 @@ public class PacketContainer implements Serializable {
// Current structure modifier
protected transient StructureModifier<Object> structureModifier;
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
// The getEntity method
private static Method getEntity;
// Support for serialization
private static Method writeMethod;
private static Method readMethod;
static {
try {
Class.forName("net.minecraft.server.WorldType");
hasWorldType = true;
} catch (ClassNotFoundException e) {
}
}
/**
* Creates a packet container for a new packet.
* @param id - ID of the packet to create.
@ -150,22 +132,8 @@ public class PacketContainer implements Serializable {
*/
public StructureModifier<ItemStack> getItemModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class,
getIgnoreNull(new EquivalentConverter<ItemStack>() {
public Object getGeneric(ItemStack specific) {
return toStackNMS(specific);
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@Override
public Class<ItemStack> getSpecificType() {
return ItemStack.class;
}
}));
return structureModifier.<ItemStack>withType(
net.minecraft.server.ItemStack.class, BukkitConverters.getItemStackConverter());
}
/**
@ -176,17 +144,21 @@ public class PacketContainer implements Serializable {
* @return A modifier for ItemStack array fields.
*/
public StructureModifier<ItemStack[]> getItemArrayModifier() {
final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter();
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack[]>withType(
net.minecraft.server.ItemStack[].class,
getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
BukkitConverters.getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
public Object getGeneric(ItemStack[] specific) {
public Object getGeneric(Class<?>genericType, ItemStack[] specific) {
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
// Unwrap every item
for (int i = 0; i < result.length; i++) {
result[i] = toStackNMS(specific[i]);
result[i] = (net.minecraft.server.ItemStack) stackConverter.getGeneric(
net.minecraft.server.ItemStack.class, specific[i]);
}
return result;
}
@ -198,7 +170,7 @@ public class PacketContainer implements Serializable {
// Add the wrapper
for (int i = 0; i < result.length; i++) {
result[i] = new CraftItemStack(input[i]);
result[i] = stackConverter.getSpecific(input[i]);
}
return result;
}
@ -210,20 +182,6 @@ public class PacketContainer implements Serializable {
}));
}
/**
* Convert an item stack to the NMS equivalent.
* @param stack - Bukkit stack to convert.
* @return A bukkit stack.
*/
private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
// We must be prepared for an object that simply implements ItemStcak
if (stack instanceof CraftItemStack) {
return ((CraftItemStack) stack).getHandle();
} else {
return (new CraftItemStack(stack)).getHandle();
}
}
/**
* Retrieves a read/write structure for the world type enum.
* <p>
@ -232,33 +190,21 @@ public class PacketContainer implements Serializable {
* @return A modifier for world type fields.
*/
public StructureModifier<WorldType> getWorldTypeModifier() {
if (!hasWorldType) {
// We couldn't find the Minecraft equivalent
return structureModifier.withType(null);
}
// Convert to and from the Bukkit wrapper
return structureModifier.<WorldType>withType(
net.minecraft.server.WorldType.class,
getIgnoreNull(new EquivalentConverter<WorldType>() {
@Override
public Object getGeneric(WorldType specific) {
return net.minecraft.server.WorldType.getType(specific.getName());
}
@Override
public WorldType getSpecific(Object generic) {
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
return WorldType.getByName(type.name());
}
@Override
public Class<WorldType> getSpecificType() {
return WorldType.class;
}
}));
BukkitConverters.getWorldTypeConverter());
}
/**
* Retrieves a read/write structure for data watchers.
* @return A modifier for data watchers.
*/
public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<WrappedDataWatcher>withType(
net.minecraft.server.DataWatcher.class,
BukkitConverters.getDataWatcherConverter());
}
/**
@ -272,64 +218,9 @@ public class PacketContainer implements Serializable {
* @return A modifier entity types.
*/
public StructureModifier<Entity> getEntityModifier(World world) {
final Object worldServer = ((CraftWorld) world).getHandle();
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
if (getEntity == null)
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
"getEntity", nmsEntityClass, new Class[] { int.class });
// Convert to and from the Bukkit wrapper
return structureModifier.<Entity>withType(
int.class,
getIgnoreNull(new EquivalentConverter<Entity>() {
@Override
public Object getGeneric(Entity specific) {
// Simple enough
return specific.getEntityId();
}
@Override
public Entity getSpecific(Object generic) {
try {
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
getEntity.invoke(worldServer, generic);
Integer id = (Integer) generic;
// Attempt to get the Bukkit entity
if (nmsEntity != null) {
return nmsEntity.getBukkitEntity();
} else {
Server server = Bukkit.getServer();
// Maybe it's a player that has just logged in? Try a search
if (server != null) {
for (Player player : server.getOnlinePlayers()) {
if (player.getEntityId() == id) {
return player;
}
}
}
return null;
}
} catch (IllegalArgumentException e) {
throw new RuntimeException("Incorrect arguments detected.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read field due to a security limitation.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
}
}
@Override
public Class<Entity> getSpecificType() {
return Entity.class;
}
}));
int.class, BukkitConverters.getEntityConverter(world));
}
/**
@ -348,100 +239,35 @@ public class PacketContainer implements Serializable {
* <p>
* This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the
* internal Minecraft ChunkPosition.
* @return A modifier for ChunkPosition array fields.
* @return A modifier for ChunkPosition list fields.
*/
public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() {
final EquivalentConverter<ChunkPosition> converter = ChunkPosition.getConverter();
// Convert to and from the ProtocolLib wrapper
final StructureModifier<List<ChunkPosition>> modifier = structureModifier.withType(
return structureModifier.withType(
Collection.class,
getIgnoreNull(new EquivalentConverter<List<ChunkPosition>>() {
private Class<?> collectionType;
@SuppressWarnings("unchecked")
@Override
public List<ChunkPosition> getSpecific(Object generic) {
if (generic instanceof Collection) {
List<ChunkPosition> positions = new ArrayList<ChunkPosition>();
// Save the type
collectionType = generic.getClass();
// Copy everything to a new list
for (Object item : (Collection<Object>) generic) {
ChunkPosition result = converter.getSpecific(item);
if (item != null)
positions.add(result);
}
return positions;
}
// Not valid
return null;
}
@SuppressWarnings("unchecked")
@Override
public Object getGeneric(List<ChunkPosition> specific) {
// Just go by the first field
if (collectionType == null) {
collectionType = structureModifier.withType(Collection.class).getFields().get(0).getType();
}
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(collectionType);
// Convert each object
for (ChunkPosition position : specific) {
Object converted = converter.getGeneric(position);
if (position == null)
newContainer.add(null);
else if (converted != null)
newContainer.add(converted);
}
return newContainer;
}
@SuppressWarnings("unchecked")
@Override
public Class<List<ChunkPosition>> getSpecificType() {
// Damn you Java
Class<?> dummy = List.class;
return (Class<List<ChunkPosition>>) dummy;
}
}
));
return modifier;
BukkitConverters.getListConverter(
net.minecraft.server.ChunkPosition.class,
ChunkPosition.getConverter())
);
}
private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
// Automatically wrap all parameters to the delegate with a NULL check
return new EquivalentConverter<TType>() {
public Object getGeneric(TType specific) {
if (specific != null)
return delegate.getGeneric(specific);
else
return null;
}
@Override
public TType getSpecific(Object generic) {
if (generic != null)
return delegate.getSpecific(generic);
else
return null;
}
@Override
public Class<TType> getSpecificType() {
return delegate.getSpecificType();
}
};
/**
* Retrieves a read/write structure for collections of watchable objects.
* <p>
* This modifier will automatically marshall between the visible WrappedWatchableObject and the
* internal Minecraft WatchableObject.
* @return A modifier for watchable object list fields.
*/
public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() {
// Convert to and from the ProtocolLib wrapper
return structureModifier.withType(
Collection.class,
BukkitConverters.getListConverter(
net.minecraft.server.WatchableObject.class,
BukkitConverters.getWatchableObjectConverter())
);
}
/**
* Retrieves the ID of this packet.
* @return Packet ID.

View File

@ -24,8 +24,25 @@ package com.comphenix.protocol.reflect;
* @param <TType> The specific type.
*/
public interface EquivalentConverter<TType> {
/**
* Retrieve a copy of the specific type using an instance of the generic type.
* @param generic - the generic type.
* @return The new specific type.
*/
public TType getSpecific(Object generic);
public Object getGeneric(TType specific);
/**
* Retrieve a copy of the generic type from a specific type.
* @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.
*/
public Object getGeneric(Class<?> genericType, TType specific);
/**
* Due to type erasion, we need to explicitly keep a reference to the specific type.
* @return The specific type.
*/
public Class<TType> getSpecificType();
}

View File

@ -256,7 +256,7 @@ public class StructureModifier<TField> {
throw new IllegalStateException("Cannot write to a NULL target.");
// Use the converter, if it exists
Object obj = needConversion() ? converter.getGeneric(value) : value;
Object obj = needConversion() ? converter.getGeneric(getFieldType(fieldIndex), value) : value;
try {
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
@ -268,6 +268,15 @@ public class StructureModifier<TField> {
return this;
}
/**
* Retrieve the type of a specified field.
* @param index - the index.
* @return The type of the given field.
*/
protected Class<?> getFieldType(int index) {
return data.get(index).getType();
}
/**
* Whether or not we should use the converter instance.
* @return TRUE if we should, FALSE otherwise.

View File

@ -108,7 +108,7 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
@Override
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
if (converter != null)
value = converter.getGeneric((TField) value);
value = converter.getGeneric(getFieldType(index), (TField) value);
return writeGenerated(index, value);
}

View File

@ -0,0 +1,307 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import net.minecraft.server.DataWatcher;
import net.minecraft.server.WatchableObject;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
/**
* Contains several useful equivalent converters for normal Bukkit types.
*
* @author Kristian
*/
public class BukkitConverters {
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
// The getEntity method
private static Method getEntity;
static {
try {
Class.forName("net.minecraft.server.WorldType");
hasWorldType = true;
} catch (ClassNotFoundException e) {
}
}
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
// Convert to and from the wrapper
return getIgnoreNull(new EquivalentConverter<List<T>>() {
@SuppressWarnings("unchecked")
@Override
public List<T> getSpecific(Object generic) {
if (generic instanceof Collection) {
List<T> items = new ArrayList<T>();
// Copy everything to a new list
for (Object item : (Collection<Object>) generic) {
T result = itemConverter.getSpecific(item);
if (item != null)
items.add(result);
}
return items;
}
// Not valid
return null;
}
@SuppressWarnings("unchecked")
@Override
public Object getGeneric(Class<?> genericType, List<T> specific) {
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(genericType);
// Convert each object
for (T position : specific) {
Object converted = itemConverter.getGeneric(genericItemType, position);
if (position == null)
newContainer.add(null);
else if (converted != null)
newContainer.add(converted);
}
return newContainer;
}
@SuppressWarnings("unchecked")
@Override
public Class<List<T>> getSpecificType() {
// Damn you Java
Class<?> dummy = List.class;
return (Class<List<T>>) dummy;
}
}
);
}
/**
* Retrieve a converter for watchable objects and the respective wrapper.
* @return A watchable object converter.
*/
public static EquivalentConverter<WrappedWatchableObject> getWatchableObjectConverter() {
return getIgnoreNull(new EquivalentConverter<WrappedWatchableObject>() {
@Override
public Object getGeneric(Class<?> genericType, WrappedWatchableObject specific) {
return specific.getHandle();
}
public WrappedWatchableObject getSpecific(Object generic) {
if (generic instanceof WatchableObject)
return new WrappedWatchableObject((WatchableObject) generic);
else if (generic instanceof WrappedWatchableObject)
return (WrappedWatchableObject) generic;
else
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
};
@Override
public Class<WrappedWatchableObject> getSpecificType() {
return WrappedWatchableObject.class;
}
});
}
/**
* Retrieve a converter for the NMS DataWatcher class and our wrapper.
* @return A DataWatcher converter.
*/
public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() {
return getIgnoreNull(new EquivalentConverter<WrappedDataWatcher>() {
@Override
public Object getGeneric(Class<?> genericType, WrappedDataWatcher specific) {
return specific.getHandle();
}
@Override
public WrappedDataWatcher getSpecific(Object generic) {
if (generic instanceof DataWatcher)
return new WrappedDataWatcher((DataWatcher) generic);
else if (generic instanceof WrappedDataWatcher)
return (WrappedDataWatcher) generic;
else
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
}
@Override
public Class<WrappedDataWatcher> getSpecificType() {
return WrappedDataWatcher.class;
}
});
}
/**
* Retrieve a converter for Bukkit's world type enum and the NMS equivalent.
* @return A world type enum converter.
*/
public static EquivalentConverter<WorldType> getWorldTypeConverter() {
// Check that we can actually use this converter
if (!hasWorldType)
return null;
return getIgnoreNull(new EquivalentConverter<WorldType>() {
@Override
public Object getGeneric(Class<?> genericType, WorldType specific) {
return net.minecraft.server.WorldType.getType(specific.getName());
}
@Override
public WorldType getSpecific(Object generic) {
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
return WorldType.getByName(type.name());
}
@Override
public Class<WorldType> getSpecificType() {
return WorldType.class;
}
});
}
/**
* Retrieve a converter for NMS entities and Bukkit entities.
* @param world - the current world.
* @return A converter between the underlying NMS entity and Bukkit's wrapper.
*/
public static EquivalentConverter<Entity> getEntityConverter(World world) {
final Object worldServer = ((CraftWorld) world).getHandle();
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
if (getEntity == null)
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
"getEntity", nmsEntityClass, new Class[] { int.class });
return getIgnoreNull(new EquivalentConverter<Entity>() {
@Override
public Object getGeneric(Class<?> genericType, Entity specific) {
// Simple enough
return specific.getEntityId();
}
@Override
public Entity getSpecific(Object generic) {
try {
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
getEntity.invoke(worldServer, generic);
Integer id = (Integer) generic;
// Attempt to get the Bukkit entity
if (nmsEntity != null) {
return nmsEntity.getBukkitEntity();
} else {
Server server = Bukkit.getServer();
// Maybe it's a player that has just logged in? Try a search
if (server != null) {
for (Player player : server.getOnlinePlayers()) {
if (player.getEntityId() == id) {
return player;
}
}
}
return null;
}
} catch (IllegalArgumentException e) {
throw new RuntimeException("Incorrect arguments detected.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read field due to a security limitation.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
}
}
@Override
public Class<Entity> getSpecificType() {
return Entity.class;
}
});
}
/**
* Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack.
* @return Item stack converter.
*/
public static EquivalentConverter<ItemStack> getItemStackConverter() {
return getIgnoreNull(new EquivalentConverter<ItemStack>() {
public Object getGeneric(Class<?> genericType, ItemStack specific) {
return toStackNMS(specific);
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@Override
public Class<ItemStack> getSpecificType() {
return ItemStack.class;
}
});
}
/**
* Convert an item stack to the NMS equivalent.
* @param stack - Bukkit stack to convert.
* @return A bukkit stack.
*/
private static net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
// We must be prepared for an object that simply implements ItemStcak
if (stack instanceof CraftItemStack) {
return ((CraftItemStack) stack).getHandle();
} else {
return (new CraftItemStack(stack)).getHandle();
}
}
/**
* 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.
*/
public static <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
// Automatically wrap all parameters to the delegate with a NULL check
return new EquivalentConverter<TType>() {
public Object getGeneric(Class<?> genericType, TType specific) {
if (specific != null)
return delegate.getGeneric(genericType, specific);
else
return null;
}
@Override
public TType getSpecific(Object generic) {
if (generic != null)
return delegate.getSpecific(generic);
else
return null;
}
@Override
public Class<TType> getSpecificType() {
return delegate.getSpecificType();
}
};
}
}

View File

@ -8,11 +8,17 @@ import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects;
/**
* Wraps a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector.
* Copies a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector.
*
* @author Kristian
*/
public class ChunkPosition {
/**
* Represents the null (0, 0, 0) origin.
*/
public static ChunkPosition ORIGIN = new ChunkPosition(0, 0, 0);
// Use protected members, like Bukkit
protected final int x;
protected final int y;
@ -74,14 +80,56 @@ public class ChunkPosition {
return z;
}
/**
* Adds the current position and a given position together, producing a result position.
* @param other - the other position.
* @return The new result position.
*/
public ChunkPosition add(ChunkPosition other) {
if (other == null)
throw new IllegalArgumentException("other cannot be NULL");
return new ChunkPosition(x + other.x, y + other.y, z + other.z);
}
/**
* Adds the current position and a given position together, producing a result position.
* @param other - the other position.
* @return The new result position.
*/
public ChunkPosition subtract(ChunkPosition other) {
if (other == null)
throw new IllegalArgumentException("other cannot be NULL");
return new ChunkPosition(x - other.x, y - other.y, z - other.z);
}
/**
* Multiply each dimension in the current position by the given factor.
* @param factor - multiplier.
* @return The new result.
*/
public ChunkPosition multiply(int factor) {
return new ChunkPosition(x * factor, y * factor, z * factor);
}
/**
* Divide each dimension in the current position by the given divisor.
* @param divisor - the divisor.
* @return The new result.
*/
public ChunkPosition divide(int divisor) {
if (divisor == 0)
throw new IllegalArgumentException("Cannot divide by null.");
return new ChunkPosition(x / divisor, y / divisor, z / divisor);
}
/**
* Used to convert between NMS ChunkPosition and the wrapper instance.
* @return
* @return A new converter.
*/
public static EquivalentConverter<ChunkPosition> getConverter() {
return new EquivalentConverter<ChunkPosition>() {
@Override
public Object getGeneric(ChunkPosition specific) {
public Object getGeneric(Class<?> genericType, ChunkPosition specific) {
return new net.minecraft.server.ChunkPosition(specific.x, specific.z, specific.z);
}

View File

@ -0,0 +1,105 @@
package com.comphenix.protocol.wrappers;
import com.google.common.base.Objects;
import net.minecraft.server.ChunkCoordinates;
/**
* Allows access to a chunk coordinate.
*
* @author Kristian
*/
public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate> {
/**
* If TRUE, NULLs should be put before non-null instances of this class.
*/
private static final boolean LARGER_THAN_NULL = true;
protected ChunkCoordinates handle;
public WrappedChunkCoordinate(ChunkCoordinates handle) {
if (handle == null)
throw new IllegalArgumentException("handle cannot be NULL");
this.handle = handle;
}
public ChunkCoordinates getHandle() {
return handle;
}
/**
* Retrieve the x coordinate of the underlying coordiate.
* @return The x coordinate.
*/
public int getX() {
return handle.x;
}
/**
* Set the x coordinate of the underlying coordiate.
* @param newX - the new x coordinate.
*/
public void setX(int newX) {
handle.x = newX;
}
/**
* Retrieve the y coordinate of the underlying coordiate.
* @return The y coordinate.
*/
public int getY() {
return handle.y;
}
/**
* Set the y coordinate of the underlying coordiate.
* @param newY - the new y coordinate.
*/
public void setY(int newY) {
handle.y = newY;
}
/**
* Retrieve the z coordinate of the underlying coordiate.
* @return The z coordinate.
*/
public int getZ() {
return handle.z;
}
/**
* Set the z coordinate of the underlying coordiate.
* @param newZ - the new z coordinate.
*/
public void setZ(int newZ) {
handle.z = newZ;
}
@Override
public int compareTo(WrappedChunkCoordinate other) {
// We'll handle NULL objects too, unlike ChunkCoordinates
if (other.handle == null)
return LARGER_THAN_NULL ? -1 : 1;
else
return handle.compareTo(other.handle);
}
@Override
public boolean equals(Object other) {
if (other instanceof WrappedChunkCoordinate) {
WrappedChunkCoordinate wrapper = (WrappedChunkCoordinate) other;
return Objects.equal(handle, wrapper.handle);
}
// It's tempting to handle the ChunkCoordinate case too, but then
// the equals() method won't be commutative, causing a.equals(b) to
// be different to b.equals(a).
return false;
}
@Override
public int hashCode() {
return handle.hashCode();
}
}

View File

@ -0,0 +1,434 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects;
import net.minecraft.server.ChunkCoordinates;
import net.minecraft.server.DataWatcher;
import net.minecraft.server.WatchableObject;
/**
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
*
* @author Kristian
*/
public class WrappedDataWatcher {
/**
* Used to assign integer IDs to given types.
*/
private static Map<Class<?>, Integer> typeMap;
// Fields
private static Field valueMapField;
private static Field readWriteLockField;
// Methods
private static Method createKeyValueMethod;
private static Method updateKeyValueMethod;
private static Method getKeyValueMethod;
/**
* Whether or not this class has already been initialized.
*/
private static boolean hasInitialized;
// The underlying DataWatcher we're modifying
protected DataWatcher handle;
// Lock
private ReadWriteLock readWriteLock;
// Map of watchable objects
private Map<Integer, Object> watchableObjects;
/**
* Initialize a new data watcher.
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
*/
public WrappedDataWatcher() {
// Just create a new watcher
this(new DataWatcher());
}
/**
* Create a wrapper for a given data watcher.
* @param dataWatcher - the data watcher to wrap.
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
*/
public WrappedDataWatcher(DataWatcher handle) {
this.handle = handle;
try {
initialize();
} catch (FieldAccessException e) {
throw new RuntimeException("Cannot initialize wrapper.", e);
}
}
/**
* Retrieves the underlying data watcher.
* @return The underlying data watcher.
*/
public DataWatcher getHandle() {
return handle;
}
/**
* Retrieve the ID of a given type, if it's allowed to be watched.
* @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();
return typeMap.get(clazz);
}
/**
* Retrieve the type of a given ID, if it's allowed to be watched.
* @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 : typeMap.entrySet()) {
if (Objects.equal(entry.getValue(), id)) {
return entry.getKey();
}
}
// Unknown class type
return null;
}
/**
* Get a watched byte.
* @param index - index of the watched byte.
* @return The watched byte, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Byte getByte(int index) throws FieldAccessException {
return (Byte) getObjectRaw(index);
}
/**
* Get a watched short.
* @param index - index of the watched short.
* @return The watched short, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Short getShort(int index) throws FieldAccessException {
return (Short) getObjectRaw(index);
}
/**
* Get a watched integer.
* @param index - index of the watched integer.
* @return The watched integer, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Integer getInteger(int index) throws FieldAccessException {
return (Integer) getObjectRaw(index);
}
/**
* Get a watched float.
* @param index - index of the watched float.
* @return The watched float, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Float getFloat(int index) throws FieldAccessException {
return (Float) getObjectRaw(index);
}
/**
* Get a watched string.
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public String getString(int index) throws FieldAccessException {
return (String) getObjectRaw(index);
}
/**
* Get a watched string.
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public ItemStack getItemStack(int index) throws FieldAccessException {
return (ItemStack) getObject(index);
}
/**
* Get a watched string.
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public WrappedChunkCoordinate getChunkCoordinate(int index) throws FieldAccessException {
return (WrappedChunkCoordinate) getObject(index);
}
/**
* Retrieve a watchable object by index.
* @param index - index of the object to retrieve.
* @return The watched object.
* @throws FieldAccessException Cannot read underlying field.
*/
public Object getObject(int index) throws FieldAccessException {
Object result = getObjectRaw(index);
// Handle the special cases too
if (result instanceof net.minecraft.server.ItemStack) {
return BukkitConverters.getItemStackConverter().getSpecific(result);
} else if (result instanceof ChunkCoordinates) {
return new WrappedChunkCoordinate((ChunkCoordinates) result);
} else {
return result;
}
}
/**
* Retrieve a watchable object by index.
* @param index - index of the object to retrieve.
* @return The watched object.
* @throws FieldAccessException Cannot read underlying field.
*/
private Object getObjectRaw(int index) throws FieldAccessException {
// The get method will take care of concurrency
WatchableObject watchable = getWatchedObject(index);
if (watchable != null) {
return new WrappedWatchableObject(watchable).getValue();
} else {
return null;
}
}
/**
* 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 {
try {
getReadWriteLock().readLock().lock();
return new HashSet<Integer>(getWatchableObjectMap().keySet());
} finally {
getReadWriteLock().readLock().unlock();
}
}
/**
* 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 {
try {
getReadWriteLock().readLock().lock();
return getWatchableObjectMap().size();
} finally {
getReadWriteLock().readLock().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 clients.
* @throws FieldAccessException Cannot read underlying field.
*/
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
// Convert special cases
if (newValue instanceof WrappedChunkCoordinate)
newValue = ((WrappedChunkCoordinate) newValue).getHandle();
if (newValue instanceof ItemStack)
newValue = BukkitConverters.getItemStackConverter().getGeneric(
net.minecraft.server.ItemStack.class, (ItemStack) newValue);
// Next, set the object
setObjectRaw(index, newValue, update);
}
/**
* Set a watchable object by index.
* @param index - index of the object to retrieve.
* @param newValue - the new watched value.
* @param update - whether or not to refresh every listening clients.
* @return The watched object.
* @throws FieldAccessException Cannot read underlying field.
*/
private void setObjectRaw(int index, Object newValue, boolean update) throws FieldAccessException {
WatchableObject watchable;
try {
// Aquire write lock
getReadWriteLock().writeLock().lock();
watchable = getWatchedObject(index);
if (watchable != null) {
new WrappedWatchableObject(watchable).setValue(newValue, update);
}
} finally {
getReadWriteLock().writeLock().unlock();
}
}
private WatchableObject getWatchedObject(int index) throws FieldAccessException {
// We use the get-method first and foremost
if (getKeyValueMethod != null) {
try {
return (WatchableObject) getKeyValueMethod.invoke(handle, index);
} catch (Exception e) {
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
}
} else {
try {
getReadWriteLock().readLock().lock();
return (WatchableObject) 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 (readWriteLockField != null)
return readWriteLock = (ReadWriteLock) FieldUtils.readField(readWriteLockField, 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) {
try {
watchableObjects = (Map<Integer, Object>) FieldUtils.readField(valueMapField, handle, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot read watchable object field.", e);
}
}
return watchableObjects;
}
/**
* 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 (!hasInitialized)
hasInitialized = true;
else
return;
FuzzyReflection fuzzy = FuzzyReflection.fromClass(DataWatcher.class, true);
for (Field lookup : fuzzy.getFieldListByType(Map.class)) {
if (Modifier.isStatic(lookup.getModifiers())) {
// This must be the type map
try {
typeMap = (Map<Class<?>, Integer>) FieldUtils.readStaticField(lookup, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access type map field.", e);
}
} else {
// If not, then we're probably dealing with the value map
valueMapField = lookup;
}
}
try {
readWriteLockField = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
} catch (IllegalArgumentException e) {
// It's not a big deal
}
initializeMethods(fuzzy);
}
private static void initializeMethods(FuzzyReflection fuzzy) {
List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
new Class<?>[] { int.class, Object.class});
for (Method method : candidates) {
if (!method.getName().startsWith("watch")) {
createKeyValueMethod = method;
} else {
updateKeyValueMethod = method;
}
}
// Did we succeed?
if (updateKeyValueMethod == null || createKeyValueMethod == null) {
// Go by index instead
if (candidates.size() > 1) {
createKeyValueMethod = candidates.get(0);
updateKeyValueMethod = candidates.get(1);
} else {
throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
}
}
// Load the get-method
try {
getKeyValueMethod = fuzzy.getMethodByParameters(
"getWatchableObject", ".*WatchableObject", new String[] { int.class.getName() });
getKeyValueMethod.setAccessible(true);
} catch (IllegalArgumentException e) {
// Use fallback method
}
}
}

View File

@ -0,0 +1,160 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier;
import net.minecraft.server.WatchableObject;
/**
* Represents a watchable object.
*
* @author Kristian
*/
public class WrappedWatchableObject {
// Whether or not the reflection machinery has been initialized
private static boolean hasInitialized;
// The field containing the value itself
private static StructureModifier<Object> baseModifier;
protected WatchableObject handle;
protected StructureModifier<Object> modifier;
// Type of the stored value
private Class<?> typeClass;
public WrappedWatchableObject(WatchableObject handle) {
initialize();
this.handle = handle;
this.modifier = baseModifier.withTarget(handle);
}
/**
* Retrieves the underlying watchable object.
* @return The underlying watchable object.
*/
public WatchableObject getHandle() {
return handle;
}
/**
* Initialize reflection machinery.
*/
private static void initialize() {
if (!hasInitialized) {
hasInitialized = true;
baseModifier = new StructureModifier<Object>(WatchableObject.class, null, false);
}
}
/**
* Retrieve the correct super type of the current value.
* @return Super type.
* @throws FieldAccessException Unable to read values.
*/
public Class<?> getValueType() 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.
* @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.
*/
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 (!getValueType().isAssignableFrom(newValue.getClass()))
throw new IllegalArgumentException("Object " + newValue + " must be of type " + getValueType().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, newValue);
}
/**
* Read the value field.
* @return The watched value.
* @throws FieldAccessException Unable to use reflection.
*/
public Object getValue() throws FieldAccessException {
return modifier.withType(Object.class).read(0);
}
/**
* 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);
}
}