CollectionUtil xref
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 }