diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/DefaultInstances.java b/ProtocolLib/src/com/comphenix/protocol/reflect/DefaultInstances.java new file mode 100644 index 00000000..b0747261 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/DefaultInstances.java @@ -0,0 +1,184 @@ +package com.comphenix.protocol.reflect; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.util.*; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.ArrayUtils; + +import com.google.common.base.Defaults; +import com.google.common.base.Function; +import com.google.gson.internal.Primitives; + +/** + * Used to construct default instances of any type. + * @author Kristian + * + */ +public class DefaultInstances { + + private static List, Object>> registered = new ArrayList, Object>>(); + + /** + * Default value for Strings. + */ + public final static String STRING_DEFAULT = ""; + + /** + * 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()); + } + + /** + * Retrieves the default object providers used to generate default values. + * @return Table of object providers. + */ + public static List, Object>> getRegistered() { + return registered; + } + + /** + * 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. + * @return A default value/instance, or NULL if not possible. + */ + public static T getDefault(Class type) { + return getDefaultInternal(type, 0); + } + + @SuppressWarnings("unchecked") + private static T getDefaultInternal(Class type, int recursionLevel) { + + // Guard against recursion + if (recursionLevel > MAXIMUM_RECURSION) { + return null; + } + + for (Function, Object> generator : registered) { + Object value = generator.apply(type); + + if (value != null) + return (T) value; + } + + Constructor 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 (!ArrayUtils.contains(types, type)) { + minimum = candidate; + lastCount = types.length; + + // Don't loop again if we've already found the best possible constructor + if (lastCount == 0) + break; + } + } + } + + // Create the type with this constructor using default values. This might fail, though. + try { + if (minimum != null) { + Object[] params = new Object[lastCount]; + Class[] types = minimum.getParameterTypes(); + + // Fill out + for (int i = 0; i < lastCount; i++) { + params[i] = getDefaultInternal(types[i], recursionLevel + 1); + } + + return (T) minimum.newInstance(params); + } + + } catch (Exception e) { + // Nope, we couldn't create this type + } + + // No suitable default value could be found + return null; + } + + /** + * 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 (Primitives.isPrimitive(type)) { + return Defaults.defaultValue(type); + } else if (Primitives.isWrapperType(type)) { + return Defaults.defaultValue(Primitives.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; + } + } +}