View Javadoc

    1   package net.sourceforge.pmd.util;
    2
    3   import java.lang.reflect.Array;
    4   import java.util.ArrayList;
    5   import java.util.Arrays;
    6   import java.util.Collection;
    7   import java.util.HashMap;
    8   import java.util.HashSet;
    9   import java.util.List;
    10  import java.util.Map;
    11  import java.util.Set;
    12
    13  /**
    14   * Generic collection and array-related utility functions for java.util types. See ClassUtil
    15   * for comparable facilities for short name lookup.
    16   *
    17   * @author Brian Remedios
    18   * @version $Revision$
    19   */
    20  public final class CollectionUtil {
    21
    22      @SuppressWarnings("PMD.UnnecessaryFullyQualifiedName")
    23      public static final TypeMap COLLECTION_INTERFACES_BY_NAMES = new TypeMap(new Class[] { java.util.List.class,
    24  	    java.util.Collection.class, java.util.Map.class, java.util.Set.class, });
    25
    26      @SuppressWarnings({"PMD.LooseCoupling", "PMD.UnnecessaryFullyQualifiedName"})
    27      public static final TypeMap COLLECTION_CLASSES_BY_NAMES = new TypeMap(new Class[] { java.util.ArrayList.class,
    28  	    java.util.LinkedList.class, java.util.Vector.class, java.util.HashMap.class, java.util.LinkedHashMap.class,
    29  	    java.util.TreeMap.class, java.util.TreeSet.class, java.util.HashSet.class, java.util.LinkedHashSet.class,
    30  	    java.util.Hashtable.class});
    31
    32      private CollectionUtil() {
    33      };
    34
    35  	/**
    36  	 * Add elements from the source to the target as long as they don't already exist there.
    37  	 * Return the number of items actually added.
    38  	 *
    39  	 * @param source
    40  	 * @param target
    41  	 * @return int
    42  	 */
    43  	public static int addWithoutDuplicates(Collection<String> source, Collection<String> target) {
    44
    45  		int added = 0;
    46
    47  		for (String item : source) {
    48  			if (target.contains(item)) continue;
    49  			target.add(item);
    50  			added++;
    51  		}
    52
    53  		return added;
    54  	}
    55
    56      /**
    57       * Returns the collection type if we recognize it by its short name.
    58       *
    59       * @param shortName String
    60       * @return Class
    61       */
    62      public static Class<?> getCollectionTypeFor(String shortName) {
    63  	   Class<?> cls = COLLECTION_CLASSES_BY_NAMES.typeFor(shortName);
    64  	   if (cls != null) {
    65  	     return cls;
    66  	   }
    67
    68  	   return COLLECTION_INTERFACES_BY_NAMES.typeFor(shortName);
    69      }
    70
    71      /**
    72       * Return whether we can identify the typeName as a java.util collection class
    73       * or interface as specified.
    74       *
    75       * @param typeName String
    76       * @param includeInterfaces boolean
    77       * @return boolean
    78       */
    79      public static boolean isCollectionType(String typeName, boolean includeInterfaces) {
    80
    81  	   if (COLLECTION_CLASSES_BY_NAMES.contains(typeName)) {
    82  	     return true;
    83  	   }
    84
    85  	   return includeInterfaces && COLLECTION_INTERFACES_BY_NAMES.contains(typeName);
    86      }
    87
    88      /**
    89       * Return whether we can identify the typeName as a java.util collection class
    90       * or interface as specified.
    91       *
    92       * @param clazzType Class
    93       * @param includeInterfaces boolean
    94       * @return boolean
    95       */
    96      public static boolean isCollectionType(Class<?> clazzType, boolean includeInterfaces) {
    97
    98  	   if (COLLECTION_CLASSES_BY_NAMES.contains(clazzType)) {
    99  	      return true;
    100 	   }
    101
    102 	   return includeInterfaces && COLLECTION_INTERFACES_BY_NAMES.contains(clazzType);
    103     }
    104
    105     /**
    106      * Returns the items as a populated set.
    107      *
    108      * @param items Object[]
    109      * @return Set
    110      */
    111     public static <T> Set<T> asSet(T[] items) {
    112
    113 	   return new HashSet<T>(Arrays.asList(items));
    114     }
    115
    116     /**
    117      * Creates and returns a map populated with the keyValuesSets where
    118      * the value held by the tuples are they key and value in that order.
    119      *
    120      * @param keys K[]
    121      * @param values V[]
    122      * @return Map
    123      */
    124     public static <K, V> Map<K, V> mapFrom(K[] keys, V[] values) {
    125 	   if (keys.length != values.length) {
    126 	     throw new RuntimeException("mapFrom keys and values arrays have different sizes");
    127 	   }
    128 	   Map<K, V> map = new HashMap<K, V>(keys.length);
    129 	   for (int i = 0; i < keys.length; i++) {
    130 	      map.put(keys[i], values[i]);
    131 	      }
    132 	   return map;
    133     }
    134
    135     /**
    136      * Returns a map based on the source but with the key & values swapped.
    137      *
    138      * @param source Map
    139      * @return Map
    140      */
    141     public static <K, V> Map<V, K> invertedMapFrom(Map<K, V> source) {
    142 	   Map<V, K> map = new HashMap<V, K>(source.size());
    143 	   for (Map.Entry<K, V> entry : source.entrySet()) {
    144 	      map.put(entry.getValue(), entry.getKey());
    145 	   }
    146 	   return map;
    147     }
    148
    149     /**
    150      * Returns true if the objects are array instances and each of their elements compares
    151      * via equals as well.
    152      *
    153      * @param value Object
    154      * @param otherValue Object
    155      * @return boolean
    156      */
    157     public static boolean arraysAreEqual(Object value, Object otherValue) {
    158 	   if (value instanceof Object[]) {
    159 	      if (otherValue instanceof Object[]) {
    160 		  return valuesAreTransitivelyEqual((Object[]) value, (Object[]) otherValue);
    161 	      }
    162 	      return false;
    163 	   }
    164 	   return false;
    165     }
    166
    167     /**
    168      * Returns whether the arrays are equal by examining each of their elements, even if they are
    169      * arrays themselves.
    170      *
    171      * @param thisArray Object[]
    172      * @param thatArray Object[]
    173      * @return boolean
    174      */
    175     public static boolean valuesAreTransitivelyEqual(Object[] thisArray, Object[] thatArray) {
    176 	   if (thisArray == thatArray) {
    177 	     return true;
    178 	   }
    179 	   if (thisArray == null || thatArray == null) {
    180 	     return false;
    181 	   }
    182 	   if (thisArray.length != thatArray.length) {
    183 	     return false;
    184 	   }
    185 	   for (int i = 0; i < thisArray.length; i++) {
    186 	     if (!areEqual(thisArray[i], thatArray[i])) {
    187 	   	 return false; // recurse if req'd
    188 	     }
    189 	   }
    190 	   return true;
    191     }
    192
    193     /**
    194      * A comprehensive isEqual method that handles nulls and arrays safely.
    195      *
    196      * @param value Object
    197      * @param otherValue Object
    198      * @return boolean
    199      */
    200     @SuppressWarnings("PMD.CompareObjectsWithEquals")
    201     public static boolean areEqual(Object value, Object otherValue) {
    202     	if (value == otherValue) {
    203     	    return true;
    204     	}
    205     	if (value == null) {
    206     	    return false;
    207     	}
    208     	if (otherValue == null) {
    209     	    return false;
    210     	}
    211
    212     	if (value.getClass().getComponentType() != null) {
    213     	    return arraysAreEqual(value, otherValue);
    214     	    }
    215 	    return value.equals(otherValue);
    216     }
    217
    218     /**
    219      * Returns whether the items array is null or has zero length.
    220      * @param items
    221      * @return boolean
    222      */
    223     public static boolean isEmpty(Object[] items) {
    224         return items == null || items.length == 0;
    225     }
    226
    227     /**
    228      * Returns whether the items array is non-null and has
    229      * at least one entry.
    230      *
    231      * @param items
    232      * @return boolean
    233      */
    234     public static boolean isNotEmpty(Object[] items) {
    235         return !isEmpty(items);
    236     }
    237
    238     /**
    239      * Returns true if both arrays are if both are null or have zero-length,
    240      * otherwise return the false if their respective elements are not
    241      * equal by position.
    242      *
    243      * @param <T>
    244      * @param a
    245      * @param b
    246      * @return boolean
    247      */
    248     public static <T> boolean areSemanticEquals(T[] a, T[] b) {
    249
    250         if (a == null) { return isEmpty(b); }
    251         if (b == null) { return isEmpty(a); }
    252
    253         if (a.length != b.length) return false;
    254
    255         for (int i=0; i<a.length; i++) {
    256         	if (!areEqual(a[i], b[i])) return false;
    257         }
    258
    259         return true;
    260     }
    261
    262     /**
    263      * If the newValue is already held within the values array then the values array
    264      * is returned, otherwise a new array is created appending the newValue to the
    265      * end.
    266      *
    267      * @param <T>
    268      * @param values
    269      * @param newValue
    270      * @return an array containing the union of values and newValue
    271      */
    272     public static <T> T[] addWithoutDuplicates(T[] values, T newValue) {
    273
    274         for (T value : values) {
    275             if (value.equals(newValue)) {
    276                 return values;
    277             }
    278         }
    279
    280         T[] largerOne = (T[])Array.newInstance(values.getClass().getComponentType(), values.length + 1);
    281         System.arraycopy(values, 0, largerOne, 0, values.length);
    282         largerOne[values.length] = newValue;
    283         return largerOne;
    284     }
    285
    286     /**
    287      * Returns an array of values as a union set of the two input arrays.
    288      *
    289      * @param <T>
    290      * @param values
    291      * @param newValues
    292      * @return the union of the two arrays
    293      */
    294     public static <T> T[] addWithoutDuplicates(T[] values, T[] newValues) {
    295
    296         Set<T> originals = new HashSet<T>(values.length);
    297         for (T value : values) { originals.add(value); }
    298         List<T> newOnes = new ArrayList<T>(newValues.length);
    299         for (T value : newValues) {
    300             if (originals.contains(value)) { continue; }
    301             newOnes.add(value);
    302         }
    303
    304         T[] largerOne = (T[])Array.newInstance(values.getClass().getComponentType(), values.length + newOnes.size());
    305         System.arraycopy(values, 0, largerOne, 0, values.length);
    306         for (int i=values.length; i<largerOne.length; i++) { largerOne[i] = newOnes.get(i-values.length); }
    307         return largerOne;
    308     }
    309 }