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