Refactor the wrapper classes so they inherit from a common class.
Dieser Commit ist enthalten in:
Ursprung
143ed2ff02
Commit
e8759d0b72
@ -43,6 +43,10 @@ public class PacketType implements Serializable {
|
||||
public static class Handshake {
|
||||
private static final Protocol PROTOCOL = Protocol.HANDSHAKING;
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
/**
|
||||
@ -92,6 +96,10 @@ public class PacketType implements Serializable {
|
||||
public static class Play {
|
||||
private static final Protocol PROTOCOL = Protocol.GAME;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
|
||||
@ -183,6 +191,10 @@ public class PacketType implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
||||
@ -236,6 +248,10 @@ public class PacketType implements Serializable {
|
||||
public static class Status {
|
||||
private static final Protocol PROTOCOL = Protocol.STATUS;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
|
||||
@ -256,6 +272,10 @@ public class PacketType implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
||||
@ -288,6 +308,10 @@ public class PacketType implements Serializable {
|
||||
public static class Login {
|
||||
private static final Protocol PROTOCOL = Protocol.LOGIN;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
|
||||
@ -309,6 +333,10 @@ public class PacketType implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
||||
@ -341,6 +369,10 @@ public class PacketType implements Serializable {
|
||||
public static class Legacy {
|
||||
private static final Protocol PROTOCOL = Protocol.LEGACY;
|
||||
|
||||
/**
|
||||
* Outgoing packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
// Missing server packets: [10, 11, 12, 21, 107, 252]
|
||||
public static class Server extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.SERVER;
|
||||
@ -377,6 +409,10 @@ public class PacketType implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
// Missing client packets: [1, 9, 255]
|
||||
public static class Client extends ObjectEnum<PacketType> {
|
||||
private final static Sender SENDER = Sender.CLIENT;
|
||||
|
@ -0,0 +1,50 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Represents a wrapper for an NMS object.
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class AbstractWrapper {
|
||||
protected Object handle;
|
||||
protected Class<?> handleType;
|
||||
|
||||
/**
|
||||
* Construct a new NMS wrapper.
|
||||
* @param handle - the wrapped NMS object.
|
||||
*/
|
||||
public AbstractWrapper(Class<?> handleType) {
|
||||
this.handleType = Preconditions.checkNotNull(handleType, "handleType cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the underlying NMS object.
|
||||
* @param handle - the NMS object.
|
||||
* @throws IllegalArgumentException If the handle is NULL.
|
||||
* @throws IllegalArgumentException If the handle is not assignable to {@link #getHandleType()}.
|
||||
*/
|
||||
protected void setHandle(Object handle) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be NULL.");
|
||||
if (!handleType.isAssignableFrom(handle.getClass()))
|
||||
throw new IllegalArgumentException("handle (" + handle + ") is not a " + handleType);
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying NMS object.
|
||||
* @return The underlying NMS object.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of the handle.
|
||||
* @return The type of the handle.
|
||||
*/
|
||||
public Class<?> getHandleType() {
|
||||
return handleType;
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ import com.google.common.collect.Sets;
|
||||
* Represents a single attribute sent in packet 44.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedAttribute {
|
||||
public class WrappedAttribute extends AbstractWrapper {
|
||||
// Shared structure modifier
|
||||
private static StructureModifier<Object> ATTRIBUTE_MODIFIER;
|
||||
|
||||
@ -43,6 +43,22 @@ public class WrappedAttribute {
|
||||
// Cached modifiers list
|
||||
private Set<WrappedAttributeModifier> attributeModifiers;
|
||||
|
||||
/**
|
||||
* Construct a wrapper around a specific NMS instance.
|
||||
* @param handle - the NMS instance.
|
||||
*/
|
||||
private WrappedAttribute(@Nonnull Object handle) {
|
||||
super(MinecraftReflection.getAttributeSnapshotClass());
|
||||
setHandle(handle);
|
||||
|
||||
// Initialize modifier
|
||||
if (ATTRIBUTE_MODIFIER == null) {
|
||||
ATTRIBUTE_MODIFIER = new StructureModifier<Object>(MinecraftReflection.getAttributeSnapshotClass());
|
||||
}
|
||||
this.modifier = ATTRIBUTE_MODIFIER.withTarget(handle);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new wrapped attribute around a specific NMS instance.
|
||||
* @param handle - handle to a NMS AttributeSnapshot.
|
||||
@ -70,33 +86,6 @@ public class WrappedAttribute {
|
||||
return new Builder(Preconditions.checkNotNull(template, "template cannot be NULL."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a wrapper around a specific NMS instance.
|
||||
* @param handle - the NMS instance.
|
||||
*/
|
||||
private WrappedAttribute(@Nonnull Object handle) {
|
||||
this.handle = Preconditions.checkNotNull(handle, "handle cannot be NULL.");
|
||||
|
||||
// Check handle type
|
||||
if (!MinecraftReflection.getAttributeSnapshotClass().isAssignableFrom(handle.getClass())) {
|
||||
throw new IllegalArgumentException("handle (" + handle + ") must be a AttributeSnapshot.");
|
||||
}
|
||||
|
||||
// Initialize modifier
|
||||
if (ATTRIBUTE_MODIFIER == null) {
|
||||
ATTRIBUTE_MODIFIER = new StructureModifier<Object>(MinecraftReflection.getAttributeSnapshotClass());
|
||||
}
|
||||
this.modifier = ATTRIBUTE_MODIFIER.withTarget(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying NMS attribute snapshot.
|
||||
* @return The underlying attribute snapshot.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique attribute key that identifies its function.
|
||||
* <p>
|
||||
|
@ -19,7 +19,7 @@ import com.google.common.base.Preconditions;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedAttributeModifier {
|
||||
public class WrappedAttributeModifier extends AbstractWrapper {
|
||||
/**
|
||||
* Represents the different modifier operations.
|
||||
* <p>
|
||||
@ -86,10 +86,7 @@ public class WrappedAttributeModifier {
|
||||
// The constructor we are interested in
|
||||
private static Constructor<?> ATTRIBUTE_MODIFIER_CONSTRUCTOR;
|
||||
|
||||
/**
|
||||
* Handle to the underlying AttributeModifier.
|
||||
*/
|
||||
protected Object handle;
|
||||
// A modifier for the wrapped handler
|
||||
protected StructureModifier<Object> modifier;
|
||||
|
||||
// Cached values
|
||||
@ -98,6 +95,58 @@ public class WrappedAttributeModifier {
|
||||
private final Operation operation;
|
||||
private final double amount;
|
||||
|
||||
/**
|
||||
* Construct a new wrapped attribute modifier with no associated handle.
|
||||
* <p>
|
||||
* Note that the handle object is not initialized after this constructor.
|
||||
* @param uuid - the UUID.
|
||||
* @param name - the human readable name.
|
||||
* @param amount - the amount.
|
||||
* @param operation - the operation.
|
||||
*/
|
||||
protected WrappedAttributeModifier(UUID uuid, String name, double amount, Operation operation) {
|
||||
super(MinecraftReflection.getAttributeModifierClass());
|
||||
|
||||
// Use the supplied values instead of reading from the NMS instance
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.amount = amount;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an attribute modifier wrapper around a given NMS instance.
|
||||
* @param handle - the NMS instance.
|
||||
*/
|
||||
protected WrappedAttributeModifier(@Nonnull Object handle) {
|
||||
// Update handle and modifier
|
||||
super(MinecraftReflection.getAttributeModifierClass());
|
||||
setHandle(handle);
|
||||
initializeModifier(handle);
|
||||
|
||||
// Load final values, caching them
|
||||
this.uuid = (UUID) modifier.withType(UUID.class).read(0);
|
||||
this.name = (String) modifier.withType(String.class).read(0);
|
||||
this.amount = (Double) modifier.withType(double.class).read(0);
|
||||
this.operation = Operation.fromId((Integer) modifier.withType(int.class).read(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an attribute modifier wrapper around a NMS instance.
|
||||
* @param handle - the NMS instance.
|
||||
* @param uuid - the UUID.
|
||||
* @param name - the human readable name.
|
||||
* @param amount - the amount.
|
||||
* @param operation - the operation.
|
||||
*/
|
||||
protected WrappedAttributeModifier(@Nonnull Object handle, UUID uuid, String name, double amount, Operation operation) {
|
||||
this(uuid, name, amount, operation);
|
||||
|
||||
// Initialize handle and modifier
|
||||
setHandle(handle);
|
||||
initializeModifier(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new attribute modifier builder.
|
||||
* <p>
|
||||
@ -136,53 +185,6 @@ public class WrappedAttributeModifier {
|
||||
return new WrappedAttributeModifier(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new wrapped attribute modifier with no associated handle.
|
||||
* @param uuid - the UUID.
|
||||
* @param name - the human readable name.
|
||||
* @param amount - the amount.
|
||||
* @param operation - the operation.
|
||||
*/
|
||||
protected WrappedAttributeModifier(UUID uuid, String name, double amount, Operation operation) {
|
||||
// Use the supplied values instead of reading from the NMS instance
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.amount = amount;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an attribute modifier wrapper around a given NMS instance.
|
||||
* @param handle - the NMS instance.
|
||||
*/
|
||||
protected WrappedAttributeModifier(@Nonnull Object handle) {
|
||||
// Update handle and modifier
|
||||
setHandle(handle);
|
||||
initializeModifier(handle);
|
||||
|
||||
// Load final values, caching them
|
||||
this.uuid = (UUID) modifier.withType(UUID.class).read(0);
|
||||
this.name = (String) modifier.withType(String.class).read(0);
|
||||
this.amount = (Double) modifier.withType(double.class).read(0);
|
||||
this.operation = Operation.fromId((Integer) modifier.withType(int.class).read(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an attribute modifier wrapper around a NMS instance.
|
||||
* @param handle - the NMS instance.
|
||||
* @param uuid - the UUID.
|
||||
* @param name - the human readable name.
|
||||
* @param amount - the amount.
|
||||
* @param operation - the operation.
|
||||
*/
|
||||
protected WrappedAttributeModifier(@Nonnull Object handle, UUID uuid, String name, double amount, Operation operation) {
|
||||
this(uuid, name, amount, operation);
|
||||
|
||||
// Initialize handle and modifier
|
||||
setHandle(handle);
|
||||
initializeModifier(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize modifier from a given handle.
|
||||
* @param handle - the handle.
|
||||
@ -196,17 +198,6 @@ public class WrappedAttributeModifier {
|
||||
this.modifier = BASE_MODIFIER.withTarget(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the handle of a modifier.
|
||||
* @param handle - the underlying handle.
|
||||
*/
|
||||
private void setHandle(Object handle) {
|
||||
// Check handle type
|
||||
if (!MinecraftReflection.getAttributeModifierClass().isAssignableFrom(handle.getClass()))
|
||||
throw new IllegalArgumentException("handle (" + handle + ") must be a AttributeModifier.");
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique UUID that identifies the origin of this modifier.
|
||||
* @return The unique UUID.
|
||||
@ -241,16 +232,6 @@ public class WrappedAttributeModifier {
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when we need to construct a handle object.
|
||||
*/
|
||||
protected void checkHandle() {
|
||||
if (handle == null) {
|
||||
handle = newBuilder(this).build().getHandle();
|
||||
initializeModifier(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying attribute modifier.
|
||||
* @return The underlying modifier.
|
||||
|
@ -11,7 +11,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
* Represents a chat component added in Minecraft 1.7.2
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedChatComponent {
|
||||
public class WrappedChatComponent extends AbstractWrapper {
|
||||
private static final Class<?> SERIALIZER = MinecraftReflection.getChatSerializer();
|
||||
private static final Class<?> COMPONENT = MinecraftReflection.getIChatBaseComponent();
|
||||
private static MethodAccessor SERIALIZE_COMPONENT = null;
|
||||
@ -32,11 +32,11 @@ public class WrappedChatComponent {
|
||||
MinecraftReflection.getCraftChatMessage(), "fromString", String.class);
|
||||
}
|
||||
|
||||
private Object handle;
|
||||
private transient String cache;
|
||||
|
||||
private WrappedChatComponent(Object handle, String cache) {
|
||||
this.handle = handle;
|
||||
super(MinecraftReflection.getIChatBaseComponent());
|
||||
setHandle(handle);
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@ -46,10 +46,6 @@ public class WrappedChatComponent {
|
||||
* @return The wrapper.
|
||||
*/
|
||||
public static WrappedChatComponent fromHandle(Object handle) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be NULL.");
|
||||
if (!COMPONENT.isAssignableFrom(handle.getClass()))
|
||||
throw new IllegalArgumentException("handle (" + handle + ") is not a " + COMPONENT);
|
||||
return new WrappedChatComponent(handle, null);
|
||||
}
|
||||
|
||||
@ -101,14 +97,6 @@ public class WrappedChatComponent {
|
||||
this.cache = obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying IChatBaseComponent instance.
|
||||
* @return The underlying instance.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
|
@ -26,27 +26,26 @@ import com.google.common.base.Objects;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate> {
|
||||
|
||||
public class WrappedChunkCoordinate extends AbstractWrapper implements Comparable<WrappedChunkCoordinate> {
|
||||
/**
|
||||
* If TRUE, NULLs should be put before non-null instances of this class.
|
||||
*/
|
||||
private static final boolean LARGER_THAN_NULL = true;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected Comparable handle;
|
||||
|
||||
// Used to access a ChunkCoordinate
|
||||
private static StructureModifier<Integer> intModifier;
|
||||
private static StructureModifier<Integer> SHARED_MODIFIER;
|
||||
|
||||
// The current modifier
|
||||
private StructureModifier<Integer> handleModifier;
|
||||
|
||||
/**
|
||||
* Create a new empty wrapper.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public WrappedChunkCoordinate() {
|
||||
super(MinecraftReflection.getChunkCoordinatesClass());
|
||||
|
||||
try {
|
||||
this.handle = (Comparable) MinecraftReflection.getChunkCoordinatesClass().newInstance();
|
||||
initializeModifier();
|
||||
setHandle(getHandleType().newInstance());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct chunk coordinate.");
|
||||
}
|
||||
@ -58,17 +57,17 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public WrappedChunkCoordinate(Comparable handle) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be NULL");
|
||||
this.handle = handle;
|
||||
initializeModifier();
|
||||
super(MinecraftReflection.getChunkCoordinatesClass());
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
// Ensure that the structure modifier is initialized
|
||||
private void initializeModifier() {
|
||||
if (intModifier == null) {
|
||||
intModifier = new StructureModifier<Object>(handle.getClass(), null, false).withType(int.class);
|
||||
}
|
||||
private StructureModifier<Integer> getModifier() {
|
||||
if (SHARED_MODIFIER == null)
|
||||
SHARED_MODIFIER = new StructureModifier<Object>(handle.getClass(), null, false).withType(int.class);
|
||||
if (handleModifier == null)
|
||||
handleModifier = SHARED_MODIFIER.withTarget(handle);
|
||||
return handleModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +100,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
* @return The x coordinate.
|
||||
*/
|
||||
public int getX() {
|
||||
return intModifier.read(0);
|
||||
return getModifier().read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,7 +108,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
* @param newX - the new x coordinate.
|
||||
*/
|
||||
public void setX(int newX) {
|
||||
intModifier.write(0, newX);
|
||||
getModifier().write(0, newX);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,7 +116,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
* @return The y coordinate.
|
||||
*/
|
||||
public int getY() {
|
||||
return intModifier.read(1);
|
||||
return getModifier().read(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,7 +124,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
* @param newY - the new y coordinate.
|
||||
*/
|
||||
public void setY(int newY) {
|
||||
intModifier.write(1, newY);
|
||||
getModifier().write(1, newY);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +132,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
* @return The z coordinate.
|
||||
*/
|
||||
public int getZ() {
|
||||
return intModifier.read(2);
|
||||
return getModifier().read(2);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,7 +140,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
* @param newZ - the new z coordinate.
|
||||
*/
|
||||
public void setZ(int newZ) {
|
||||
intModifier.write(2, newZ);
|
||||
getModifier().write(2, newZ);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +158,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
||||
if (other.handle == null)
|
||||
return LARGER_THAN_NULL ? -1 : 1;
|
||||
else
|
||||
return handle.compareTo(other.handle);
|
||||
return ((Comparable<Object>) handle).compareTo(other.handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,35 +52,32 @@ import com.google.common.collect.Iterators;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
public class WrappedDataWatcher extends AbstractWrapper implements Iterable<WrappedWatchableObject> {
|
||||
/**
|
||||
* Used to assign integer IDs to given types.
|
||||
*/
|
||||
private static Map<Class<?>, Integer> typeMap;
|
||||
private static Map<Class<?>, Integer> TYPE_MAP;
|
||||
|
||||
// Fields
|
||||
private static Field valueMapField;
|
||||
private static Field readWriteLockField;
|
||||
private static Field entityField;
|
||||
private static Field VALUE_MAP_FIELD;
|
||||
private static Field READ_WRITE_LOCK_FIELD;
|
||||
private static Field ENTITY_FIELD;
|
||||
|
||||
// Methods
|
||||
private static Method createKeyValueMethod;
|
||||
private static Method updateKeyValueMethod;
|
||||
private static Method getKeyValueMethod;
|
||||
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<?> createDataWatcherConstructor;
|
||||
private static Constructor<?> CREATE_DATA_WATCHER_CONSTRUCTOR;
|
||||
|
||||
// Entity methods
|
||||
private volatile static Field entityDataField;
|
||||
private volatile static Field ENTITY_DATA_FIELD;
|
||||
|
||||
/**
|
||||
* Whether or not this class has already been initialized.
|
||||
*/
|
||||
private static boolean hasInitialized;
|
||||
|
||||
// The underlying DataWatcher we're modifying
|
||||
protected Object handle;
|
||||
private static boolean HAS_INITIALIZED;
|
||||
|
||||
// Lock
|
||||
private ReadWriteLock readWriteLock;
|
||||
@ -96,12 +93,14 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher() {
|
||||
super(MinecraftReflection.getDataWatcherClass());
|
||||
|
||||
// Just create a new watcher
|
||||
try {
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
this.handle = newEntityHandle(null);
|
||||
setHandle(newEntityHandle(null));
|
||||
} else {
|
||||
this.handle = MinecraftReflection.getDataWatcherClass().newInstance();
|
||||
setHandle(getHandleType().newInstance());
|
||||
}
|
||||
initialize();
|
||||
|
||||
@ -116,12 +115,8 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher(Object handle) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("Handle cannot be NULL.");
|
||||
if (!MinecraftReflection.isDataWatcher(handle))
|
||||
throw new IllegalArgumentException("The value " + handle + " is not a DataWatcher.");
|
||||
|
||||
this.handle = handle;
|
||||
super(MinecraftReflection.getDataWatcherClass());
|
||||
setHandle(handle);
|
||||
initialize();
|
||||
}
|
||||
|
||||
@ -150,10 +145,10 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
Class<?> dataWatcher = MinecraftReflection.getDataWatcherClass();
|
||||
|
||||
try {
|
||||
if (createDataWatcherConstructor == null)
|
||||
createDataWatcherConstructor = dataWatcher.getConstructor(MinecraftReflection.getEntityClass());
|
||||
if (CREATE_DATA_WATCHER_CONSTRUCTOR == null)
|
||||
CREATE_DATA_WATCHER_CONSTRUCTOR = dataWatcher.getConstructor(MinecraftReflection.getEntityClass());
|
||||
|
||||
return createDataWatcherConstructor.newInstance(
|
||||
return CREATE_DATA_WATCHER_CONSTRUCTOR.newInstance(
|
||||
BukkitUnwrapper.getInstance().unwrapItem(entity)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
@ -190,14 +185,6 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying data watcher.
|
||||
* @return The underlying data watcher.
|
||||
*/
|
||||
public Object 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.
|
||||
@ -205,7 +192,7 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
*/
|
||||
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
|
||||
initialize();
|
||||
return typeMap.get(WrappedWatchableObject.getUnwrappedType(clazz));
|
||||
return TYPE_MAP.get(WrappedWatchableObject.getUnwrappedType(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,7 +203,7 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
public static Class<?> getTypeClass(int id) throws FieldAccessException {
|
||||
initialize();
|
||||
|
||||
for (Map.Entry<Class<?>, Integer> entry : typeMap.entrySet()) {
|
||||
for (Map.Entry<Class<?>, Integer> entry : TYPE_MAP.entrySet()) {
|
||||
if (Objects.equal(entry.getValue(), id)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
@ -462,7 +449,7 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
if (watchable != null) {
|
||||
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
||||
} else {
|
||||
createKeyValueMethod.invoke(handle, index, WrappedWatchableObject.getUnwrapped(newValue));
|
||||
CREATE_KEY_VALUE_METHOD.invoke(handle, index, WrappedWatchableObject.getUnwrapped(newValue));
|
||||
}
|
||||
|
||||
// Handle invoking the method
|
||||
@ -479,9 +466,9 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
|
||||
private Object getWatchedObject(int index) throws FieldAccessException {
|
||||
// We use the get-method first and foremost
|
||||
if (getKeyValueMethod != null) {
|
||||
if (GET_KEY_VALUE_METHOD != null) {
|
||||
try {
|
||||
return getKeyValueMethod.invoke(handle, index);
|
||||
return GET_KEY_VALUE_METHOD.invoke(handle, index);
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
|
||||
}
|
||||
@ -506,8 +493,8 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
// Cache the read write lock
|
||||
if (readWriteLock != null)
|
||||
return readWriteLock;
|
||||
else if (readWriteLockField != null)
|
||||
return readWriteLock = (ReadWriteLock) FieldUtils.readField(readWriteLockField, handle, true);
|
||||
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) {
|
||||
@ -524,7 +511,7 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
protected Map<Integer, Object> getWatchableObjectMap() throws FieldAccessException {
|
||||
if (watchableObjects == null) {
|
||||
try {
|
||||
watchableObjects = (Map<Integer, Object>) FieldUtils.readField(valueMapField, handle, true);
|
||||
watchableObjects = (Map<Integer, Object>) FieldUtils.readField(VALUE_MAP_FIELD, handle, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot read watchable object field.", e);
|
||||
}
|
||||
@ -539,14 +526,14 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException {
|
||||
if (entityDataField == null)
|
||||
entityDataField = FuzzyReflection.fromClass(MinecraftReflection.getEntityClass(), true).
|
||||
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(entityDataField, unwrapper.unwrapItem(entity), true);
|
||||
Object nsmWatcher = FieldUtils.readField(ENTITY_DATA_FIELD, unwrapper.unwrapItem(entity), true);
|
||||
|
||||
if (nsmWatcher != null)
|
||||
return new WrappedDataWatcher(nsmWatcher);
|
||||
@ -564,8 +551,8 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
@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;
|
||||
if (!HAS_INITIALIZED)
|
||||
HAS_INITIALIZED = true;
|
||||
else
|
||||
return;
|
||||
|
||||
@ -575,27 +562,27 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
if (Modifier.isStatic(lookup.getModifiers())) {
|
||||
// This must be the type map
|
||||
try {
|
||||
typeMap = (Map<Class<?>, Integer>) FieldUtils.readStaticField(lookup, true);
|
||||
TYPE_MAP = (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;
|
||||
VALUE_MAP_FIELD = lookup;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
readWriteLockField = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
|
||||
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()) {
|
||||
entityField = fuzzy.getFieldByType("entity", MinecraftReflection.getEntityClass());
|
||||
entityField.setAccessible(true);
|
||||
ENTITY_FIELD = fuzzy.getFieldByType("entity", MinecraftReflection.getEntityClass());
|
||||
ENTITY_FIELD.setAccessible(true);
|
||||
}
|
||||
initializeMethods(fuzzy);
|
||||
}
|
||||
@ -606,9 +593,9 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
|
||||
// Load the get-method
|
||||
try {
|
||||
getKeyValueMethod = fuzzy.getMethodByParameters(
|
||||
GET_KEY_VALUE_METHOD = fuzzy.getMethodByParameters(
|
||||
"getWatchableObject", MinecraftReflection.getWatchableObjectClass(), new Class[] { int.class });
|
||||
getKeyValueMethod.setAccessible(true);
|
||||
GET_KEY_VALUE_METHOD.setAccessible(true);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Use the fallback method
|
||||
@ -616,18 +603,18 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
|
||||
for (Method method : candidates) {
|
||||
if (!method.getName().startsWith("watch")) {
|
||||
createKeyValueMethod = method;
|
||||
CREATE_KEY_VALUE_METHOD = method;
|
||||
} else {
|
||||
updateKeyValueMethod = method;
|
||||
UPDATE_KEY_VALUE_METHOD = method;
|
||||
}
|
||||
}
|
||||
|
||||
// Did we succeed?
|
||||
if (updateKeyValueMethod == null || createKeyValueMethod == null) {
|
||||
if (UPDATE_KEY_VALUE_METHOD == null || CREATE_KEY_VALUE_METHOD == null) {
|
||||
// Go by index instead
|
||||
if (candidates.size() > 1) {
|
||||
createKeyValueMethod = candidates.get(0);
|
||||
updateKeyValueMethod = candidates.get(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.");
|
||||
}
|
||||
@ -643,8 +630,8 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Nope
|
||||
updateKeyValueMethod = candidates.get(0);
|
||||
createKeyValueMethod = candidates.get(1);
|
||||
UPDATE_KEY_VALUE_METHOD = candidates.get(0);
|
||||
CREATE_KEY_VALUE_METHOD = candidates.get(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -709,7 +696,7 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
|
||||
|
||||
try {
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(entityField.get(handle));
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(ENTITY_FIELD.get(handle));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to retrieve entity.", e);
|
||||
}
|
||||
@ -726,7 +713,7 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
throw new IllegalStateException("This method is only supported on 1.7.2 and above.");
|
||||
|
||||
try {
|
||||
entityField.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity));
|
||||
ENTITY_FIELD.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to set entity.", e);
|
||||
}
|
||||
|
@ -6,16 +6,11 @@ import net.minecraft.util.com.mojang.authlib.GameProfile;
|
||||
* Represents a wrapper for a game profile.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedGameProfile {
|
||||
private GameProfile profile;
|
||||
|
||||
public class WrappedGameProfile extends AbstractWrapper {
|
||||
// Profile from a handle
|
||||
private WrappedGameProfile(Object profile) {
|
||||
if (profile == null)
|
||||
throw new IllegalArgumentException("Profile cannot be NULL.");
|
||||
if (!(profile instanceof GameProfile))
|
||||
throw new IllegalArgumentException(profile + " is not a GameProfile");
|
||||
this.profile = (GameProfile) profile;
|
||||
super(GameProfile.class);
|
||||
setHandle(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,20 +30,12 @@ public class WrappedGameProfile {
|
||||
return new WrappedGameProfile(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying game profile.
|
||||
* @return The profile.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the UUID of the player.
|
||||
* @return The UUID of the player, or NULL if not computed.
|
||||
*/
|
||||
public String getId() {
|
||||
return profile.getId();
|
||||
return getProfile().getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +43,11 @@ public class WrappedGameProfile {
|
||||
* @return The player name.
|
||||
*/
|
||||
public String getName() {
|
||||
return profile.getName();
|
||||
return getProfile().getName();
|
||||
}
|
||||
|
||||
private GameProfile getProfile() {
|
||||
return (GameProfile) handle;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,12 +73,12 @@ public class WrappedGameProfile {
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean isComplete() {
|
||||
return profile.isComplete();
|
||||
return getProfile().isComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return profile.hashCode();
|
||||
return getProfile().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -97,7 +88,7 @@ public class WrappedGameProfile {
|
||||
|
||||
if (obj instanceof WrappedGameProfile) {
|
||||
WrappedGameProfile other = (WrappedGameProfile) obj;
|
||||
return profile.equals(other.profile);
|
||||
return getProfile().equals(other.getProfile());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -15,21 +15,18 @@ import com.google.common.base.Preconditions;
|
||||
* Represents a wrapper for the internal IntHashMap in Minecraft.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedIntHashMap {
|
||||
private static final Class<?> INT_HASH_MAP = MinecraftReflection.getIntHashMapClass();
|
||||
|
||||
public class WrappedIntHashMap extends AbstractWrapper {
|
||||
private static Method PUT_METHOD;
|
||||
private static Method GET_METHOD;
|
||||
private static Method REMOVE_METHOD;
|
||||
|
||||
private Object handle;
|
||||
|
||||
/**
|
||||
* Construct an IntHashMap wrapper around an instance.
|
||||
* @param handle - the NMS instance.
|
||||
*/
|
||||
private WrappedIntHashMap(Object handle) {
|
||||
this.handle = handle;
|
||||
super(MinecraftReflection.getIntHashMapClass());
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,7 +35,7 @@ public class WrappedIntHashMap {
|
||||
*/
|
||||
public static WrappedIntHashMap newMap() {
|
||||
try {
|
||||
return new WrappedIntHashMap(INT_HASH_MAP.newInstance());
|
||||
return new WrappedIntHashMap(MinecraftReflection.getIntHashMapClass().newInstance());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to construct IntHashMap.", e);
|
||||
}
|
||||
@ -51,10 +48,6 @@ public class WrappedIntHashMap {
|
||||
* @throws IllegalArgumentException If the handle is not an IntHasMap.
|
||||
*/
|
||||
public static WrappedIntHashMap fromHandle(@Nonnull Object handle) {
|
||||
Preconditions.checkNotNull(handle, "handle cannot be NULL");
|
||||
Preconditions.checkState(MinecraftReflection.isIntHashMap(handle),
|
||||
"handle is a " + handle.getClass() + ", not an IntHashMap.");
|
||||
|
||||
return new WrappedIntHashMap(handle);
|
||||
}
|
||||
|
||||
@ -138,7 +131,7 @@ public class WrappedIntHashMap {
|
||||
private void initializePutMethod() {
|
||||
if (PUT_METHOD == null) {
|
||||
// Fairly straight forward
|
||||
PUT_METHOD = FuzzyReflection.fromClass(INT_HASH_MAP).getMethod(
|
||||
PUT_METHOD = FuzzyReflection.fromClass(MinecraftReflection.getIntHashMapClass()).getMethod(
|
||||
FuzzyMethodContract.newBuilder().
|
||||
banModifier(Modifier.STATIC).
|
||||
parameterCount(2).
|
||||
@ -154,7 +147,7 @@ public class WrappedIntHashMap {
|
||||
String expected = "hello";
|
||||
|
||||
// Determine which method to trust
|
||||
for (Method method : FuzzyReflection.fromClass(INT_HASH_MAP).
|
||||
for (Method method : FuzzyReflection.fromClass(MinecraftReflection.getIntHashMapClass()).
|
||||
getMethodListByParameters(Object.class, new Class<?>[] { int.class })) {
|
||||
|
||||
// Initialize a value
|
||||
@ -183,12 +176,4 @@ public class WrappedIntHashMap {
|
||||
throw new IllegalStateException("Unable to find appropriate GET_METHOD for IntHashMap.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying IntHashMap object.
|
||||
* @return The underlying object.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,7 @@ import com.google.common.base.Objects;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedWatchableObject {
|
||||
|
||||
public class WrappedWatchableObject extends AbstractWrapper {
|
||||
// Whether or not the reflection machinery has been initialized
|
||||
private static boolean hasInitialized;
|
||||
|
||||
@ -47,7 +46,6 @@ public class WrappedWatchableObject {
|
||||
// The watchable object class type
|
||||
private static Class<?> watchableObjectClass;
|
||||
|
||||
protected Object handle;
|
||||
protected StructureModifier<Object> modifier;
|
||||
|
||||
// Type of the stored value
|
||||
@ -58,6 +56,7 @@ public class WrappedWatchableObject {
|
||||
* @param handle - the raw watchable object to wrap.
|
||||
*/
|
||||
public WrappedWatchableObject(Object handle) {
|
||||
super(MinecraftReflection.getWatchableObjectClass());
|
||||
load(handle);
|
||||
}
|
||||
|
||||
@ -67,6 +66,8 @@ public class WrappedWatchableObject {
|
||||
* @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.");
|
||||
|
||||
@ -107,14 +108,6 @@ public class WrappedWatchableObject {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying watchable object.
|
||||
* @return The underlying watchable object.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize reflection machinery.
|
||||
*/
|
||||
|
@ -0,0 +1,24 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
|
||||
public class WrappedChunkCoordinateTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
WrappedChunkCoordinate coordinate = new WrappedChunkCoordinate(1, 2, 3);
|
||||
|
||||
assertEquals(1, coordinate.getX());
|
||||
assertEquals(2, coordinate.getY());
|
||||
assertEquals(3, coordinate.getZ());
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
|
||||
public class WrappedIntHashMapTest {
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
BukkitInitialization.initializePackage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntMap() {
|
||||
WrappedIntHashMap test = WrappedIntHashMap.newMap();
|
||||
test.put(1, "hello");
|
||||
|
||||
assertNull(test.get(0));
|
||||
assertEquals(test.get(1), "hello");
|
||||
}
|
||||
}
|
In neuem Issue referenzieren
Einen Benutzer sperren