/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Generic collection and array-related utility functions for java.util types.
* See ClassUtil for comparable facilities for short name lookup.
*
* @author Brian Remedios
* @version $Revision$
*/
public final class CollectionUtil {
@SuppressWarnings("PMD.UnnecessaryFullyQualifiedName")
public static final TypeMap COLLECTION_INTERFACES_BY_NAMES = new TypeMap(new Class[] { java.util.List.class,
java.util.Collection.class, java.util.Map.class, java.util.Set.class, });
@SuppressWarnings({ "PMD.LooseCoupling", "PMD.UnnecessaryFullyQualifiedName" })
public static final TypeMap COLLECTION_CLASSES_BY_NAMES = new TypeMap(new Class[] { java.util.ArrayList.class,
java.util.LinkedList.class, java.util.Vector.class, java.util.HashMap.class, java.util.LinkedHashMap.class,
java.util.TreeMap.class, java.util.TreeSet.class, java.util.HashSet.class, java.util.LinkedHashSet.class,
java.util.Hashtable.class, });
private CollectionUtil() {
}
/**
* Add elements from the source to the target as long as they don't already
* exist there. Return the number of items actually added.
*
* @param source
* @param target
* @return int
*/
public static int addWithoutDuplicates(Collection<String> source, Collection<String> target) {
int added = 0;
for (String item : source) {
if (target.contains(item)) {
continue;
}
target.add(item);
added++;
}
return added;
}
/**
* Returns the collection type if we recognize it by its short name.
*
* @param shortName
* String
* @return Class
*/
public static Class<?> getCollectionTypeFor(String shortName) {
Class<?> cls = COLLECTION_CLASSES_BY_NAMES.typeFor(shortName);
if (cls != null) {
return cls;
}
return COLLECTION_INTERFACES_BY_NAMES.typeFor(shortName);
}
/**
* Return whether we can identify the typeName as a java.util collection
* class or interface as specified.
*
* @param typeName
* String
* @param includeInterfaces
* boolean
* @return boolean
*/
public static boolean isCollectionType(String typeName, boolean includeInterfaces) {
if (COLLECTION_CLASSES_BY_NAMES.contains(typeName)) {
return true;
}
return includeInterfaces && COLLECTION_INTERFACES_BY_NAMES.contains(typeName);
}
/**
* Return whether we can identify the typeName as a java.util collection
* class or interface as specified.
*
* @param clazzType
* Class
* @param includeInterfaces
* boolean
* @return boolean
*/
public static boolean isCollectionType(Class<?> clazzType, boolean includeInterfaces) {
if (COLLECTION_CLASSES_BY_NAMES.contains(clazzType)) {
return true;
}
return includeInterfaces && COLLECTION_INTERFACES_BY_NAMES.contains(clazzType);
}
/**
* Returns the items as a populated set.
*
* @param items
* Object[]
* @return Set
*/
public static <T> Set<T> asSet(T[] items) {
return new HashSet<>(Arrays.asList(items));
}
/**
* Creates and returns a map populated with the keyValuesSets where the
* value held by the tuples are they key and value in that order.
*
* @param keys
* K[]
* @param values
* V[]
* @return Map
*/
public static <K, V> Map<K, V> mapFrom(K[] keys, V[] values) {
if (keys.length != values.length) {
throw new RuntimeException("mapFrom keys and values arrays have different sizes");
}
Map<K, V> map = new HashMap<>(keys.length);
for (int i = 0; i < keys.length; i++) {
map.put(keys[i], values[i]);
}
return map;
}
/**
* Returns a map based on the source but with the key & values swapped.
*
* @param source
* Map
* @return Map
*/
public static <K, V> Map<V, K> invertedMapFrom(Map<K, V> source) {
Map<V, K> map = new HashMap<>(source.size());
for (Map.Entry<K, V> entry : source.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
return map;
}
/**
* Returns true if the objects are array instances and each of their
* elements compares via equals as well.
*
* @param value
* Object
* @param otherValue
* Object
* @return boolean
*/
public static boolean arraysAreEqual(Object value, Object otherValue) {
if (value instanceof Object[]) {
if (otherValue instanceof Object[]) {
return valuesAreTransitivelyEqual((Object[]) value, (Object[]) otherValue);
}
return false;
}
return false;
}
/**
* Returns whether the arrays are equal by examining each of their elements,
* even if they are arrays themselves.
*
* @param thisArray
* Object[]
* @param thatArray
* Object[]
* @return boolean
*/
public static boolean valuesAreTransitivelyEqual(Object[] thisArray, Object[] thatArray) {
if (thisArray == thatArray) {
return true;
}
if (thisArray == null || thatArray == null) {
return false;
}
if (thisArray.length != thatArray.length) {
return false;
}
for (int i = 0; i < thisArray.length; i++) {
if (!areEqual(thisArray[i], thatArray[i])) {
return false; // recurse if req'd
}
}
return true;
}
/**
* A comprehensive isEqual method that handles nulls and arrays safely.
*
* @param value
* Object
* @param otherValue
* Object
* @return boolean
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public static boolean areEqual(Object value, Object otherValue) {
if (value == otherValue) {
return true;
}
if (value == null) {
return false;
}
if (otherValue == null) {
return false;
}
if (value.getClass().getComponentType() != null) {
return arraysAreEqual(value, otherValue);
}
return value.equals(otherValue);
}
/**
* Returns whether the items array is null or has zero length.
*
* @param items
* @return boolean
*/
public static boolean isEmpty(Object[] items) {
return items == null || items.length == 0;
}
/**
* Returns whether the items array is non-null and has at least one entry.
*
* @param items
* @return boolean
*/
public static boolean isNotEmpty(Object[] items) {
return !isEmpty(items);
}
/**
* Returns true if both arrays are if both are null or have zero-length,
* otherwise return the false if their respective elements are not equal by
* position.
*
* @param <T>
* @param a
* @param b
* @return boolean
*/
public static <T> boolean areSemanticEquals(T[] a, T[] b) {
if (a == null) {
return isEmpty(b);
}
if (b == null) {
return isEmpty(a);
}
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (!areEqual(a[i], b[i])) {
return false;
}
}
return true;
}
/**
* If the newValue is already held within the values array then the values
* array is returned, otherwise a new array is created appending the
* newValue to the end.
*
* @param <T>
* @param values
* @param newValue
* @return an array containing the union of values and newValue
*/
public static <T> T[] addWithoutDuplicates(T[] values, T newValue) {
for (T value : values) {
if (value.equals(newValue)) {
return values;
}
}
T[] largerOne = (T[]) Array.newInstance(values.getClass().getComponentType(), values.length + 1);
System.arraycopy(values, 0, largerOne, 0, values.length);
largerOne[values.length] = newValue;
return largerOne;
}
/**
* Returns an array of values as a union set of the two input arrays.
*
* @param <T>
* @param values
* @param newValues
* @return the union of the two arrays
*/
public static <T> T[] addWithoutDuplicates(T[] values, T[] newValues) {
Set<T> originals = new HashSet<>(values.length);
for (T value : values) {
originals.add(value);
}
List<T> newOnes = new ArrayList<>(newValues.length);
for (T value : newValues) {
if (originals.contains(value)) {
continue;
}
newOnes.add(value);
}
T[] largerOne = (T[]) Array.newInstance(values.getClass().getComponentType(), values.length + newOnes.size());
System.arraycopy(values, 0, largerOne, 0, values.length);
for (int i = values.length; i < largerOne.length; i++) {
largerOne[i] = newOnes.get(i - values.length);
}
return largerOne;
}
}