001 package net.sourceforge.pmd.util;
002
003 import java.lang.reflect.Array;
004 import java.util.ArrayList;
005 import java.util.Arrays;
006 import java.util.HashMap;
007 import java.util.HashSet;
008 import java.util.List;
009 import java.util.Map;
010 import java.util.Set;
011
012 /**
013 * Generic collection and array-related utility functions.
014 *
015 * @author Brian Remedios
016 * @version $Revision$
017 */
018 public final class CollectionUtil {
019
020 @SuppressWarnings("PMD.UnnecessaryFullyQualifiedName")
021 public static final TypeMap COLLECTION_INTERFACES_BY_NAMES = new TypeMap(new Class[] { java.util.List.class,
022 java.util.Collection.class, java.util.Map.class, java.util.Set.class, });
023
024 @SuppressWarnings({"PMD.LooseCoupling", "PMD.UnnecessaryFullyQualifiedName"})
025 public static final TypeMap COLLECTION_CLASSES_BY_NAMES = new TypeMap(new Class[] { java.util.ArrayList.class,
026 java.util.LinkedList.class, java.util.Vector.class, java.util.HashMap.class, java.util.LinkedHashMap.class,
027 java.util.TreeMap.class, java.util.TreeSet.class, java.util.HashSet.class, java.util.LinkedHashSet.class });
028
029 private CollectionUtil() {
030 };
031
032 /**
033 * Returns the collection type if we recognize it by its short name.
034 *
035 * @param shortName String
036 * @return Class
037 */
038 public static Class<?> getCollectionTypeFor(String shortName) {
039 Class<?> cls = COLLECTION_CLASSES_BY_NAMES.typeFor(shortName);
040 if (cls != null) {
041 return cls;
042 }
043
044 return COLLECTION_INTERFACES_BY_NAMES.typeFor(shortName);
045 }
046
047 /**
048 * Return whether we can identify the typeName as a java.util collection class
049 * or interface as specified.
050 *
051 * @param typeName String
052 * @param includeInterfaces boolean
053 * @return boolean
054 */
055 public static boolean isCollectionType(String typeName, boolean includeInterfaces) {
056
057 if (COLLECTION_CLASSES_BY_NAMES.contains(typeName)) {
058 return true;
059 }
060
061 return includeInterfaces && COLLECTION_INTERFACES_BY_NAMES.contains(typeName);
062 }
063
064 /**
065 * Return whether we can identify the typeName as a java.util collection class
066 * or interface as specified.
067 *
068 * @param clazzType Class
069 * @param includeInterfaces boolean
070 * @return boolean
071 */
072 public static boolean isCollectionType(Class<?> clazzType, boolean includeInterfaces) {
073
074 if (COLLECTION_CLASSES_BY_NAMES.contains(clazzType)) {
075 return true;
076 }
077
078 return includeInterfaces && COLLECTION_INTERFACES_BY_NAMES.contains(clazzType);
079 }
080
081 /**
082 * Returns the items as a populated set.
083 *
084 * @param items Object[]
085 * @return Set
086 */
087 public static <T> Set<T> asSet(T[] items) {
088
089 return new HashSet<T>(Arrays.asList(items));
090 }
091
092 /**
093 * Creates and returns a map populated with the keyValuesSets where
094 * the value held by the tuples are they key and value in that order.
095 *
096 * @param keys K[]
097 * @param values V[]
098 * @return Map
099 */
100 public static <K, V> Map<K, V> mapFrom(K[] keys, V[] values) {
101 if (keys.length != values.length) {
102 throw new RuntimeException("mapFrom keys and values arrays have different sizes");
103 }
104 Map<K, V> map = new HashMap<K, V>(keys.length);
105 for (int i = 0; i < keys.length; i++) {
106 map.put(keys[i], values[i]);
107 }
108 return map;
109 }
110
111 /**
112 * Returns a map based on the source but with the key & values swapped.
113 *
114 * @param source Map
115 * @return Map
116 */
117 public static <K, V> Map<V, K> invertedMapFrom(Map<K, V> source) {
118 Map<V, K> map = new HashMap<V, K>(source.size());
119 for (Map.Entry<K, V> entry : source.entrySet()) {
120 map.put(entry.getValue(), entry.getKey());
121 }
122 return map;
123 }
124
125 /**
126 * Returns true if the objects are array instances and each of their elements compares
127 * via equals as well.
128 *
129 * @param value Object
130 * @param otherValue Object
131 * @return boolean
132 */
133 public static boolean arraysAreEqual(Object value, Object otherValue) {
134 if (value instanceof Object[]) {
135 if (otherValue instanceof Object[]) {
136 return valuesAreTransitivelyEqual((Object[]) value, (Object[]) otherValue);
137 }
138 return false;
139 }
140 return false;
141 }
142
143 /**
144 * Returns whether the arrays are equal by examining each of their elements, even if they are
145 * arrays themselves.
146 *
147 * @param thisArray Object[]
148 * @param thatArray Object[]
149 * @return boolean
150 */
151 public static boolean valuesAreTransitivelyEqual(Object[] thisArray, Object[] thatArray) {
152 if (thisArray == thatArray) {
153 return true;
154 }
155 if (thisArray == null || thatArray == null) {
156 return false;
157 }
158 if (thisArray.length != thatArray.length) {
159 return false;
160 }
161 for (int i = 0; i < thisArray.length; i++) {
162 if (!areEqual(thisArray[i], thatArray[i])) {
163 return false; // recurse if req'd
164 }
165 }
166 return true;
167 }
168
169 /**
170 * A comprehensive isEqual method that handles nulls and arrays safely.
171 *
172 * @param value Object
173 * @param otherValue Object
174 * @return boolean
175 */
176 @SuppressWarnings("PMD.CompareObjectsWithEquals")
177 public static boolean areEqual(Object value, Object otherValue) {
178 if (value == otherValue) {
179 return true;
180 }
181 if (value == null) {
182 return false;
183 }
184 if (otherValue == null) {
185 return false;
186 }
187
188 if (value.getClass().getComponentType() != null) {
189 return arraysAreEqual(value, otherValue);
190 }
191 return value.equals(otherValue);
192 }
193
194 /**
195 * Returns whether the items array is null or has zero length.
196 * @param items
197 * @return boolean
198 */
199 public static boolean isEmpty(Object[] items) {
200 return items == null || items.length == 0;
201 }
202
203 /**
204 * Returns true if both arrays are if both are null or have zero-length,
205 * otherwise return the .equals() result on the pair.
206 *
207 * @param <T>
208 * @param a
209 * @param b
210 * @return boolean
211 */
212 public static <T> boolean areSemanticEquals(T[] a, T[] b) {
213
214 if (a == null) { return isEmpty(b); }
215 if (b == null) { return isEmpty(a); }
216 return a.equals(b);
217 }
218
219 /**
220 * If the newValue is already held within the values array then the values array
221 * is returned, otherwise a new array is created appending the newValue to the
222 * end.
223 *
224 * @param <T>
225 * @param values
226 * @param newValue
227 * @return an array containing the union of values and newValue
228 */
229 public static <T> T[] addWithoutDuplicates(T[] values, T newValue) {
230
231 for (T value : values) {
232 if (value.equals(newValue)) {
233 return values;
234 }
235 }
236
237 T[] largerOne = (T[])Array.newInstance(values.getClass().getComponentType(), values.length + 1);
238 System.arraycopy(values, 0, largerOne, 0, values.length);
239 largerOne[values.length] = newValue;
240 return largerOne;
241 }
242
243 /**
244 * Returns an array of values as a union set of the two input arrays.
245 *
246 * @param <T>
247 * @param values
248 * @param newValues
249 * @return the union of the two arrays
250 */
251 public static <T> T[] addWithoutDuplicates(T[] values, T[] newValues) {
252
253 Set<T> originals = new HashSet<T>(values.length);
254 for (T value : values) { originals.add(value); }
255 List<T> newOnes = new ArrayList<T>(newValues.length);
256 for (T value : newValues) {
257 if (originals.contains(value)) { continue; }
258 newOnes.add(value);
259 }
260
261 T[] largerOne = (T[])Array.newInstance(values.getClass().getComponentType(), values.length + newOnes.size());
262 System.arraycopy(values, 0, largerOne, 0, values.length);
263 for (int i=values.length; i<largerOne.length; i++) { largerOne[i] = newOnes.get(i-values.length); }
264 return largerOne;
265 }
266 }
|