View Javadoc
1   /*
2      Copyright 2011 Atlassian
3   
4      Licensed under the Apache License, Version 2.0 (the "License");
5      you may not use this file except in compliance with the License.
6      You may obtain a copy of the License at
7   
8          http://www.apache.org/licenses/LICENSE-2.0
9   
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15   */
16  package io.atlassian.fugue.extras;
17  
18  import com.google.common.collect.ImmutableMap;
19  import com.google.common.collect.Maps;
20  import io.atlassian.fugue.Functions;
21  import io.atlassian.fugue.Iterables;
22  import io.atlassian.fugue.Option;
23  import io.atlassian.fugue.Options;
24  
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.function.BiFunction;
28  import java.util.function.Function;
29  
30  /**
31   * Provides some utility methods to convert Iterables to ImmutableMap, and to
32   * transform Maps.
33   *
34   * @since 2.0
35   */
36  public class ImmutableMaps {
37  
38    // /CLOVER:OFF
39  
40    private ImmutableMaps() {}
41  
42    // /CLOVER:ON
43  
44    /**
45     * Returns a function that takes a key of type K and a value of type V and
46     * returns a Map entry.
47     *
48     * @param <K> the key type
49     * @param <V> the value type
50     * @return a function that takes a K and a V and return the corresponding Map
51     * entry
52     */
53    public static <K, V> BiFunction<K, V, Entry<K, V>> mapEntry() {
54      return Maps::immutableEntry;
55    }
56  
57    /**
58     * Builds an immutable map from the given iterable of
59     * {@link java.util.Map.Entry}.
60     * <p>
61     * Any <code>null</code> entries will be filtered out. Additionally, any
62     * entries containing <code>null</code> key or value will also be filtered
63     * out. If multiple entries return the same key,
64     * {@link java.lang.IllegalArgumentException} will be thrown.
65     *
66     * @param <K> the key type
67     * @param <V> the value type
68     * @param from the iterable we use as the source
69     * @return the transformed map
70     */
71    public static <K, V> ImmutableMap<K, V> toMap(Iterable<Map.Entry<K, V>> from) {
72      ImmutableMap.Builder<K, V> mapBuilder = ImmutableMap.builder();
73      for (Map.Entry<K, V> entry : from) {
74        if (entry != null) {
75          K key = entry.getKey();
76          V value = entry.getValue();
77          if (key != null && value != null) {
78            mapBuilder.put(key, value);
79          }
80        }
81      }
82      return mapBuilder.build();
83    }
84  
85    /**
86     * Builds an immutable map from the given iterable, with key derived from the
87     * application of the iterable to the keyTransformer, and value derived from
88     * the application of the iterable to the valueTransformer.
89     * <p>
90     * <code>null</code> value is allowed and will be passed to the keyTransformer
91     * and valueTransformer. However, if either the keyTransformer or the
92     * valueTransformer returns <code>null</code> for an entry, the entry is
93     * ignored. If keyTransformer returns the same key for multiple entries,
94     * {@link java.lang.IllegalArgumentException} will be thrown.
95     *
96     * @param <T> the input type
97     * @param <K> the key type
98     * @param <V> the value type
99     * @param from the iterable we use as the source
100    * @param keyTransformer transform keys
101    * @param valueTransformer transform values
102    * @return the transformed map
103    */
104   public static <T, K, V> ImmutableMap<K, V> toMap(Iterable<T> from, final Function<? super T, ? extends K> keyTransformer,
105     final Function<? super T, ? extends V> valueTransformer) {
106     return toMap(com.google.common.collect.Iterables.transform(from,
107       entry -> Maps.immutableEntry(keyTransformer.apply(entry), valueTransformer.apply(entry))));
108   }
109 
110   /**
111    * Builds an immutable map that is keyed by the result of applying
112    * keyTransformer to each element of the given iterable of values.
113    * <p>
114    * <code>null</code> value is allowed but will be ignored. If keyTransformer
115    * returns the same key for multiple entries,
116    * {@link java.lang.IllegalArgumentException} will be thrown.
117    *
118    * @param <K> the key type
119    * @param <V> the value type
120    * @param from the iterable we use as the source
121    * @param keyTransformer transform keys
122    * @return the transformed map
123    */
124   public static <K, V> ImmutableMap<K, V> mapBy(Iterable<V> from, final Function<? super V, ? extends K> keyTransformer) {
125     return toMap(from, keyTransformer, Functions.<V> identity());
126   }
127 
128   /**
129    * Builds an immutable map from the given iterable and compute the value by
130    * applying the valueTransformer.
131    * <p>
132    * <code>null</code> value is allowed but will be ignored. If there are
133    * duplicate entries in the iterable,
134    * {@link java.lang.IllegalArgumentException} will be thrown.
135    *
136    * @param <K> the key type
137    * @param <V> the value type
138    * @param from the iterable we use as the source
139    * @param valueTransformer transform values
140    * @return the transformed map
141    */
142   public static <K, V> ImmutableMap<K, V> mapTo(Iterable<K> from, final Function<? super K, ? extends V> valueTransformer) {
143     return toMap(from, Functions.<K> identity(), valueTransformer);
144   }
145 
146   /**
147    * Returns an immutable map that applies function to each entry of
148    * {@code fromMap}. If <code>null</code> is returned by the function for any
149    * entry, or if an entry returned by the function contains a <code>null</code>
150    * key or value, that entry is discarded in the result. If the function
151    * returns entries with the same key for multiple entries,
152    * {@link java.lang.IllegalArgumentException} will be thrown.
153    *
154    * @param <K1> the input key type
155    * @param <K2> the output key type
156    * @param <V1> the input value type
157    * @param <V2> the output value type
158    * @param from the map we use as the source
159    * @param function transform keys and values
160    * @return the transformed map
161    */
162   public static <K1, K2, V1, V2> ImmutableMap<K2, V2> transform(Map<K1, V1> from, Function<Map.Entry<K1, V1>, Map.Entry<K2, V2>> function) {
163     return toMap(com.google.common.collect.Iterables.transform(from.entrySet(), function::apply));
164   }
165 
166   /**
167    * Returns an immutable map that applies the keyTransformer and
168    * valueTransformer functions to each entry of {@code fromMap}. If for any
169    * entry, a <code>null</code> key or value is returned, that entry is
170    * discarded in the result. If the keyTransformer function returns the same
171    * key for multiple entries, {@link java.lang.IllegalArgumentException} will
172    * be thrown.
173    *
174    * @param <K1> the input key type
175    * @param <K2> the output key type
176    * @param <V1> the input value type
177    * @param <V2> the output value type
178    * @param from the map we use as the source
179    * @param keyTransformer transform keys
180    * @param valueTransformer transform values
181    * @return the transformed map
182    */
183   public static <K1, K2, V1, V2> ImmutableMap<K2, V2> transform(Map<K1, V1> from, final Function<? super K1, ? extends K2> keyTransformer,
184     final Function<? super V1, ? extends V2> valueTransformer) {
185     return toMap(com.google.common.collect.Iterables.transform(from.entrySet(),
186       entry -> Maps.immutableEntry(keyTransformer.apply(entry.getKey()), valueTransformer.apply(entry.getValue()))));
187   }
188 
189   /**
190    * Returns an immutable map that applies keyTransformer to the key of each
191    * entry of the source map. If <code>null</code> is returned by the
192    * keyTransformer for any entry, that entry is discarded in the result. If an
193    * entry contains a <code>null</code> value, it will also be discarded in the
194    * result. If the {@code function} returns the same result key for multiple
195    * keys, {@link java.lang.IllegalArgumentException} will be thrown.
196    *
197    * @param <K1> the input key type
198    * @param <K2> the output key type
199    * @param <V> the value type
200    * @param from the map we use as the source
201    * @param keyTransformer transform keys
202    * @return the transformed map
203    */
204   public static <K1, K2, V> ImmutableMap<K2, V> transformKey(Map<K1, V> from, final Function<? super K1, ? extends K2> keyTransformer) {
205     return transform(from, keyTransformer, Functions.<V> identity());
206   }
207 
208   /**
209    * Returns an immutable map that applies valueTransformer to the value of each
210    * entry of the source map. If <code>null</code> is returned by the
211    * valueTransformer for any entry, that entry is discarded in the result. If
212    * an entry contains a <code>null</code> key, it will also be discarded in the
213    * result.
214    *
215    * @param <K> the key type
216    * @param <V1> the input value type
217    * @param <V2> the output value type
218    * @param from the iterable we use as the source
219    * @param valueTransformer transform values
220    * @return the transformed map
221    */
222   public static <K, V1, V2> ImmutableMap<K, V2> transformValue(Map<K, V1> from, final Function<? super V1, ? extends V2> valueTransformer) {
223     return transform(from, Functions.<K> identity(), valueTransformer);
224   }
225 
226   /**
227    * Filters and maps (aka transforms) the source map.
228    *
229    * Applies the given partial function to each entry of the unfiltered map. If
230    * the application returns none, the entry will be left out; otherwise, the
231    * transformed entry contained in the Option will be added to the result map.
232    *
233    * @param <K1> the input key type
234    * @param <K2> the output key type
235    * @param <V1> the input value type
236    * @param <V2> the output value type
237    * @param from the iterable we use as the source
238    * @param partial transform and select entries
239    * @return the transformed map
240    */
241   public static <K1, K2, V1, V2> ImmutableMap<K2, V2> collect(Map<K1, V1> from, Function<Map.Entry<K1, V1>, Option<Entry<K2, V2>>> partial) {
242     return toMap(Iterables.collect(from.entrySet(), partial));
243   }
244 
245   /**
246    * Filters and maps (aka transforms) the source map.
247    *
248    * Applies the given partial key function and partial value function to the
249    * key and value of each entry of the unfiltered map. If either of the
250    * application returns none, the entry will be left out; otherwise, an entry
251    * of transformed key and transformed value contained in the options will be
252    * added to the result map.
253    *
254    * @param <K1> the input key type
255    * @param <K2> the output key type
256    * @param <V1> the input value type
257    * @param <V2> the output value type
258    * @param from the iterable we use as the source
259    * @param keyPartial transform and collect keys
260    * @param valuePartial transform and collect values
261    * @return the transformed map
262    */
263   public static <K1, K2, V1, V2> ImmutableMap<K2, V2> collect(Map<K1, V1> from, final Function<? super K1, Option<K2>> keyPartial,
264     final Function<? super V1, Option<V2>> valuePartial) {
265     return collect(from, input -> {
266       Option<K2> ok = keyPartial.apply(input.getKey());
267       Option<V2> ov = valuePartial.apply(input.getValue());
268       return Options.lift2(ImmutableMaps.<K2, V2> mapEntry()).apply(ok, ov);
269     });
270   }
271 
272   /**
273    * Filters and maps (aka transforms) the source map.
274    *
275    * Applies the given partial key function to the key of each entry of the
276    * unfiltered map. If the application returns none, the entry will be left
277    * out; otherwise, an entry of transformed key contained in the option and the
278    * original value will be added to the result map.
279    *
280    * @param <K1> the input key type
281    * @param <K2> the output key type
282    * @param <V> the value type
283    * @param from the iterable we use as the source
284    * @param keyPartial transform and collect keys
285    * @return the transformed map
286    */
287   public static <K1, K2, V> ImmutableMap<K2, V> collectByKey(Map<K1, V> from, final Function<? super K1, Option<K2>> keyPartial) {
288     return collect(from, keyPartial, Options.toOption());
289   }
290 
291   /**
292    * Filters and maps (aka transforms) the source map.
293    *
294    * Applies the given partial value function to the value of each entry of the
295    * unfiltered map. If the application returns none, the entry will be left
296    * out; otherwise, an entry of the original key and the transformed key
297    * contained in the option will be added to the result map.
298    *
299    * @param <K> the key type
300    * @param <V1> the input value type
301    * @param <V2> the output value type
302    * @param from the iterable we use as the source
303    * @param valuePartial transform and collect values
304    * @return the transformed map
305    */
306   public static <K, V1, V2> ImmutableMap<K, V2> collectByValue(Map<K, V1> from, final Function<? super V1, Option<V2>> valuePartial) {
307     return collect(from, Options.toOption(), valuePartial);
308   }
309 }