package jalse.entities.functions;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import jalse.entities.annotations.EntityID;
/**
* A utility for creating {@link EntityFunction} implementations.
*
* @author Elliot Ford
*
*/
public final class Functions {
private static final Map<Class<?>, Class<?>> PRIMITIVES_WRAPPERS = new HashMap<>();
private static final Map<Class<?>, Object> WRAPPER_DEFAULTS = new HashMap<>();
static {
/*
* Primitives to wrappers.
*/
PRIMITIVES_WRAPPERS.put(int.class, Integer.class);
PRIMITIVES_WRAPPERS.put(boolean.class, Boolean.class);
PRIMITIVES_WRAPPERS.put(short.class, Short.class);
PRIMITIVES_WRAPPERS.put(long.class, Long.class);
PRIMITIVES_WRAPPERS.put(char.class, Character.class);
PRIMITIVES_WRAPPERS.put(byte.class, Byte.class);
PRIMITIVES_WRAPPERS.put(double.class, Double.class);
PRIMITIVES_WRAPPERS.put(float.class, Float.class);
/*
* Default primitive values.
*/
WRAPPER_DEFAULTS.put(Integer.class, 0);
WRAPPER_DEFAULTS.put(Boolean.class, false);
WRAPPER_DEFAULTS.put(Short.class, (short) 0);
WRAPPER_DEFAULTS.put(Long.class, 0L);
WRAPPER_DEFAULTS.put(Character.class, '\0');
WRAPPER_DEFAULTS.put(Byte.class, (byte) 0);
WRAPPER_DEFAULTS.put(Double.class, 0d);
WRAPPER_DEFAULTS.put(Float.class, 0f);
}
/**
* An supplier for random IDs.
*
* @see UUID#randomUUID()
*/
public static final Supplier<UUID> RANDOM_ID_SUPPLIER = UUID::randomUUID;
/**
* Checks whether the method has params.
*
* @param m
* Method to check.
* @throws IllegalArgumentException
* If method has no params.
*/
public static void checkHasParams(final Method m) throws IllegalArgumentException {
if (!hasParams(m)) {
throw new IllegalArgumentException("Must have params");
}
}
/**
* Checks whether the method has a return type.
*
* @param m
* Method to check.
* @throws IllegalArgumentException
* If method has no return type.
*/
public static void checkHasReturnType(final Method m) throws IllegalArgumentException {
if (!hasReturnType(m)) {
throw new IllegalArgumentException("Must have a return type");
}
}
/**
* Checks whether the method has no params.
*
* @param m
* Method to check.
* @throws IllegalArgumentException
* If method has params.
*/
public static void checkNoParams(final Method m) throws IllegalArgumentException {
if (hasParams(m)) {
throw new IllegalArgumentException("Must not have params");
}
}
/**
* Checks whether the method has a void return type.
*
* @param m
* Method to check.
* @throws IllegalArgumentException
* If method has a non-void return type.
*/
public static void checkNoReturnType(final Method m) throws IllegalArgumentException {
if (hasReturnType(m)) {
throw new IllegalArgumentException("Must have a void return type");
}
}
/**
* Checks whether the method is not default.
*
* @param m
* Method to check.
* @throws IllegalArgumentException
* If method is default.
*/
public static void checkNotDefault(final Method m) throws IllegalArgumentException {
if (m.isDefault()) {
throw new IllegalArgumentException("Cannot be default");
}
}
/**
* Gets the default value for a primitive wrapper.
*
* @param type
* Primitive wrapper type.
* @return The default value.
*/
public static Object defaultValue(final Class<?> type) {
final Object value = WRAPPER_DEFAULTS.get(Objects.requireNonNull(type));
if (value == null) {
throw new IllegalArgumentException("Not primitive wrapper");
}
return value;
}
/**
* Gets the first generic type argument for a parameterised type.
*
* @param pt
* Type to take from.
* @return The first generic type argument or null if not a parameterised type.
*/
public static Type firstGenericTypeArg(final Type pt) {
return pt instanceof ParameterizedType ? ((ParameterizedType) pt).getActualTypeArguments()[0] : null;
}
/**
* Gets all the {@link EntityID} annotations and transforms into ID suppliers.
*
* @param m
* Method to check.
* @return ID supplier set.
*
* @see #toIDSupplier(EntityID)
*/
public static Set<Supplier<UUID>> getIDSuppliers(final Method m) {
final Set<Supplier<UUID>> idSuppliers = new HashSet<>();
for (final EntityID entityID : m.getAnnotationsByType(EntityID.class)) {
idSuppliers.add(toIDSupplier(entityID));
}
return idSuppliers;
}
/**
* Gets a single ID supplier from a method.
*
* @param m
* Method to check.
* @return The single ID supplier or {@code null} if none found.
* @throws IllegalArgumentException
* If there are multiple ID suppliers found.
*/
public static Supplier<UUID> getSingleIDSupplier(final Method m) throws IllegalArgumentException {
// Check only has one ID max
final EntityID[] entityIDs = m.getAnnotationsByType(EntityID.class);
if (entityIDs.length > 1) {
throw new IllegalArgumentException("Cannot have more than one entity ID");
}
// Get and validate ID
Supplier<UUID> idSupplier = null;
if (entityIDs.length == 1) {
idSupplier = toIDSupplier(entityIDs[0]);
}
return idSupplier;
}
/**
* Wether the method has parameters.
*
* @param m
* Method to check.
* @return Whether it had parameters.
*/
public static boolean hasParams(final Method m) {
return m.getParameterCount() > 0;
}
/**
* Checks whether a method has a return type.
*
* @param m
* Method to check.
* @return Whether it had a return type.
*/
public static boolean hasReturnType(final Method m) {
return !Void.TYPE.equals(m.getGenericReturnType());
}
/**
* Whether the type represents a primitive type.
*
* @param t
* Type to check.
* @return Whether the type is primitive.
*/
public static boolean isPrimitive(final Type t) {
return t instanceof Class<?> && ((Class<?>) t).isPrimitive();
}
/**
* Checks to see if the specified type is a primitive wrapper.
*
* @param type
* Type to check.
* @return Whether the type is a primitive wrapper.
*/
public static boolean isWrapper(final Class<?> type) {
return unwrap0(type).isPresent();
}
/**
* Whether the method return type matches the supplied type.
*
* @param m
* Method to check.
* @param clazz
* Possible return type to check.
* @return Whether the actual return type matches the check type.
*/
public static boolean returnTypeIs(final Method m, final Class<?> clazz) {
return clazz.equals(toClass(m.getGenericReturnType()));
}
/**
* Attempts to resolve raw class from type.
*
* @param type
* Type to resolve for.
* @return Resolved class.
*/
public static Class<?> toClass(final Type type) {
return type instanceof ParameterizedType ? toClass(((ParameterizedType) type).getRawType()) : (Class<?>) type;
}
/**
* Transforms the {@link EntityID} to a {@link UUID} supplier.
*
* @param id
* ID annotation.
* @return ID supplier using annotation.
*
* @see #validateEntityID(EntityID)
*/
public static Supplier<UUID> toIDSupplier(final EntityID id) {
validateEntityID(id);
// Random supplier
if (id.random()) {
return RANDOM_ID_SUPPLIER;
}
// Create ID
final UUID newID;
if (id.name().length() > 0) {
newID = UUID.fromString(id.name());
} else {
newID = new UUID(id.mostSigBits(), id.leastSigBits());
}
// Create static ID supplier.
return () -> newID;
}
/**
* Unwraps the primitive wrapper to the primitive type.
*
* @param type
* Wrapper type to unwrap.
* @return The unwrapped primitive type.
* @throws IllegalArgumentException
* If the type is not a primitive wrapper.
*/
public static Class<?> unwrap(final Class<?> type) {
return unwrap0(type).orElseThrow(() -> new IllegalArgumentException("Not primitive wrapper"));
}
private static Optional<Class<?>> unwrap0(final Class<?> type) {
Objects.requireNonNull(type);
final Optional<Entry<Class<?>, Class<?>>> op = PRIMITIVES_WRAPPERS.entrySet().stream()
.filter(e -> type.equals(e.getValue())).findAny();
return op.map(e -> e.getValue());
}
/**
* Validates an {@link EntityID} annotation is correctly formed.
*
* @param id
* ID to check.
*/
public static void validateEntityID(final EntityID id) {
int changes = 0;
// Changed new UUID(m, l)
if (id.mostSigBits() != EntityID.DEFAULT_MOST_SIG_BITS
|| id.leastSigBits() != EntityID.DEFAULT_LEAST_SIG_BITS) {
changes++;
}
// Changed fromString(n)
if (!EntityID.DEFAULT_NAME.equals(id.name())) {
changes++;
}
// Changed random
if (id.random() != EntityID.DEFAULT_RANDOM) {
changes++;
}
// Check changed more than once
if (changes > 1) {
throw new IllegalArgumentException(
String.format("%s annotation provides multiple ID source info", EntityID.class));
}
}
/**
* Gets the wrapper type for the primitive type.
*
* @param type
* Primitive type to wrap.
* @return The primitive wrapper type.
* @throws IllegalArgumentException
* If the type is not primitive.
*/
public static Class<?> wrap(final Class<?> type) {
if (!type.isPrimitive()) {
throw new IllegalArgumentException("Not primitive");
}
return PRIMITIVES_WRAPPERS.get(type);
}
private Functions() {
throw new UnsupportedOperationException();
}
}