CollectionUtil.java
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 }