Ensure that hook method #3 receieves Packet0KeepAlive.
Dieser Commit ist enthalten in:
Ursprung
9770257a74
Commit
3ad24921d9
@ -53,8 +53,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onLoad() {
|
public void onLoad() {
|
||||||
logger = getLoggerSafely();
|
logger = getLoggerSafely();
|
||||||
protocolManager = new PacketFilterManager(
|
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger);
|
||||||
getClassLoader(), getServer().getScheduler(), logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -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
|
// Initialize it, if we haven't already
|
||||||
if (packetToID == null) {
|
if (packetToID == null) {
|
||||||
try {
|
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);
|
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
||||||
|
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
|
@ -13,8 +13,10 @@ import net.sf.cglib.proxy.MethodProxy;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
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.CollectionGenerator;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||||
@ -28,9 +30,14 @@ import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
|||||||
public class NetworkServerInjector extends PlayerInjector {
|
public class NetworkServerInjector extends PlayerInjector {
|
||||||
|
|
||||||
private static Method sendPacketMethod;
|
private static Method sendPacketMethod;
|
||||||
|
|
||||||
|
private StructureModifier<Object> serverHandlerModifier;
|
||||||
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
public NetworkServerInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException {
|
public NetworkServerInjector(Player player, PacketFilterManager manager,
|
||||||
|
Set<Integer> sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException {
|
||||||
super(player, manager, sendingFilters);
|
super(player, manager, sendingFilters);
|
||||||
|
this.serverInjection = serverInjection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -41,6 +48,8 @@ public class NetworkServerInjector extends PlayerInjector {
|
|||||||
if (hasInitialized) {
|
if (hasInitialized) {
|
||||||
if (sendPacketMethod == null)
|
if (sendPacketMethod == null)
|
||||||
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
|
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
|
||||||
|
if (serverHandlerModifier == null)
|
||||||
|
serverHandlerModifier = new StructureModifier<Object>(serverHandler.getClass(), null, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,12 +107,8 @@ public class NetworkServerInjector extends PlayerInjector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delegate to our underlying class
|
// Call the method directly
|
||||||
try {
|
return proxy.invokeSuper(obj, args);
|
||||||
return method.invoke(serverHandler, args);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
throw e.getCause();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,22 +119,46 @@ public class NetworkServerInjector extends PlayerInjector {
|
|||||||
CollectionGenerator.INSTANCE);
|
CollectionGenerator.INSTANCE);
|
||||||
|
|
||||||
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
|
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
|
||||||
|
serverInjection.replaceServerHandler(serverHandler, proxyObject);
|
||||||
|
|
||||||
// Inject it now
|
// Inject it now
|
||||||
if (proxyObject != null) {
|
if (proxyObject != null) {
|
||||||
|
copyTo(serverHandler, proxyObject);
|
||||||
serverHandlerRef.setValue(proxyObject);
|
serverHandlerRef.setValue(proxyObject);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
|
"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
|
@Override
|
||||||
public void cleanupAll() {
|
public void cleanupAll() {
|
||||||
if (serverHandlerRef != null) {
|
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
|
||||||
|
copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue());
|
||||||
serverHandlerRef.revertValue();
|
serverHandlerRef.revertValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverInjection.revertServerHandler(serverHandler);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (getNetHandler() != null) {
|
if (getNetHandler() != null) {
|
||||||
// Restore packet listener
|
// Restore packet listener
|
||||||
|
@ -34,6 +34,7 @@ import net.sf.cglib.proxy.Enhancer;
|
|||||||
import net.sf.cglib.proxy.MethodInterceptor;
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
import net.sf.cglib.proxy.MethodProxy;
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@ -44,7 +45,6 @@ import org.bukkit.event.player.PlayerQuitEvent;
|
|||||||
import org.bukkit.event.server.PluginDisableEvent;
|
import org.bukkit.event.server.PluginDisableEvent;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
|
||||||
|
|
||||||
import com.comphenix.protocol.AsynchronousManager;
|
import com.comphenix.protocol.AsynchronousManager;
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
@ -92,11 +92,14 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
|
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
|
||||||
|
|
||||||
// Player injection type
|
// Player injection type
|
||||||
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
|
||||||
|
|
||||||
// Packet injection
|
// Packet injection
|
||||||
private PacketInjector packetInjector;
|
private PacketInjector packetInjector;
|
||||||
|
|
||||||
|
// Server connection injection
|
||||||
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
// Enabled packet filters
|
// Enabled packet filters
|
||||||
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
||||||
|
|
||||||
@ -122,7 +125,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
/**
|
/**
|
||||||
* Only create instances of this class if protocol lib is disabled.
|
* Only create instances of this class if protocol lib is disabled.
|
||||||
*/
|
*/
|
||||||
public PacketFilterManager(ClassLoader classLoader, BukkitScheduler scheduler, Logger logger) {
|
public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) {
|
||||||
if (logger == null)
|
if (logger == null)
|
||||||
throw new IllegalArgumentException("logger cannot be NULL.");
|
throw new IllegalArgumentException("logger cannot be NULL.");
|
||||||
if (classLoader == null)
|
if (classLoader == null)
|
||||||
@ -133,7 +136,8 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
|
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
|
||||||
this.asyncFilterManager = new AsyncFilterManager(logger, scheduler, this);
|
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
|
||||||
|
this.serverInjection = new InjectedServerConnection(logger, server);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
|
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
|
||||||
}
|
}
|
||||||
@ -474,7 +478,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
case NETWORK_MANAGER_OBJECT:
|
case NETWORK_MANAGER_OBJECT:
|
||||||
return new NetworkObjectInjector(player, this, sendingFilters);
|
return new NetworkObjectInjector(player, this, sendingFilters);
|
||||||
case NETWORK_SERVER_OBJECT:
|
case NETWORK_SERVER_OBJECT:
|
||||||
return new NetworkServerInjector(player, this, sendingFilters);
|
return new NetworkServerInjector(player, this, sendingFilters, serverInjection);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||||
}
|
}
|
||||||
@ -714,6 +718,8 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
if (packetInjector != null)
|
if (packetInjector != null)
|
||||||
packetInjector.cleanupAll();
|
packetInjector.cleanupAll();
|
||||||
|
|
||||||
|
// Remove server handler
|
||||||
|
serverInjection.cleanupAll();
|
||||||
hasClosed = true;
|
hasClosed = true;
|
||||||
|
|
||||||
// Remove listeners
|
// Remove listeners
|
||||||
|
@ -221,6 +221,7 @@ abstract class PlayerInjector {
|
|||||||
Packet handlePacketRecieved(Packet packet) {
|
Packet handlePacketRecieved(Packet packet) {
|
||||||
// Get the packet ID too
|
// Get the packet ID too
|
||||||
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
||||||
|
System.out.println(id);
|
||||||
|
|
||||||
// Make sure we're listening
|
// Make sure we're listening
|
||||||
if (id != null && sendingFilters.contains(id)) {
|
if (id != null && sendingFilters.contains(id)) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -142,11 +142,38 @@ public class FuzzyReflection {
|
|||||||
* @return The first method that satisfies the parameter types.
|
* @return The first method that satisfies the parameter types.
|
||||||
*/
|
*/
|
||||||
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
|
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
|
// Find the correct method to call
|
||||||
for (Method method : getMethods()) {
|
for (Method method : getMethods()) {
|
||||||
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
|
if (match.matcher(method.getReturnType().getName()).matches()) {
|
||||||
return method;
|
if (matchParameters(argMatch, method.getParameterTypes()))
|
||||||
|
return method;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +181,19 @@ public class FuzzyReflection {
|
|||||||
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
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.
|
* Retrieves every method that has the given parameter types and return type.
|
||||||
* @param returnType - return type of the method to find.
|
* @param returnType - return type of the method to find.
|
||||||
@ -195,6 +235,46 @@ public class FuzzyReflection {
|
|||||||
nameRegex + " in " + source.getName());
|
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.
|
* Retrieves a field by type.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -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() {
|
private void ensureLoaded() {
|
||||||
// Load the value if we haven't already
|
// Load the value if we haven't already
|
||||||
if (!previousLoaded) {
|
if (!previousLoaded) {
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren