Merge branch 'master' into gh-pages
Dieser Commit ist enthalten in:
Commit
0129a0575c
@ -4,7 +4,7 @@
|
|||||||
<groupId>com.comphenix.protocol</groupId>
|
<groupId>com.comphenix.protocol</groupId>
|
||||||
<artifactId>ProtocolLib</artifactId>
|
<artifactId>ProtocolLib</artifactId>
|
||||||
<name>ProtocolLib</name>
|
<name>ProtocolLib</name>
|
||||||
<version>1.7.0</version>
|
<version>1.7.1</version>
|
||||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||||
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
|
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
|
||||||
<developers>
|
<developers>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.comphenix.protocol</groupId>
|
<groupId>com.comphenix.protocol</groupId>
|
||||||
<artifactId>ProtocolLib</artifactId>
|
<artifactId>ProtocolLib</artifactId>
|
||||||
<version>1.7.0</version>
|
<version>1.7.1</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ import java.util.logging.LogRecord;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.PluginCommand;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
@ -187,8 +189,8 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set up command handlers
|
// Set up command handlers
|
||||||
getCommand(CommandProtocol.NAME).setExecutor(commandProtocol);
|
registerCommand(CommandProtocol.NAME, commandProtocol);
|
||||||
getCommand(CommandPacket.NAME).setExecutor(commandPacket);
|
registerCommand(CommandPacket.NAME, commandPacket);
|
||||||
|
|
||||||
// Notify server managers of incompatible plugins
|
// Notify server managers of incompatible plugins
|
||||||
checkForIncompatibility(manager);
|
checkForIncompatibility(manager);
|
||||||
@ -218,6 +220,24 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerCommand(String name, CommandExecutor executor) {
|
||||||
|
try {
|
||||||
|
if (executor == null)
|
||||||
|
throw new RuntimeException("Executor was NULL.");
|
||||||
|
|
||||||
|
PluginCommand command = getCommand(name);
|
||||||
|
|
||||||
|
// Try to load the command
|
||||||
|
if (command != null)
|
||||||
|
command.setExecutor(executor);
|
||||||
|
else
|
||||||
|
throw new RuntimeException("plugin.yml might be corrupt.");
|
||||||
|
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
reporter.reportWarning(this, "Cannot register command " + name + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable the current plugin.
|
* Disable the current plugin.
|
||||||
*/
|
*/
|
||||||
|
@ -130,7 +130,7 @@ public interface ProtocolManager extends PacketStream {
|
|||||||
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completely refresh all clients about an entity.
|
* Completely resend an entity to a list of clients.
|
||||||
* <p>
|
* <p>
|
||||||
* Note that this method is NOT thread safe. If you call this method from anything
|
* Note that this method is NOT thread safe. If you call this method from anything
|
||||||
* but the main thread, it will throw an exception.
|
* but the main thread, it will throw an exception.
|
||||||
@ -148,6 +148,14 @@ public interface ProtocolManager extends PacketStream {
|
|||||||
*/
|
*/
|
||||||
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
|
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every client that is receiving information about a given entity.
|
||||||
|
* @param entity - the entity that is being tracked.
|
||||||
|
* @return Every client/player that is tracking the given entity.
|
||||||
|
* @throws FieldAccessException If reflection failed.
|
||||||
|
*/
|
||||||
|
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
||||||
* @return Every filtered server packet.
|
* @return Every filtered server packet.
|
||||||
|
@ -132,7 +132,7 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
* Removes every interval that intersects with the given range.
|
* Removes every interval that intersects with the given range.
|
||||||
* @param lowerBound - lowest value to remove.
|
* @param lowerBound - lowest value to remove.
|
||||||
* @param upperBound - highest value to remove.
|
* @param upperBound - highest value to remove.
|
||||||
* @param preserveOutside - whether or not to preserve the intervals that are partially outside.
|
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
|
||||||
*/
|
*/
|
||||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
|
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
|
||||||
checkBounds(lowerBound, upperBound);
|
checkBounds(lowerBound, upperBound);
|
||||||
|
@ -0,0 +1,165 @@
|
|||||||
|
package com.comphenix.protocol.concurrency;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map that supports blocking on read operations. Null keys are not supported.
|
||||||
|
* <p>
|
||||||
|
* Keys are stored as weak references, and will be automatically removed once they've all been dereferenced.
|
||||||
|
* <p>
|
||||||
|
* @author Kristian
|
||||||
|
*
|
||||||
|
* @param <TKey> - type of the key.
|
||||||
|
* @param <TValue> - type of the value.
|
||||||
|
*/
|
||||||
|
public class BlockingHashMap<TKey, TValue> {
|
||||||
|
|
||||||
|
// Map of values
|
||||||
|
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||||
|
|
||||||
|
// Map of locked objects
|
||||||
|
private final ConcurrentMap<TKey, Object> locks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new map.
|
||||||
|
*/
|
||||||
|
public BlockingHashMap() {
|
||||||
|
backingMap = new MapMaker().weakKeys().makeMap();
|
||||||
|
locks = new MapMaker().weakKeys().makeMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new map.
|
||||||
|
* @return The created map.
|
||||||
|
*/
|
||||||
|
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
|
||||||
|
return new BlockingHashMap<TKey, TValue>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||||
|
* @param key - the key whose associated value is to be returned
|
||||||
|
* @return The value to which the specified key is mapped.
|
||||||
|
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||||
|
*/
|
||||||
|
public TValue get(TKey key) throws InterruptedException {
|
||||||
|
if (key == null)
|
||||||
|
throw new IllegalArgumentException("key cannot be NULL.");
|
||||||
|
|
||||||
|
TValue value = backingMap.get(key);
|
||||||
|
|
||||||
|
// Only lock if no value is available
|
||||||
|
if (value == null) {
|
||||||
|
final Object lock = getLock(key);
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
while (value == null) {
|
||||||
|
lock.wait();
|
||||||
|
value = backingMap.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||||
|
* @param key - the key whose associated value is to be returned
|
||||||
|
* @param timeout - the amount of time to wait until an association has been made.
|
||||||
|
* @param unit - unit of timeout.
|
||||||
|
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||||
|
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||||
|
*/
|
||||||
|
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
if (key == null)
|
||||||
|
throw new IllegalArgumentException("key cannot be NULL.");
|
||||||
|
if (unit == null)
|
||||||
|
throw new IllegalArgumentException("Unit cannot be NULL.");
|
||||||
|
|
||||||
|
TValue value = backingMap.get(key);
|
||||||
|
|
||||||
|
// Only lock if no value is available
|
||||||
|
if (value == null) {
|
||||||
|
final Object lock = getLock(key);
|
||||||
|
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||||
|
|
||||||
|
// Don't exceed the timeout
|
||||||
|
synchronized (lock) {
|
||||||
|
while (value == null) {
|
||||||
|
long remainingTime = stopTimeNS - System.nanoTime();
|
||||||
|
|
||||||
|
if (remainingTime > 0) {
|
||||||
|
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||||
|
value = backingMap.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate a given key with the given value.
|
||||||
|
* <p>
|
||||||
|
* Wakes up any blocking getters on this specific key.
|
||||||
|
*
|
||||||
|
* @param key - the key to associate.
|
||||||
|
* @param value - the value.
|
||||||
|
* @return The previously associated value.
|
||||||
|
*/
|
||||||
|
public TValue put(TKey key, TValue value) {
|
||||||
|
if (value == null)
|
||||||
|
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||||
|
|
||||||
|
final TValue previous = backingMap.put(key, value);
|
||||||
|
final Object lock = getLock(key);
|
||||||
|
|
||||||
|
// Inform our readers about this change
|
||||||
|
synchronized (lock) {
|
||||||
|
lock.notifyAll();
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return backingMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<TValue> values() {
|
||||||
|
return backingMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<TKey> keys() {
|
||||||
|
return backingMap.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomically retrieve the lock associated with a given key.
|
||||||
|
* @param key - the current key.
|
||||||
|
* @return An asssociated lock.
|
||||||
|
*/
|
||||||
|
private Object getLock(TKey key) {
|
||||||
|
Object lock = locks.get(key);
|
||||||
|
|
||||||
|
if (lock == null) {
|
||||||
|
Object created = new Object();
|
||||||
|
|
||||||
|
// Do this atomically
|
||||||
|
lock = locks.putIfAbsent(key, created);
|
||||||
|
|
||||||
|
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
|
||||||
|
if (lock == null) {
|
||||||
|
lock = created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.events;
|
package com.comphenix.protocol.events;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -194,6 +196,14 @@ public class PacketContainer implements Serializable {
|
|||||||
return structureModifier.withType(byte[].class);
|
return structureModifier.withType(byte[].class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a read/write structure for every int array field.
|
||||||
|
* @return A modifier for every int array field.
|
||||||
|
*/
|
||||||
|
public StructureModifier<int[]> getIntegerArrays() {
|
||||||
|
return structureModifier.withType(int[].class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a read/write structure for ItemStack.
|
* Retrieves a read/write structure for ItemStack.
|
||||||
* <p>
|
* <p>
|
||||||
@ -347,6 +357,42 @@ public class PacketContainer implements Serializable {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a deep copy of the current packet.
|
||||||
|
* @return A deep copy of the current packet.
|
||||||
|
*/
|
||||||
|
public PacketContainer deepClone() {
|
||||||
|
ObjectOutputStream output = null;
|
||||||
|
ObjectInputStream input = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use a small buffer of 32 bytes initially.
|
||||||
|
ByteArrayOutputStream bufferOut = new ByteArrayOutputStream();
|
||||||
|
output = new ObjectOutputStream(bufferOut);
|
||||||
|
output.writeObject(this);
|
||||||
|
|
||||||
|
ByteArrayInputStream bufferIn = new ByteArrayInputStream(bufferOut.toByteArray());
|
||||||
|
input = new ObjectInputStream(bufferIn);
|
||||||
|
return (PacketContainer) input.readObject();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Unexpected error occured during object cloning.", e);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Cannot happen
|
||||||
|
throw new IllegalStateException("Unexpected failure with serialization.", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (output != null)
|
||||||
|
output.close();
|
||||||
|
if (input != null)
|
||||||
|
input.close();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// STOP IT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||||
// Default serialization
|
// Default serialization
|
||||||
output.defaultWriteObject();
|
output.defaultWriteObject();
|
||||||
|
@ -21,12 +21,14 @@ import java.lang.reflect.Constructor;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import net.minecraft.server.EntityPlayer;
|
||||||
import net.minecraft.server.EntityTrackerEntry;
|
import net.minecraft.server.EntityTrackerEntry;
|
||||||
|
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
@ -118,7 +120,39 @@ class EntityUtilities {
|
|||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
|
throw new FieldAccessException("Cannot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every client that is receiving information about a given entity.
|
||||||
|
* @param entity - the entity that is being tracked.
|
||||||
|
* @return Every client/player that is tracking the given entity.
|
||||||
|
* @throws FieldAccessException If reflection failed.
|
||||||
|
*/
|
||||||
|
public static List<Player> getEntityTrackers(Entity entity) {
|
||||||
|
try {
|
||||||
|
List<Player> result = new ArrayList<Player>();
|
||||||
|
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
|
||||||
|
|
||||||
|
if (trackedPlayersField == null)
|
||||||
|
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
|
||||||
|
|
||||||
|
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
|
||||||
|
|
||||||
|
// Wrap every player - we also ensure that the underlying tracker list is immutable
|
||||||
|
for (Object tracker : trackedPlayers) {
|
||||||
|
if (tracker instanceof EntityPlayer) {
|
||||||
|
EntityPlayer nmsPlayer = (EntityPlayer) tracker;
|
||||||
|
result.add(nmsPlayer.getBukkitEntity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new FieldAccessException("Security limitation prevented access to the list of tracked players.", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new FieldAccessException("Exception occurred in Minecraft.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,6 +571,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
return EntityUtilities.getEntityFromID(container, id);
|
return EntityUtilities.getEntityFromID(container, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
|
||||||
|
return EntityUtilities.getEntityTrackers(entity);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the packet injection for every player.
|
* Initialize the packet injection for every player.
|
||||||
* @param players - list of players to inject.
|
* @param players - list of players to inject.
|
||||||
|
@ -202,13 +202,20 @@ class PacketInjector {
|
|||||||
|
|
||||||
// Called from the ReadPacketModified monitor
|
// Called from the ReadPacketModified monitor
|
||||||
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
|
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
|
||||||
Player client = playerInjection.getPlayerByConnection(input);
|
try {
|
||||||
|
Player client = playerInjection.getPlayerByConnection(input);
|
||||||
|
|
||||||
// Never invoke a event if we don't know where it's from
|
// Never invoke a event if we don't know where it's from
|
||||||
if (client != null)
|
if (client != null)
|
||||||
return packetRecieved(packet, client);
|
return packetRecieved(packet, client);
|
||||||
else
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// We will ignore this - it occurs when a player disconnects
|
||||||
|
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package com.comphenix.protocol.injector.player;
|
package com.comphenix.protocol.injector.player;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -29,8 +26,6 @@ class NetLoginInjector {
|
|||||||
// The current error rerporter
|
// The current error rerporter
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
|
|
||||||
private ReadWriteLock injectionLock = new ReentrantReadWriteLock();
|
|
||||||
|
|
||||||
// Used to create fake players
|
// Used to create fake players
|
||||||
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
||||||
|
|
||||||
@ -46,9 +41,6 @@ class NetLoginInjector {
|
|||||||
* @return An injected NetLoginHandler, or the original object.
|
* @return An injected NetLoginHandler, or the original object.
|
||||||
*/
|
*/
|
||||||
public Object onNetLoginCreated(Object inserting) {
|
public Object onNetLoginCreated(Object inserting) {
|
||||||
|
|
||||||
injectionLock.writeLock().lock();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make sure we actually need to inject during this phase
|
// Make sure we actually need to inject during this phase
|
||||||
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
||||||
@ -73,19 +65,9 @@ class NetLoginInjector {
|
|||||||
reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting);
|
reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting);
|
||||||
return inserting;
|
return inserting;
|
||||||
|
|
||||||
} finally {
|
|
||||||
injectionLock.writeLock().unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the lock used for reading.
|
|
||||||
* @return Reading lock.
|
|
||||||
*/
|
|
||||||
public Lock getReadLock() {
|
|
||||||
return injectionLock.readLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a NetLoginHandler should be reverted.
|
* Invoked when a NetLoginHandler should be reverted.
|
||||||
* @param inserting - the original NetLoginHandler.
|
* @param inserting - the original NetLoginHandler.
|
||||||
|
@ -24,12 +24,14 @@ import java.net.Socket;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import net.minecraft.server.Packet;
|
import net.minecraft.server.Packet;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.events.PacketAdapter;
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
@ -47,6 +49,10 @@ import com.google.common.collect.Maps;
|
|||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class PlayerInjectionHandler {
|
public class PlayerInjectionHandler {
|
||||||
|
/**
|
||||||
|
* The maximum number of milliseconds to wait until a player can be looked up by connection.
|
||||||
|
*/
|
||||||
|
private static final long TIMEOUT_PLAYER_LOOKUP = 1000; // ms
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The highest possible packet ID. It's unlikely that this value will ever change.
|
* The highest possible packet ID. It's unlikely that this value will ever change.
|
||||||
@ -64,9 +70,11 @@ public class PlayerInjectionHandler {
|
|||||||
|
|
||||||
// Player injection
|
// Player injection
|
||||||
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
|
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
|
||||||
private Map<DataInputStream, PlayerInjector> dataInputLookup = Maps.newConcurrentMap();
|
|
||||||
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
|
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
|
||||||
|
|
||||||
|
// Lookup player by connection
|
||||||
|
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create();
|
||||||
|
|
||||||
// Player injection types
|
// Player injection types
|
||||||
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||||
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||||
@ -193,26 +201,37 @@ public class PlayerInjectionHandler {
|
|||||||
* Retrieve a player by its DataInput connection.
|
* Retrieve a player by its DataInput connection.
|
||||||
* @param inputStream - the associated DataInput connection.
|
* @param inputStream - the associated DataInput connection.
|
||||||
* @return The player.
|
* @return The player.
|
||||||
|
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||||
*/
|
*/
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream) {
|
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
|
||||||
try {
|
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS);
|
||||||
// Concurrency issue!
|
}
|
||||||
netLoginInjector.getReadLock().lock();
|
|
||||||
|
|
||||||
PlayerInjector injector = dataInputLookup.get(inputStream);
|
/**
|
||||||
|
* Retrieve a player by its DataInput connection.
|
||||||
|
* @param inputStream - the associated DataInput connection.
|
||||||
|
* @param playerTimeout - the amount of time to wait for a result.
|
||||||
|
* @param unit - unit of playerTimeout.
|
||||||
|
* @return The player.
|
||||||
|
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||||
|
*/
|
||||||
|
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
// Wait until the connection owner has been established
|
||||||
|
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit);
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
return injector.getPlayer();
|
return injector.getPlayer();
|
||||||
} else {
|
} else {
|
||||||
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
|
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
netLoginInjector.getReadLock().unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that retrieves the injector type of a given player injector.
|
||||||
|
* @param injector - injector type.
|
||||||
|
* @return The injector type.
|
||||||
|
*/
|
||||||
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
|
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
|
||||||
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
|
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
|
||||||
}
|
}
|
||||||
@ -404,7 +423,6 @@ public class PlayerInjectionHandler {
|
|||||||
PlayerInjector injector = playerInjection.remove(player);
|
PlayerInjector injector = playerInjection.remove(player);
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
DataInputStream input = injector.getInputStream(true);
|
|
||||||
InetSocketAddress address = player.getAddress();
|
InetSocketAddress address = player.getAddress();
|
||||||
injector.cleanupAll();
|
injector.cleanupAll();
|
||||||
|
|
||||||
@ -423,8 +441,7 @@ public class PlayerInjectionHandler {
|
|||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
if (removeAuxiliary) {
|
if (removeAuxiliary) {
|
||||||
if (input != null)
|
// Note that the dataInputLookup will clean itself
|
||||||
dataInputLookup.remove(input);
|
|
||||||
if (address != null)
|
if (address != null)
|
||||||
addressLookup.remove(address);
|
addressLookup.remove(address);
|
||||||
}
|
}
|
||||||
@ -571,7 +588,6 @@ public class PlayerInjectionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
// Guard
|
// Guard
|
||||||
if (hasClosed || playerInjection == null)
|
if (hasClosed || playerInjection == null)
|
||||||
return;
|
return;
|
||||||
@ -593,7 +609,6 @@ public class PlayerInjectionHandler {
|
|||||||
hasClosed = true;
|
hasClosed = true;
|
||||||
|
|
||||||
playerInjection.clear();
|
playerInjection.clear();
|
||||||
dataInputLookup.clear();
|
|
||||||
addressLookup.clear();
|
addressLookup.clear();
|
||||||
invoker = null;
|
invoker = null;
|
||||||
}
|
}
|
||||||
@ -607,12 +622,9 @@ public class PlayerInjectionHandler {
|
|||||||
|
|
||||||
// Update the DataInputStream
|
// Update the DataInputStream
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
final DataInputStream old = injector.getInputStream(true);
|
|
||||||
|
|
||||||
injector.scheduleAction(new Runnable() {
|
injector.scheduleAction(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
dataInputLookup.remove(old);
|
|
||||||
dataInputLookup.put(injector.getInputStream(false), injector);
|
dataInputLookup.put(injector.getInputStream(false), injector);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@ package com.comphenix.protocol.reflect;
|
|||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class FieldAccessException extends Exception {
|
public class FieldAccessException extends RuntimeException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generated by Eclipse.
|
* Generated by Eclipse.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.comphenix.protocol.wrappers;
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -21,7 +22,6 @@ import com.comphenix.protocol.reflect.FieldUtils;
|
|||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
import net.minecraft.server.ChunkCoordinates;
|
|
||||||
import net.minecraft.server.DataWatcher;
|
import net.minecraft.server.DataWatcher;
|
||||||
import net.minecraft.server.WatchableObject;
|
import net.minecraft.server.WatchableObject;
|
||||||
|
|
||||||
@ -116,8 +116,7 @@ public class WrappedDataWatcher {
|
|||||||
*/
|
*/
|
||||||
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
|
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
|
||||||
initialize();
|
initialize();
|
||||||
|
return typeMap.get(WrappedWatchableObject.getUnwrappedType(clazz));
|
||||||
return typeMap.get(clazz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,7 +144,7 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public Byte getByte(int index) throws FieldAccessException {
|
public Byte getByte(int index) throws FieldAccessException {
|
||||||
return (Byte) getObjectRaw(index);
|
return (Byte) getObject(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,7 +154,7 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public Short getShort(int index) throws FieldAccessException {
|
public Short getShort(int index) throws FieldAccessException {
|
||||||
return (Short) getObjectRaw(index);
|
return (Short) getObject(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,7 +164,7 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public Integer getInteger(int index) throws FieldAccessException {
|
public Integer getInteger(int index) throws FieldAccessException {
|
||||||
return (Integer) getObjectRaw(index);
|
return (Integer) getObject(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,7 +174,7 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public Float getFloat(int index) throws FieldAccessException {
|
public Float getFloat(int index) throws FieldAccessException {
|
||||||
return (Float) getObjectRaw(index);
|
return (Float) getObject(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -185,7 +184,7 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public String getString(int index) throws FieldAccessException {
|
public String getString(int index) throws FieldAccessException {
|
||||||
return (String) getObjectRaw(index);
|
return (String) getObject(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,25 +214,6 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public Object getObject(int index) throws FieldAccessException {
|
public Object getObject(int index) throws FieldAccessException {
|
||||||
Object 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
|
// The get method will take care of concurrency
|
||||||
WatchableObject watchable = getWatchedObject(index);
|
WatchableObject watchable = getWatchedObject(index);
|
||||||
|
|
||||||
@ -320,26 +300,6 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
|
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 {
|
|
||||||
// Aquire write lock
|
// Aquire write lock
|
||||||
Lock writeLock = getReadWriteLock().writeLock();
|
Lock writeLock = getReadWriteLock().writeLock();
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
@ -349,8 +309,18 @@ public class WrappedDataWatcher {
|
|||||||
|
|
||||||
if (watchable != null) {
|
if (watchable != null) {
|
||||||
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
||||||
|
} else {
|
||||||
|
createKeyValueMethod.invoke(handle, index, WrappedWatchableObject.getUnwrapped(newValue));
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
|
// Handle invoking the method
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new FieldAccessException("Cannot convert arguments.", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new FieldAccessException("Illegal access.", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new FieldAccessException("Checked exception in Minecraft.", e);
|
||||||
|
} finally {
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import com.comphenix.protocol.reflect.FieldAccessException;
|
|||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
|
|
||||||
|
import net.minecraft.server.ChunkCoordinates;
|
||||||
import net.minecraft.server.ItemStack;
|
import net.minecraft.server.ItemStack;
|
||||||
import net.minecraft.server.WatchableObject;
|
import net.minecraft.server.WatchableObject;
|
||||||
|
|
||||||
@ -27,7 +28,35 @@ public class WrappedWatchableObject {
|
|||||||
// Type of the stored value
|
// Type of the stored value
|
||||||
private Class<?> typeClass;
|
private Class<?> typeClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a given raw Minecraft watchable object.
|
||||||
|
* @param handle - the raw watchable object to wrap.
|
||||||
|
*/
|
||||||
public WrappedWatchableObject(WatchableObject handle) {
|
public WrappedWatchableObject(WatchableObject handle) {
|
||||||
|
load(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a watchable object from an index and a given value.
|
||||||
|
* @param index - the index.
|
||||||
|
* @param value - non-null value of specific types.
|
||||||
|
*/
|
||||||
|
public WrappedWatchableObject(int index, Object value) {
|
||||||
|
if (value == null)
|
||||||
|
throw new IllegalArgumentException("Value cannot be NULL.");
|
||||||
|
|
||||||
|
// Get the correct type ID
|
||||||
|
Integer typeID = WrappedDataWatcher.getTypeID(value.getClass());
|
||||||
|
|
||||||
|
if (typeID != null) {
|
||||||
|
load(new WatchableObject(typeID, index, getUnwrapped(value)));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Cannot watch the type " + value.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap a NMS object
|
||||||
|
private void load(WatchableObject handle) {
|
||||||
initialize();
|
initialize();
|
||||||
this.handle = handle;
|
this.handle = handle;
|
||||||
this.modifier = baseModifier.withTarget(handle);
|
this.modifier = baseModifier.withTarget(handle);
|
||||||
@ -57,6 +86,15 @@ public class WrappedWatchableObject {
|
|||||||
* @throws FieldAccessException Unable to read values.
|
* @throws FieldAccessException Unable to read values.
|
||||||
*/
|
*/
|
||||||
public Class<?> getType() throws FieldAccessException {
|
public Class<?> getType() throws FieldAccessException {
|
||||||
|
return getWrappedType(getTypeRaw());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the correct super type of the current value, given the raw NMS object.
|
||||||
|
* @return Super type.
|
||||||
|
* @throws FieldAccessException Unable to read values.
|
||||||
|
*/
|
||||||
|
private Class<?> getTypeRaw() throws FieldAccessException {
|
||||||
if (typeClass == null) {
|
if (typeClass == null) {
|
||||||
typeClass = WrappedDataWatcher.getTypeClass(getTypeID());
|
typeClass = WrappedDataWatcher.getTypeClass(getTypeID());
|
||||||
|
|
||||||
@ -131,7 +169,7 @@ public class WrappedWatchableObject {
|
|||||||
setDirtyState(true);
|
setDirtyState(true);
|
||||||
|
|
||||||
// Use the modifier to set the value
|
// Use the modifier to set the value
|
||||||
modifier.withType(Object.class).write(0, newValue);
|
modifier.withType(Object.class).write(0, getUnwrapped(newValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,7 +178,7 @@ public class WrappedWatchableObject {
|
|||||||
* @throws FieldAccessException Unable to use reflection.
|
* @throws FieldAccessException Unable to use reflection.
|
||||||
*/
|
*/
|
||||||
public Object getValue() throws FieldAccessException {
|
public Object getValue() throws FieldAccessException {
|
||||||
return modifier.withType(Object.class).read(0);
|
return getWrapped(modifier.withType(Object.class).read(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,6 +199,66 @@ public class WrappedWatchableObject {
|
|||||||
return modifier.<Boolean>withType(boolean.class).read(0);
|
return modifier.<Boolean>withType(boolean.class).read(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the wrapped object value, if needed.
|
||||||
|
* @param value - the raw NMS object to wrap.
|
||||||
|
* @return The wrapped object.
|
||||||
|
*/
|
||||||
|
static Object getWrapped(Object value) {
|
||||||
|
// Handle the special cases
|
||||||
|
if (value instanceof net.minecraft.server.ItemStack) {
|
||||||
|
return BukkitConverters.getItemStackConverter().getSpecific(value);
|
||||||
|
} else if (value instanceof ChunkCoordinates) {
|
||||||
|
return new WrappedChunkCoordinate((ChunkCoordinates) value);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the wrapped type, if needed.
|
||||||
|
* @param wrapped - the wrapped class type.
|
||||||
|
* @return The wrapped class type.
|
||||||
|
*/
|
||||||
|
static Class<?> getWrappedType(Class<?> unwrapped) {
|
||||||
|
if (unwrapped.equals(net.minecraft.server.ChunkPosition.class))
|
||||||
|
return ChunkPosition.class;
|
||||||
|
else if (unwrapped.equals(ChunkCoordinates.class))
|
||||||
|
return WrappedChunkCoordinate.class;
|
||||||
|
else
|
||||||
|
return unwrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the raw NMS value.
|
||||||
|
* @param wrapped - the wrapped position.
|
||||||
|
* @return The raw NMS object.
|
||||||
|
*/
|
||||||
|
static Object getUnwrapped(Object wrapped) {
|
||||||
|
// Convert special cases
|
||||||
|
if (wrapped instanceof WrappedChunkCoordinate)
|
||||||
|
return ((WrappedChunkCoordinate) wrapped).getHandle();
|
||||||
|
else if (wrapped instanceof ItemStack)
|
||||||
|
return BukkitConverters.getItemStackConverter().getGeneric(
|
||||||
|
net.minecraft.server.ItemStack.class, (org.bukkit.inventory.ItemStack) wrapped);
|
||||||
|
else
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the unwrapped type, if needed.
|
||||||
|
* @param wrapped - the unwrapped class type.
|
||||||
|
* @return The unwrapped class type.
|
||||||
|
*/
|
||||||
|
static Class<?> getUnwrappedType(Class<?> wrapped) {
|
||||||
|
if (wrapped.equals(ChunkPosition.class))
|
||||||
|
return net.minecraft.server.ChunkPosition.class;
|
||||||
|
else if (wrapped.equals(WrappedChunkCoordinate.class))
|
||||||
|
return ChunkCoordinates.class;
|
||||||
|
else
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clone the current wrapped watchable object, along with any contained objects.
|
* Clone the current wrapped watchable object, along with any contained objects.
|
||||||
* @return A deep clone of the current watchable object.
|
* @return A deep clone of the current watchable object.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name: ProtocolLib
|
name: ProtocolLib
|
||||||
version: 1.7.0
|
version: 1.7.1
|
||||||
description: Provides read/write access to the Minecraft protocol.
|
description: Provides read/write access to the Minecraft protocol.
|
||||||
author: Comphenix
|
author: Comphenix
|
||||||
website: http://www.comphenix.net/ProtocolLib
|
website: http://www.comphenix.net/ProtocolLib
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.comphenix.protocol.concurrency;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class BlockingHashMapTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws InterruptedException, ExecutionException {
|
||||||
|
|
||||||
|
final BlockingHashMap<Integer, String> map = BlockingHashMap.create();
|
||||||
|
|
||||||
|
ExecutorService service = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
// Create a reader
|
||||||
|
Future<String> future = service.submit(new Callable<String>() {
|
||||||
|
@Override
|
||||||
|
public String call() throws Exception {
|
||||||
|
// Combine for easy reading
|
||||||
|
return map.get(0) + map.get(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait a bit
|
||||||
|
Thread.sleep(50);
|
||||||
|
|
||||||
|
// Insert values
|
||||||
|
map.put(0, "hello ");
|
||||||
|
map.put(1, "world");
|
||||||
|
|
||||||
|
// Wait for the other thread to complete
|
||||||
|
assertEquals(future.get(), "hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
In neuem Issue referenzieren
Einen Benutzer sperren