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 }