ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java

1110 lines
36 KiB
Java

/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2018 dmulloy2
*
* 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;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.collection.ConvertedMap;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableBiMap;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.*;
import java.util.*;
/**
* Represents a DataWatcher
* @author dmulloy2
*/
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject>, ClonableWrapper {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherClass();
private static MethodAccessor GETTER = null;
private static MethodAccessor SETTER = null;
private static MethodAccessor REGISTER = null;
private static FieldAccessor ENTITY_DATA_FIELD = null;
private static FieldAccessor ENTITY_FIELD = null;
private static FieldAccessor MAP_FIELD = null;
private static ConstructorAccessor constructor = null;
private static ConstructorAccessor eggConstructor = null;
private static Object fakeEntity = null;
// ---- Construction
/**
* Constructs a new DataWatcher wrapper around a NMS handle. The resulting
* DataWatcher will likely have existing values that can be removed with
* {@link #clear()}.
*
* @param handle DataWatcher handle
*/
public WrappedDataWatcher(Object handle) {
super(HANDLE_TYPE);
setHandle(handle);
}
/**
* Constructs a new DataWatcher using a fake egg entity. The
* resulting DataWatcher will not have any keys or values and new ones will
* have to be added using watcher objects.
*/
public WrappedDataWatcher() {
this(newHandle(fakeEntity()));
}
/**
* Constructs a new DataWatcher using a real entity. The resulting
* DataWatcher will not have any keys or values and new ones will have to
* be added using watcher objects.
*
* @param entity The entity
*/
public WrappedDataWatcher(Entity entity) {
this(newHandle(BukkitUnwrapper.getInstance().unwrapItem(entity)));
}
/**
* Constructs a new DataWatcher using a fake egg entity and a given
* list of watchable objects.
*
* @param objects The list of objects
*/
public WrappedDataWatcher(List<WrappedWatchableObject> objects) {
this();
if (MinecraftReflection.watcherObjectExists()) {
for (WrappedWatchableObject object : objects) {
setObject(object.getWatcherObject(), object);
}
} else {
for (WrappedWatchableObject object : objects) {
setObject(object.getIndex(), object);
}
}
}
private static Object newHandle(Object entity) {
if (constructor == null) {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass());
}
return constructor.invoke(entity);
}
private static Object fakeEntity() {
if (fakeEntity != null) {
return fakeEntity;
}
// We can create a fake egg without it affecting anything
// Mojang added difficulty to lightning strikes, so this'll have to do
if (eggConstructor == null) {
eggConstructor = Accessors.getConstructorAccessor(
MinecraftReflection.getMinecraftClass("world.entity.projectile.EntityEgg", "world.entity.projectile.ThrownEgg", "EntityEgg"),
MinecraftReflection.getNmsWorldClass(), double.class, double.class, double.class
);
}
Object world = BukkitUnwrapper.getInstance().unwrapItem(Bukkit.getWorlds().get(0));
return fakeEntity = eggConstructor.invoke(world, 0, 0, 0);
}
// ---- Collection Methods
@SuppressWarnings("unchecked")
private Map<Integer, Object> getMap() {
if (MAP_FIELD == null) {
try {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
MAP_FIELD = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract
.newBuilder()
.banModifier(Modifier.STATIC)
.typeDerivedOf(Map.class)
.build()));
} catch (IllegalArgumentException ex) {
throw new FieldAccessException("Failed to find watchable object map");
}
}
return (Map<Integer, Object>) MAP_FIELD.get(handle);
}
/**
* Gets the contents of this DataWatcher as a map.
* @return The contents
*/
public Map<Integer, WrappedWatchableObject> asMap() {
return new ConvertedMap<Integer, Object, WrappedWatchableObject>(getMap()) {
@Override
protected WrappedWatchableObject toOuter(Object inner) {
return inner != null ? new WrappedWatchableObject(inner) : null;
}
@Override
protected Object toInner(WrappedWatchableObject outer) {
return outer != null ? outer.getHandle() : null;
}
};
}
/**
* Gets a set containing the registered indexes.
* @return The set
*/
public Set<Integer> getIndexes() {
return getMap().keySet();
}
/**
* Gets a list of the contents of this DataWatcher.
* @return The contents
*/
public List<WrappedWatchableObject> getWatchableObjects() {
return new ArrayList<>(asMap().values());
}
@Override
public Iterator<WrappedWatchableObject> iterator() {
return getWatchableObjects().iterator();
}
/**
* Gets the size of this DataWatcher's contents.
* @return The size
*/
public int size() {
return getMap().size();
}
/**
* Gets the item at a given index.
*
* @param index Index to get
* @return The watchable object, or null if none exists
*/
public WrappedWatchableObject getWatchableObject(int index) {
Object handle = getMap().get(index);
if (handle != null) {
return new WrappedWatchableObject(handle);
} else {
return null;
}
}
/**
* @deprecated Renamed to {@link #remove(int)}
*/
@Deprecated
public WrappedWatchableObject removeObject(int index) {
return remove(index);
}
/**
* Removes the item at a given index.
*
* @param index Index to remove
* @return The previous value, or null if none existed
*/
public WrappedWatchableObject remove(int index) {
Object removed = getMap().remove(index);
return removed != null ? new WrappedWatchableObject(removed) : null;
}
/**
* Whether or not this DataWatcher has an object at a given index.
*
* @param index Index to check for
* @return True if it does, false if not
*/
public boolean hasIndex(int index) {
return getMap().containsKey(index);
}
/**
* Returns a set containing all the registered indexes
* @return The set
*/
public Set<Integer> indexSet() {
return getMap().keySet();
}
/**
* Clears the contents of this DataWatcher. The watcher will be empty after
* this operation is called.
*/
public void clear() {
getMap().clear();
}
// ---- Object Getters
/**
* Get a watched byte.
*
* @param index - index of the watched byte.
* @return The watched byte, or NULL if this value doesn't exist.
*/
public Byte getByte(int index) {
return (Byte) getObject(index);
}
/**
* Get a watched short.
*
* @param index - index of the watched short.
* @return The watched short, or NULL if this value doesn't exist.
*/
public Short getShort(int index) {
return (Short) getObject(index);
}
/**
* Get a watched integer.
*
* @param index - index of the watched integer.
* @return The watched integer, or NULL if this value doesn't exist.
*/
public Integer getInteger(int index) {
return (Integer) getObject(index);
}
/**
* Get a watched float.
*
* @param index - index of the watched float.
* @return The watched float, or NULL if this value doesn't exist.
*/
public Float getFloat(int index) {
return (Float) 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.
*/
public String getString(int index) {
return (String) 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.
*/
public ItemStack getItemStack(int index) {
return (ItemStack) getObject(index);
}
/**
* Retrieve a watchable object by index.
*
* @param index Index of the object to retrieve.
* @return The watched object or null if it doesn't exist.
*/
public Object getObject(int index) {
return getObject(WrappedDataWatcherObject.fromIndex(index));
}
/**
* Retrieve a watchable object by watcher object.
*
* @param object The watcher object
* @return The watched object or null if it doesn't exist.
*/
public Object getObject(WrappedDataWatcherObject object) {
Validate.notNull(object, "Watcher object cannot be null!");
if (GETTER == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
if (MinecraftReflection.watcherObjectExists()) {
GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactType(object.getHandleType())
.returnTypeExact(Object.class)
.build(), "get"));
} else {
GETTER = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactType(int.class)
.returnTypeExact(MinecraftReflection.getDataWatcherItemClass())
.build()));
}
}
try {
Object value = GETTER.invoke(handle, object.getHandle());
return WrappedWatchableObject.getWrapped(value);
} catch (Exception ex) {
// Nothing exists at this index
return null;
}
}
// ---- Object Setters
/**
* Sets the DataWatcher Item at a given index to a new value. In 1.9 and up,
* you cannot register objects without a watcher object.
*
* @param index Index of the object to set
* @param value New value
* @param update Whether or not to inform the client
*
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object, boolean)
* @throws IllegalArgumentException in 1.9 and up if there isn't already an
* object at this index
*/
public void setObject(int index, Object value, boolean update) {
if (MinecraftReflection.watcherObjectExists() && !hasIndex(index)) {
throw new IllegalArgumentException("You cannot register objects without a watcher object!");
}
setObject(WrappedDataWatcherObject.fromIndex(index), value, update);
}
/**
* Shortcut for {@link #setObject(int, Object, boolean)}
*/
public void setObject(int index, Object value) {
setObject(index, value, false);
}
/**
* Sets the DataWatcher Item at a given index to a new value.
*
* @param index Index of the object to set
* @param serializer Serializer from {@link Registry#get(Class)}
* @param value New value
* @param update Whether or not to inform the client
*
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object)
*/
public void setObject(int index, Serializer serializer, Object value, boolean update) {
setObject(new WrappedDataWatcherObject(index, serializer), value, update);
}
/**
* Alias for {@link #setObject(int, Serializer, Object, boolean)}
*/
public void setObject(int index, Serializer serializer, Object value) {
setObject(new WrappedDataWatcherObject(index, serializer), value, false);
}
/**
* Sets the DataWatcher Item at a given index to a new value.
*
* @param index Index of the object to set
* @param value New value
* @param update Whether or not to inform the client
*
* @see WrappedDataWatcher#setObject(int, Object, boolean)
*/
public void setObject(int index, WrappedWatchableObject value, boolean update) {
setObject(index, value.getRawValue(), update);
}
/**
* Alias for {@link #setObject(int, WrappedWatchableObject, boolean)}
*/
public void setObject(int index, WrappedWatchableObject value) {
setObject(index, value.getRawValue(), false);
}
/**
* Sets the DataWatcher Item associated with a given watcher object to a new value.
*
* @param object Associated watcher object
* @param value Wrapped value
* @param update Whether or not to inform the client
*
* @see #setObject(WrappedDataWatcherObject, Object)
*/
public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value, boolean update) {
setObject(object, value.getRawValue(), update);
}
/**
* Shortcut for {@link #setObject(WrappedDataWatcherObject, WrappedWatchableObject, boolean)}
*/
public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value) {
setObject(object, value.getRawValue(), false);
}
/**
* Sets the DataWatcher Item associated with a given watcher object to a
* new value. If there is not already an object at this index, the
* specified watcher object must have a serializer.
*
* @param object Associated watcher object
* @param value New value
*
* @throws IllegalArgumentException If the watcher object is null or must
* have a serializer and does not have one.
*/
public void setObject(WrappedDataWatcherObject object, Object value, boolean update) {
Validate.notNull(object, "Watcher object cannot be null!");
if (SETTER == null && REGISTER == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true);
FuzzyMethodContract contract = FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.PUBLIC)
.parameterExactArray(object.getHandleType(), Object.class)
.build();
List<Method> methods = fuzzy.getMethodList(contract);
for (Method method : methods) {
if (method.getName().equals("set") || method.getName().equals("watch") || method.getName().equals("b")) {
SETTER = Accessors.getMethodAccessor(method);
} else {
REGISTER = Accessors.getMethodAccessor(method);
}
}
}
// Unwrap the object
value = WrappedWatchableObject.getUnwrapped(value);
if (hasIndex(object.getIndex())) {
SETTER.invoke(handle, object.getHandle(), value);
} else {
object.checkSerializer();
REGISTER.invoke(handle, object.getHandle(), value);
}
if (update) {
getWatchableObject(object.getIndex()).setDirtyState(update);
}
}
/**
* Shortcut for {@link #setObject(WrappedDataWatcherObject, Object, boolean)}
*/
public void setObject(WrappedDataWatcherObject object, Object value) {
setObject(object, value, false);
}
// ---- Utility Methods
/**
* Clone the content of the current DataWatcher.
*
* @return A cloned data watcher.
*/
public WrappedDataWatcher deepClone() {
WrappedDataWatcher clone = new WrappedDataWatcher(getEntity());
if (MinecraftReflection.watcherObjectExists()) {
for (WrappedWatchableObject wrapper : this) {
clone.setObject(wrapper.getWatcherObject(), wrapper);
}
} else {
for (WrappedWatchableObject wrapper : this) {
clone.setObject(wrapper.getIndex(), wrapper);
}
}
return clone;
}
/**
* Retrieve the data watcher associated with an entity.
*
* @param entity - the entity to read from.
* @return Associated data watcher.
*/
public static WrappedDataWatcher getEntityWatcher(Entity entity) {
if (ENTITY_DATA_FIELD == null) {
ENTITY_DATA_FIELD = Accessors.getFieldAccessor(MinecraftReflection.getEntityClass(), MinecraftReflection.getDataWatcherClass(), true);
}
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
Object handle = ENTITY_DATA_FIELD.get(unwrapper.unwrapItem(entity));
return handle != null ? new WrappedDataWatcher(handle) : null;
}
/**
* Retrieve the entity associated with this data watcher.
* @return The entity, or NULL.
*/
public Entity getEntity() {
if (ENTITY_FIELD == null) {
ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), true);
}
Object entity = ENTITY_FIELD.get(handle);
if (entity == null) {
throw new NullPointerException(handle + "." + ENTITY_FIELD);
}
return (Entity) MinecraftReflection.getBukkitEntity(entity);
}
/**
* Set the entity associated with this data watcher.
* @param entity - the new entity.
*/
public void setEntity(Entity entity) {
if (ENTITY_FIELD == null) {
ENTITY_FIELD = Accessors.getFieldAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(), true);
}
ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity));
}
private static final ImmutableBiMap<Class<?>, Integer> CLASS_TO_ID = new ImmutableBiMap.Builder<Class<?>, Integer>()
.put(Byte.class, 0)
.put(Short.class, 1)
.put(Integer.class, 2)
.put(Float.class, 3)
.put(String.class, 4)
.put(MinecraftReflection.getItemStackClass(), 5)
.put(MinecraftReflection.getBlockPositionClass(), 6)
.put(Vector3F.getMinecraftClass(), 7)
.build();
/**
* Retrieves the type ID associated with a given class. No longer supported
* in 1.9 and up due to the removal of type IDs.
*
* @param clazz Class to find ID for
* @return The ID, or null if not found
*/
public static Integer getTypeID(Class<?> clazz) {
return CLASS_TO_ID.get(clazz);
}
/**
* Retrieves the class associated with a given type ID. No longer
* supported in 1.9 and up due to the removal of type IDs.
*
* @param typeID ID to find Class for
* @return The Class, or null if not found
*/
public static Class<?> getTypeClass(int typeID) {
return CLASS_TO_ID.inverse().get(typeID);
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null) return false;
if (obj instanceof WrappedDataWatcher) {
WrappedDataWatcher other = (WrappedDataWatcher) obj;
Iterator<WrappedWatchableObject> first = iterator(), second = other.iterator();
// Make sure they're the same size
if (size() != other.size())
return false;
for (; first.hasNext() && second.hasNext();) {
// See if the two elements are equal
if (!first.next().equals(second.next()))
return false;
}
return true;
}
return false;
}
@Override
public int hashCode() {
return getWatchableObjects().hashCode();
}
@Override
public String toString() {
return "WrappedDataWatcher[handle=" + handle + "]";
}
// ---- 1.9 classes
/**
* Represents a DataWatcherObject in 1.9. In order to register an object,
* the serializer must be specified.
*
* @author dmulloy2
*/
public static class WrappedDataWatcherObject {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherObjectClass();
private static ConstructorAccessor constructor = null;
private static MethodAccessor getSerializer = null;
private StructureModifier<Object> modifier;
private Object handle;
protected WrappedDataWatcherObject() {
}
/**
* Creates a new watcher object from a NMS handle.
*
* @param handle The handle
*/
public WrappedDataWatcherObject(Object handle) {
this.handle = handle;
this.modifier = new StructureModifier<>(HANDLE_TYPE).withTarget(handle);
}
/**
* Creates a new watcher object from an index and serializer.
*
* @param index Index
* @param serializer Serializer, see {@link Registry}
*/
public WrappedDataWatcherObject(int index, Serializer serializer) {
this(newHandle(index, serializer));
}
static WrappedDataWatcherObject fromIndex(int index) {
if (MinecraftReflection.watcherObjectExists()) {
return new WrappedDataWatcherObject(newHandle(index));
} else {
return new DummyWatcherObject(index);
}
}
private static Object newHandle(int index) {
Validate.isTrue(index >= 0, "index cannot be negative!");
if (constructor == null) {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]);
}
return constructor.invoke(index, null);
}
private static Object newHandle(int index, Serializer serializer) {
Validate.isTrue(index >= 0, "index cannot be negative!");
Validate.notNull(serializer, "serializer cannot be null!");
if (constructor == null) {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]);
}
Object handle = serializer.getHandle();
return constructor.invoke(index, handle);
}
/**
* Gets this watcher object's index.
*
* @return The index
*/
public int getIndex() {
return (int) modifier.read(0);
}
/**
* Gets this watcher object's serializer. Will return null if the
* serializer was never specified.
*
* @return The serializer, or null
*/
public Serializer getSerializer() {
if (getSerializer == null) {
getSerializer = Accessors.getMethodAccessor(FuzzyReflection.fromClass(HANDLE_TYPE, true)
.getMethodByReturnTypeAndParameters("getSerializer", MinecraftReflection.getDataWatcherSerializerClass(), new Class[0]));
}
Object serializer = getSerializer.invoke(handle);
if (serializer != null) {
Serializer wrapper = Registry.fromHandle(serializer);
if (wrapper != null) {
return wrapper;
} else {
return new Serializer(null, serializer, false);
}
} else {
return null;
}
}
public void checkSerializer() {
Validate.notNull(getSerializer(), "You must specify a serializer to register an object!");
}
public Object getHandle() {
return handle;
}
public Class<?> getHandleType() {
return HANDLE_TYPE;
}
@Override
public String toString() {
return "DataWatcherObject[index=" + getIndex() + ", serializer=" + getSerializer() + "]";
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj instanceof WrappedDataWatcherObject) {
WrappedDataWatcherObject other = (WrappedDataWatcherObject) obj;
return handle.equals(other.handle);
}
return false;
}
@Override
public int hashCode() {
return handle.hashCode();
}
}
private static class DummyWatcherObject extends WrappedDataWatcherObject {
private final int index;
public DummyWatcherObject(int index) {
this.index = index;
}
@Override
public int getIndex() {
return index;
}
@Override
public Serializer getSerializer() {
return null;
}
@Override
public Object getHandle() {
return getIndex();
}
@Override
public Class<?> getHandleType() {
return int.class;
}
@Override
public void checkSerializer() {
// Do nothing
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null) return false;
if (obj instanceof DummyWatcherObject) {
DummyWatcherObject that = (DummyWatcherObject) obj;
return this.index == that.index;
}
return false;
}
}
/**
* Represents a DataWatcherSerializer in 1.9. If a Serializer is optional,
* values must be wrapped in a {@link Optional}.
*
* @author dmulloy2
*/
public static class Serializer extends AbstractWrapper {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherSerializerClass();
private final Class<?> type;
private final boolean optional;
/**
* Constructs a new Serializer
*
* @param type Type it serializes
* @param handle NMS handle
* @param optional Whether or not it's {@link Optional}
*/
public Serializer(Class<?> type, Object handle, boolean optional) {
super(HANDLE_TYPE);
this.type = type;
this.optional = optional;
setHandle(handle);
}
/**
* Gets the type this serializer serializes.
*
* @return The type
*/
public Class<?> getType() {
return type;
}
/**
* Whether or not this serializer is optional, that is whether or not
* the return type is wrapped in a {@link Optional}.
*
* @return True if it is, false if not
*/
public boolean isOptional() {
return optional;
}
@Override
public String toString() {
return "Serializer[type=" + type + ", handle=" + handle + ", optional=" + optional + "]";
}
}
/**
* Represents a DataWatcherRegistry containing the supported
* {@link Serializer}s in 1.9.
*
* <ul>
* <li>Byte</li>
* <li>Integer</li>
* <li>Float</li>
* <li>String</li>
* <li>IChatBaseComponent</li>
* <li>ItemStack</li>
* <li>Optional&lt;IBlockData&gt;</li>
* <li>Boolean</li>
* <li>Vector3f</li>
* <li>BlockPosition</li>
* <li>Optional&lt;BlockPosition&gt;</li>
* <li>EnumDirection</li>
* <li>Optional&lt;UUID&gt;</li>
* <li>NBTTagCompound</li>
* </ul>
*
* @author dmulloy2
*/
public static class Registry {
private static boolean INITIALIZED = false;
private static final List<Serializer> REGISTRY = new ArrayList<>();
/**
* Gets the first serializer associated with a given class.
*
* <p><b>Note</b>: If {@link Serializer#isOptional() the serializer is optional},
* values <i>must</i> be wrapped in an {@link Optional}.</p>
*
* <p>If there are multiple serializers for a given class (i.e. BlockPosition),
* you should use {@link #get(Class, boolean)} for more precision.</p>
*
* @param clazz Class to find serializer for
* @return The serializer, or null if none exists
*/
public static Serializer get(Class<?> clazz) {
Validate.notNull(clazz,"Class cannot be null!");
initialize();
for (Serializer serializer : REGISTRY) {
if (serializer.getType().equals(clazz)) {
return serializer;
}
}
throw new IllegalArgumentException("No serializer found for " + clazz);
}
/**
* Gets the first serializer associated with a given class and optional state.
* <br/>
* <b>Note</b>: If the serializer is optional, values <i>must</i> be
* wrapped in an {@link Optional}
*
* @param clazz Class to find serializer for
* @param optional Optional state
* @return The serializer, or null if none exists
*/
public static Serializer get(Class<?> clazz, boolean optional) {
Validate.notNull(clazz, "Class cannot be null!");
initialize();
Validate.notEmpty(REGISTRY, "Registry has no elements!");
for (Serializer serializer : REGISTRY) {
if (serializer.getType().equals(clazz)
&& serializer.isOptional() == optional) {
return serializer;
}
}
throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz));
}
/**
* Gets the serializer associated with a given NMS handle.
* @param handle The handle
* @return The serializer, or null if none exists
*/
public static Serializer fromHandle(Object handle) {
Validate.notNull(handle, "handle cannot be null!");
initialize();
for (Serializer serializer : REGISTRY) {
if (serializer.getHandle().equals(handle)) {
return serializer;
}
}
return null;
}
private static void initialize() {
if (!INITIALIZED) {
INITIALIZED = true;
} else {
return;
}
List<Field> candidates = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherRegistryClass(), true)
.getFieldListByType(MinecraftReflection.getDataWatcherSerializerClass());
for (Field candidate : candidates) {
Type generic = candidate.getGenericType();
if (generic instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) generic;
Type[] args = type.getActualTypeArguments();
Type arg = args[0];
Class<?> innerClass;
boolean optional = false;
if (arg instanceof Class<?>) {
innerClass = (Class<?>) arg;
} else if (arg instanceof ParameterizedType) {
innerClass = (Class<?>) ((ParameterizedType) arg).getActualTypeArguments()[0];
optional = true;
} else {
throw new IllegalStateException("Failed to find inner class of field " + candidate);
}
Object serializer;
try {
serializer = candidate.get(null);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Failed to read field " + candidate);
}
if (serializer == null) {
throw new RuntimeException("Failed to read serializer: " + candidate.getName());
}
REGISTRY.add(new Serializer(innerClass, serializer, optional));
}
}
}
// ---- Helper methods
/**
* Gets the serializer for IChatBaseComponents
* @return The serializer
*/
public static Serializer getChatComponentSerializer() {
return getChatComponentSerializer(false);
}
/**
* Gets the serializer for IChatBaseComponents
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
* @return The serializer
*/
public static Serializer getChatComponentSerializer(boolean optional) {
return get(MinecraftReflection.getIChatBaseComponentClass(), optional);
}
/**
* Gets the serializer for ItemStacks
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
* @return The serializer
*/
public static Serializer getItemStackSerializer(boolean optional) {
return get(MinecraftReflection.getItemStackClass(), optional);
}
/**
* Gets the serializer for BlockData
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
* @return The serializer
*/
public static Serializer getBlockDataSerializer(boolean optional) {
return get(MinecraftReflection.getIBlockDataClass(), optional);
}
/**
* Gets the serializer for Vector3Fs
* @return The serializer
*/
public static Serializer getVectorSerializer() {
return get(Vector3F.getMinecraftClass());
}
/**
* Gets the serializer for BlockPositions
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
* @return The serializer
*/
public static Serializer getBlockPositionSerializer(boolean optional) {
return get(MinecraftReflection.getBlockPositionClass(), optional);
}
/**
* Gets the serializer for Directions
* @return The serializer
*/
public static Serializer getDirectionSerializer() {
return get(EnumWrappers.getDirectionClass());
}
/**
* Gets the serializer for UUIDs
* @param optional If true, objects <b>must</b> be wrapped in an {@link Optional}
* @return The serializer
*/
public static Serializer getUUIDSerializer(boolean optional) {
return get(UUID.class, optional);
}
/**
* Gets the serializer for NBT Compound tags
* @return The serializer
*/
public static Serializer getNBTCompoundSerializer() {
return get(MinecraftReflection.getNBTCompoundClass(), false);
}
}
}