package com.bergerkiller.bukkit.common.utils; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import net.minecraft.util.com.google.common.collect.BiMap; import org.bukkit.block.Block; import com.bergerkiller.bukkit.common.collections.BlockSet; import com.bergerkiller.bukkit.common.reflection.MethodAccessor; import com.bergerkiller.bukkit.common.reflection.SafeDirectMethod; /** * Logic operations, such as contains checks and collection-type transformations */ public class LogicUtil { private static final MethodAccessor<Object> objectCloneMethod; private static final Map<Class<?>, Class<?>> unboxedToBoxed = new HashMap<Class<?>, Class<?>>(); private static final Map<Class<?>, Class<?>> boxedToUnboxed = new HashMap<Class<?>, Class<?>>(); static { unboxedToBoxed.put(boolean.class, Boolean.class); unboxedToBoxed.put(char.class, Character.class); unboxedToBoxed.put(byte.class, Byte.class); unboxedToBoxed.put(short.class, Short.class); unboxedToBoxed.put(int.class, Integer.class); unboxedToBoxed.put(long.class, Long.class); unboxedToBoxed.put(float.class, Float.class); unboxedToBoxed.put(double.class, Double.class); for (Entry<Class<?>, Class<?>> entry : unboxedToBoxed.entrySet()) { boxedToUnboxed.put(entry.getValue(), entry.getKey()); } // Get the cloning method MethodAccessor<Object> objectCloneMethodAccessor; try { final Method cloneMethod = Object.class.getDeclaredMethod("clone"); cloneMethod.setAccessible(true); objectCloneMethodAccessor = new SafeDirectMethod<Object>() { @Override public Object invoke(Object instance, Object... args) { try { return cloneMethod.invoke(instance, args); } catch (Throwable t) { throw new RuntimeException("Failed to clone:", t); } } }; } catch (Throwable t) { objectCloneMethodAccessor = new SafeDirectMethod<Object>() { @Override public Object invoke(Object instance, Object... args) { try { return instance.getClass().getDeclaredMethod("clone").invoke(instance, args); } catch (Throwable t) { throw new RuntimeException("Failed to clone:", t); } } }; } objectCloneMethod = objectCloneMethodAccessor; } /** * Obtains the unboxed type (int) from a boxed type (Integer)<br> * If the input type has no unboxed type, null is returned * * @param boxedType to convert * @return the unboxed type */ public static Class<?> getUnboxedType(Class<?> boxedType) { return boxedToUnboxed.get(boxedType); } /** * Obtains the boxed type (Integer) from an unboxed type (int)<br> * If the input type has no boxed type, null is returned * * @param unboxedType to convert * @return the boxed type */ public static Class<?> getBoxedType(Class<?> unboxedType) { return unboxedToBoxed.get(unboxedType); } /** * Checks if both values are null or the values equal each other * * @param value1 to use * @param value2 to use * @return True if value1 and value2 equal or are both null, False if not */ public static boolean bothNullOrEqual(Object value1, Object value2) { return value1 == null ? value2 == null : value1.equals(value2); } /** * Checks if a Map is null or empty * * @param map to check * @return True if the collection is null or empty */ public static boolean nullOrEmpty(Map<?, ?> map) { return map == null || map.isEmpty(); } /** * Checks if a Collection is null or empty * * @param collection to check * @return True if the collection is null or empty */ public static boolean nullOrEmpty(Collection<?> collection) { return collection == null || collection.isEmpty(); } /** * Checks if a String is null or empty * * @param text to check * @return True if the text is null or empty */ public static boolean nullOrEmpty(String text) { return text == null || text.isEmpty(); } /** * Checks if an Item Stack is null or empty * * @param item to check * @return True if the item is null or empty */ public static boolean nullOrEmpty(org.bukkit.inventory.ItemStack item) { return item == null || MaterialUtil.getTypeId(item) == 0 || item.getAmount() < 1; } /** * Checks if an array is null or empty * * @param array to check * @return True if the item is null or empty */ public static boolean nullOrEmpty(Object[] array) { return array == null || array.length == 0; } /** * Checks whether an element index is within range of a collection * * @param collection to check * @param index to check * @return True if it is in bounds, False if not */ public static boolean isInBounds(Collection<?> collection, int index) { return collection != null && index >= 0 && index < collection.size(); } /** * Checks whether an element index is within range of an array<br> * Both Object and primitive arrays are supported * * @param array to check * @param index to check * @return True if it is in bounds, False if not */ public static boolean isInBounds(Object[] array, int index) { return array != null && index >= 0 && index < array.length; } /** * Checks whether an element index is within range of an array<br> * Both Object and primitive arrays are supported * * @param array to check * @param index to check * @return True if it is in bounds, False if not */ public static boolean isInBounds(Object array, int index) { return array != null && index >= 0 && index < Array.getLength(array); } /** * Returns the default value if the input value is null * * @param value to fix * @param def to return if the value is null * @return the value or the default */ public static <T> T fixNull(T value, T def) { return value == null ? def : value; } /** * Appends one or more elements to an array * This method allocates a new Array of the same type as the old array, * with the size of array + values. * * @param array input array to append to * @param values to append to array * @return new Array with the values from array and values */ @SuppressWarnings("unchecked") public static <T> T[] appendArray(T[] array, T... values) { if (nullOrEmpty(array)) { return values; } if (nullOrEmpty(values)) { return array; } T[] rval = createArray((Class<T>) array.getClass().getComponentType(), array.length + values.length); System.arraycopy(array, 0, rval, 0, array.length); System.arraycopy(values, 0, rval, array.length, values.length); return rval; } /** * Allocates a new array of the same length and writes the contents to this new array. * Unlike {@link #cloneAll(Object[])}, this method does not individually clone the elements * * @param array to re-allocate as a new array * @return new array with the contents of the input array */ @SuppressWarnings("unchecked") public static <T> T[] cloneArray(T[] array) { if (array == null) { return null; } final int length = array.length; T[] rval = createArray((Class<T>) array.getClass().getComponentType(), length); System.arraycopy(array, 0, rval, 0, length); return rval; } /** * Clones a single value * * @param value to clone * @return cloned value * @throws RuntimeException if cloning fails */ @SuppressWarnings("unchecked") public static <T> T clone(T value) { if (value == null) { return null; } return (T) objectCloneMethod.invoke(value); } /** * Clones all elements of an array * * @param values to clone * @return a new, cloned array of cloned elements * @throws RuntimeException if cloning fails */ @SuppressWarnings("unchecked") public static <T> T[] cloneAll(T[] values) { if (values == null || values.length == 0) { return values; } try { final Class<T> compType = (Class<T>) values.getClass().getComponentType(); final Method cloneMethod = compType.getDeclaredMethod("clone"); T[] rval = createArray(compType, values.length); for (int i = 0; i < rval.length; i++) { final T value = values[i]; if (value != null) { rval[i] = (T) cloneMethod.invoke(value); } } return rval; } catch (Exception ex) { throw new RuntimeException("Cloning was not possible:", ex); } } /** * Obtains the Class instance representing an array of the component type specified. * For example:<br> * - Integer.class -> Integer[].class<br> * - int.class -> int[].class * * @param componentType to convert * @return array type */ public static Class<?> getArrayType(Class<?> componentType) { if (componentType.isPrimitive()) { return Array.newInstance(componentType, 0).getClass(); } else { return CommonUtil.getClass("[L" + componentType.getName() + ";"); } } /** * Tries to get a specific element from a list. * The default value is returned when:<br> * - The list is null<br> * - The list index is out of bounds * * @param list to get an element from * @param index of the element to get * @param def value to return on failure * @return The list element, or the default value */ public static <T> T getList(List<T> list, int index, T def) { return isInBounds(list, index) ? list.get(index) : def; } /** * Tries to get a specific element from an array. * The default value is returned when:<br> * - The array is null<br> * - The array index is out of bounds * * @param array to get an element from * @param index of the element to get * @param def value to return on failure * @return The array element, or the default value */ public static <T> T getArray(T[] array, int index, T def) { return isInBounds(array, index) ? array[index] : def; } /** * Constructs a new 1-dimensional Array of a given type and length * * @param type of the new Array * @param length of the new Array * @return new Array */ @SuppressWarnings("unchecked") public static <T> T[] createArray(Class<T> type, int length) { return (T[]) Array.newInstance(type, length); } /** * Converts a collection to an Array * * @param collection to convert * @param type of the collection and the array to return (can not be primitive) * @return new Array containing the elements in the collection */ public static <T> T[] toArray(Collection<?> collection, Class<T> type) { return collection.toArray(createArray(type, collection.size())); } /** * Adds all the elements of an array to a Collection * * @param collection to add elements to * @param array to add to the Collection * @return True if the collection changed as a result of the call, False if not. */ public static <E, T extends E> boolean addArray(Collection<E> collection, T... array) { if (array.length > 20) { return collection.addAll(Arrays.asList(array)); } else { boolean changed = false; for (T element : array) { changed |= collection.add(element); } return changed; } } /** * Removes all the elements of an array from a Collection * * @param collection to remove elements from * @param array to remove from the Collection * @return True if the collection changed as a result of the call, False if not. */ public static boolean removeArray(Collection<?> collection, Object... array) { if (array.length > 100) { return collection.removeAll(Arrays.asList(array)); } else { boolean changed = false; for (Object element : array) { changed |= collection.remove(element); } return changed; } } /** * Removes or adds an element from/to a Collection, and returns whether something has changed. * * @param collection to add or remove an element from * @param value to add or remove * @param add option: True to add, False to remove * @return True if the collection changed (element removed or added), False if not */ public static boolean addOrRemove(BlockSet collection, Block value, boolean add) { return add ? collection.add(value) : collection.remove(value); } /** * Removes or adds an element from/to a Collection, and returns whether something has changed. * * @param collection to add or remove an element from * @param value to add or remove * @param add option: True to add, False to remove * @return True if the collection changed (element removed or added), False if not */ public static <T> boolean addOrRemove(Collection<T> collection, T value, boolean add) { return add ? collection.add(value) : collection.remove(value); } /** * Checks whether one map contains all the contents of another map * * @param map to check for contents * @param contents to check the map for * @return True if all contents are contained in the map, False if not */ public static boolean containsAll(Map<?, ?> map, Map<?, ?> contents) { for (Map.Entry<?, ?> entry : contents.entrySet()) { Object value = map.get(entry.getKey()); // Null value stored in the map? if (value == null) { if (entry.getValue() != null || !map.containsKey(entry.getKey())) { return false; } } else if (!value.equals(entry.getValue())) { return false; } } return true; } /** * Checks if an array of values contains the value specified * * @param value to find * @param values to search in * @return True if it is contained, False if not */ public static <T> boolean contains(T value, T... values) { for (T v : values) { if (bothNullOrEqual(v, value)) { return true; } } return false; } /** * Checks if a list of bytes contains the byte specified * * @param value to find * @param values to search in * @return True if it is contained, False if not */ public static boolean containsByte(byte value, byte... values) { for (byte v : values) { if (v == value) { return true; } } return false; } /** * Checks if a sequence of characters contains the character specified * * @param value to find * @param sequence of char values to search in * @return True if it is contained, False if not */ public static boolean containsChar(char value, CharSequence sequence) { for (int i = 0; i < sequence.length(); i++) { if (sequence.charAt(i) == value) { return true; } } return false; } /** * Checks if a list of characters contains the character specified * * @param value to find * @param values to search in * @return True if it is contained, False if not */ public static boolean containsChar(char value, char... values) { for (char v : values) { if (v == value) { return true; } } return false; } /** * Checks if a list of integers contains the integer specified * * @param value to find * @param values to search in * @return True if it is contained, False if not */ public static boolean containsInt(int value, int... values) { for (int v : values) { if (v == value) { return true; } } return false; } /** * Checks if a list of booleans contains the boolean specified * * @param value to find * @param values to search in * @return True if it is contained, False if not */ public static boolean containsBool(boolean value, boolean... values) { for (boolean v : values) { if (v == value) { return true; } } return false; } /** * Skips elements from an iterator by calling 'next' a given amount of times (if possible). * If the count exceeds the amount of elements the iterator provides, further elements are ignored. * In that case, calling {@link Iterator#hasNext()} would yield false. * * @param iterator to skip * @param count to skip * @return the iterator */ public static <T extends Iterator<?>> T skipIterator(T iterator, int count) { for (int i = 0; i < count && iterator.hasNext(); i++) { iterator.next(); } return iterator; } /** * Obtains the key at which a specific value is mapped to in a Map. * This is essentially the reverse key lookup in a map, and is thus slow. * For 'BiMap' maps, the inverse is used to obtain the key faster. * * @param map to check * @param value to look for * @return key the value is mapped to, or null if not found */ public static <K, V> K getKeyAtValue(Map<K, V> map, V value) { if (map instanceof BiMap) { return ((BiMap<K, V>) map).inverse().get(value); } for (Entry<K, V> entry : map.entrySet()) { if (bothNullOrEqual(entry.getValue(), value)) { return entry.getKey(); } } return null; } }