From 7e28aefb7516ee2b46b9a53661b23f6dbde237da Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 15 Sep 2012 19:12:59 +0200 Subject: [PATCH] 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. --- .../comphenix/protocol/ProtocolLibrary.java | 4 +- .../protocol/injector/MockSocket.java | 81 ++++++++++ .../injector/NetworkObjectInjector.java | 75 +++++++-- .../injector/PacketFilterManager.java | 64 +++++++- .../protocol/injector/PlayerInjector.java | 17 --- .../protocol/reflect/StructureModifier.java | 28 +++- .../instances/CollectionGenerator.java | 51 +++++++ .../{ => instances}/DefaultInstances.java | 144 ++++++++---------- .../reflect/instances/InstanceProvider.java | 18 +++ .../reflect/instances/PrimitiveGenerator.java | 62 ++++++++ 10 files changed, 424 insertions(+), 120 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/MockSocket.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/reflect/instances/CollectionGenerator.java rename ProtocolLib/src/com/comphenix/protocol/reflect/{ => instances}/DefaultInstances.java (54%) create mode 100644 ProtocolLib/src/com/comphenix/protocol/reflect/instances/InstanceProvider.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index 9e9d53cd..52f2d322 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -21,12 +21,12 @@ import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; -import org.bukkit.ChatColor; import org.bukkit.Server; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import com.comphenix.protocol.injector.PacketFilterManager; +import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.metrics.Statistics; public class ProtocolLibrary extends JavaPlugin { @@ -79,7 +79,7 @@ public class ProtocolLibrary extends JavaPlugin { // Check for versions, ect. logger.severe("Detected incompatible plugin: " + plugin); logger.info("Using woraround."); - + protocolManager.setPlayerHook(PlayerInjectHooks.OVERRIDE_NETWORK_HANDLER); } } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/MockSocket.java b/ProtocolLib/src/com/comphenix/protocol/injector/MockSocket.java new file mode 100644 index 00000000..c9d31cbc --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/MockSocket.java @@ -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); + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java index b5ae0a9a..6bf87444 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java @@ -1,15 +1,26 @@ package com.comphenix.protocol.injector; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Proxy; import java.lang.reflect.Method; +import java.net.Socket; +import java.util.Collections; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.Nullable; 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 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. * @@ -20,6 +31,9 @@ public class NetworkObjectInjector extends PlayerInjector { super(player, manager, sendingFilters); } + // This is why we don't normally use this method. It will create two extra threads per user. + private Set threadKillList = Collections.newSetFromMap(new ConcurrentHashMap()); + @Override public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException { Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue(); @@ -45,15 +59,30 @@ public class NetworkObjectInjector extends PlayerInjector { public void injectManager() { if (networkManager != null) { - final Class networkInterface = networkManagerField.getType(); final Object networkDelegate = networkManagerRef.getOldValue(); + + Enhancer ex = new Enhancer(); - // Create our proxy object - Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(), - new Class[] { networkInterface }, new InvocationHandler() { - + ex.setSuperclass(networkManager.getClass()); + ex.setClassLoader(manager.getClassLoader()); + ex.setCallback(new MethodInterceptor() { @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! if (method.equals(queueMethod)) { 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).withType(Thread.class).getValues()) { + threadKillList.add(thread.getId()); + } + } catch (FieldAccessException e) { + // Oh damn + } + // Inject it, if we can. networkManagerRef.setValue(networkProxy); } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 2a697290..9620882e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -53,6 +53,22 @@ import com.google.common.collect.ImmutableSet; 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 private Set packetListeners = Collections.newSetFromMap(new ConcurrentHashMap()); @@ -61,6 +77,9 @@ public final class PacketFilterManager implements ProtocolManager { private Map connectionLookup = new ConcurrentHashMap(); private Map playerInjection = new HashMap(); + // Player injection type + private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS; + // Packet injection 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() { return logger; } @@ -309,12 +344,35 @@ public final class PacketFilterManager implements ProtocolManager { 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 if (!hasClosed && player != null && !playerInjection.containsKey(player)) { try { - PlayerInjector injector = new PlayerInjector(player, this, sendingFilters); - + PlayerInjector injector = getPlayerHookInstance(player); + injector.injectManager(); playerInjection.put(player, injector); connectionLookup.put(injector.getInputStream(false), player); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index fa5eaa01..79842052 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -38,22 +38,6 @@ import com.comphenix.protocol.reflect.VolatileField; 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 protected static Field serverHandlerField; protected static Field networkManagerField; @@ -97,7 +81,6 @@ abstract class PlayerInjector { EntityPlayer notchEntity = craft.getHandle(); Object serverHandler = null; - Object networkManager = null; if (!hasInitialized) { // Do this first, in case we encounter an exception diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java index bd23ed76..aa6aea67 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java @@ -26,11 +26,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; -import net.minecraft.server.Packet; - @SuppressWarnings("rawtypes") public class StructureModifier { @@ -195,11 +194,13 @@ public class StructureModifier { */ public StructureModifier writeDefaults() throws FieldAccessException { + DefaultInstances generator = DefaultInstances.DEFAULT; + // Write a default instance to every field for (Field field : defaultFields) { try { FieldUtils.writeField(field, target, - DefaultInstances.getDefault(field.getType()), true); + generator.getDefault(field.getType()), true); } catch (IllegalAccessException e) { throw new FieldAccessException("Cannot write to field due to a security limitation.", e); } @@ -312,18 +313,33 @@ public class StructureModifier { 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 getValues() throws FieldAccessException { + List values = new ArrayList(); + + for (int i = 0; i < size(); i++) + values.add(read(i)); + + return values; + } + // Used to generate plausible default values private static Set generateDefaultFields(List fields) { Set requireDefaults = new HashSet(); - + DefaultInstances generator = DefaultInstances.DEFAULT; + for (Field field : fields) { Class type = field.getType(); // First, ignore primitive fields if (!PrimitiveUtils.isPrimitive(type)) { // Next, see if we actually can generate a default value - if (DefaultInstances.getDefault(type) != null) { + if (generator.getDefault(type) != null) { // If so, require it requireDefaults.add(field); } @@ -343,7 +359,7 @@ public class StructureModifier { // Ignore static, final and "abstract packet" fields if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && ( - superclassExclude == null || !field.getDeclaringClass().equals(Packet.class) + superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude) )) { result.add(field); diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/CollectionGenerator.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/CollectionGenerator.java new file mode 100644 index 00000000..c7fe4de7 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/CollectionGenerator.java @@ -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(); + else if (type.equals(Set.class)) + return new HashSet(); + else if (type.equals(Map.class)) + return new HashMap(); + else if (type.equals(SortedSet.class)) + return new TreeSet(); + else if (type.equals(SortedMap.class)) + return new TreeMap(); + else if (type.equals(Queue.class)) + return new LinkedList(); + } + + // Cannot provide an instance + return null; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/DefaultInstances.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java similarity index 54% rename from ProtocolLib/src/com/comphenix/protocol/reflect/DefaultInstances.java rename to ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 53428f17..d61b153d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/DefaultInstances.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -15,17 +15,13 @@ * 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.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.collect.ImmutableList; /** * Used to construct default instances of any type. @@ -34,29 +30,46 @@ import com.google.common.base.Objects; */ public class DefaultInstances { - private static List, Object>> registered = new ArrayList, 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. */ private final static int MAXIMUM_RECURSION = 20; - // Provide default registrations - static { - registered.add(new PrimitiveGenerator()); - registered.add(new CollectionGenerator()); + /** + * Ordered list of instance provider, from highest priority to lowest. + */ + private ImmutableList 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 registered) { + this.registered = registered; } /** - * Retrieves the default object providers used to generate default values. - * @return Table of object providers. + * Construct a default instance generator using the given instance providers. + * @param instaceProviders - array of instance providers. + * @return An default instance generator. */ - public static List, 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 getRegistered() { return registered; } @@ -77,20 +90,42 @@ public class DefaultInstances { * @param type - the type to construct a default value. * @return A default value/instance, or NULL if not possible. */ - public static T getDefault(Class type) { - return getDefaultInternal(type, 0); + public T getDefault(Class type) { + return getDefaultInternal(type, registered, 0); + } + + /** + * Retrieves a default instance or value that is assignable to this type. + *

+ * This includes, but isn't limited too: + *

    + *
  • Primitive types. Returns either zero or null.
  • + *
  • Primitive wrappers.
  • + *
  • String types. Returns an empty string.
  • + *
  • Arrays. Returns a zero-length array of the same type.
  • + *
  • Enums. Returns the first declared element.
  • + *
  • Collection interfaces, such as List and Set. Returns the most appropriate empty container.
  • + *
  • Any type with a public constructor that has parameters with defaults.
  • + *
+ * + * @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 getDefault(Class type, List providers) { + return getDefaultInternal(type, providers, 0); } @SuppressWarnings("unchecked") - private static T getDefaultInternal(Class type, int recursionLevel) { + private T getDefaultInternal(Class type, List providers, int recursionLevel) { // Guard against recursion if (recursionLevel > MAXIMUM_RECURSION) { return null; } - for (Function, Object> generator : registered) { - Object value = generator.apply(type); + for (InstanceProvider generator : providers) { + Object value = generator.create(type); if (value != null) return (T) value; @@ -125,7 +160,7 @@ public class DefaultInstances { // Fill out 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); @@ -139,7 +174,8 @@ public class DefaultInstances { return null; } - private static boolean contains(T[] elements, T elementToFind) { + // We avoid Apache's utility methods to stay backwards compatible + private boolean contains(T[] elements, T elementToFind) { // Search for the given element in the array for (T element : elements) { if (Objects.equal(elementToFind, element)) @@ -147,62 +183,4 @@ public class DefaultInstances { } return false; } - - /** - * Provides constructors for primtive types, wrappers, arrays and strings. - * @author Kristian - */ - private static class PrimitiveGenerator implements Function, 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, 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(); - else if (type.equals(Set.class)) - return new HashSet(); - else if (type.equals(Map.class)) - return new HashMap(); - else if (type.equals(SortedSet.class)) - return new TreeSet(); - else if (type.equals(SortedMap.class)) - return new TreeMap(); - else if (type.equals(Queue.class)) - return new LinkedList(); - } - - // Cannot provide an instance - return null; - } - } } diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/InstanceProvider.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/InstanceProvider.java new file mode 100644 index 00000000..98775af7 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/InstanceProvider.java @@ -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); +} \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java new file mode 100644 index 00000000..c569ad16 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java @@ -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; + } +} \ No newline at end of file