View Javadoc
1   package org.codehaus.plexus.util;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.NoSuchElementException;
27  import java.util.Set;
28  
29  /**
30   * @author <a href="mailto:olamy@codehaus.org">olamy</a>
31   *
32   */
33  public class CollectionUtils {
34      // ----------------------------------------------------------------------
35      // Static methods that can probably be moved to a real util class.
36      // ----------------------------------------------------------------------
37  
38      /**
39       * Take a dominant and recessive Map and merge the key:value pairs where the recessive Map may add key:value pairs
40       * to the dominant Map but may not override any existing key:value pairs. If we have two Maps, a dominant and
41       * recessive, and their respective keys are as follows: dominantMapKeys = { a, b, c, d, e, f } recessiveMapKeys = {
42       * a, b, c, x, y, z } Then the result should be the following: resultantKeys = { a, b, c, d, e, f, x, y, z }
43       *
44       * @param dominantMap Dominant Map.
45       * @param recessiveMap Recessive Map.
46       * @param <K> type
47       * @param <V> type
48       * @return The result map with combined dominant and recessive values.
49       */
50      public static <K, V> Map<K, V> mergeMaps(Map<K, V> dominantMap, Map<K, V> recessiveMap) {
51  
52          if (dominantMap == null && recessiveMap == null) {
53              return null;
54          }
55  
56          if (dominantMap != null && recessiveMap == null) {
57              return dominantMap;
58          }
59  
60          if (dominantMap == null) {
61              return recessiveMap;
62          }
63  
64          Map<K, V> result = new HashMap<>();
65  
66          // Grab the keys from the dominant and recessive maps.
67          Set<K> dominantMapKeys = dominantMap.keySet();
68          Set<K> recessiveMapKeys = recessiveMap.keySet();
69  
70          // Create the set of keys that will be contributed by the
71          // recessive Map by subtracting the intersection of keys
72          // from the recessive Map's keys.
73          Collection<K> contributingRecessiveKeys = CollectionUtils.subtract(
74                  recessiveMapKeys, CollectionUtils.intersection(dominantMapKeys, recessiveMapKeys));
75  
76          result.putAll(dominantMap);
77  
78          // Now take the keys we just found and extract the values from
79          // the recessiveMap and put the key:value pairs into the dominantMap.
80          for (K key : contributingRecessiveKeys) {
81              result.put(key, recessiveMap.get(key));
82          }
83  
84          return result;
85      }
86  
87      /**
88       * Take a series of <code>Map</code>s and merge them where the ordering of the array from 0..n is the dominant
89       * order.
90       *
91       * @param maps An array of Maps to merge.
92       * @param <K> type
93       * @param <V> type
94       * @return Map The result Map produced after the merging process.
95       */
96      public static <K, V> Map<K, V> mergeMaps(Map<K, V>[] maps) {
97          Map<K, V> result;
98  
99          if (maps.length == 0) {
100             result = null;
101         } else if (maps.length == 1) {
102             result = maps[0];
103         } else {
104             result = mergeMaps(maps[0], maps[1]);
105 
106             for (int i = 2; i < maps.length; i++) {
107                 result = mergeMaps(result, maps[i]);
108             }
109         }
110 
111         return result;
112     }
113 
114     /**
115      * <p>
116      * Returns a {@link Collection} containing the intersection of the given {@link Collection}s.
117      * </p>
118      * The cardinality of each element in the returned {@link Collection} will be equal to the minimum of the
119      * cardinality of that element in the two given {@link Collection}s.
120      *
121      * @param a The first collection
122      * @param b The second collection
123      * @param <E> the type
124      * @see Collection#retainAll
125      * @return The intersection of a and b, never null
126      */
127     public static <E> Collection<E> intersection(final Collection<E> a, final Collection<E> b) {
128         ArrayList<E> list = new ArrayList<>();
129         Map<E, Integer> mapa = getCardinalityMap(a);
130         Map<E, Integer> mapb = getCardinalityMap(b);
131         Set<E> elts = new HashSet<>(a);
132         elts.addAll(b);
133         for (E obj : elts) {
134             for (int i = 0, m = Math.min(getFreq(obj, mapa), getFreq(obj, mapb)); i < m; i++) {
135                 list.add(obj);
136             }
137         }
138         return list;
139     }
140 
141     /**
142      * Returns a {@link Collection} containing <code>a - b</code>. The cardinality of each element <i>e</i> in
143      * the returned {@link Collection} will be the cardinality of <i>e</i> in <i>a</i> minus the cardinality of <i>e</i>
144      * in <i>b</i>, or zero, whichever is greater.
145      *
146      * @param a The start collection
147      * @param b The collection that will be subtracted
148      * @param <T> the type
149      * @see Collection#removeAll
150      * @return The result of the subtraction
151      */
152     public static <T> Collection<T> subtract(final Collection<T> a, final Collection<T> b) {
153         ArrayList<T> list = new ArrayList<>(a);
154         for (T aB : b) {
155             list.remove(aB);
156         }
157         return list;
158     }
159 
160     /**
161      * Returns a {@link Map} mapping each unique element in the given {@link Collection} to an {@link Integer}
162      * representing the number of occurrences of that element in the {@link Collection}. An entry that maps to
163      * <code>null</code> indicates that the element does not appear in the given {@link Collection}.
164      *
165      * @param col The collection to count cardinalities for
166      * @param <E> the type
167      * @return A map of counts, indexed on each element in the collection
168      */
169     public static <E> Map<E, Integer> getCardinalityMap(final Collection<E> col) {
170         HashMap<E, Integer> count = new HashMap<>();
171         for (E obj : col) {
172             Integer c = count.get(obj);
173             if (null == c) {
174                 count.put(obj, 1);
175             } else {
176                 count.put(obj, c + 1);
177             }
178         }
179         return count;
180     }
181 
182     public static <E> List<E> iteratorToList(Iterator<E> it) {
183         if (it == null) {
184             throw new NullPointerException("it cannot be null.");
185         }
186 
187         List<E> list = new ArrayList<E>();
188 
189         while (it.hasNext()) {
190             list.add(it.next());
191         }
192 
193         return list;
194     }
195 
196     // ----------------------------------------------------------------------
197     //
198     // ----------------------------------------------------------------------
199 
200     private static <E> int getFreq(final E obj, final Map<E, Integer> freqMap) {
201         try {
202             Integer o = freqMap.get(obj);
203             if (o != null) // minimize NullPointerExceptions
204             {
205                 return o;
206             }
207         } catch (NullPointerException | NoSuchElementException ignore) {
208         }
209         return 0;
210     }
211 }