Merge branch 'master' into gh-pages
Dieser Commit ist enthalten in:
Commit
22cb77d78e
78
ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java
Normale Datei
78
ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java
Normale Datei
@ -0,0 +1,78 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Represents a asynchronous packet handler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface AsynchronousManager {
|
||||
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* @param plugin - the plugin that is registering the handler.
|
||||
* @param listener - the packet listener that will recieve these asynchronous events.
|
||||
* @return An asynchrouns handler.
|
||||
*/
|
||||
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the given asynchronous handler.
|
||||
* @param handler - asynchronous handler.
|
||||
*/
|
||||
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
|
||||
/**
|
||||
* Unregisters every asynchronous handler associated with this plugin.
|
||||
* @param plugin - the original plugin.
|
||||
*/
|
||||
public void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public abstract Set<Integer> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the recieved client packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public abstract Set<Integer> getReceivingFilters();
|
||||
|
||||
/**
|
||||
* Determine if a given synchronous packet has asynchronous listeners.
|
||||
* @param packet - packet to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Retrieve the default packet stream.
|
||||
* @return Default packet stream.
|
||||
*/
|
||||
public abstract PacketStream getPacketStream();
|
||||
|
||||
/**
|
||||
* Retrieve the default error logger.
|
||||
* @return Default logger.
|
||||
*/
|
||||
public abstract Logger getLogger();
|
||||
|
||||
/**
|
||||
* Remove listeners, close threads and transmit every delayed packet.
|
||||
*/
|
||||
public abstract void cleanupAll();
|
||||
}
|
55
ProtocolLib/src/com/comphenix/protocol/PacketStream.java
Normale Datei
55
ProtocolLib/src/com/comphenix/protocol/PacketStream.java
Normale Datei
@ -0,0 +1,55 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
|
||||
/**
|
||||
* Represents a object capable of sending or receiving packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketStream {
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
}
|
@ -25,8 +25,10 @@ import org.bukkit.Server;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.metrics.Statistics;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
|
||||
public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
@ -39,10 +41,19 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// Metrics and statistisc
|
||||
private Statistics statistisc;
|
||||
|
||||
// Structure compiler
|
||||
private BackgroundCompiler backgroundCompiler;
|
||||
|
||||
// Used to clean up server packets that have expired.
|
||||
// But mostly required to simulate recieving client packets.
|
||||
private int asyncPacketTask = -1;
|
||||
private int tickCounter = 0;
|
||||
private static final int ASYNC_PACKET_DELAY = 1;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
logger = getLoggerSafely();
|
||||
protocolManager = new PacketFilterManager(getClassLoader(), logger);
|
||||
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -50,6 +61,12 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
Server server = getServer();
|
||||
PluginManager manager = server.getPluginManager();
|
||||
|
||||
// Initialize background compiler
|
||||
if (backgroundCompiler == null) {
|
||||
backgroundCompiler = new BackgroundCompiler(getClassLoader());
|
||||
BackgroundCompiler.setInstance(backgroundCompiler);
|
||||
}
|
||||
|
||||
// Notify server managers of incompatible plugins
|
||||
checkForIncompatibility(manager);
|
||||
|
||||
@ -59,6 +76,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// Inject our hook into already existing players
|
||||
protocolManager.initializePlayers(server.getOnlinePlayers());
|
||||
|
||||
// Timeout
|
||||
createAsyncTask(server);
|
||||
|
||||
// Try to enable statistics
|
||||
try {
|
||||
statistisc = new Statistics(this);
|
||||
@ -69,9 +89,32 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void createAsyncTask(Server server) {
|
||||
try {
|
||||
if (asyncPacketTask >= 0)
|
||||
throw new IllegalStateException("Async task has already been created");
|
||||
|
||||
// Attempt to create task
|
||||
asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
|
||||
|
||||
// We KNOW we're on the main thread at the moment
|
||||
manager.sendProcessedPackets(tickCounter++, true);
|
||||
}
|
||||
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
|
||||
|
||||
} catch (Throwable e) {
|
||||
if (asyncPacketTask == -1) {
|
||||
logger.log(Level.SEVERE, "Unable to create packet timeout task.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForIncompatibility(PluginManager manager) {
|
||||
// Plugin authors: Notify me to remove these
|
||||
String[] incompatiblePlugins = { "TagAPI" };
|
||||
String[] incompatiblePlugins = {};
|
||||
|
||||
for (String plugin : incompatiblePlugins) {
|
||||
if (manager.getPlugin(plugin) != null) {
|
||||
@ -83,6 +126,19 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Disable compiler
|
||||
if (backgroundCompiler != null) {
|
||||
backgroundCompiler.shutdownAll();
|
||||
backgroundCompiler = null;
|
||||
BackgroundCompiler.setInstance(null);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (asyncPacketTask >= 0) {
|
||||
getServer().getScheduler().cancelTask(asyncPacketTask);
|
||||
asyncPacketTask = -1;
|
||||
}
|
||||
|
||||
protocolManager.close();
|
||||
protocolManager = null;
|
||||
statistisc = null;
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@ -35,7 +34,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
* Represents an API for accessing the Minecraft protocol.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ProtocolManager {
|
||||
public interface ProtocolManager extends PacketStream {
|
||||
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
@ -66,46 +65,6 @@ public interface ProtocolManager {
|
||||
* @param plugin - the plugin to unload.
|
||||
*/
|
||||
public void removePacketListeners(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters.
|
||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters.
|
||||
* @throws InvocationTargetException If the reflection machinery failed.
|
||||
* @throws IllegalAccessException If the underlying method caused an error.
|
||||
*/
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
@ -163,4 +122,10 @@ public interface ProtocolManager {
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed();
|
||||
|
||||
/**
|
||||
* Retrieve the current asyncronous packet manager.
|
||||
* @return Asyncronous packet manager.
|
||||
*/
|
||||
public AsynchronousManager getAsynchronousManager();
|
||||
}
|
301
ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java
Normale Datei
301
ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java
Normale Datei
@ -0,0 +1,301 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Represents a filter manager for asynchronous packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
private PacketProcessingQueue serverProcessingQueue;
|
||||
private PacketSendingQueue serverQueue;
|
||||
|
||||
private PacketProcessingQueue clientProcessingQueue;
|
||||
private PacketSendingQueue clientQueue;
|
||||
|
||||
private Logger logger;
|
||||
|
||||
// The likely main thread
|
||||
private Thread mainThread;
|
||||
|
||||
// Default scheduler
|
||||
private BukkitScheduler scheduler;
|
||||
|
||||
// Our protocol manager
|
||||
private ProtocolManager manager;
|
||||
|
||||
// Current packet index
|
||||
private AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||
|
||||
// Whether or not we're currently cleaning up
|
||||
private volatile boolean cleaningUp;
|
||||
|
||||
public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) {
|
||||
|
||||
// Server packets are synchronized already
|
||||
this.serverQueue = new PacketSendingQueue(false);
|
||||
// Client packets must be synchronized
|
||||
this.clientQueue = new PacketSendingQueue(true);
|
||||
|
||||
this.serverProcessingQueue = new PacketProcessingQueue(serverQueue);
|
||||
this.clientProcessingQueue = new PacketProcessingQueue(clientQueue);
|
||||
|
||||
this.scheduler = scheduler;
|
||||
this.manager = manager;
|
||||
|
||||
this.logger = logger;
|
||||
this.mainThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
||||
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
|
||||
|
||||
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
|
||||
|
||||
// We need a synchronized listener to get the ball rolling
|
||||
boolean hasListener = true;
|
||||
|
||||
// Add listener to either or both processing queue
|
||||
if (hasValidWhitelist(sendingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
|
||||
serverProcessingQueue.addListener(handler, sendingWhitelist);
|
||||
hasListener &= hasPacketListener(sendingWhitelist);
|
||||
}
|
||||
|
||||
if (hasValidWhitelist(receivingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
|
||||
clientProcessingQueue.addListener(handler, receivingWhitelist);
|
||||
hasListener &= hasPacketListener(receivingWhitelist);
|
||||
}
|
||||
|
||||
if (!hasListener) {
|
||||
handler.setNullPacketListener(new NullPacketListener(listener));
|
||||
manager.addPacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packets are represented.
|
||||
* @param whitelist - list of packets.
|
||||
* @return TRUE if they are all registered, FALSE otherwise.
|
||||
*/
|
||||
private boolean hasPacketListener(ListeningWhitelist whitelist) {
|
||||
return manager.getSendingFilters().containsAll(whitelist.getWhitelist());
|
||||
}
|
||||
|
||||
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
|
||||
return whitelist != null && whitelist.getWhitelist().size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
|
||||
if (handler == null)
|
||||
throw new IllegalArgumentException("listenerToken cannot be NULL");
|
||||
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
// Called by AsyncListenerHandler
|
||||
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
|
||||
|
||||
PacketListener listener = handler.getAsyncListener();
|
||||
boolean synchronusOK = onMainThread();
|
||||
|
||||
// Unregister null packet listeners
|
||||
if (handler.getNullPacketListener() != null) {
|
||||
manager.removePacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
|
||||
// Just remove it from the queue(s)
|
||||
if (hasValidWhitelist(listener.getSendingWhitelist())) {
|
||||
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
||||
|
||||
// We're already taking care of this, so don't do anything
|
||||
if (!cleaningUp)
|
||||
serverQueue.signalPacketUpdate(removed, synchronusOK);
|
||||
}
|
||||
|
||||
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
||||
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
||||
|
||||
if (!cleaningUp)
|
||||
clientQueue.signalPacketUpdate(removed, synchronusOK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're running on the main thread.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
private boolean onMainThread() {
|
||||
return Thread.currentThread().getId() == mainThread.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandlers(Plugin plugin) {
|
||||
unregisterAsyncHandlers(serverProcessingQueue, plugin);
|
||||
unregisterAsyncHandlers(clientProcessingQueue, plugin);
|
||||
}
|
||||
|
||||
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
|
||||
unregisterAsyncHandler(listener.getListener());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for asynchronous processing.
|
||||
* @param syncPacket - synchronous packet event.
|
||||
* @param asyncMarker - the asynchronous marker to use.
|
||||
*/
|
||||
public void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
|
||||
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
|
||||
|
||||
// Start the process
|
||||
getSendingQueue(syncPacket).enqueue(newEvent);
|
||||
|
||||
// We know this is occuring on the main thread, so pass TRUE
|
||||
getProcessingQueue(syncPacket).enqueue(newEvent, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return serverProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getReceivingFilters() {
|
||||
return clientProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to create a default asynchronous task.
|
||||
* @param plugin - the calling plugin.
|
||||
* @param runnable - the runnable.
|
||||
*/
|
||||
public void scheduleAsyncTask(Plugin plugin, Runnable runnable) {
|
||||
scheduler.scheduleAsyncDelayedTask(plugin, runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAsynchronousListeners(PacketEvent packet) {
|
||||
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketID());
|
||||
return list != null && list.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a asynchronous marker with all the default values.
|
||||
* @return Asynchronous marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker() {
|
||||
return createAsyncMarker(AsyncMarker.DEFAULT_SENDING_DELTA, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an async marker with the given sending priority delta and timeout delta.
|
||||
* @param sendingDelta - how many packets we're willing to wait.
|
||||
* @param timeoutDelta - how long (in ms) until the packet expire.
|
||||
* @return An async marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta) {
|
||||
return createAsyncMarker(sendingDelta, timeoutDelta,
|
||||
currentSendingIndex.incrementAndGet(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// Helper method
|
||||
private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) {
|
||||
return new AsyncMarker(manager, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketStream getPacketStream() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
cleaningUp = true;
|
||||
serverProcessingQueue.cleanupAll();
|
||||
serverQueue.cleanupAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public void signalPacketUpdate(PacketEvent packet) {
|
||||
getSendingQueue(packet).signalPacketUpdate(packet, onMainThread());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
private PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return packet.isServerPacket() ? serverQueue : clientQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet has finished processing.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public void signalProcessingDone(PacketEvent packet) {
|
||||
getProcessingQueue(packet).signalProcessingDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the processing queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending processing the packet belongs to.
|
||||
*/
|
||||
private PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
|
||||
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any due packets, or clean up packets that have expired.
|
||||
*/
|
||||
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
||||
|
||||
// The server queue is unlikely to need checking that often
|
||||
if (tickCounter % 10 == 0) {
|
||||
serverQueue.trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
clientQueue.trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
210
ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java
Normale Datei
210
ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java
Normale Datei
@ -0,0 +1,210 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Represents a handler for an asynchronous event.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncListenerHandler {
|
||||
|
||||
/**
|
||||
* Signal an end to the packet processing.
|
||||
*/
|
||||
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
|
||||
|
||||
// Default queue capacity
|
||||
private static int DEFAULT_CAPACITY = 1024;
|
||||
|
||||
// Cancel the async handler
|
||||
private volatile boolean cancelled;
|
||||
|
||||
// If we've started the listener loop before
|
||||
private volatile boolean started;
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// The filter manager
|
||||
private AsyncFilterManager filterManager;
|
||||
private NullPacketListener nullPacketListener;
|
||||
|
||||
// List of queued packets
|
||||
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
|
||||
|
||||
// Minecraft main thread
|
||||
private Thread mainThread;
|
||||
|
||||
public AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
|
||||
if (filterManager == null)
|
||||
throw new IllegalArgumentException("filterManager cannot be NULL");
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL");
|
||||
|
||||
this.mainThread = mainThread;
|
||||
this.filterManager = filterManager;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
public PacketListener getAsyncListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the synchronized listener that has been automatically created.
|
||||
* @param nullPacketListener - automatically created listener.
|
||||
*/
|
||||
void setNullPacketListener(NullPacketListener nullPacketListener) {
|
||||
this.nullPacketListener = nullPacketListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the synchronized listener that was automatically created.
|
||||
* @return Automatically created listener.
|
||||
*/
|
||||
PacketListener getNullPacketListener() {
|
||||
return nullPacketListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the handler.
|
||||
*/
|
||||
public void cancel() {
|
||||
// Remove the listener as quickly as possible
|
||||
close();
|
||||
|
||||
// Poison Pill Shutdown
|
||||
queuedPackets.clear();
|
||||
queuedPackets.add(INTERUPT_PACKET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a packet for processing.
|
||||
* @param packet - a packet for processing.
|
||||
* @throws IllegalStateException If the underlying packet queue is full.
|
||||
*/
|
||||
public void enqueuePacket(PacketEvent packet) {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("packet is NULL");
|
||||
|
||||
queuedPackets.add(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a runnable that will initiate the listener loop.
|
||||
* <p>
|
||||
* <b>Warning</b>: Never call the run() method in the main thread.
|
||||
*/
|
||||
public Runnable getListenerLoop() {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listenerLoop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// DO NOT call this method from the main thread
|
||||
private void listenerLoop() {
|
||||
|
||||
// Danger, danger!
|
||||
if (Thread.currentThread().getId() == mainThread.getId())
|
||||
throw new IllegalStateException("Do not call this method from the main thread.");
|
||||
if (started)
|
||||
throw new IllegalStateException("A listener cannot be run by multiple threads. Create a new listener instead.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
||||
|
||||
// Proceed
|
||||
started = true;
|
||||
|
||||
try {
|
||||
mainLoop:
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.take();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
|
||||
// Handle cancel requests
|
||||
if (packet == null || marker == null || !packet.isAsynchronous()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Here's the core of the asynchronous processing
|
||||
try {
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
filterManager.getLogger().log(Level.SEVERE,
|
||||
"Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e);
|
||||
}
|
||||
|
||||
// Now, get the next non-cancelled listener
|
||||
for (; marker.getListenerTraversal().hasNext(); ) {
|
||||
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
|
||||
|
||||
if (!handler.isCancelled()) {
|
||||
handler.enqueuePacket(packet);
|
||||
continue mainLoop;
|
||||
}
|
||||
}
|
||||
|
||||
// There are no more listeners - queue the packet for transmission
|
||||
filterManager.signalPacketUpdate(packet);
|
||||
filterManager.signalProcessingDone(packet);
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// We're done
|
||||
}
|
||||
|
||||
// Clean up
|
||||
close();
|
||||
}
|
||||
|
||||
private void close() {
|
||||
// Remove the listener itself
|
||||
if (!cancelled) {
|
||||
filterManager.unregisterAsyncHandlerInternal(this);
|
||||
cancelled = true;
|
||||
started = false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getPluginName() {
|
||||
return PacketAdapter.getPluginName(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the plugin associated with this async listener.
|
||||
* @return The plugin.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return listener != null ? listener.getPlugin() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the asynchronous listener using the Bukkit scheduler.
|
||||
*/
|
||||
public void start() {
|
||||
if (listener.getPlugin() == null)
|
||||
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||
|
||||
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
|
||||
}
|
||||
}
|
305
ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java
Normale Datei
305
ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java
Normale Datei
@ -0,0 +1,305 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
/**
|
||||
* Contains information about the packet that is being processed by asynchronous listeners.
|
||||
* <p>
|
||||
* Asynchronous listeners can use this to set packet timeout or transmission order.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2621498096616187384L;
|
||||
|
||||
/**
|
||||
* Default number of milliseconds until a packet will rejected.
|
||||
*/
|
||||
public static final int DEFAULT_TIMEOUT_DELTA = 60000;
|
||||
|
||||
/**
|
||||
* Default number of packets to skip.
|
||||
*/
|
||||
public static final int DEFAULT_SENDING_DELTA = 0;
|
||||
|
||||
/**
|
||||
* The packet stream responsible for transmitting the packet when it's done processing.
|
||||
*/
|
||||
private transient PacketStream packetStream;
|
||||
|
||||
/**
|
||||
* Current list of async packet listeners.
|
||||
*/
|
||||
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
||||
|
||||
// Timeout handling
|
||||
private long initialTime;
|
||||
private long timeout;
|
||||
|
||||
// Packet order
|
||||
private long originalSendingIndex;
|
||||
private long newSendingIndex;
|
||||
|
||||
// Whether or not the packet has been processed by the listeners
|
||||
private volatile boolean processed;
|
||||
|
||||
// Whether or not the packet has been sent
|
||||
private volatile boolean transmitted;
|
||||
|
||||
// Whether or not the asynchronous processing itself should be cancelled
|
||||
private volatile boolean asyncCancelled;
|
||||
|
||||
// Determine if Minecraft processes this packet asynchronously
|
||||
private static Method isMinecraftAsync;
|
||||
private static boolean alwaysSync;
|
||||
|
||||
/**
|
||||
* Create a container for asyncronous packets.
|
||||
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
AsyncMarker(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) {
|
||||
if (packetStream == null)
|
||||
throw new IllegalArgumentException("packetStream cannot be NULL");
|
||||
|
||||
this.packetStream = packetStream;
|
||||
|
||||
// Timeout
|
||||
this.initialTime = initialTime;
|
||||
this.timeout = initialTime + timeoutDelta;
|
||||
|
||||
// Sending index
|
||||
this.originalSendingIndex = sendingIndex;
|
||||
this.newSendingIndex = sendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet was initially queued for asynchronous processing.
|
||||
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getInitialTime() {
|
||||
return initialTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet will be forcefully rejected.
|
||||
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the packet will be forcefully rejected.
|
||||
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the order the packet was originally transmitted.
|
||||
* @return The original packet index.
|
||||
*/
|
||||
public long getOriginalSendingIndex() {
|
||||
return originalSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @return Desired sending order.
|
||||
*/
|
||||
public long getNewSendingIndex() {
|
||||
return newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @param newSendingIndex - new packet send index.
|
||||
*/
|
||||
public void setNewSendingIndex(long newSendingIndex) {
|
||||
this.newSendingIndex = newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet stream responsible for transmitting this packet.
|
||||
* @return The packet stream.
|
||||
*/
|
||||
public PacketStream getPacketStream() {
|
||||
return packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output packet stream responsible for transmitting this packet.
|
||||
* @param packetStream - new output packet stream.
|
||||
*/
|
||||
public void setPacketStream(PacketStream packetStream) {
|
||||
this.packetStream = packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has been processed by the async listeners.
|
||||
* @return TRUE if it has been processed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isProcessed() {
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this packet has been processed by the async listeners.
|
||||
* @param processed - TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
void setProcessed(boolean processed) {
|
||||
this.processed = processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has already been sent.
|
||||
* @return TRUE if it has been sent before, FALSE otherwise.
|
||||
*/
|
||||
public boolean isTransmitted() {
|
||||
return transmitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired() {
|
||||
return hasExpired(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired given this time.
|
||||
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired(long currentTime) {
|
||||
return timeout < currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the asynchronous handling should be cancelled.
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsyncCancelled() {
|
||||
return asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the asynchronous handling should be cancelled.
|
||||
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
|
||||
*/
|
||||
public void setAsyncCancelled(boolean asyncCancelled) {
|
||||
this.asyncCancelled = asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve iterator for the next listener in line.
|
||||
* @return Next async packet listener iterator.
|
||||
*/
|
||||
public Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
return listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the iterator for the next listener.
|
||||
* @param listenerTraversal - the new async packet listener iterator.
|
||||
*/
|
||||
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
|
||||
this.listenerTraversal = listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a given packet to the current packet stream.
|
||||
* @param event - the packet to send.
|
||||
* @throws IOException If the packet couldn't be sent.
|
||||
*/
|
||||
public void sendPacket(PacketEvent event) throws IOException {
|
||||
try {
|
||||
if (event.isServerPacket()) {
|
||||
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), false);
|
||||
} else {
|
||||
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false);
|
||||
}
|
||||
transmitted = true;
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Cannot send packet", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOException("Cannot send packet", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Minecraft allows asynchronous processing of this packet.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
|
||||
|
||||
if (isMinecraftAsync == null && !alwaysSync) {
|
||||
try {
|
||||
isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*");
|
||||
} catch (RuntimeException e) {
|
||||
// This will occur in 1.2.5 (or possibly in later versions)
|
||||
List<Method> methods = FuzzyReflection.fromClass(Packet.class).
|
||||
getMethodListByParameters(boolean.class, new Class[] {});
|
||||
|
||||
// Try to look for boolean methods
|
||||
if (methods.size() == 2) {
|
||||
isMinecraftAsync = methods.get(1);
|
||||
} else if (methods.size() == 1) {
|
||||
// We're in 1.2.5
|
||||
alwaysSync = true;
|
||||
} else {
|
||||
System.err.println("Cannot determine asynchronous state of packets!");
|
||||
alwaysSync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alwaysSync) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
// Wrap exceptions
|
||||
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldAccessException("Illegal argument", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Minecraft error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(AsyncMarker o) {
|
||||
if (o == null)
|
||||
return 1;
|
||||
else
|
||||
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
||||
}
|
||||
}
|
62
ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java
Normale Datei
62
ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java
Normale Datei
@ -0,0 +1,62 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Represents a NO OPERATION listener.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NullPacketListener implements PacketListener {
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
private Plugin plugin;
|
||||
|
||||
/**
|
||||
* Create a no-op listener with the same whitelist and plugin as the given listener.
|
||||
* @param original - the packet listener to copy.
|
||||
*/
|
||||
public NullPacketListener(PacketListener original) {
|
||||
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
|
||||
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
|
||||
this.plugin = original.getPlugin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
|
||||
if (whitelist != null)
|
||||
return new ListeningWhitelist(priority, whitelist.getWhitelist());
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
133
ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java
Normale Datei
133
ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java
Normale Datei
@ -0,0 +1,133 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
|
||||
/**
|
||||
* Handles the processing of every packet type.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to process concurrently.
|
||||
*/
|
||||
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to queue for processing.
|
||||
*/
|
||||
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
|
||||
|
||||
/**
|
||||
* Number of packets we're processing concurrently.
|
||||
*/
|
||||
private final int maximumConcurrency;
|
||||
private Semaphore concurrentProcessing;
|
||||
|
||||
// Queued packets for being processed
|
||||
private ArrayBlockingQueue<PacketEvent> processingQueue;
|
||||
|
||||
// Packets for sending
|
||||
private PacketSendingQueue sendingQueue;
|
||||
|
||||
public PacketProcessingQueue(PacketSendingQueue sendingQueue) {
|
||||
this(sendingQueue, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||
}
|
||||
|
||||
public PacketProcessingQueue(PacketSendingQueue sendingQueue, int queueLimit, int maximumConcurrency) {
|
||||
super();
|
||||
this.processingQueue = new ArrayBlockingQueue<PacketEvent>(queueLimit);
|
||||
this.maximumConcurrency = maximumConcurrency;
|
||||
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||
this.sendingQueue = sendingQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for processing by the asynchronous listeners.
|
||||
* @param packet - packet to process.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
|
||||
*/
|
||||
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
|
||||
try {
|
||||
processingQueue.add(packet);
|
||||
|
||||
// Begin processing packets
|
||||
signalBeginProcessing(onMainThread);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the current method and each thread to signal that a packet might be ready for processing.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void signalBeginProcessing(boolean onMainThread) {
|
||||
while (concurrentProcessing.tryAcquire()) {
|
||||
PacketEvent packet = processingQueue.poll();
|
||||
|
||||
// Any packet queued?
|
||||
if (packet != null) {
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
|
||||
// Yes, removing the marker will cause the chain to stop
|
||||
if (list != null) {
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
marker.setListenerTraversal(iterator);
|
||||
iterator.next().getListener().enqueuePacket(packet);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The packet has no further listeners. Just send it.
|
||||
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
||||
signalProcessingDone();
|
||||
|
||||
} else {
|
||||
// No more queued packets.
|
||||
signalProcessingDone();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet has been processed.
|
||||
*/
|
||||
public void signalProcessingDone() {
|
||||
concurrentProcessing.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum number of packets to process at any given time.
|
||||
* @return Number of simultaneous packet to process.
|
||||
*/
|
||||
public int getMaximumConcurrency() {
|
||||
return maximumConcurrency;
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Cancel all the threads and every listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
|
||||
if (handler != null) {
|
||||
handler.getListener().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the rest, just in case
|
||||
clearListeners();
|
||||
}
|
||||
}
|
177
ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java
Normale Datei
177
ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java
Normale Datei
@ -0,0 +1,177 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
|
||||
/**
|
||||
* Represents packets ready to be transmitted to a client.
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketSendingQueue {
|
||||
|
||||
private static final int INITIAL_CAPACITY = 64;
|
||||
|
||||
private PriorityBlockingQueue<PacketEvent> sendingQueue;
|
||||
|
||||
// Whether or not packet transmission can only occur on the main thread
|
||||
private final boolean synchronizeMain;
|
||||
|
||||
/**
|
||||
* Create a packet sending queue.
|
||||
* @param synchronizeMain - whether or not to synchronize with the main thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean synchronizeMain) {
|
||||
this.synchronizeMain = synchronizeMain;
|
||||
this.sendingQueue = new PriorityBlockingQueue<PacketEvent>(INITIAL_CAPACITY, new Comparator<PacketEvent>() {
|
||||
// Compare using the async marker
|
||||
@Override
|
||||
public int compare(PacketEvent o1, PacketEvent o2) {
|
||||
return ComparisonChain.start().
|
||||
compare(o1.getAsyncMarker(), o2.getAsyncMarker()).
|
||||
result();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for sending.
|
||||
* @param packet
|
||||
*/
|
||||
public void enqueue(PacketEvent packet) {
|
||||
sendingQueue.add(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when one of the packets have finished processing.
|
||||
* @param packetUpdated - the packet that has now been updated.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
|
||||
// Mark this packet as finished
|
||||
packetUpdated.getAsyncMarker().setProcessed(true);
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/***
|
||||
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||
* @param packetsRemoved - packets that no longer have any listeners.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) {
|
||||
|
||||
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
|
||||
|
||||
// Note that this is O(n), so it might be expensive
|
||||
for (PacketEvent event : sendingQueue) {
|
||||
if (lookup.contains(event.getPacketID())) {
|
||||
event.getAsyncMarker().setProcessed(true);
|
||||
}
|
||||
}
|
||||
|
||||
// This is likely to have changed the situation a bit
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send any remaining packets.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendPackets(boolean onMainThread) {
|
||||
|
||||
// Transmit as many packets as we can
|
||||
while (true) {
|
||||
PacketEvent current = sendingQueue.peek();
|
||||
|
||||
if (current != null) {
|
||||
AsyncMarker marker = current.getAsyncMarker();
|
||||
|
||||
// Abort if we're not on the main thread
|
||||
if (synchronizeMain) {
|
||||
try {
|
||||
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||
boolean wantSync = !wantAsync;
|
||||
|
||||
// Quit if we haven't fulfilled our promise
|
||||
if ((onMainThread && wantAsync) || (!onMainThread && wantSync))
|
||||
return;
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (marker.isProcessed() || marker.hasExpired()) {
|
||||
if (marker.isProcessed() && !current.isCancelled()) {
|
||||
sendPacket(current);
|
||||
}
|
||||
|
||||
sendingQueue.poll();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only repeat when packets are removed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send every packet, regardless of the processing state.
|
||||
*/
|
||||
private void forceSend() {
|
||||
while (true) {
|
||||
PacketEvent current = sendingQueue.poll();
|
||||
|
||||
if (current != null) {
|
||||
sendPacket(current);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the packet transmission must synchronize with the main thread.
|
||||
* @return TRUE if it must, FALSE otherwise.
|
||||
*/
|
||||
public boolean isSynchronizeMain() {
|
||||
return synchronizeMain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a packet, if it hasn't already.
|
||||
* @param event - the packet to transmit.
|
||||
*/
|
||||
private void sendPacket(PacketEvent event) {
|
||||
|
||||
AsyncMarker marker = event.getAsyncMarker();
|
||||
|
||||
try {
|
||||
// Don't send a packet twice
|
||||
if (marker != null && !marker.isTransmitted()) {
|
||||
marker.sendPacket(event);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// Just print the error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically transmits every delayed packet.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
// Note that the cleanup itself will always occur on the main thread
|
||||
forceSend();
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* A thread-safe implementation of a listener multimap.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||
|
||||
// The core of our map
|
||||
private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> listeners =
|
||||
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
|
||||
|
||||
/**
|
||||
* Adds a listener to its requested list of packet recievers.
|
||||
* @param listener - listener with a list of packets to recieve notifcations for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
|
||||
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
addListener(packetID, prioritized);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the listener to a specific packet notifcation list
|
||||
private void addListener(Integer packetID, PrioritizedListener<TListener> listener) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
||||
// which is a essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
|
||||
|
||||
list = listeners.putIfAbsent(packetID, value);
|
||||
|
||||
// We may end up creating multiple multisets, but we'll agree
|
||||
// on the one to use.
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
List<Integer> removedPackets = new ArrayList<Integer>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
|
||||
|
||||
// Remove any listeners
|
||||
if (list != null) {
|
||||
// Don't remove from newly created lists
|
||||
if (list.size() > 0) {
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
|
||||
|
||||
if (list.size() == 0) {
|
||||
listeners.remove(packetID);
|
||||
removedPackets.add(packetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move on to the next
|
||||
}
|
||||
|
||||
return removedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the registered listeners, in order from the lowest to the highest priority.
|
||||
* <p>
|
||||
* The returned list is thread-safe and doesn't require synchronization.
|
||||
* @param packetID - packet ID.
|
||||
* @return Registered listeners.
|
||||
*/
|
||||
public Collection<PrioritizedListener<TListener>> getListener(int packetID) {
|
||||
return listeners.get(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every listener.
|
||||
* @return Every listener.
|
||||
*/
|
||||
public Iterable<PrioritizedListener<TListener>> values() {
|
||||
return Iterables.concat(listeners.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every registered packet ID:
|
||||
* @return Registered packet ID.
|
||||
*/
|
||||
public Set<Integer> keySet() {
|
||||
return listeners.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all packet listeners.
|
||||
*/
|
||||
protected void clearListeners() {
|
||||
listeners.clear();
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ public class ListeningWhitelist {
|
||||
* @return TRUE if there are any packets, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return whitelist != null || whitelist.size() > 0;
|
||||
return whitelist != null && whitelist.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,6 +86,23 @@ public class ListeningWhitelist {
|
||||
return Objects.hashCode(priority, whitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any of the given IDs can be found in the whitelist.
|
||||
* @param whitelist - whitelist to test.
|
||||
* @param idList - list of packet IDs to find.
|
||||
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
|
||||
*/
|
||||
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
|
||||
if (whitelist != null) {
|
||||
for (int i = 0; i < idList.length; i++) {
|
||||
if (whitelist.getWhitelist().contains(idList[i]))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj){
|
||||
if(obj instanceof ListeningWhitelist){
|
||||
|
@ -17,6 +17,12 @@
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@ -40,13 +46,18 @@ import net.minecraft.server.Packet;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketContainer {
|
||||
public class PacketContainer implements Serializable {
|
||||
|
||||
protected Packet handle;
|
||||
protected int id;
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 2074805748222377230L;
|
||||
|
||||
protected int id;
|
||||
protected transient Packet handle;
|
||||
|
||||
// Current structure modifier
|
||||
protected StructureModifier<Object> structureModifier;
|
||||
protected transient StructureModifier<Object> structureModifier;
|
||||
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
@ -54,6 +65,10 @@ public class PacketContainer {
|
||||
// The getEntity method
|
||||
private static Method getEntity;
|
||||
|
||||
// Support for serialization
|
||||
private static Method writeMethod;
|
||||
private static Method readMethod;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.WorldType");
|
||||
@ -291,4 +306,60 @@ public class PacketContainer {
|
||||
public int getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
// We'll take care of NULL packets as well
|
||||
output.writeBoolean(handle != null);
|
||||
|
||||
// Retrieve the write method by reflection
|
||||
if (writeMethod == null)
|
||||
writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class);
|
||||
|
||||
try {
|
||||
// Call the write-method
|
||||
writeMethod.invoke(handle, new DataOutputStream(output));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Insufficient security privileges.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Could not serialize Minecraft packet.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
|
||||
// Get structure modifier
|
||||
structureModifier = StructureCache.getStructure(id);
|
||||
|
||||
// Don't read NULL packets
|
||||
if (input.readBoolean()) {
|
||||
|
||||
// Create a default instance of the packet
|
||||
handle = StructureCache.newPacket(id);
|
||||
|
||||
// Retrieve the read method by reflection
|
||||
if (readMethod == null)
|
||||
readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class);
|
||||
|
||||
// Call the read method
|
||||
try {
|
||||
readMethod.invoke(handle, new DataInputStream(input));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Insufficient security privileges.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Could not deserialize Minecraft packet.", e);
|
||||
}
|
||||
|
||||
// And we're done
|
||||
structureModifier = structureModifier.withTarget(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,22 +17,30 @@
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.EventObject;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
|
||||
public class PacketEvent extends EventObject implements Cancellable {
|
||||
/**
|
||||
* Automatically generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -5360289379097430620L;
|
||||
|
||||
private transient Player player;
|
||||
private PacketContainer packet;
|
||||
private Player player;
|
||||
private boolean serverPacket;
|
||||
private boolean cancel;
|
||||
|
||||
private AsyncMarker asyncMarker;
|
||||
private boolean asynchronous;
|
||||
|
||||
/**
|
||||
* Use the static constructors to create instances of this event.
|
||||
* @param source - the event source.
|
||||
@ -47,6 +55,16 @@ public class PacketEvent extends EventObject implements Cancellable {
|
||||
this.player = player;
|
||||
this.serverPacket = serverPacket;
|
||||
}
|
||||
|
||||
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
|
||||
super(origial.source);
|
||||
this.packet = origial.packet;
|
||||
this.player = origial.player;
|
||||
this.cancel = origial.cancel;
|
||||
this.serverPacket = origial.serverPacket;
|
||||
this.asyncMarker = asyncMarker;
|
||||
this.asynchronous = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
@ -70,6 +88,16 @@ public class PacketEvent extends EventObject implements Cancellable {
|
||||
return new PacketEvent(source, packet, recipient, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an asynchronous packet event from a synchronous event and a async marker.
|
||||
* @param event - the original synchronous event.
|
||||
* @param marker - the asynchronous marker.
|
||||
* @return The new packet event.
|
||||
*/
|
||||
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
|
||||
return new PacketEvent(event, marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet that will be sent to the player.
|
||||
* @return Packet to send to the player.
|
||||
@ -120,9 +148,67 @@ public class PacketEvent extends EventObject implements Cancellable {
|
||||
|
||||
/**
|
||||
* Whether or not this packet was created by the server.
|
||||
* <p>
|
||||
* Most listeners can deduce this by noting which listener method was invoked.
|
||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||
*/
|
||||
public boolean isServerPacket() {
|
||||
return serverPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the asynchronous marker.
|
||||
* <p>
|
||||
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
|
||||
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
|
||||
* @return The current asynchronous marker, or NULL.
|
||||
*/
|
||||
public AsyncMarker getAsyncMarker() {
|
||||
return asyncMarker;
|
||||
}
|
||||
/**
|
||||
* Set the asynchronous marker.
|
||||
* <p>
|
||||
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
|
||||
* to be processed asynchronously with the given settings.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
|
||||
* @param asyncMarker - the new asynchronous marker, or NULL.
|
||||
* @throws IllegalStateException If the current event is asynchronous.
|
||||
*/
|
||||
public void setAsyncMarker(AsyncMarker asyncMarker) {
|
||||
if (isAsynchronous())
|
||||
throw new IllegalStateException("The marker is immutable for asynchronous events");
|
||||
this.asyncMarker = asyncMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the packet event has been executed asynchronously or not.
|
||||
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsynchronous() {
|
||||
return asynchronous;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
// Write the name of the player (or NULL if it's not set)
|
||||
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
|
||||
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
|
||||
|
||||
if (offlinePlayer != null) {
|
||||
// Better than nothing
|
||||
player = offlinePlayer.getPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,216 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Represents a player object that can be serialized by Java.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2728976288470282810L;
|
||||
|
||||
private transient Location bedSpawnLocation;
|
||||
|
||||
// Relevant data about an offline player
|
||||
private String name;
|
||||
private long firstPlayed;
|
||||
private long lastPlayed;
|
||||
private boolean operator;
|
||||
private boolean banned;
|
||||
private boolean playedBefore;
|
||||
private boolean online;
|
||||
private boolean whitelisted;
|
||||
|
||||
// Proxy helper
|
||||
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
|
||||
|
||||
/**
|
||||
* Constructor used by serialization.
|
||||
*/
|
||||
public SerializedOfflinePlayer() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this serializable offline player from another player.
|
||||
* @param offline - another player.
|
||||
*/
|
||||
public SerializedOfflinePlayer(OfflinePlayer offline) {
|
||||
this.name = offline.getName();
|
||||
this.firstPlayed = offline.getFirstPlayed();
|
||||
this.lastPlayed = offline.getLastPlayed();
|
||||
this.operator = offline.isOp();
|
||||
this.banned = offline.isBanned();
|
||||
this.playedBefore = offline.hasPlayedBefore();
|
||||
this.online = offline.isOnline();
|
||||
this.whitelisted = offline.isWhitelisted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOp() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOp(boolean operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getBedSpawnLocation() {
|
||||
return bedSpawnLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFirstPlayed() {
|
||||
return firstPlayed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastPlayed() {
|
||||
return lastPlayed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPlayedBefore() {
|
||||
return playedBefore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBanned() {
|
||||
return banned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBanned(boolean banned) {
|
||||
this.banned = banned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return online;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWhitelisted() {
|
||||
return whitelisted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWhitelisted(boolean whitelisted) {
|
||||
this.whitelisted = whitelisted;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
output.defaultWriteObject();
|
||||
|
||||
// Serialize the bed spawn location
|
||||
output.writeUTF(bedSpawnLocation.getWorld().getName());
|
||||
output.writeDouble(bedSpawnLocation.getX());
|
||||
output.writeDouble(bedSpawnLocation.getY());
|
||||
output.writeDouble(bedSpawnLocation.getZ());
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
input.defaultReadObject();
|
||||
|
||||
// Well, this is a problem
|
||||
bedSpawnLocation = new Location(
|
||||
getWorld(input.readUTF()),
|
||||
input.readDouble(),
|
||||
input.readDouble(),
|
||||
input.readDouble()
|
||||
);
|
||||
}
|
||||
|
||||
private World getWorld(String name) {
|
||||
try {
|
||||
// Try to get the world at least
|
||||
return Bukkit.getServer().getWorld(name);
|
||||
} catch (Exception e) {
|
||||
// Screw it
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
try {
|
||||
// Try to get the real player underneath
|
||||
return Bukkit.getServer().getPlayerExact(name);
|
||||
} catch (Exception e) {
|
||||
return getProxyPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player object that implements OfflinePlayer by refering to this object.
|
||||
* <p>
|
||||
* All other methods cause an exception.
|
||||
* @return Proxy object.
|
||||
*/
|
||||
public Player getProxyPlayer() {
|
||||
|
||||
// Remember to initialize the method filter
|
||||
if (lookup.size() == 0) {
|
||||
// Add all public methods
|
||||
for (Method method : OfflinePlayer.class.getMethods()) {
|
||||
lookup.put(method.getName(), method);
|
||||
}
|
||||
}
|
||||
|
||||
// MORE CGLIB magic!
|
||||
Enhancer ex = new Enhancer();
|
||||
ex.setSuperclass(Player.class);
|
||||
ex.setCallback(new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
|
||||
// There's no overloaded methods, so we don't care
|
||||
Method offlineMethod = lookup.get(method.getName());
|
||||
|
||||
// Ignore all other methods
|
||||
if (offlineMethod == null) {
|
||||
throw new UnsupportedOperationException(
|
||||
"The method " + method.getName() + " is not supported for offline players.");
|
||||
}
|
||||
|
||||
// Invoke our on method
|
||||
return offlineMethod.invoke(SerializedOfflinePlayer.this, args);
|
||||
}
|
||||
});
|
||||
|
||||
return (Player) ex.create();
|
||||
}
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
/**
|
||||
* A thread-safe implementation of a listener multimap.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ConcurrentListenerMultimap {
|
||||
|
||||
// The core of our map
|
||||
protected ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener>> listeners =
|
||||
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener>>();
|
||||
|
||||
/**
|
||||
* Adds a listener to its requested list of packet recievers.
|
||||
* @param listener - listener with a list of packets to recieve notifcations for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(PacketListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
PrioritizedListener prioritized = new PrioritizedListener(listener, whitelist.getPriority());
|
||||
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
addListener(packetID, prioritized);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the listener to a specific packet notifcation list
|
||||
private void addListener(Integer packetID, PrioritizedListener listener) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(packetID);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
||||
// which is a essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener> value = new SortedCopyOnWriteArray<PrioritizedListener>();
|
||||
|
||||
list = listeners.putIfAbsent(packetID, value);
|
||||
|
||||
// We may end up creating multiple multisets, but we'll agree
|
||||
// on the one to use.
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<Integer> removeListener(PacketListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
List<Integer> removedPackets = new ArrayList<Integer>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(packetID);
|
||||
|
||||
// Remove any listeners
|
||||
if (list != null) {
|
||||
// Don't remove from newly created lists
|
||||
if (list.size() > 0) {
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener(listener, whitelist.getPriority()));
|
||||
|
||||
if (list.size() == 0) {
|
||||
listeners.remove(packetID);
|
||||
removedPackets.add(packetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move on to the next
|
||||
}
|
||||
|
||||
return removedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(Logger logger, PacketEvent event) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// We have to be careful. Cannot modify the underlying list when sending notifications.
|
||||
synchronized (list) {
|
||||
for (PrioritizedListener element : list) {
|
||||
try {
|
||||
element.getListener().onPacketReceiving(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE,
|
||||
"Exception occured in onPacketReceiving() for " +
|
||||
PacketAdapter.getPluginName(element.getListener()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(Logger logger, PacketEvent event) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
synchronized (list) {
|
||||
for (PrioritizedListener element : list) {
|
||||
try {
|
||||
element.getListener().onPacketSending(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE,
|
||||
"Exception occured in onPacketReceiving() for " +
|
||||
PacketAdapter.getPluginName(element.getListener()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener with an associated priority.
|
||||
*/
|
||||
private class PrioritizedListener implements Comparable<PrioritizedListener> {
|
||||
private PacketListener listener;
|
||||
private ListenerPriority priority;
|
||||
|
||||
public PrioritizedListener(PacketListener listener, ListenerPriority priority) {
|
||||
this.listener = listener;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PrioritizedListener other) {
|
||||
// This ensures that lower priority listeners are executed first
|
||||
return Ints.compare(this.getPriority().getSlot(),
|
||||
other.getPriority().getSlot());
|
||||
}
|
||||
|
||||
// Note that this equals() method is NOT consistent with compareTo().
|
||||
// But, it's a private class so who cares.
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// We only care about the listener - priority itself should not make a difference
|
||||
if(obj instanceof PrioritizedListener){
|
||||
final PrioritizedListener other = (PrioritizedListener) obj;
|
||||
return Objects.equal(listener, other.listener);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public PacketListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,8 +35,39 @@ class EntityUtilities {
|
||||
private static Method hashGetMethod;
|
||||
private static Method scanPlayersMethod;
|
||||
|
||||
/*
|
||||
* While this function may look pretty bad, it's essentially just a reflection-warped
|
||||
* version of the following:
|
||||
*
|
||||
* @SuppressWarnings("unchecked")
|
||||
* public static void updateEntity2(Entity entity, List<Player> observers) {
|
||||
*
|
||||
* World world = entity.getWorld();
|
||||
* WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
*
|
||||
* EntityTracker tracker = worldServer.tracker;
|
||||
* EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||
*
|
||||
* List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
|
||||
*
|
||||
* entry.trackedPlayers.removeAll(nmsPlayers);
|
||||
* entry.scanPlayers(nmsPlayers);
|
||||
* }
|
||||
*
|
||||
* private static List<EntityPlayer> getNmsPlayers(List<Player> players) {
|
||||
* List<EntityPlayer> nsmPlayers = new ArrayList<EntityPlayer>();
|
||||
*
|
||||
* for (Player bukkitPlayer : players) {
|
||||
* CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer;
|
||||
* nsmPlayers.add(craftPlayer.getHandle());
|
||||
* }
|
||||
*
|
||||
* return nsmPlayers;
|
||||
* }
|
||||
*
|
||||
*/
|
||||
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||
|
||||
|
||||
World world = entity.getWorld();
|
||||
Object worldServer = ((CraftWorld) world).getHandle();
|
||||
|
||||
|
@ -0,0 +1,189 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Server;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
|
||||
/**
|
||||
* Used to ensure that the 1.3 server is referencing the correct server handler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class InjectedServerConnection {
|
||||
|
||||
private static Field listenerThreadField;
|
||||
private static Field minecraftServerField;
|
||||
private static Method serverConnectionMethod;
|
||||
private static Field listField;
|
||||
|
||||
private List<VolatileField> listFields;
|
||||
private List<ReplacedArrayList<Object>> replacedLists;
|
||||
|
||||
private Server server;
|
||||
private Logger logger;
|
||||
private boolean hasAttempted;
|
||||
private boolean hasSuccess;
|
||||
|
||||
private Object minecraftServer = null;
|
||||
|
||||
public InjectedServerConnection(Logger logger, Server server) {
|
||||
this.listFields = new ArrayList<VolatileField>();
|
||||
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||
this.logger = logger;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public void injectList() {
|
||||
|
||||
// Only execute this method once
|
||||
if (!hasAttempted)
|
||||
hasAttempted = true;
|
||||
else
|
||||
return;
|
||||
|
||||
if (minecraftServerField == null)
|
||||
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
|
||||
|
||||
try {
|
||||
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
||||
} catch (IllegalAccessException e1) {
|
||||
logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (serverConnectionMethod == null)
|
||||
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
||||
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {});
|
||||
// We're using Minecraft 1.3.1
|
||||
injectServerConnection();
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
// Minecraft 1.2.5 or lower
|
||||
injectListenerThread();
|
||||
}
|
||||
}
|
||||
|
||||
private void injectListenerThread() {
|
||||
|
||||
try {
|
||||
|
||||
if (listenerThreadField == null)
|
||||
listenerThreadField = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
||||
getFieldByType(".*NetworkListenThread");
|
||||
} catch (RuntimeException e) {
|
||||
logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer.");
|
||||
return;
|
||||
}
|
||||
|
||||
Object listenerThread = null;
|
||||
|
||||
// Attempt to get the thread
|
||||
try {
|
||||
listenerThread = listenerThreadField.get(minecraftServer);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Unable to read the listener thread.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, great. Get every list field
|
||||
List<Field> lists = FuzzyReflection.fromClass(listenerThreadField.getType()).getFieldListByType(List.class);
|
||||
|
||||
for (Field list : lists) {
|
||||
injectIntoList(listenerThread, list);
|
||||
}
|
||||
|
||||
hasSuccess = true;
|
||||
}
|
||||
|
||||
private void injectServerConnection() {
|
||||
|
||||
Object serverConnection = null;
|
||||
|
||||
// Careful - we might fail
|
||||
try {
|
||||
serverConnection = serverConnectionMethod.invoke(minecraftServer);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.WARNING, "Unable to retrieve server connection", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (listField == null)
|
||||
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
|
||||
getFieldByType("serverConnection", List.class);
|
||||
injectIntoList(serverConnection, listField);
|
||||
hasSuccess = true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void injectIntoList(Object instance, Field field) {
|
||||
VolatileField listFieldRef = new VolatileField(listField, instance, true);
|
||||
List<Object> list = (List<Object>) listFieldRef.getValue();
|
||||
|
||||
// Careful not to inject twice
|
||||
if (list instanceof ReplacedArrayList) {
|
||||
replacedLists.add((ReplacedArrayList<Object>) list);
|
||||
} else {
|
||||
replacedLists.add(new ReplacedArrayList<Object>(list));
|
||||
listFieldRef.setValue(replacedLists.get(0));
|
||||
listFields.add(listFieldRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the server handler instance kept by the "keep alive" object.
|
||||
* @param oldHandler - old server handler.
|
||||
* @param newHandler - new, proxied server handler.
|
||||
*/
|
||||
public void replaceServerHandler(Object oldHandler, Object newHandler) {
|
||||
if (!hasAttempted) {
|
||||
injectList();
|
||||
}
|
||||
|
||||
if (hasSuccess) {
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.addMapping(oldHandler, newHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to the old vanilla server handler, if it has been replaced.
|
||||
* @param oldHandler - old vanilla server handler.
|
||||
*/
|
||||
public void revertServerHandler(Object oldHandler) {
|
||||
if (hasSuccess) {
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.removeMapping(oldHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes everything.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (replacedLists.size() > 0) {
|
||||
// Repair the underlying lists
|
||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||
replacedList.revertAll();
|
||||
}
|
||||
for (VolatileField field : listFields) {
|
||||
field.revertValue();
|
||||
}
|
||||
|
||||
listFields.clear();
|
||||
replacedLists.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ class MinecraftRegistry {
|
||||
// Initialize it, if we haven't already
|
||||
if (packetToID == null) {
|
||||
try {
|
||||
Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("java\\.util\\.Map");
|
||||
Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("packetsField", Map.class);
|
||||
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
@ -88,8 +88,8 @@ class MinecraftRegistry {
|
||||
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
|
||||
|
||||
// Optimized lookup
|
||||
if (lookup.containsKey(packetToID)) {
|
||||
return lookup.get(packetToID);
|
||||
if (lookup.containsKey(packetID)) {
|
||||
return lookup.get(packetID);
|
||||
}
|
||||
|
||||
// Will most likely not be used
|
||||
|
@ -10,6 +10,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
@ -48,7 +51,7 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() throws IllegalAccessException {
|
||||
protected synchronized void initialize() throws IllegalAccessException {
|
||||
super.initialize();
|
||||
|
||||
// Get the sync field as well
|
||||
@ -83,6 +86,15 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkListener(PacketListener listener) {
|
||||
// Unfortunately, we don't support chunk packets
|
||||
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
|
||||
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
|
||||
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
||||
|
@ -11,6 +11,10 @@ import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
||||
*
|
||||
@ -42,6 +46,15 @@ class NetworkObjectInjector extends PlayerInjector {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkListener(PacketListener listener) {
|
||||
// Unfortunately, we don't support chunk packets
|
||||
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
|
||||
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
|
||||
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
||||
|
@ -0,0 +1,181 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||
|
||||
/**
|
||||
* Represents a player hook into the NetServerHandler class.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NetworkServerInjector extends PlayerInjector {
|
||||
|
||||
private static Method sendPacketMethod;
|
||||
|
||||
private StructureModifier<Object> serverHandlerModifier;
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
public NetworkServerInjector(Player player, PacketFilterManager manager,
|
||||
Set<Integer> sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException {
|
||||
super(player, manager, sendingFilters);
|
||||
this.serverInjection = serverInjection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() throws IllegalAccessException {
|
||||
super.initialize();
|
||||
|
||||
// Get the send packet method!
|
||||
if (hasInitialized) {
|
||||
if (sendPacketMethod == null)
|
||||
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
|
||||
if (serverHandlerModifier == null)
|
||||
serverHandlerModifier = new StructureModifier<Object>(serverHandler.getClass(), null, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
|
||||
|
||||
if (serverDeleage != null) {
|
||||
try {
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
sendPacketMethod.invoke(serverDeleage, packet);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Unable to access send packet method.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
||||
if (serverHandlerRef == null)
|
||||
throw new IllegalStateException("Cannot find server handler.");
|
||||
// Don't inject twice
|
||||
if (serverHandlerRef.getValue() instanceof Factory)
|
||||
return;
|
||||
|
||||
Class<?> serverClass = serverHandler.getClass();
|
||||
|
||||
Enhancer ex = new Enhancer();
|
||||
ex.setClassLoader(manager.getClassLoader());
|
||||
ex.setSuperclass(serverClass);
|
||||
ex.setCallback(new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
|
||||
// The send packet method!
|
||||
if (method.equals(sendPacketMethod)) {
|
||||
Packet packet = (Packet) args[0];
|
||||
|
||||
if (packet != null) {
|
||||
packet = handlePacketRecieved(packet);
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
if (packet != null)
|
||||
args[0] = packet;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Call the method directly
|
||||
return proxy.invokeSuper(obj, args);
|
||||
}
|
||||
});
|
||||
|
||||
// Use the existing field values when we create our copy
|
||||
DefaultInstances serverInstances = DefaultInstances.fromArray(
|
||||
ExistingGenerator.fromObjectFields(serverHandler),
|
||||
PrimitiveGenerator.INSTANCE,
|
||||
CollectionGenerator.INSTANCE);
|
||||
|
||||
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
|
||||
serverInjection.replaceServerHandler(serverHandler, proxyObject);
|
||||
|
||||
// Inject it now
|
||||
if (proxyObject != null) {
|
||||
copyTo(serverHandler, proxyObject);
|
||||
serverHandlerRef.setValue(proxyObject);
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy every field in server handler A to server handler B.
|
||||
* @param source - fields to copy.
|
||||
* @param destination - fields to copy to.
|
||||
*/
|
||||
private void copyTo(Object source, Object destination) {
|
||||
StructureModifier<Object> modifierSource = serverHandlerModifier.withTarget(source);
|
||||
StructureModifier<Object> modifierDest = serverHandlerModifier.withTarget(destination);
|
||||
|
||||
// Copy every field
|
||||
try {
|
||||
for (int i = 0; i < modifierSource.size(); i++) {
|
||||
modifierDest.write(i, modifierSource.read(i));
|
||||
}
|
||||
} catch (FieldAccessException e) {
|
||||
throw new RuntimeException("Unable to copy fields from NetServerHandler.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
|
||||
copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue());
|
||||
serverHandlerRef.revertValue();
|
||||
}
|
||||
|
||||
serverInjection.revertServerHandler(serverHandler);
|
||||
|
||||
try {
|
||||
if (getNetHandler() != null) {
|
||||
// Restore packet listener
|
||||
try {
|
||||
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Oh well
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkListener(PacketListener listener) {
|
||||
// We support everything
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@ -45,7 +46,10 @@ import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
@ -60,14 +64,23 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
*/
|
||||
public enum PlayerInjectHooks {
|
||||
/**
|
||||
* Override the packet queue lists in NetworkHandler.
|
||||
* Override the packet queue lists in NetworkHandler.
|
||||
* <p>
|
||||
* Cannot intercept MapChunk packets.
|
||||
*/
|
||||
NETWORK_HANDLER_FIELDS,
|
||||
|
||||
/**
|
||||
* Override the network handler object itself.
|
||||
* Override the network handler object itself. Only works in 1.3.
|
||||
* <p>
|
||||
* Cannot intercept MapChunk packets.
|
||||
*/
|
||||
NETWORK_MANAGER_OBJECT
|
||||
NETWORK_MANAGER_OBJECT,
|
||||
|
||||
/**
|
||||
* Override the server handler object. Versatile, but a tad slower.
|
||||
*/
|
||||
NETWORK_SERVER_OBJECT;
|
||||
}
|
||||
|
||||
// Create a concurrent set
|
||||
@ -84,26 +97,35 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
// Packet injection
|
||||
private PacketInjector packetInjector;
|
||||
|
||||
// Server connection injection
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// Enabled packet filters
|
||||
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
||||
|
||||
// The two listener containers
|
||||
private ConcurrentListenerMultimap recievedListeners = new ConcurrentListenerMultimap();
|
||||
private ConcurrentListenerMultimap sendingListeners = new ConcurrentListenerMultimap();
|
||||
private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
|
||||
private SortedPacketListenerList sendingListeners = new SortedPacketListenerList();
|
||||
|
||||
// Whether or not this class has been closed
|
||||
private boolean hasClosed;
|
||||
private volatile boolean hasClosed;
|
||||
|
||||
// The default class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// The last successful player hook
|
||||
private PlayerInjector lastSuccessfulHook;
|
||||
|
||||
// Error logger
|
||||
private Logger logger;
|
||||
|
||||
// The async packet handler
|
||||
private AsyncFilterManager asyncFilterManager;
|
||||
|
||||
/**
|
||||
* Only create instances of this class if protocol lib is disabled.
|
||||
*/
|
||||
public PacketFilterManager(ClassLoader classLoader, Logger logger) {
|
||||
public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) {
|
||||
if (logger == null)
|
||||
throw new IllegalArgumentException("logger cannot be NULL.");
|
||||
if (classLoader == null)
|
||||
@ -114,11 +136,18 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
this.classLoader = classLoader;
|
||||
this.logger = logger;
|
||||
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
|
||||
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
|
||||
this.serverInjection = new InjectedServerConnection(logger, server);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsynchronousManager getAsynchronousManager() {
|
||||
return asyncFilterManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
@ -133,6 +162,13 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
*/
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||
this.playerHook = playerHook;
|
||||
|
||||
// Make sure the current listeners are compatible
|
||||
if (lastSuccessfulHook != null) {
|
||||
for (PacketListener listener : packetListeners) {
|
||||
checkListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
@ -161,12 +197,17 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
if (hasSending || hasReceiving) {
|
||||
// Add listeners and hooks
|
||||
if (hasSending) {
|
||||
verifyWhitelist(listener, sending);
|
||||
sendingListeners.addListener(listener, sending);
|
||||
enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist());
|
||||
}
|
||||
if (hasReceiving) {
|
||||
verifyWhitelist(listener, receiving);
|
||||
recievedListeners.addListener(listener, receiving);
|
||||
enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
||||
|
||||
// We don't know if we've hooked any players yet
|
||||
checkListener(listener);
|
||||
}
|
||||
|
||||
// Inform our injected hooks
|
||||
@ -174,6 +215,36 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the packet IDs in a whitelist is valid.
|
||||
* @param listener - the listener that will be mentioned in the error.
|
||||
* @param whitelist - whitelist of packet IDs.
|
||||
* @throws IllegalArgumentException If the whitelist is illegal.
|
||||
*/
|
||||
public static void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
|
||||
for (Integer id : whitelist.getWhitelist()) {
|
||||
if (id >= 256 || id < 0) {
|
||||
throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.",
|
||||
id, PacketAdapter.getPluginName(listener))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a listener is valid or not.
|
||||
* @param listener - listener to check.
|
||||
* @throws IllegalStateException If the given listener's whitelist cannot be fulfilled.
|
||||
*/
|
||||
public void checkListener(PacketListener listener) {
|
||||
try {
|
||||
if (lastSuccessfulHook != null)
|
||||
lastSuccessfulHook.checkListener(listener);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
@ -212,6 +283,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
removePacketListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Do the same for the asynchronous events
|
||||
asyncFilterManager.unregisterAsyncHandlers(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,7 +293,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(PacketEvent event) {
|
||||
recievedListeners.invokePacketRecieving(logger, event);
|
||||
handlePacket(recievedListeners, event, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +301,41 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(PacketEvent event) {
|
||||
sendingListeners.invokePacketSending(logger, event);
|
||||
handlePacket(sendingListeners, event, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a packet sending or receiving event.
|
||||
* <p>
|
||||
* Note that we also handle asynchronous events.
|
||||
* @param packetListeners - packet listeners that will receive this event.
|
||||
* @param event - the evnet to broadcast.
|
||||
*/
|
||||
private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) {
|
||||
|
||||
// By default, asynchronous packets are queued for processing
|
||||
if (asyncFilterManager.hasAsynchronousListeners(event)) {
|
||||
event.setAsyncMarker(asyncFilterManager.createAsyncMarker());
|
||||
}
|
||||
|
||||
// Process synchronous events
|
||||
if (sending)
|
||||
packetListeners.invokePacketSending(logger, event);
|
||||
else
|
||||
packetListeners.invokePacketRecieving(logger, event);
|
||||
|
||||
// To cancel asynchronous processing, use the async marker
|
||||
if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
|
||||
asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
|
||||
|
||||
// The above makes a copy of the event, so it's safe to cancel it
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// NULL marker mean we're dealing with no asynchronous listeners
|
||||
private boolean hasAsyncCancelled(AsyncMarker marker) {
|
||||
return marker == null || marker.isAsyncCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -298,6 +406,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
PlayerInjector injector = getInjector(sender);
|
||||
Packet mcPacket = packet.getHandle();
|
||||
|
||||
// Make sure the packet isn't cancelled
|
||||
packetInjector.undoCancel(packet.getID(), mcPacket);
|
||||
|
||||
if (filters) {
|
||||
mcPacket = injector.handlePacketRecieved(mcPacket);
|
||||
}
|
||||
@ -343,8 +454,6 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
@Override
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||
|
||||
|
||||
EntityUtilities.updateEntity(entity, observers);
|
||||
}
|
||||
|
||||
@ -360,20 +469,22 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
/**
|
||||
* Used to construct a player hook.
|
||||
* @param player - the player to hook.
|
||||
* @param hook - the hook type.
|
||||
* @return A new player hoook
|
||||
* @throws IllegalAccessException Unable to do our reflection magic.
|
||||
*/
|
||||
protected PlayerInjector getPlayerHookInstance(Player player) throws IllegalAccessException {
|
||||
|
||||
protected PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
|
||||
// Construct the correct player hook
|
||||
switch (playerHook) {
|
||||
switch (hook) {
|
||||
case NETWORK_HANDLER_FIELDS:
|
||||
return new NetworkFieldInjector(player, this, sendingFilters);
|
||||
case NETWORK_MANAGER_OBJECT:
|
||||
return new NetworkObjectInjector(player, this, sendingFilters);
|
||||
case NETWORK_SERVER_OBJECT:
|
||||
return new NetworkServerInjector(player, this, sendingFilters, serverInjection);
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -381,20 +492,51 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param player - player to hook.
|
||||
*/
|
||||
protected void injectPlayer(Player player) {
|
||||
|
||||
PlayerInjector injector = null;
|
||||
PlayerInjectHooks currentHook = playerHook;
|
||||
boolean firstPlayer = lastSuccessfulHook == null;
|
||||
|
||||
// Don't inject if the class has closed
|
||||
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
|
||||
try {
|
||||
PlayerInjector injector = getPlayerHookInstance(player);
|
||||
while (true) {
|
||||
try {
|
||||
injector = getHookInstance(player, currentHook);
|
||||
injector.injectManager();
|
||||
playerInjection.put(player, injector);
|
||||
connectionLookup.put(injector.getInputStream(false), player);
|
||||
break;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
injector.injectManager();
|
||||
playerInjection.put(player, injector);
|
||||
connectionLookup.put(injector.getInputStream(false), player);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
// Mark this injection attempt as a failure
|
||||
playerInjection.put(player, null);
|
||||
logger.log(Level.SEVERE, "Unable to access fields.", e);
|
||||
// Mark this injection attempt as a failure
|
||||
logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e);
|
||||
|
||||
// Clean up as much as possible
|
||||
try {
|
||||
if (injector != null)
|
||||
injector.cleanupAll();
|
||||
} catch (Exception e2) {
|
||||
logger.log(Level.WARNING, "Cleaing up after player hook failed.", e);
|
||||
}
|
||||
|
||||
if (currentHook.ordinal() > 0) {
|
||||
// Choose the previous player hook type
|
||||
currentHook = PlayerInjectHooks.values()[currentHook.ordinal() - 1];
|
||||
logger.log(Level.INFO, "Switching to " + currentHook.toString() + " instead.");
|
||||
} else {
|
||||
// UTTER FAILURE
|
||||
playerInjection.put(player, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update values
|
||||
if (injector != null)
|
||||
lastSuccessfulHook = injector;
|
||||
if (currentHook != playerHook || firstPlayer)
|
||||
setPlayerHook(currentHook);
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,7 +619,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
if (event instanceof PlayerJoinEvent)
|
||||
injectPlayer(((PlayerJoinEvent) event).getPlayer());
|
||||
else if (event instanceof PlayerQuitEvent)
|
||||
injectPlayer(((PlayerQuitEvent) event).getPlayer());
|
||||
uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -524,12 +666,12 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
if (!hasClosed && player != null) {
|
||||
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
DataInputStream input = injector.getInputStream(true);
|
||||
|
||||
if (injector != null) {
|
||||
DataInputStream input = injector.getInputStream(true);
|
||||
injector.cleanupAll();
|
||||
|
||||
playerInjection.remove(injector);
|
||||
playerInjection.remove(player);
|
||||
connectionLookup.remove(input);
|
||||
}
|
||||
}
|
||||
@ -565,23 +707,31 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
public void close() {
|
||||
// Guard
|
||||
if (hasClosed)
|
||||
if (hasClosed || playerInjection == null)
|
||||
return;
|
||||
|
||||
|
||||
// Remove everything
|
||||
for (PlayerInjector injection : playerInjection.values()) {
|
||||
injection.cleanupAll();
|
||||
if (injection != null) {
|
||||
injection.cleanupAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Remove packet handlers
|
||||
if (packetInjector != null)
|
||||
packetInjector.cleanupAll();
|
||||
|
||||
// Remove server handler
|
||||
serverInjection.cleanupAll();
|
||||
hasClosed = true;
|
||||
|
||||
// Remove listeners
|
||||
packetListeners.clear();
|
||||
playerInjection.clear();
|
||||
connectionLookup.clear();
|
||||
hasClosed = true;
|
||||
|
||||
// Clean up async handlers. We have to do this last.
|
||||
asyncFilterManager.cleanupAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@ -52,6 +53,9 @@ class PacketInjector {
|
||||
// Allows us to determine the sender
|
||||
private Map<DataInputStream, Player> playerLookup;
|
||||
|
||||
// Allows us to look up read packet injectors
|
||||
private Map<Integer, ReadPacketModifier> readModifier;
|
||||
|
||||
// Class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
@ -61,9 +65,24 @@ class PacketInjector {
|
||||
this.classLoader = classLoader;
|
||||
this.manager = manager;
|
||||
this.playerLookup = playerLookup;
|
||||
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo a packet cancel.
|
||||
* @param id - the id of the packet.
|
||||
* @param packet - packet to uncancel.
|
||||
*/
|
||||
public void undoCancel(Integer id, Packet packet) {
|
||||
ReadPacketModifier modifier = readModifier.get(id);
|
||||
|
||||
// Cancelled packets are represented with NULL
|
||||
if (modifier != null && modifier.getOverride(packet) == null) {
|
||||
modifier.removeOverride(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize() throws IllegalAccessException {
|
||||
if (intHashMap == null) {
|
||||
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
|
||||
@ -109,10 +128,12 @@ class PacketInjector {
|
||||
ex.setClassLoader(classLoader);
|
||||
Class proxy = ex.createClass();
|
||||
|
||||
// Create the proxy handler
|
||||
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this);
|
||||
readModifier.put(packetID, modifier);
|
||||
|
||||
// Add a static reference
|
||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] {
|
||||
new ReadPacketModifier(packetID, this)
|
||||
});
|
||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
|
||||
|
||||
try {
|
||||
// Override values
|
||||
@ -147,6 +168,7 @@ class PacketInjector {
|
||||
|
||||
putMethod.invoke(intHashMap, packetID, old);
|
||||
previous.remove(packetID);
|
||||
readModifier.remove(packetID);
|
||||
registry.remove(proxy);
|
||||
overwritten.remove(packetID);
|
||||
return true;
|
||||
|
@ -31,6 +31,7 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
@ -56,9 +57,11 @@ abstract class PlayerInjector {
|
||||
|
||||
// Reference to the player's network manager
|
||||
protected VolatileField networkManagerRef;
|
||||
protected VolatileField serverHandlerRef;
|
||||
protected Object networkManager;
|
||||
|
||||
// Current net handler
|
||||
protected Object serverHandler;
|
||||
protected Object netHandler;
|
||||
|
||||
// The packet manager and filters
|
||||
@ -75,12 +78,18 @@ abstract class PlayerInjector {
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the notch (NMS) entity player object.
|
||||
* @return Notch player object.
|
||||
*/
|
||||
protected EntityPlayer getEntityPlayer() {
|
||||
CraftPlayer craft = (CraftPlayer) player;
|
||||
return craft.getHandle();
|
||||
}
|
||||
|
||||
protected void initialize() throws IllegalAccessException {
|
||||
|
||||
CraftPlayer craft = (CraftPlayer) player;
|
||||
EntityPlayer notchEntity = craft.getHandle();
|
||||
|
||||
Object serverHandler = null;
|
||||
EntityPlayer notchEntity = getEntityPlayer();
|
||||
|
||||
if (!hasInitialized) {
|
||||
// Do this first, in case we encounter an exception
|
||||
@ -89,7 +98,8 @@ abstract class PlayerInjector {
|
||||
// Retrieve the server handler
|
||||
if (serverHandlerField == null)
|
||||
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
|
||||
serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
|
||||
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
|
||||
serverHandler = serverHandlerRef.getValue();
|
||||
|
||||
// Next, get the network manager
|
||||
if (networkManagerField == null)
|
||||
@ -118,26 +128,31 @@ abstract class PlayerInjector {
|
||||
* @return Current net handler.
|
||||
* @throws IllegalAccessException Unable to find or retrieve net handler.
|
||||
*/
|
||||
private Object getNetHandler() throws IllegalAccessException {
|
||||
protected Object getNetHandler() throws IllegalAccessException {
|
||||
|
||||
// What a mess
|
||||
try {
|
||||
if (netHandlerField == null)
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||
getFieldByType("net\\.minecraft\\.NetHandler");
|
||||
} catch (RuntimeException e1) {
|
||||
// Swallow it
|
||||
}
|
||||
|
||||
// Second attempt
|
||||
if (netHandlerField == null) {
|
||||
try {
|
||||
// Well, that sucks. Try just Minecraft objects then.
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
||||
|
||||
} catch (RuntimeException e2) {
|
||||
return new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
|
||||
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Get the handler
|
||||
if (netHandler != null)
|
||||
if (netHandler == null)
|
||||
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
|
||||
return netHandler;
|
||||
}
|
||||
@ -190,6 +205,14 @@ abstract class PlayerInjector {
|
||||
*/
|
||||
public abstract void cleanupAll();
|
||||
|
||||
/**
|
||||
* Invoked before a new listener is registered.
|
||||
* <p>
|
||||
* The player injector should throw an exception if this listener cannot be properly supplied with packet events.
|
||||
* @param listener - the listener that is about to be registered.
|
||||
*/
|
||||
public abstract void checkListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Allows a packet to be recieved by the listeners.
|
||||
* @param packet - packet to recieve.
|
||||
@ -200,7 +223,7 @@ abstract class PlayerInjector {
|
||||
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
||||
|
||||
// Make sure we're listening
|
||||
if (sendingFilters.contains(id)) {
|
||||
if (id != null && sendingFilters.contains(id)) {
|
||||
// A packet has been sent guys!
|
||||
PacketContainer container = new PacketContainer(id, packet);
|
||||
PacketEvent event = PacketEvent.fromServer(manager, container, player);
|
||||
|
@ -0,0 +1,64 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
/**
|
||||
* Represents a listener with a priority.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PrioritizedListener<TListener> implements Comparable<PrioritizedListener<TListener>> {
|
||||
|
||||
private TListener listener;
|
||||
private ListenerPriority priority;
|
||||
|
||||
public PrioritizedListener(TListener listener, ListenerPriority priority) {
|
||||
this.listener = listener;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PrioritizedListener<TListener> other) {
|
||||
// This ensures that lower priority listeners are executed first
|
||||
return Ints.compare(
|
||||
this.getPriority().getSlot(),
|
||||
other.getPriority().getSlot());
|
||||
}
|
||||
|
||||
// Note that this equals() method is NOT consistent with compareTo().
|
||||
// But, it's a private class so who cares.
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// We only care about the listener - priority itself should not make a difference
|
||||
if(obj instanceof PrioritizedListener){
|
||||
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
|
||||
return Objects.equal(listener, other.listener);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying listener.
|
||||
* @return Underlying listener.
|
||||
*/
|
||||
public TListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the priority of this listener.
|
||||
* @return Listener priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
}
|
@ -45,6 +45,23 @@ class ReadPacketModifier implements MethodInterceptor {
|
||||
this.packetID = packetID;
|
||||
this.packetInjector = packetInjector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any packet overrides.
|
||||
* @param packet - the packet to rever
|
||||
*/
|
||||
public void removeOverride(Packet packet) {
|
||||
override.remove(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet that overrides the methods of the given packet.
|
||||
* @param packet - the given packet.
|
||||
* @return Overriden object.
|
||||
*/
|
||||
public Object getOverride(Packet packet) {
|
||||
return override.get(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
@ -60,9 +77,14 @@ class ReadPacketModifier implements MethodInterceptor {
|
||||
if (override.containsKey(thisObj)) {
|
||||
Object overridenObject = override.get(thisObj);
|
||||
|
||||
// Cancel EVERYTHING, including "processPacket"
|
||||
if (overridenObject == null)
|
||||
return null;
|
||||
// This packet has been cancelled
|
||||
if (overridenObject == null) {
|
||||
// So, cancel all void methods
|
||||
if (method.getReturnType().equals(Void.TYPE))
|
||||
return null;
|
||||
else // Revert to normal for everything else
|
||||
overridenObject = thisObj;
|
||||
}
|
||||
|
||||
returnValue = proxy.invokeSuper(overridenObject, args);
|
||||
} else {
|
||||
@ -75,7 +97,7 @@ class ReadPacketModifier implements MethodInterceptor {
|
||||
|
||||
// We need this in order to get the correct player
|
||||
DataInputStream input = (DataInputStream) args[0];
|
||||
|
||||
|
||||
// Let the people know
|
||||
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
|
||||
PacketEvent event = packetInjector.packetRecieved(container, input);
|
||||
|
136
ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java
Normale Datei
136
ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java
Normale Datei
@ -0,0 +1,136 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.ForwardingList;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
|
||||
/**
|
||||
* Represents an array list that wraps another list, while automatically replacing one element with another.
|
||||
* <p>
|
||||
* The replaced elements can be recovered.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TKey> - type of the elements we're replacing.
|
||||
*/
|
||||
class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
|
||||
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
|
||||
private List<TKey> underlyingList;
|
||||
|
||||
public ReplacedArrayList(List<TKey> underlyingList) {
|
||||
this.underlyingList = underlyingList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(TKey element) {
|
||||
if (replaceMap.containsKey(element)) {
|
||||
return super.add(replaceMap.get(element));
|
||||
} else {
|
||||
return super.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, TKey element) {
|
||||
if (replaceMap.containsKey(element)) {
|
||||
super.add(index, replaceMap.get(element));
|
||||
} else {
|
||||
super.add(index, element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends TKey> collection) {
|
||||
int oldSize = size();
|
||||
|
||||
for (TKey element : collection)
|
||||
add(element);
|
||||
return size() != oldSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends TKey> elements) {
|
||||
int oldSize = size();
|
||||
|
||||
for (TKey element : elements)
|
||||
add(index++, element);
|
||||
return size() != oldSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TKey> delegate() {
|
||||
return underlyingList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a replace rule.
|
||||
* <p>
|
||||
* This automatically replaces every existing element.
|
||||
* @param target - instance to find.
|
||||
* @param replacement - instance to replace with.
|
||||
*/
|
||||
public synchronized void addMapping(TKey target, TKey replacement) {
|
||||
replaceMap.put(target, replacement);
|
||||
|
||||
// Replace existing elements
|
||||
replaceAll(target, replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the given mapping.
|
||||
* @param target - the instance we replaced.
|
||||
*/
|
||||
public synchronized void removeMapping(TKey target) {
|
||||
// Make sure the mapping exist
|
||||
if (replaceMap.containsKey(target)) {
|
||||
TKey replacement = replaceMap.get(target);
|
||||
replaceMap.remove(target);
|
||||
|
||||
// Revert existing elements
|
||||
replaceAll(replacement, target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all instances of the given object.
|
||||
* @param find - object to find.
|
||||
* @param replace - object to replace it with.
|
||||
*/
|
||||
public synchronized void replaceAll(TKey find, TKey replace) {
|
||||
for (int i = 0; i < underlyingList.size(); i++) {
|
||||
if (Objects.equal(underlyingList.get(i), find))
|
||||
underlyingList.set(i, replace);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo all replacements.
|
||||
*/
|
||||
public synchronized void revertAll() {
|
||||
|
||||
// No need to do anything else
|
||||
if (replaceMap.size() < 1)
|
||||
return;
|
||||
|
||||
BiMap<TKey, TKey> inverse = replaceMap.inverse();
|
||||
|
||||
for (int i = 0; i < underlyingList.size(); i++) {
|
||||
TKey replaced = underlyingList.get(i);
|
||||
|
||||
if (inverse.containsKey(replaced)) {
|
||||
underlyingList.set(i, inverse.get(replaced));
|
||||
}
|
||||
}
|
||||
|
||||
replaceMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
revertAll();
|
||||
super.finalize();
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
/**
|
||||
* Registry of synchronous packet listeners.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(Logger logger, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// The returned list is thread-safe
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
try {
|
||||
element.getListener().onPacketReceiving(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE,
|
||||
"Exception occured in onPacketReceiving() for " +
|
||||
PacketAdapter.getPluginName(element.getListener()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(Logger logger, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
try {
|
||||
element.getListener().onPacketSending(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE,
|
||||
"Exception occured in onPacketSending() for " +
|
||||
PacketAdapter.getPluginName(element.getListener()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -17,12 +17,17 @@
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.reflect.compiler.CompileListener;
|
||||
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
|
||||
|
||||
/**
|
||||
* Caches structure modifiers.
|
||||
@ -30,7 +35,10 @@ import com.comphenix.protocol.reflect.StructureModifier;
|
||||
*/
|
||||
public class StructureCache {
|
||||
// Structure modifiers
|
||||
private static Map<Integer, StructureModifier<Object>> structureModifiers = new HashMap<Integer, StructureModifier<Object>>();
|
||||
private static ConcurrentMap<Integer, StructureModifier<Object>> structureModifiers =
|
||||
new ConcurrentHashMap<Integer, StructureModifier<Object>>();
|
||||
|
||||
private static Set<Integer> compiling = new HashSet<Integer>();
|
||||
|
||||
/**
|
||||
* Creates an empty Minecraft packet of the given ID.
|
||||
@ -53,15 +61,51 @@ public class StructureCache {
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(int id) {
|
||||
// Compile structures by default
|
||||
return getStructure(id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet id.
|
||||
* @param id - packet ID.
|
||||
* @param compile - whether or not to asynchronously compile the structure modifier.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(int id, boolean compile) {
|
||||
|
||||
StructureModifier<Object> result = structureModifiers.get(id);
|
||||
|
||||
// Use the vanilla class definition
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (result == null) {
|
||||
result = new StructureModifier<Object>(
|
||||
// Use the vanilla class definition
|
||||
final StructureModifier<Object> value = new StructureModifier<Object>(
|
||||
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
|
||||
|
||||
structureModifiers.put(id, result);
|
||||
result = structureModifiers.putIfAbsent(id, value);
|
||||
|
||||
// We may end up creating multiple modifiers, but we'll agree on which to use
|
||||
if (result == null) {
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically compile the structure modifier
|
||||
if (compile && !(result instanceof CompiledStructureModifier)) {
|
||||
// Compilation is many orders of magnitude slower than synchronization
|
||||
synchronized (compiling) {
|
||||
final int idCopy = id;
|
||||
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
|
||||
|
||||
if (!compiling.contains(id) && compiler != null) {
|
||||
compiler.scheduleCompilation(result, new CompileListener<Object>() {
|
||||
@Override
|
||||
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||
structureModifiers.put(idCopy, compiledModifier);
|
||||
}
|
||||
});
|
||||
compiling.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -19,8 +19,10 @@ package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -96,6 +98,7 @@ public class FuzzyReflection {
|
||||
* Retrieves a method by looking at its name.
|
||||
* @param nameRegex - regular expression that will match method names.
|
||||
* @return The first method that satisfies the regular expression.
|
||||
* @throws RuntimeException If the method cannot be found.
|
||||
*/
|
||||
public Method getMethodByName(String nameRegex) {
|
||||
|
||||
@ -139,11 +142,38 @@ public class FuzzyReflection {
|
||||
* @return The first method that satisfies the parameter types.
|
||||
*/
|
||||
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
|
||||
// Find the correct method to call
|
||||
List<Method> methods = getMethodListByParameters(returnType, args);
|
||||
|
||||
if (methods.size() > 0) {
|
||||
return methods.get(0);
|
||||
} else {
|
||||
// That sucks
|
||||
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a method by looking at the parameter types and return type only.
|
||||
* @param name - potential name of the method. Only used by the error mechanism.
|
||||
* @param returnType - regular expression matching the return type of the method to find.
|
||||
* @param args - regular expressions of the matching parameter types.
|
||||
* @return The first method that satisfies the parameter types.
|
||||
*/
|
||||
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
|
||||
|
||||
Pattern match = Pattern.compile(returnTypeRegex);
|
||||
Pattern[] argMatch = new Pattern[argsRegex.length];
|
||||
|
||||
for (int i = 0; i < argsRegex.length; i++) {
|
||||
argMatch[i] = Pattern.compile(argsRegex[i]);
|
||||
}
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
|
||||
return method;
|
||||
if (match.matcher(method.getReturnType().getName()).matches()) {
|
||||
if (matchParameters(argMatch, method.getParameterTypes()))
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,6 +181,39 @@ public class FuzzyReflection {
|
||||
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
||||
}
|
||||
|
||||
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
|
||||
if (parameterMatchers.length != argTypes.length)
|
||||
throw new IllegalArgumentException("Arrays must have the same cardinality.");
|
||||
|
||||
// Check types against the regular expressions
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves every method that has the given parameter types and return type.
|
||||
* @param returnType - return type of the method to find.
|
||||
* @param args - parameter types of the method to find.
|
||||
* @return Every method that satisfies the given constraints.
|
||||
*/
|
||||
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
|
||||
|
||||
List<Method> methods = new ArrayList<Method>();
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field by name.
|
||||
* @param nameRegex - regular expression that will match a field name.
|
||||
@ -172,6 +235,46 @@ public class FuzzyReflection {
|
||||
nameRegex + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first field with a type equal to or more specific to the given type.
|
||||
* @param name - name the field probably is given. This will only be used in the error message.
|
||||
* @param type - type of the field to find.
|
||||
* @return The first field with a type that is an instance of the given type.
|
||||
*/
|
||||
public Field getFieldByType(String name, Class<?> type) {
|
||||
|
||||
List<Field> fields = getFieldListByType(type);
|
||||
|
||||
if (fields.size() > 0) {
|
||||
return fields.get(0);
|
||||
} else {
|
||||
// Looks like we're outdated. Too bad.
|
||||
throw new RuntimeException(String.format("Unable to find a field %s with the type %s in %s",
|
||||
name, type.getName(), source.getName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves every field with a type equal to or more specific to the given type.
|
||||
* @param type - type of the fields to find.
|
||||
* @return Every field with a type that is an instance of the given type.
|
||||
*/
|
||||
public List<Field> getFieldListByType(Class<?> type) {
|
||||
|
||||
List<Field> fields = new ArrayList<Field>();
|
||||
|
||||
// Field with a compatible type
|
||||
for (Field field : getFields()) {
|
||||
// A assignable from B -> B instanceOf A
|
||||
if (type.isAssignableFrom(field.getType())) {
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field by type.
|
||||
* <p>
|
||||
|
@ -21,34 +21,45 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Provides list-oriented access to the fields of a Minecraft packet.
|
||||
* <p>
|
||||
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TField> Type of the fields to retrieve.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StructureModifier<TField> {
|
||||
|
||||
// Object and its type
|
||||
private Class targetType;
|
||||
private Object target;
|
||||
protected Class targetType;
|
||||
protected Object target;
|
||||
|
||||
// Converter. May be NULL.
|
||||
private EquivalentConverter<TField> converter;
|
||||
protected EquivalentConverter<TField> converter;
|
||||
|
||||
// The fields to read in order
|
||||
private Class fieldType;
|
||||
private List<Field> data = new ArrayList<Field>();
|
||||
protected Class fieldType;
|
||||
protected List<Field> data = new ArrayList<Field>();
|
||||
|
||||
// Improved default values
|
||||
private Set<Field> defaultFields;
|
||||
protected Map<Field, Integer> defaultFields;
|
||||
|
||||
// Cache of previous types
|
||||
private Map<Class, StructureModifier> subtypeCache;
|
||||
protected Map<Class, StructureModifier> subtypeCache;
|
||||
|
||||
// Whether or subclasses should handle conversion
|
||||
protected boolean customConvertHandling;
|
||||
|
||||
/**
|
||||
* Creates a structure modifier.
|
||||
@ -58,22 +69,38 @@ public class StructureModifier<TField> {
|
||||
*/
|
||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
||||
List<Field> fields = getFields(targetType, superclassExclude);
|
||||
Set<Field> defaults = requireDefault ? generateDefaultFields(fields) : new HashSet<Field>();
|
||||
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
|
||||
|
||||
initialize(targetType, Object.class, fields, defaults, null, new HashMap<Class, StructureModifier>());
|
||||
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
|
||||
}
|
||||
|
||||
private StructureModifier(StructureModifier<TField> other, Object target) {
|
||||
initialize(other.targetType, other.fieldType, other.data, other.defaultFields, other.converter, other.subtypeCache);
|
||||
this.target = target;
|
||||
/**
|
||||
* Consumers of this method should call "initialize".
|
||||
*/
|
||||
protected StructureModifier() {
|
||||
|
||||
}
|
||||
|
||||
private StructureModifier() {
|
||||
// Consumers of this method should call "initialize"
|
||||
/**
|
||||
* Initialize using the same field types.
|
||||
* @param other - information to set.
|
||||
*/
|
||||
protected void initialize(StructureModifier<TField> other) {
|
||||
initialize(other.targetType, other.fieldType, other.data,
|
||||
other.defaultFields, other.converter, other.subtypeCache);
|
||||
}
|
||||
|
||||
private void initialize(Class targetType, Class fieldType,
|
||||
List<Field> data, Set<Field> defaultFields,
|
||||
/**
|
||||
* Initialize every field of this class.
|
||||
* @param targetType - type of the object we're reading and writing from.
|
||||
* @param fieldType - the common type of the fields we're modifying.
|
||||
* @param data - list of fields to modify.
|
||||
* @param defaultFields - list of fields that will be automatically initialized.
|
||||
* @param converter - converts between the common field type and the actual type the consumer expects.
|
||||
* @param subTypeCache - a structure modifier cache.
|
||||
*/
|
||||
protected void initialize(Class targetType, Class fieldType,
|
||||
List<Field> data, Map<Field, Integer> defaultFields,
|
||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
@ -101,7 +128,7 @@ public class StructureModifier<TField> {
|
||||
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
||||
|
||||
// Use the converter, if we have it
|
||||
if (converter != null)
|
||||
if (needConversion())
|
||||
return converter.getSpecific(result);
|
||||
else
|
||||
return (TField) result;
|
||||
@ -140,7 +167,7 @@ public class StructureModifier<TField> {
|
||||
throw new IllegalStateException("Cannot write to a NULL target.");
|
||||
|
||||
// Use the converter, if it exists
|
||||
Object obj = converter != null ? converter.getGeneric(value) : value;
|
||||
Object obj = needConversion() ? converter.getGeneric(value) : value;
|
||||
|
||||
try {
|
||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||
@ -152,6 +179,14 @@ public class StructureModifier<TField> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should use the converter instance.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
private final boolean needConversion() {
|
||||
return converter != null && !customConvertHandling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value of a given field IF and ONLY if it exists.
|
||||
* @param fieldIndex - index of the potential field.
|
||||
@ -197,7 +232,7 @@ public class StructureModifier<TField> {
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
|
||||
// Write a default instance to every field
|
||||
for (Field field : defaultFields) {
|
||||
for (Field field : defaultFields.keySet()) {
|
||||
try {
|
||||
FieldUtils.writeField(field, target,
|
||||
generator.getDefault(field.getType()), true);
|
||||
@ -223,24 +258,32 @@ public class StructureModifier<TField> {
|
||||
// Do we need to update the cache?
|
||||
if (result == null) {
|
||||
List<Field> filtered = new ArrayList<Field>();
|
||||
Set<Field> defaults = new HashSet<Field>();
|
||||
Map<Field, Integer> defaults = new HashMap<Field, Integer>();
|
||||
int index = 0;
|
||||
|
||||
for (Field field : data) {
|
||||
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
|
||||
filtered.add(field);
|
||||
|
||||
if (defaultFields.contains(field))
|
||||
defaults.add(field);
|
||||
// Don't use the original index
|
||||
if (defaultFields.containsKey(field))
|
||||
defaults.put(field, index);
|
||||
}
|
||||
|
||||
// Keep track of the field index
|
||||
index++;
|
||||
}
|
||||
|
||||
// Cache structure modifiers
|
||||
result = new StructureModifier<T>();
|
||||
result.initialize(targetType, fieldType, filtered, defaults,
|
||||
converter, new HashMap<Class, StructureModifier>());
|
||||
result = withFieldType(fieldType, filtered, defaults, converter);
|
||||
|
||||
if (fieldType != null)
|
||||
if (fieldType != null) {
|
||||
subtypeCache.put(fieldType, result);
|
||||
|
||||
// Automatically compile the structure modifier
|
||||
if (BackgroundCompiler.getInstance() != null)
|
||||
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the target too
|
||||
@ -296,28 +339,59 @@ public class StructureModifier<TField> {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new structure modifier for the new field type.
|
||||
* @param fieldType - common type of each field.
|
||||
* @param filtered - list of fields after filtering the original modifier.
|
||||
* @param defaults - list of default values after filtering the original.
|
||||
* @param converter - the new converter.
|
||||
* @return A new structure modifier.
|
||||
*/
|
||||
protected <T> StructureModifier<T> withFieldType(
|
||||
Class fieldType, List<Field> filtered,
|
||||
Map<Field, Integer> defaults, EquivalentConverter<T> converter) {
|
||||
|
||||
StructureModifier<T> result = new StructureModifier<T>();
|
||||
result.initialize(targetType, fieldType, filtered, defaults,
|
||||
converter, new ConcurrentHashMap<Class, StructureModifier>());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structure modifier of the same type for a different object target.
|
||||
* @param target - different target of the same type.
|
||||
* @return Structure modifier with the new target.
|
||||
*/
|
||||
public StructureModifier<TField> withTarget(Object target) {
|
||||
return new StructureModifier<TField>(this, target);
|
||||
StructureModifier<TField> copy = new StructureModifier<TField>();
|
||||
|
||||
// Create a new instance
|
||||
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
|
||||
copy.target = target;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structure modifier with the same type and target, but using a new object converter.
|
||||
* @param converter- the object converter to use.
|
||||
* @param converter - the object converter to use.
|
||||
* @return Structure modifier with the new converter.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
|
||||
StructureModifier copy = new StructureModifier(this, target);
|
||||
StructureModifier copy = withTarget(target);
|
||||
|
||||
copy.converter = converter;
|
||||
copy.setConverter(converter);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current object converter. Should only be called during construction.
|
||||
* @param converter - current object converter.
|
||||
*/
|
||||
protected void setConverter(EquivalentConverter<TField> converter) {
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the fields matching the constraints of this structure modifier.
|
||||
* @return List of fields.
|
||||
@ -325,7 +399,7 @@ public class StructureModifier<TField> {
|
||||
public List<Field> getFields() {
|
||||
return ImmutableList.copyOf(data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve every value stored in the fields of the current type.
|
||||
* @return Every field value.
|
||||
@ -334,17 +408,19 @@ public class StructureModifier<TField> {
|
||||
public List<TField> getValues() throws FieldAccessException {
|
||||
List<TField> values = new ArrayList<TField>();
|
||||
|
||||
for (int i = 0; i < size(); i++)
|
||||
for (int i = 0; i < size(); i++) {
|
||||
values.add(read(i));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
// Used to generate plausible default values
|
||||
private static Set<Field> generateDefaultFields(List<Field> fields) {
|
||||
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
|
||||
|
||||
Set<Field> requireDefaults = new HashSet<Field>();
|
||||
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
int index = 0;
|
||||
|
||||
for (Field field : fields) {
|
||||
Class<?> type = field.getType();
|
||||
@ -354,9 +430,12 @@ public class StructureModifier<TField> {
|
||||
// Next, see if we actually can generate a default value
|
||||
if (generator.getDefault(type) != null) {
|
||||
// If so, require it
|
||||
requireDefaults.add(field);
|
||||
requireDefaults.put(field, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Increment field index
|
||||
index++;
|
||||
}
|
||||
|
||||
return requireDefaults;
|
||||
|
@ -155,6 +155,13 @@ public class VolatileField {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not we'll need to revert the value.
|
||||
*/
|
||||
public boolean isCurrentSet() {
|
||||
return currentSet;
|
||||
}
|
||||
|
||||
private void ensureLoaded() {
|
||||
// Load the value if we haven't already
|
||||
if (!previousLoaded) {
|
||||
|
@ -0,0 +1,183 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
/**
|
||||
* Compiles structure modifiers on a background thread.
|
||||
* <p>
|
||||
* This is necessary as we cannot block the main thread.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BackgroundCompiler {
|
||||
|
||||
// How long to wait for a shutdown
|
||||
public static final int SHUTDOWN_DELAY_MS = 2000;
|
||||
|
||||
// The single background compiler we're using
|
||||
private static BackgroundCompiler backgroundCompiler;
|
||||
|
||||
private StructureCompiler compiler;
|
||||
private boolean enabled;
|
||||
private boolean shuttingDown;
|
||||
|
||||
private ExecutorService executor;
|
||||
|
||||
/**
|
||||
* Retrieves the current background compiler.
|
||||
* @return Current background compiler.
|
||||
*/
|
||||
public static BackgroundCompiler getInstance() {
|
||||
return backgroundCompiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the single background compiler we're using.
|
||||
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
|
||||
*/
|
||||
public static void setInstance(BackgroundCompiler backgroundCompiler) {
|
||||
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a background compiler.
|
||||
* @param loader - class loader from Bukkit.
|
||||
*/
|
||||
public BackgroundCompiler(ClassLoader loader) {
|
||||
this(loader, Executors.newSingleThreadExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a background compiler utilizing the given thread pool.
|
||||
* @param loader - class loader from Bukkit.
|
||||
* @param executor - thread pool we'll use.
|
||||
*/
|
||||
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
|
||||
if (loader == null)
|
||||
throw new IllegalArgumentException("loader cannot be NULL");
|
||||
if (executor == null)
|
||||
throw new IllegalArgumentException("executor cannot be NULL");
|
||||
|
||||
this.compiler = new StructureCompiler(loader);
|
||||
this.executor = executor;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the indirectly given structure modifier is eventually compiled.
|
||||
* @param cache - store of structure modifiers.
|
||||
* @param key - key of the structure modifier to compile.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final StructureModifier<Object> uncompiled = cache.get(key);
|
||||
|
||||
if (uncompiled != null) {
|
||||
scheduleCompilation(uncompiled, new CompileListener<Object>() {
|
||||
@Override
|
||||
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||
// Update cache
|
||||
cache.put(key, compiledModifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the given structure modifier is eventually compiled.
|
||||
* @param uncompiled - structure modifier to compile.
|
||||
* @param listener - listener responsible for responding to the compilation.
|
||||
*/
|
||||
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
||||
|
||||
// Only schedule if we're enabled
|
||||
if (enabled && !shuttingDown) {
|
||||
|
||||
// Don't try to schedule anything
|
||||
if (executor == null || executor.isShutdown())
|
||||
return;
|
||||
|
||||
try {
|
||||
executor.submit(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
|
||||
StructureModifier<TKey> modifier = uncompiled;
|
||||
|
||||
// Do our compilation
|
||||
modifier = compiler.compile(modifier);
|
||||
listener.onCompiled(modifier);
|
||||
|
||||
// We'll also return the new structure modifier
|
||||
return modifier;
|
||||
}
|
||||
});
|
||||
} catch (RejectedExecutionException e) {
|
||||
// Occures when the underlying queue is overflowing. Since the compilation
|
||||
// is only an optmization and not really essential we'll just log this failure
|
||||
// and move on.
|
||||
Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after ourselves using the default timeout.
|
||||
*/
|
||||
public void shutdownAll() {
|
||||
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after ourselves.
|
||||
* @param timeout - the maximum time to wait.
|
||||
* @param unit - the time unit of the timeout argument.
|
||||
*/
|
||||
public void shutdownAll(long timeout, TimeUnit unit) {
|
||||
setEnabled(false);
|
||||
shuttingDown = true;
|
||||
executor.shutdown();
|
||||
|
||||
try {
|
||||
executor.awaitTermination(timeout, unit);
|
||||
} catch (InterruptedException e) {
|
||||
// Unlikely to ever occur.
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not the background compiler is enabled.
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the background compiler is enabled.
|
||||
* @param enabled - TRUE to enable it, FALSE otherwise.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current structure compiler.
|
||||
* @return Current structure compiler.
|
||||
*/
|
||||
public StructureCompiler getCompiler() {
|
||||
return compiler;
|
||||
}
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import net.sf.cglib.asm.*;
|
||||
|
||||
/**
|
||||
* Used by the compiler to automatically box and unbox values.
|
||||
*/
|
||||
class BoxingHelper {
|
||||
|
||||
private final static Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
|
||||
private final static Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
|
||||
private final static Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
|
||||
private final static Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
|
||||
private final static Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
|
||||
private final static Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
|
||||
private final static Type LONG_TYPE = Type.getObjectType("java/lang/Long");
|
||||
private final static Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
|
||||
private final static Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
|
||||
private final static Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
|
||||
|
||||
private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()");
|
||||
private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()");
|
||||
private final static MethodDescriptor INT_VALUE = MethodDescriptor.getMethod("int intValue()");
|
||||
private final static MethodDescriptor FLOAT_VALUE = MethodDescriptor.getMethod("float floatValue()");
|
||||
private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()");
|
||||
private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()");
|
||||
|
||||
private MethodVisitor mv;
|
||||
|
||||
public BoxingHelper(MethodVisitor mv) {
|
||||
this.mv = mv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instructions to box the top stack value. This value is
|
||||
* replaced by its boxed equivalent on top of the stack.
|
||||
*
|
||||
* @param type the type of the top stack value.
|
||||
*/
|
||||
public void box(final Type type){
|
||||
if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(type == Type.VOID_TYPE) {
|
||||
push((String) null);
|
||||
} else {
|
||||
Type boxed = type;
|
||||
|
||||
switch(type.getSort()) {
|
||||
case Type.BYTE:
|
||||
boxed = BYTE_TYPE;
|
||||
break;
|
||||
case Type.BOOLEAN:
|
||||
boxed = BOOLEAN_TYPE;
|
||||
break;
|
||||
case Type.SHORT:
|
||||
boxed = SHORT_TYPE;
|
||||
break;
|
||||
case Type.CHAR:
|
||||
boxed = CHARACTER_TYPE;
|
||||
break;
|
||||
case Type.INT:
|
||||
boxed = INTEGER_TYPE;
|
||||
break;
|
||||
case Type.FLOAT:
|
||||
boxed = FLOAT_TYPE;
|
||||
break;
|
||||
case Type.LONG:
|
||||
boxed = LONG_TYPE;
|
||||
break;
|
||||
case Type.DOUBLE:
|
||||
boxed = DOUBLE_TYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
newInstance(boxed);
|
||||
if(type.getSize() == 2) {
|
||||
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
|
||||
dupX2();
|
||||
dupX2();
|
||||
pop();
|
||||
} else {
|
||||
// p -> po -> opo -> oop -> o
|
||||
dupX1();
|
||||
swap();
|
||||
}
|
||||
|
||||
invokeConstructor(boxed, new MethodDescriptor("<init>", Type.VOID_TYPE, new Type[] {type}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to invoke a constructor.
|
||||
*
|
||||
* @param type the class in which the constructor is defined.
|
||||
* @param method the constructor to be invoked.
|
||||
*/
|
||||
public void invokeConstructor(final Type type, final MethodDescriptor method){
|
||||
invokeInsn(Opcodes.INVOKESPECIAL, type, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a DUP_X1 instruction.
|
||||
*/
|
||||
public void dupX1(){
|
||||
mv.visitInsn(Opcodes.DUP_X1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a DUP_X2 instruction.
|
||||
*/
|
||||
public void dupX2(){
|
||||
mv.visitInsn(Opcodes.DUP_X2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a POP instruction.
|
||||
*/
|
||||
public void pop(){
|
||||
mv.visitInsn(Opcodes.POP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a SWAP instruction.
|
||||
*/
|
||||
public void swap(){
|
||||
mv.visitInsn(Opcodes.SWAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to push the given value on the stack.
|
||||
*
|
||||
* @param value the value to be pushed on the stack.
|
||||
*/
|
||||
public void push(final boolean value){
|
||||
push(value ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to push the given value on the stack.
|
||||
*
|
||||
* @param value the value to be pushed on the stack.
|
||||
*/
|
||||
public void push(final int value) {
|
||||
if (value >= -1 && value <= 5) {
|
||||
mv.visitInsn(Opcodes.ICONST_0 + value);
|
||||
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.BIPUSH, value);
|
||||
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
|
||||
mv.visitIntInsn(Opcodes.SIPUSH, value);
|
||||
} else {
|
||||
mv.visitLdcInsn(new Integer(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to create a new object.
|
||||
*
|
||||
* @param type the class of the object to be created.
|
||||
*/
|
||||
public void newInstance(final Type type){
|
||||
typeInsn(Opcodes.NEW, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to push the given value on the stack.
|
||||
*
|
||||
* @param value the value to be pushed on the stack. May be <tt>null</tt>.
|
||||
*/
|
||||
public void push(final String value) {
|
||||
if (value == null) {
|
||||
mv.visitInsn(Opcodes.ACONST_NULL);
|
||||
} else {
|
||||
mv.visitLdcInsn(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instructions to unbox the top stack value. This value is
|
||||
* replaced by its unboxed equivalent on top of the stack.
|
||||
*
|
||||
* @param type
|
||||
* the type of the top stack value.
|
||||
*/
|
||||
public void unbox(final Type type){
|
||||
Type t = NUMBER_TYPE;
|
||||
MethodDescriptor sig = null;
|
||||
|
||||
switch(type.getSort()) {
|
||||
case Type.VOID:
|
||||
return;
|
||||
case Type.CHAR:
|
||||
t = CHARACTER_TYPE;
|
||||
sig = CHAR_VALUE;
|
||||
break;
|
||||
case Type.BOOLEAN:
|
||||
t = BOOLEAN_TYPE;
|
||||
sig = BOOLEAN_VALUE;
|
||||
break;
|
||||
case Type.DOUBLE:
|
||||
sig = DOUBLE_VALUE;
|
||||
break;
|
||||
case Type.FLOAT:
|
||||
sig = FLOAT_VALUE;
|
||||
break;
|
||||
case Type.LONG:
|
||||
sig = LONG_VALUE;
|
||||
break;
|
||||
case Type.INT:
|
||||
case Type.SHORT:
|
||||
case Type.BYTE:
|
||||
sig = INT_VALUE;
|
||||
}
|
||||
|
||||
if(sig == null) {
|
||||
checkCast(type);
|
||||
} else {
|
||||
checkCast(t);
|
||||
invokeVirtual(t, sig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to check that the top stack value is of the
|
||||
* given type.
|
||||
*
|
||||
* @param type a class or interface type.
|
||||
*/
|
||||
public void checkCast(final Type type){
|
||||
if(!type.equals(OBJECT_TYPE)) {
|
||||
typeInsn(Opcodes.CHECKCAST, type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the instruction to invoke a normal method.
|
||||
*
|
||||
* @param owner the class in which the method is defined.
|
||||
* @param method the method to be invoked.
|
||||
*/
|
||||
public void invokeVirtual(final Type owner, final MethodDescriptor method){
|
||||
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an invoke method instruction.
|
||||
*
|
||||
* @param opcode the instruction's opcode.
|
||||
* @param type the class in which the method is defined.
|
||||
* @param method the method to be invoked.
|
||||
*/
|
||||
private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
|
||||
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
|
||||
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a type dependent instruction.
|
||||
*
|
||||
* @param opcode the instruction's opcode.
|
||||
* @param type the instruction's operand.
|
||||
*/
|
||||
private void typeInsn(final int opcode, final Type type){
|
||||
String desc;
|
||||
|
||||
if(type.getSort() == Type.ARRAY) {
|
||||
desc = type.getDescriptor();
|
||||
} else {
|
||||
desc = type.getInternalName();
|
||||
}
|
||||
|
||||
mv.visitTypeInsn(opcode, desc);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
/**
|
||||
* Used to save the result of an compilation.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TKey> - type of the structure modifier field.
|
||||
*/
|
||||
public interface CompileListener<TKey> {
|
||||
/**
|
||||
* Invoked when a structure modifier has been successfully compiled.
|
||||
* @param compiledModifier - the compiled structure modifier.
|
||||
*/
|
||||
public void onCompiled(StructureModifier<TKey> compiledModifier);
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
|
||||
/**
|
||||
* Represents a compiled structure modifier.
|
||||
*
|
||||
* @author Kristian
|
||||
* @param <TField> Field type.
|
||||
*/
|
||||
public abstract class CompiledStructureModifier<TField> extends StructureModifier<TField> {
|
||||
// Used to compile instances of structure modifiers
|
||||
protected StructureCompiler compiler;
|
||||
|
||||
public CompiledStructureModifier() {
|
||||
super();
|
||||
customConvertHandling = true;
|
||||
}
|
||||
|
||||
// Speed up the default writer
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
||||
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
|
||||
// Write a default instance to every field
|
||||
for (Map.Entry<Field, Integer> entry : defaultFields.entrySet()) {
|
||||
Integer index = entry.getValue();
|
||||
Field field = entry.getKey();
|
||||
|
||||
write(index, (TField) generator.getDefault(field.getType()));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public final TField read(int fieldIndex) throws FieldAccessException {
|
||||
Object result = readGenerated(fieldIndex);
|
||||
|
||||
if (converter != null)
|
||||
return converter.getSpecific(result);
|
||||
else
|
||||
return (TField) result;
|
||||
}
|
||||
|
||||
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
|
||||
if (converter != null)
|
||||
value = converter.getGeneric((TField) value);
|
||||
return writeGenerated(index, value);
|
||||
}
|
||||
|
||||
protected abstract StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException;
|
||||
|
||||
@Override
|
||||
public StructureModifier<TField> withTarget(Object target) {
|
||||
if (compiler != null)
|
||||
return compiler.compile(super.withTarget(target));
|
||||
else
|
||||
return super.withTarget(target);
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.cglib.asm.Type;
|
||||
|
||||
/**
|
||||
* Represents a method.
|
||||
*/
|
||||
class MethodDescriptor {
|
||||
|
||||
/**
|
||||
* The method name.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The method descriptor.
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
/**
|
||||
* Maps primitive Java type names to their descriptors.
|
||||
*/
|
||||
private static final Map<String, String> DESCRIPTORS;
|
||||
|
||||
static {
|
||||
DESCRIPTORS = new HashMap<String, String>();
|
||||
DESCRIPTORS.put("void", "V");
|
||||
DESCRIPTORS.put("byte", "B");
|
||||
DESCRIPTORS.put("char", "C");
|
||||
DESCRIPTORS.put("double", "D");
|
||||
DESCRIPTORS.put("float", "F");
|
||||
DESCRIPTORS.put("int", "I");
|
||||
DESCRIPTORS.put("long", "J");
|
||||
DESCRIPTORS.put("short", "S");
|
||||
DESCRIPTORS.put("boolean", "Z");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Method}.
|
||||
*
|
||||
* @param name the method's name.
|
||||
* @param desc the method's descriptor.
|
||||
*/
|
||||
public MethodDescriptor(final String name, final String desc) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Method}.
|
||||
*
|
||||
* @param name the method's name.
|
||||
* @param returnType the method's return type.
|
||||
* @param argumentTypes the method's argument types.
|
||||
*/
|
||||
public MethodDescriptor(
|
||||
final String name,
|
||||
final Type returnType,
|
||||
final Type[] argumentTypes)
|
||||
{
|
||||
this(name, Type.getMethodDescriptor(returnType, argumentTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
*
|
||||
* @param method a Java method declaration, without argument names, of the
|
||||
* form "returnType name (argumentType1, ... argumentTypeN)", where
|
||||
* the types are in plain Java (e.g. "int", "float",
|
||||
* "java.util.List", ...). Classes of the java.lang package can be
|
||||
* specified by their unqualified name; all other classes names must
|
||||
* be fully qualified.
|
||||
* @return a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
* @throws IllegalArgumentException if <code>method</code> could not get
|
||||
* parsed.
|
||||
*/
|
||||
public static MethodDescriptor getMethod(final String method)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
return getMethod(method, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
*
|
||||
* @param method a Java method declaration, without argument names, of the
|
||||
* form "returnType name (argumentType1, ... argumentTypeN)", where
|
||||
* the types are in plain Java (e.g. "int", "float",
|
||||
* "java.util.List", ...). Classes of the java.lang package may be
|
||||
* specified by their unqualified name, depending on the
|
||||
* defaultPackage argument; all other classes names must be fully
|
||||
* qualified.
|
||||
* @param defaultPackage true if unqualified class names belong to the
|
||||
* default package, or false if they correspond to java.lang classes.
|
||||
* For instance "Object" means "Object" if this option is true, or
|
||||
* "java.lang.Object" otherwise.
|
||||
* @return a {@link Method} corresponding to the given Java method
|
||||
* declaration.
|
||||
* @throws IllegalArgumentException if <code>method</code> could not get
|
||||
* parsed.
|
||||
*/
|
||||
public static MethodDescriptor getMethod(
|
||||
final String method,
|
||||
final boolean defaultPackage) throws IllegalArgumentException
|
||||
{
|
||||
int space = method.indexOf(' ');
|
||||
int start = method.indexOf('(', space) + 1;
|
||||
int end = method.indexOf(')', start);
|
||||
if (space == -1 || start == -1 || end == -1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
String returnType = method.substring(0, space);
|
||||
String methodName = method.substring(space + 1, start - 1).trim();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append('(');
|
||||
int p;
|
||||
do {
|
||||
String s;
|
||||
p = method.indexOf(',', start);
|
||||
if (p == -1) {
|
||||
s = map(method.substring(start, end).trim(), defaultPackage);
|
||||
} else {
|
||||
s = map(method.substring(start, p).trim(), defaultPackage);
|
||||
start = p + 1;
|
||||
}
|
||||
sb.append(s);
|
||||
} while (p != -1);
|
||||
sb.append(')');
|
||||
sb.append(map(returnType, defaultPackage));
|
||||
return new MethodDescriptor(methodName, sb.toString());
|
||||
}
|
||||
|
||||
private static String map(final String type, final boolean defaultPackage) {
|
||||
if ("".equals(type)) {
|
||||
return type;
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
int index = 0;
|
||||
while ((index = type.indexOf("[]", index) + 1) > 0) {
|
||||
sb.append('[');
|
||||
}
|
||||
|
||||
String t = type.substring(0, type.length() - sb.length() * 2);
|
||||
String desc = (String) DESCRIPTORS.get(t);
|
||||
if (desc != null) {
|
||||
sb.append(desc);
|
||||
} else {
|
||||
sb.append('L');
|
||||
if (t.indexOf('.') < 0) {
|
||||
if (!defaultPackage) {
|
||||
sb.append("java/lang/");
|
||||
}
|
||||
sb.append(t);
|
||||
} else {
|
||||
sb.append(t.replace('.', '/'));
|
||||
}
|
||||
sb.append(';');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the method described by this object.
|
||||
*
|
||||
* @return the name of the method described by this object.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the descriptor of the method described by this object.
|
||||
*
|
||||
* @return the descriptor of the method described by this object.
|
||||
*/
|
||||
public String getDescriptor() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the return type of the method described by this object.
|
||||
*
|
||||
* @return the return type of the method described by this object.
|
||||
*/
|
||||
public Type getReturnType() {
|
||||
return Type.getReturnType(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the argument types of the method described by this object.
|
||||
*
|
||||
* @return the argument types of the method described by this object.
|
||||
*/
|
||||
public Type[] getArgumentTypes() {
|
||||
return Type.getArgumentTypes(desc);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name + desc;
|
||||
}
|
||||
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof MethodDescriptor)) {
|
||||
return false;
|
||||
}
|
||||
MethodDescriptor other = (MethodDescriptor) o;
|
||||
return name.equals(other.name) && desc.equals(other.desc);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return name.hashCode() ^ desc.hashCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,443 @@
|
||||
package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.comphenix.protocol.reflect.PrimitiveUtils;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import net.sf.cglib.asm.*;
|
||||
|
||||
// public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> {
|
||||
//
|
||||
// private Packet20NamedEntitySpawn typedTarget;
|
||||
//
|
||||
// public CompiledStructureModifierPacket20(StructureModifier<TField> other, StructureCompiler compiler) {
|
||||
// super();
|
||||
// initialize(other);
|
||||
// this.target = other.getTarget();
|
||||
// this.typedTarget = (Packet20NamedEntitySpawn) target;
|
||||
// this.compiler = compiler;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected Object readGenerated(int fieldIndex) throws FieldAccessException {
|
||||
//
|
||||
// Packet20NamedEntitySpawn target = typedTarget;
|
||||
//
|
||||
// switch (fieldIndex) {
|
||||
// case 0: return (Object) target.a;
|
||||
// case 1: return (Object) target.b;
|
||||
// case 2: return (Object) target.c;
|
||||
// case 3: return super.read(fieldIndex);
|
||||
// case 4: return super.read(fieldIndex);
|
||||
// case 5: return (Object) target.f;
|
||||
// case 6: return (Object) target.g;
|
||||
// case 7: return (Object) target.h;
|
||||
// default:
|
||||
// throw new FieldAccessException("Invalid index " + fieldIndex);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException {
|
||||
//
|
||||
// Packet20NamedEntitySpawn target = typedTarget;
|
||||
//
|
||||
// switch (index) {
|
||||
// case 0: target.a = (Integer) value; break;
|
||||
// case 1: target.b = (String) value; break;
|
||||
// case 2: target.c = (Integer) value; break;
|
||||
// case 3: target.d = (Integer) value; break;
|
||||
// case 4: super.write(index, value); break;
|
||||
// case 5: super.write(index, value); break;
|
||||
// case 6: target.g = (Byte) value; break;
|
||||
// case 7: target.h = (Integer) value; break;
|
||||
// default:
|
||||
// throw new FieldAccessException("Invalid index " + index);
|
||||
// }
|
||||
//
|
||||
// // Chaining
|
||||
// return this;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Represents a StructureModifier compiler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class StructureCompiler {
|
||||
|
||||
// Used to store generated classes of different types
|
||||
@SuppressWarnings("rawtypes")
|
||||
private class StructureKey {
|
||||
private Class targetType;
|
||||
private Class fieldType;
|
||||
|
||||
public StructureKey(Class targetType, Class fieldType) {
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(targetType, fieldType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof StructureKey) {
|
||||
StructureKey other = (StructureKey) obj;
|
||||
return Objects.equal(targetType, other.targetType) &&
|
||||
Objects.equal(fieldType, other.fieldType);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Used to load classes
|
||||
private volatile static Method defineMethod;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Map<StructureKey, Class> compiledCache = new HashMap<StructureKey, Class>();
|
||||
|
||||
// The class loader we'll store our classes
|
||||
private ClassLoader loader;
|
||||
|
||||
// References to other classes
|
||||
private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler";
|
||||
private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier";
|
||||
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
|
||||
private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException";
|
||||
|
||||
/**
|
||||
* Construct a structure compiler.
|
||||
* @param loader - main class loader.
|
||||
*/
|
||||
StructureCompiler(ClassLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the given structure modifier.
|
||||
* <p>
|
||||
* WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already
|
||||
* over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead.
|
||||
* @param source - structure modifier to compile.
|
||||
* @return A compiled structure modifier.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized <TField> StructureModifier<TField> compile(StructureModifier<TField> source) {
|
||||
|
||||
// We cannot optimize a structure modifier with no public fields
|
||||
if (!isAnyPublic(source.getFields())) {
|
||||
return source;
|
||||
}
|
||||
|
||||
StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType());
|
||||
Class<?> compiledClass = compiledCache.get(key);
|
||||
|
||||
if (!compiledCache.containsKey(key)) {
|
||||
compiledClass = generateClass(source);
|
||||
compiledCache.put(key, compiledClass);
|
||||
}
|
||||
|
||||
// Next, create an instance of this class
|
||||
try {
|
||||
return (StructureModifier<TField>) compiledClass.getConstructor(
|
||||
StructureModifier.class, StructureCompiler.class).
|
||||
newInstance(source, this);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Used invalid parameters in instance creation", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new RuntimeException("Security limitation!", e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Error occured while instancing generated class.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Security limitation! Cannot create instance of dynamic class.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured while instancing generated class.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("Cannot happen.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
|
||||
|
||||
ClassWriter cw = new ClassWriter(0);
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class targetType = source.getTargetType();
|
||||
|
||||
String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName();
|
||||
String targetSignature = Type.getDescriptor(targetType);
|
||||
String targetName = targetType.getName().replace('.', '/');
|
||||
|
||||
try {
|
||||
// This class might have been generated before. Try to load it.
|
||||
Class<?> before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className);
|
||||
|
||||
if (before != null)
|
||||
return before;
|
||||
} catch (ClassNotFoundException e) {
|
||||
// That's ok.
|
||||
}
|
||||
|
||||
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
|
||||
"<TField:Ljava/lang/Object;>L" + COMPILED_CLASS + "<TTField;>;",
|
||||
COMPILED_CLASS, null);
|
||||
|
||||
createFields(cw, targetSignature);
|
||||
createConstructor(cw, className, targetSignature, targetName);
|
||||
createReadMethod(cw, className, source.getFields(), targetSignature, targetName);
|
||||
createWriteMethod(cw, className, source.getFields(), targetSignature, targetName);
|
||||
cw.visitEnd();
|
||||
|
||||
byte[] data = cw.toByteArray();
|
||||
|
||||
// Call the define method
|
||||
try {
|
||||
if (defineMethod == null) {
|
||||
Method defined = ClassLoader.class.getDeclaredMethod("defineClass",
|
||||
new Class<?>[] { String.class, byte[].class, int.class, int.class });
|
||||
|
||||
// Awesome. Now, create and return it.
|
||||
defined.setAccessible(true);
|
||||
defineMethod = defined;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length);
|
||||
|
||||
// DEBUG CODE: Print the content of the generated class.
|
||||
//org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data);
|
||||
//cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
|
||||
|
||||
return clazz;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
throw new RuntimeException("Cannot use reflection to dynamically load a class.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalStateException("Incompatible JVM.", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Security limitation! Cannot dynamically load class.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured in code generator.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if at least one of the given fields is public.
|
||||
* @param fields - field to test.
|
||||
* @return TRUE if one or more field is publically accessible, FALSE otherwise.
|
||||
*/
|
||||
private boolean isAnyPublic(List<Field> fields) {
|
||||
// Are any of the fields public?
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
if (isPublic(fields.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPublic(Field field) {
|
||||
return Modifier.isPublic(field.getModifiers());
|
||||
}
|
||||
|
||||
private void createFields(ClassWriter cw, String targetSignature) {
|
||||
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
|
||||
typedField.visitEnd();
|
||||
}
|
||||
|
||||
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
||||
|
||||
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
|
||||
String methodSignature = "(ITTField;)L" + SUPER_CLASS + "<TTField;>;";
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
|
||||
new String[] { FIELD_EXCEPTION_CLASS });
|
||||
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||
mv.visitVarInsn(Opcodes.ASTORE, 3);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
|
||||
// The last label is for the default switch
|
||||
Label[] labels = new Label[fields.size()];
|
||||
Label errorLabel = new Label();
|
||||
Label returnLabel = new Label();
|
||||
|
||||
// Generate labels
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
labels[i] = new Label();
|
||||
}
|
||||
|
||||
mv.visitTableSwitchInsn(0, labels.length - 1, errorLabel, labels);
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
|
||||
Class<?> outputType = fields.get(i).getType();
|
||||
Class<?> inputType = PrimitiveUtils.wrap(outputType);
|
||||
String typeDescriptor = Type.getDescriptor(outputType);
|
||||
String inputPath = inputType.getName().replace('.', '/');
|
||||
|
||||
mv.visitLabel(labels[i]);
|
||||
|
||||
// Push the compare object
|
||||
if (i == 0)
|
||||
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
|
||||
else
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
|
||||
// Only write to public fields
|
||||
if (isPublic(fields.get(i))) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 3);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
|
||||
if (!PrimitiveUtils.isPrimitive(outputType))
|
||||
mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
|
||||
else
|
||||
boxingHelper.unbox(Type.getType(outputType));
|
||||
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||
|
||||
} else {
|
||||
// Use reflection. We don't have a choice, unfortunately.
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";");
|
||||
mv.visitInsn(Opcodes.POP);
|
||||
}
|
||||
|
||||
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
|
||||
}
|
||||
|
||||
mv.visitLabel(errorLabel);
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitLdcInsn("Invalid index ");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitInsn(Opcodes.ATHROW);
|
||||
|
||||
mv.visitLabel(returnLabel);
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
mv.visitMaxs(5, 4);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void createReadMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
|
||||
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
|
||||
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||
mv.visitVarInsn(Opcodes.ASTORE, 2);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
|
||||
// The last label is for the default switch
|
||||
Label[] labels = new Label[fields.size()];
|
||||
Label errorLabel = new Label();
|
||||
|
||||
// Generate labels
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
labels[i] = new Label();
|
||||
}
|
||||
|
||||
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
Class<?> outputType = fields.get(i).getType();
|
||||
String typeDescriptor = Type.getDescriptor(outputType);
|
||||
|
||||
mv.visitLabel(labels[i]);
|
||||
|
||||
// Push the compare object
|
||||
if (i == 0)
|
||||
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
|
||||
else
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
|
||||
// Note that byte code cannot access non-public fields
|
||||
if (isPublic(fields.get(i))) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||
|
||||
boxingHelper.box(Type.getType(outputType));
|
||||
} else {
|
||||
// We have to use reflection for private and protected fields.
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
}
|
||||
|
||||
mv.visitLabel(errorLabel);
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitLdcInsn("Invalid index ");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
|
||||
mv.visitInsn(Opcodes.ATHROW);
|
||||
mv.visitMaxs(5, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
|
||||
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
|
||||
"(L" + SUPER_CLASS + "<TTField;>;L" + SUPER_CLASS + ";)V", null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, PACKAGE_NAME + "/" + className, "initialize", "(L" + SUPER_CLASS + ";)V");
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
mv.visitMaxs(2, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@ package com.comphenix.protocol.reflect.instances;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.*;
|
||||
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
@ -100,6 +102,38 @@ public class DefaultInstances {
|
||||
return getDefaultInternal(type, registered, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the constructor with the fewest number of parameters.
|
||||
* @param type - type to construct.
|
||||
* @return A constructor with the fewest number of parameters, or NULL if the type has no constructors.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Constructor<T> getMinimumConstructor(Class<T> type) {
|
||||
|
||||
Constructor<T> minimum = null;
|
||||
int lastCount = Integer.MAX_VALUE;
|
||||
|
||||
// Find the constructor with the fewest parameters
|
||||
for (Constructor<?> candidate : type.getConstructors()) {
|
||||
Class<?>[] types = candidate.getParameterTypes();
|
||||
|
||||
// Note that we don't allow recursive types - that is, types that
|
||||
// require itself in the constructor.
|
||||
if (types.length < lastCount) {
|
||||
if (!contains(types, type)) {
|
||||
minimum = (Constructor<T>) candidate;
|
||||
lastCount = types.length;
|
||||
|
||||
// Don't loop again if we've already found the best possible constructor
|
||||
if (lastCount == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a default instance or value that is assignable to this type.
|
||||
* <p>
|
||||
@ -136,36 +170,18 @@ public class DefaultInstances {
|
||||
if (value != null)
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
Constructor<T> minimum = null;
|
||||
int lastCount = Integer.MAX_VALUE;
|
||||
|
||||
// Find the constructor with the fewest parameters
|
||||
for (Constructor<?> candidate : type.getConstructors()) {
|
||||
Class<?>[] types = candidate.getParameterTypes();
|
||||
|
||||
// Note that we don't allow recursive types - that is, types that
|
||||
// require itself in the constructor.
|
||||
if (types.length < lastCount) {
|
||||
if (!contains(types, type)) {
|
||||
minimum = (Constructor<T>) candidate;
|
||||
lastCount = types.length;
|
||||
|
||||
// Don't loop again if we've already found the best possible constructor
|
||||
if (lastCount == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Constructor<T> minimum = getMinimumConstructor(type);
|
||||
|
||||
// Create the type with this constructor using default values. This might fail, though.
|
||||
try {
|
||||
if (minimum != null) {
|
||||
Object[] params = new Object[lastCount];
|
||||
int parameterCount = minimum.getParameterTypes().length;
|
||||
Object[] params = new Object[parameterCount];
|
||||
Class<?>[] types = minimum.getParameterTypes();
|
||||
|
||||
// Fill out
|
||||
for (int i = 0; i < lastCount; i++) {
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
|
||||
}
|
||||
|
||||
@ -180,6 +196,24 @@ public class DefaultInstances {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct default instances using the CGLIB enhancer object instead.
|
||||
* @param enhancer - a CGLIB enhancer to use.
|
||||
* @return A default instance generator that uses the CGLIB enhancer.
|
||||
*/
|
||||
public DefaultInstances forEnhancer(Enhancer enhancer) {
|
||||
final Enhancer ex = enhancer;
|
||||
|
||||
return new DefaultInstances(registered) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
|
||||
// Use the enhancer instead
|
||||
return (T) ex.create(types, params);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the default instance provider to create a class from a given constructor.
|
||||
* The default method uses reflection.
|
||||
|
@ -0,0 +1,89 @@
|
||||
package com.comphenix.protocol.reflect.instances;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
|
||||
/**
|
||||
* Provides instance constructors using a list of existing values.
|
||||
* <p>
|
||||
* Only one instance per individual class.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ExistingGenerator implements InstanceProvider {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Map<Class, Object> existingValues = new HashMap<Class, Object>();
|
||||
|
||||
private ExistingGenerator() {
|
||||
// Only accessible to the constructors
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically create an instance provider from a objects public and private fields.
|
||||
* <p>
|
||||
* If two or more fields share the same type, the last declared non-null field will take
|
||||
* precedent.
|
||||
* @param object - object to create an instance generator from.
|
||||
* @return The instance generator.
|
||||
*/
|
||||
public static ExistingGenerator fromObjectFields(Object object) {
|
||||
ExistingGenerator generator = new ExistingGenerator();
|
||||
|
||||
// Read instances from every field.
|
||||
for (Field field : FuzzyReflection.fromObject(object, true).getFields()) {
|
||||
try {
|
||||
Object value = FieldUtils.readField(field, object, true);
|
||||
|
||||
// Use the type of the field, not the object itself
|
||||
if (value != null)
|
||||
generator.addObject(field.getType(), value);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Yes, swallow it. No, really.
|
||||
}
|
||||
}
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance generator from a pre-defined array of values.
|
||||
* @param values - values to provide.
|
||||
* @return An instance provider that uses these values.
|
||||
*/
|
||||
public static ExistingGenerator fromObjectArray(Object[] values) {
|
||||
ExistingGenerator generator = new ExistingGenerator();
|
||||
|
||||
for (Object value : values)
|
||||
generator.addObject(value);
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
private void addObject(Object value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("Value cannot be NULL.");
|
||||
|
||||
existingValues.put(value.getClass(), value);
|
||||
}
|
||||
|
||||
private void addObject(Class<?> type, Object value) {
|
||||
existingValues.put(type, value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object create(@Nullable Class<?> type) {
|
||||
|
||||
Object value = existingValues.get(type);
|
||||
|
||||
// NULL values indicate that the generator failed
|
||||
return value;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
name: ProtocolLib
|
||||
version: 1.1.0
|
||||
version: 1.2.0
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ProtocolLib
|
||||
|
@ -129,5 +129,3 @@ types. It's remarkably consistent across different versions.
|
||||
### Incompatiblity
|
||||
|
||||
The following plugins (to be expanded) are not compatible with ProtocolLib:
|
||||
|
||||
* TagAPI
|
In neuem Issue referenzieren
Einen Benutzer sperren