mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-27 21:26:17 +01:00
Moved the instance generator to a separate package.
In addition, added a new injection method. It's still not finalized, and may get removed in the end.
This commit is contained in:
parent
aa2dcefa0d
commit
7e28aefb75
@ -21,12 +21,12 @@ import java.io.IOException;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||||
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
import com.comphenix.protocol.metrics.Statistics;
|
import com.comphenix.protocol.metrics.Statistics;
|
||||||
|
|
||||||
public class ProtocolLibrary extends JavaPlugin {
|
public class ProtocolLibrary extends JavaPlugin {
|
||||||
@ -79,7 +79,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
// Check for versions, ect.
|
// Check for versions, ect.
|
||||||
logger.severe("Detected incompatible plugin: " + plugin);
|
logger.severe("Detected incompatible plugin: " + plugin);
|
||||||
logger.info("Using woraround.");
|
logger.info("Using woraround.");
|
||||||
|
protocolManager.setPlayerHook(PlayerInjectHooks.OVERRIDE_NETWORK_HANDLER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PipedInputStream;
|
||||||
|
import java.io.PipedOutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mock socket that simply waits forever all on input and output reads.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class MockSocket extends Socket {
|
||||||
|
InputStream input;
|
||||||
|
OutputStream output;
|
||||||
|
private String host = "localhost";
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
public MockSocket() throws Exception {
|
||||||
|
PipedInputStream serverInput = new PipedInputStream();
|
||||||
|
@SuppressWarnings({ "unused", "resource" })
|
||||||
|
PipedOutputStream clientOutput = new PipedOutputStream(serverInput);
|
||||||
|
PipedInputStream clientInput = new PipedInputStream();
|
||||||
|
PipedOutputStream serverOutput = new PipedOutputStream(clientInput);
|
||||||
|
input = serverInput;
|
||||||
|
output = serverOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MockSocket(String input) {
|
||||||
|
this.input = new ByteArrayInputStream(input.getBytes());
|
||||||
|
output = new ByteArrayOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MockSocket(InputStream input, OutputStream output) {
|
||||||
|
this.input = input;
|
||||||
|
this.output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
closed = true;
|
||||||
|
try {
|
||||||
|
input.close();
|
||||||
|
output.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOutput() throws Exception {
|
||||||
|
if (output instanceof ByteArrayOutputStream)
|
||||||
|
return ((ByteArrayOutputStream) output).toString("UTF-8");
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
return new InetSocketAddress(host, 123);
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,26 @@
|
|||||||
package com.comphenix.protocol.injector;
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationHandler;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import net.minecraft.server.Packet;
|
import net.minecraft.server.Packet;
|
||||||
|
import net.sf.cglib.proxy.Enhancer;
|
||||||
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
|
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
||||||
|
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
||||||
*
|
*
|
||||||
@ -20,6 +31,9 @@ public class NetworkObjectInjector extends PlayerInjector {
|
|||||||
super(player, manager, sendingFilters);
|
super(player, manager, sendingFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is why we don't normally use this method. It will create two extra threads per user.
|
||||||
|
private Set<Long> threadKillList = Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||||
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
|
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
|
||||||
@ -45,15 +59,30 @@ public class NetworkObjectInjector extends PlayerInjector {
|
|||||||
public void injectManager() {
|
public void injectManager() {
|
||||||
|
|
||||||
if (networkManager != null) {
|
if (networkManager != null) {
|
||||||
final Class<?> networkInterface = networkManagerField.getType();
|
|
||||||
final Object networkDelegate = networkManagerRef.getOldValue();
|
final Object networkDelegate = networkManagerRef.getOldValue();
|
||||||
|
|
||||||
|
Enhancer ex = new Enhancer();
|
||||||
|
|
||||||
// Create our proxy object
|
ex.setSuperclass(networkManager.getClass());
|
||||||
Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
|
ex.setClassLoader(manager.getClassLoader());
|
||||||
new Class<?>[] { networkInterface }, new InvocationHandler() {
|
ex.setCallback(new MethodInterceptor() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
|
|
||||||
|
// Kill this thread?
|
||||||
|
if (threadKillList.size() > 0) {
|
||||||
|
Thread current = Thread.currentThread();
|
||||||
|
|
||||||
|
if (threadKillList.contains(current.getId())) {
|
||||||
|
// Yes, die!
|
||||||
|
threadKillList.remove(current.getId());
|
||||||
|
System.out.println("[Thread " + current.getId() + "] I'm committing suicide!");
|
||||||
|
|
||||||
|
// This is bad. Very bad. Thus, we prefer the NetworkFieldInjector ...
|
||||||
|
throw new Error("Killing current thread.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// OH OH! The queue method!
|
// OH OH! The queue method!
|
||||||
if (method.equals(queueMethod)) {
|
if (method.equals(queueMethod)) {
|
||||||
Packet packet = (Packet) args[0];
|
Packet packet = (Packet) args[0];
|
||||||
@ -77,7 +106,35 @@ public class NetworkObjectInjector extends PlayerInjector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create instances of our network proxy.
|
||||||
|
DefaultInstances generator = DefaultInstances.fromArray(PrimitiveGenerator.INSTANCE, new InstanceProvider() {
|
||||||
|
@Override
|
||||||
|
public Object create(@Nullable Class<?> type) {
|
||||||
|
if (type.equals(Socket.class))
|
||||||
|
try {
|
||||||
|
return new MockSocket();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create our proxy object
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Object networkProxy = generator.getDefault(ex.createClass());
|
||||||
|
|
||||||
|
// Get the two threads we'll have to kill
|
||||||
|
try {
|
||||||
|
for (Thread thread : networkModifier.withTarget(networkProxy).<Thread>withType(Thread.class).getValues()) {
|
||||||
|
threadKillList.add(thread.getId());
|
||||||
|
}
|
||||||
|
} catch (FieldAccessException e) {
|
||||||
|
// Oh damn
|
||||||
|
}
|
||||||
|
|
||||||
// Inject it, if we can.
|
// Inject it, if we can.
|
||||||
networkManagerRef.setValue(networkProxy);
|
networkManagerRef.setValue(networkProxy);
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,22 @@ import com.google.common.collect.ImmutableSet;
|
|||||||
|
|
||||||
public final class PacketFilterManager implements ProtocolManager {
|
public final class PacketFilterManager implements ProtocolManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the inject hook type. Different types allow for maximum compatibility.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public enum PlayerInjectHooks {
|
||||||
|
/**
|
||||||
|
* Override the packet queue lists in NetworkHandler.
|
||||||
|
*/
|
||||||
|
NETWORK_HANDLER_FIELDS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the NetworkHandler itself, and it's sendPacket-method.
|
||||||
|
*/
|
||||||
|
OVERRIDE_NETWORK_HANDLER
|
||||||
|
}
|
||||||
|
|
||||||
// Create a concurrent set
|
// Create a concurrent set
|
||||||
private Set<PacketListener> packetListeners =
|
private Set<PacketListener> packetListeners =
|
||||||
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
||||||
@ -61,6 +77,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
private Map<DataInputStream, Player> connectionLookup = new ConcurrentHashMap<DataInputStream, Player>();
|
private Map<DataInputStream, Player> connectionLookup = new ConcurrentHashMap<DataInputStream, Player>();
|
||||||
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
|
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
|
||||||
|
|
||||||
|
// Player injection type
|
||||||
|
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
|
||||||
|
|
||||||
// Packet injection
|
// Packet injection
|
||||||
private PacketInjector packetInjector;
|
private PacketInjector packetInjector;
|
||||||
|
|
||||||
@ -99,6 +118,22 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves how the server packets are read.
|
||||||
|
* @return Injection method for reading server packets.
|
||||||
|
*/
|
||||||
|
public PlayerInjectHooks getPlayerHook() {
|
||||||
|
return playerHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how the server packets are read.
|
||||||
|
* @param playerHook - the new injection method for reading server packets.
|
||||||
|
*/
|
||||||
|
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||||
|
this.playerHook = playerHook;
|
||||||
|
}
|
||||||
|
|
||||||
public Logger getLogger() {
|
public Logger getLogger() {
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
@ -309,12 +344,35 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
injectPlayer(player);
|
injectPlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void injectPlayer(Player player) {
|
/**
|
||||||
|
* Used to construct a player hook.
|
||||||
|
* @param player - the player to hook.
|
||||||
|
* @return A new player hoook
|
||||||
|
* @throws IllegalAccessException Unable to do our reflection magic.
|
||||||
|
*/
|
||||||
|
protected PlayerInjector getPlayerHookInstance(Player player) throws IllegalAccessException {
|
||||||
|
|
||||||
|
// Construct the correct player hook
|
||||||
|
switch (playerHook) {
|
||||||
|
case NETWORK_HANDLER_FIELDS:
|
||||||
|
return new NetworkFieldInjector(player, this, sendingFilters);
|
||||||
|
case OVERRIDE_NETWORK_HANDLER:
|
||||||
|
return new NetworkObjectInjector(player, this, sendingFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a player hook, allowing us to read server packets.
|
||||||
|
* @param player - player to hook.
|
||||||
|
*/
|
||||||
|
protected void injectPlayer(Player player) {
|
||||||
// Don't inject if the class has closed
|
// Don't inject if the class has closed
|
||||||
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
|
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
|
||||||
try {
|
try {
|
||||||
PlayerInjector injector = new PlayerInjector(player, this, sendingFilters);
|
PlayerInjector injector = getPlayerHookInstance(player);
|
||||||
|
|
||||||
injector.injectManager();
|
injector.injectManager();
|
||||||
playerInjection.put(player, injector);
|
playerInjection.put(player, injector);
|
||||||
connectionLookup.put(injector.getInputStream(false), player);
|
connectionLookup.put(injector.getInputStream(false), player);
|
||||||
|
@ -38,22 +38,6 @@ import com.comphenix.protocol.reflect.VolatileField;
|
|||||||
|
|
||||||
abstract class PlayerInjector {
|
abstract class PlayerInjector {
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the inject hook type. Different types allow for maximum compatibility.
|
|
||||||
* @author Kristian
|
|
||||||
*/
|
|
||||||
public enum InjectHooks {
|
|
||||||
/**
|
|
||||||
* Override the packet queue lists in NetworkHandler.
|
|
||||||
*/
|
|
||||||
NETWORK_HANDLER_FIELDS,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override the NetworkHandler itself, and it's sendPacket-method.
|
|
||||||
*/
|
|
||||||
OVERRIDE_NETWORK_HANDLER
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache previously retrieved fields
|
// Cache previously retrieved fields
|
||||||
protected static Field serverHandlerField;
|
protected static Field serverHandlerField;
|
||||||
protected static Field networkManagerField;
|
protected static Field networkManagerField;
|
||||||
@ -97,7 +81,6 @@ abstract class PlayerInjector {
|
|||||||
EntityPlayer notchEntity = craft.getHandle();
|
EntityPlayer notchEntity = craft.getHandle();
|
||||||
|
|
||||||
Object serverHandler = null;
|
Object serverHandler = null;
|
||||||
Object networkManager = null;
|
|
||||||
|
|
||||||
if (!hasInitialized) {
|
if (!hasInitialized) {
|
||||||
// Do this first, in case we encounter an exception
|
// Do this first, in case we encounter an exception
|
||||||
|
@ -26,11 +26,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import net.minecraft.server.Packet;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public class StructureModifier<TField> {
|
public class StructureModifier<TField> {
|
||||||
|
|
||||||
@ -195,11 +194,13 @@ public class StructureModifier<TField> {
|
|||||||
*/
|
*/
|
||||||
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
||||||
|
|
||||||
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
|
||||||
// Write a default instance to every field
|
// Write a default instance to every field
|
||||||
for (Field field : defaultFields) {
|
for (Field field : defaultFields) {
|
||||||
try {
|
try {
|
||||||
FieldUtils.writeField(field, target,
|
FieldUtils.writeField(field, target,
|
||||||
DefaultInstances.getDefault(field.getType()), true);
|
generator.getDefault(field.getType()), true);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new FieldAccessException("Cannot write to field due to a security limitation.", e);
|
throw new FieldAccessException("Cannot write to field due to a security limitation.", e);
|
||||||
}
|
}
|
||||||
@ -312,18 +313,33 @@ public class StructureModifier<TField> {
|
|||||||
return ImmutableList.copyOf(data);
|
return ImmutableList.copyOf(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every value stored in the fields of the current type.
|
||||||
|
* @return Every field value.
|
||||||
|
* @throws FieldAccessException Unable to access one or all of the fields
|
||||||
|
*/
|
||||||
|
public List<TField> getValues() throws FieldAccessException {
|
||||||
|
List<TField> values = new ArrayList<TField>();
|
||||||
|
|
||||||
|
for (int i = 0; i < size(); i++)
|
||||||
|
values.add(read(i));
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
// Used to generate plausible default values
|
// Used to generate plausible default values
|
||||||
private static Set<Field> generateDefaultFields(List<Field> fields) {
|
private static Set<Field> generateDefaultFields(List<Field> fields) {
|
||||||
|
|
||||||
Set<Field> requireDefaults = new HashSet<Field>();
|
Set<Field> requireDefaults = new HashSet<Field>();
|
||||||
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
Class<?> type = field.getType();
|
Class<?> type = field.getType();
|
||||||
|
|
||||||
// First, ignore primitive fields
|
// First, ignore primitive fields
|
||||||
if (!PrimitiveUtils.isPrimitive(type)) {
|
if (!PrimitiveUtils.isPrimitive(type)) {
|
||||||
// Next, see if we actually can generate a default value
|
// Next, see if we actually can generate a default value
|
||||||
if (DefaultInstances.getDefault(type) != null) {
|
if (generator.getDefault(type) != null) {
|
||||||
// If so, require it
|
// If so, require it
|
||||||
requireDefaults.add(field);
|
requireDefaults.add(field);
|
||||||
}
|
}
|
||||||
@ -343,7 +359,7 @@ public class StructureModifier<TField> {
|
|||||||
|
|
||||||
// Ignore static, final and "abstract packet" fields
|
// Ignore static, final and "abstract packet" fields
|
||||||
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
|
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
|
||||||
superclassExclude == null || !field.getDeclaringClass().equals(Packet.class)
|
superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
|
||||||
)) {
|
)) {
|
||||||
|
|
||||||
result.add(field);
|
result.add(field);
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.comphenix.protocol.reflect.instances;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides simple constructors for collection interfaces.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class CollectionGenerator implements InstanceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared instance of this generator.
|
||||||
|
*/
|
||||||
|
public static CollectionGenerator INSTANCE = new CollectionGenerator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object create(@Nullable Class<?> type) {
|
||||||
|
// Standard collection types
|
||||||
|
if (type.isInterface()) {
|
||||||
|
if (type.equals(Collection.class) || type.equals(List.class))
|
||||||
|
return new ArrayList<Object>();
|
||||||
|
else if (type.equals(Set.class))
|
||||||
|
return new HashSet<Object>();
|
||||||
|
else if (type.equals(Map.class))
|
||||||
|
return new HashMap<Object, Object>();
|
||||||
|
else if (type.equals(SortedSet.class))
|
||||||
|
return new TreeSet<Object>();
|
||||||
|
else if (type.equals(SortedMap.class))
|
||||||
|
return new TreeMap<Object, Object>();
|
||||||
|
else if (type.equals(Queue.class))
|
||||||
|
return new LinkedList<Object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot provide an instance
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -15,17 +15,13 @@
|
|||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.reflect;
|
package com.comphenix.protocol.reflect.instances;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.google.common.base.Defaults;
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to construct default instances of any type.
|
* Used to construct default instances of any type.
|
||||||
@ -34,29 +30,46 @@ import com.google.common.base.Objects;
|
|||||||
*/
|
*/
|
||||||
public class DefaultInstances {
|
public class DefaultInstances {
|
||||||
|
|
||||||
private static List<Function<Class<?>, Object>> registered = new ArrayList<Function<Class<?>, Object>>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default value for Strings.
|
* Standard default instance provider.
|
||||||
*/
|
*/
|
||||||
public final static String STRING_DEFAULT = "";
|
public static DefaultInstances DEFAULT = DefaultInstances.fromArray(
|
||||||
|
PrimitiveGenerator.INSTANCE, CollectionGenerator.INSTANCE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum height of the hierachy of creates types. Used to prevent cycles.
|
* The maximum height of the hierachy of creates types. Used to prevent cycles.
|
||||||
*/
|
*/
|
||||||
private final static int MAXIMUM_RECURSION = 20;
|
private final static int MAXIMUM_RECURSION = 20;
|
||||||
|
|
||||||
// Provide default registrations
|
/**
|
||||||
static {
|
* Ordered list of instance provider, from highest priority to lowest.
|
||||||
registered.add(new PrimitiveGenerator());
|
*/
|
||||||
registered.add(new CollectionGenerator());
|
private ImmutableList<InstanceProvider> registered;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a default instance generator using the given instance providers.
|
||||||
|
* @param registered - list of instance providers.
|
||||||
|
* @param stringDefault - default string value.
|
||||||
|
*/
|
||||||
|
public DefaultInstances(ImmutableList<InstanceProvider> registered) {
|
||||||
|
this.registered = registered;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the default object providers used to generate default values.
|
* Construct a default instance generator using the given instance providers.
|
||||||
* @return Table of object providers.
|
* @param instaceProviders - array of instance providers.
|
||||||
|
* @return An default instance generator.
|
||||||
*/
|
*/
|
||||||
public static List<Function<Class<?>, Object>> getRegistered() {
|
public static DefaultInstances fromArray(InstanceProvider... instaceProviders) {
|
||||||
|
return new DefaultInstances(ImmutableList.copyOf(instaceProviders));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a immutable list of every default object providers that generates instances.
|
||||||
|
* @return Table of instance providers.
|
||||||
|
*/
|
||||||
|
public ImmutableList<InstanceProvider> getRegistered() {
|
||||||
return registered;
|
return registered;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,20 +90,42 @@ public class DefaultInstances {
|
|||||||
* @param type - the type to construct a default value.
|
* @param type - the type to construct a default value.
|
||||||
* @return A default value/instance, or NULL if not possible.
|
* @return A default value/instance, or NULL if not possible.
|
||||||
*/
|
*/
|
||||||
public static <T> T getDefault(Class<T> type) {
|
public <T> T getDefault(Class<T> type) {
|
||||||
return getDefaultInternal(type, 0);
|
return getDefaultInternal(type, registered, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a default instance or value that is assignable to this type.
|
||||||
|
* <p>
|
||||||
|
* This includes, but isn't limited too:
|
||||||
|
* <ul>
|
||||||
|
* <li>Primitive types. Returns either zero or null.</li>
|
||||||
|
* <li>Primitive wrappers.</li>
|
||||||
|
* <li>String types. Returns an empty string.</li>
|
||||||
|
* <li>Arrays. Returns a zero-length array of the same type.</li>
|
||||||
|
* <li>Enums. Returns the first declared element.</li>
|
||||||
|
* <li>Collection interfaces, such as List and Set. Returns the most appropriate empty container.</li>
|
||||||
|
* <li>Any type with a public constructor that has parameters with defaults.</li>
|
||||||
|
* </ul>
|
||||||
|
* </ul>
|
||||||
|
* @param type - the type to construct a default value.
|
||||||
|
* @param providers - instance providers used during the
|
||||||
|
* @return A default value/instance, or NULL if not possible.
|
||||||
|
*/
|
||||||
|
public <T> T getDefault(Class<T> type, List<InstanceProvider> providers) {
|
||||||
|
return getDefaultInternal(type, providers, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static <T> T getDefaultInternal(Class<T> type, int recursionLevel) {
|
private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
|
||||||
|
|
||||||
// Guard against recursion
|
// Guard against recursion
|
||||||
if (recursionLevel > MAXIMUM_RECURSION) {
|
if (recursionLevel > MAXIMUM_RECURSION) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Function<Class<?>, Object> generator : registered) {
|
for (InstanceProvider generator : providers) {
|
||||||
Object value = generator.apply(type);
|
Object value = generator.create(type);
|
||||||
|
|
||||||
if (value != null)
|
if (value != null)
|
||||||
return (T) value;
|
return (T) value;
|
||||||
@ -125,7 +160,7 @@ public class DefaultInstances {
|
|||||||
|
|
||||||
// Fill out
|
// Fill out
|
||||||
for (int i = 0; i < lastCount; i++) {
|
for (int i = 0; i < lastCount; i++) {
|
||||||
params[i] = getDefaultInternal(types[i], recursionLevel + 1);
|
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (T) minimum.newInstance(params);
|
return (T) minimum.newInstance(params);
|
||||||
@ -139,7 +174,8 @@ public class DefaultInstances {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> boolean contains(T[] elements, T elementToFind) {
|
// We avoid Apache's utility methods to stay backwards compatible
|
||||||
|
private <T> boolean contains(T[] elements, T elementToFind) {
|
||||||
// Search for the given element in the array
|
// Search for the given element in the array
|
||||||
for (T element : elements) {
|
for (T element : elements) {
|
||||||
if (Objects.equal(elementToFind, element))
|
if (Objects.equal(elementToFind, element))
|
||||||
@ -147,62 +183,4 @@ public class DefaultInstances {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides constructors for primtive types, wrappers, arrays and strings.
|
|
||||||
* @author Kristian
|
|
||||||
*/
|
|
||||||
private static class PrimitiveGenerator implements Function<Class<?>, Object> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object apply(@Nullable Class<?> type) {
|
|
||||||
|
|
||||||
if (PrimitiveUtils.isPrimitive(type)) {
|
|
||||||
return Defaults.defaultValue(type);
|
|
||||||
} else if (PrimitiveUtils.isWrapperType(type)) {
|
|
||||||
return Defaults.defaultValue(PrimitiveUtils.unwrap(type));
|
|
||||||
} else if (type.isArray()) {
|
|
||||||
Class<?> arrayType = type.getComponentType();
|
|
||||||
return Array.newInstance(arrayType, 0);
|
|
||||||
} else if (type.isEnum()) {
|
|
||||||
Object[] values = type.getEnumConstants();
|
|
||||||
if (values != null && values.length > 0)
|
|
||||||
return values[0];
|
|
||||||
} else if (type.equals(String.class)) {
|
|
||||||
return STRING_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cannot handle this type
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides simple constructors for collection interfaces.
|
|
||||||
* @author Kristian
|
|
||||||
*/
|
|
||||||
private static class CollectionGenerator implements Function<Class<?>, Object> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object apply(@Nullable Class<?> type) {
|
|
||||||
// Standard collection types
|
|
||||||
if (type.isInterface()) {
|
|
||||||
if (type.equals(Collection.class) || type.equals(List.class))
|
|
||||||
return new ArrayList<Object>();
|
|
||||||
else if (type.equals(Set.class))
|
|
||||||
return new HashSet<Object>();
|
|
||||||
else if (type.equals(Map.class))
|
|
||||||
return new HashMap<Object, Object>();
|
|
||||||
else if (type.equals(SortedSet.class))
|
|
||||||
return new TreeSet<Object>();
|
|
||||||
else if (type.equals(SortedMap.class))
|
|
||||||
return new TreeMap<Object, Object>();
|
|
||||||
else if (type.equals(Queue.class))
|
|
||||||
return new LinkedList<Object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cannot provide an instance
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.comphenix.protocol.reflect.instances;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a type generator for specific types.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public interface InstanceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance given a type, if possible.
|
||||||
|
* @param type - type to create.
|
||||||
|
* @return The instance, or NULL if the type cannot be created.
|
||||||
|
*/
|
||||||
|
public abstract Object create(@Nullable Class<?> type);
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.comphenix.protocol.reflect.instances;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.PrimitiveUtils;
|
||||||
|
import com.google.common.base.Defaults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides constructors for primtive types, wrappers, arrays and strings.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class PrimitiveGenerator implements InstanceProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value for Strings.
|
||||||
|
*/
|
||||||
|
public static final String STRING_DEFAULT = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared instance of this generator.
|
||||||
|
*/
|
||||||
|
public static PrimitiveGenerator INSTANCE = new PrimitiveGenerator(STRING_DEFAULT);
|
||||||
|
|
||||||
|
// Our default string value
|
||||||
|
private String stringDefault;
|
||||||
|
|
||||||
|
public PrimitiveGenerator(String stringDefault) {
|
||||||
|
this.stringDefault = stringDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the string default.
|
||||||
|
* @return Default instance of a string.
|
||||||
|
*/
|
||||||
|
public String getStringDefault() {
|
||||||
|
return stringDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object create(@Nullable Class<?> type) {
|
||||||
|
|
||||||
|
if (PrimitiveUtils.isPrimitive(type)) {
|
||||||
|
return Defaults.defaultValue(type);
|
||||||
|
} else if (PrimitiveUtils.isWrapperType(type)) {
|
||||||
|
return Defaults.defaultValue(PrimitiveUtils.unwrap(type));
|
||||||
|
} else if (type.isArray()) {
|
||||||
|
Class<?> arrayType = type.getComponentType();
|
||||||
|
return Array.newInstance(arrayType, 0);
|
||||||
|
} else if (type.isEnum()) {
|
||||||
|
Object[] values = type.getEnumConstants();
|
||||||
|
if (values != null && values.length > 0)
|
||||||
|
return values[0];
|
||||||
|
} else if (type.equals(String.class)) {
|
||||||
|
return stringDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot handle this type
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user