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