Java tutorial
/* * Copyright 2013 Lyor Goldstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.collections15; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.lang3.ExtendedObjectUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.tuple.Pair; /** * @author lgoldstein */ public class ExtendedMapUtils extends MapUtils { public ExtendedMapUtils() { super(); } public static final boolean isEmpty(Map<?, ?> map) { return (map == null) || map.isEmpty(); } public static final int size(Map<?, ?> map) { return (map == null) ? 0 : map.size(); } /** * @param map The {@link Map} to be hashed - may be <code>null</code> * @return The result of hashing each entry via {@link #entryHashCode(Entry)} */ public static final int hashCode(Map<?, ?> map) { return entriesHashCode((map == null) ? null : map.entrySet()); } /** * @param eSet The {@link Collection} of {@link java.util.Map.Entry}-ies to hash - * may be <code>null</code> * @return The result of hashing each entry via {@link #entryHashCode(Entry)} */ public static final int entriesHashCode(Collection<? extends Map.Entry<?, ?>> eSet) { if (ExtendedCollectionUtils.isEmpty(eSet)) { return 0; } int hash = 1; for (Map.Entry<?, ?> e : eSet) { hash = 31 * hash + entryHashCode(e); } return hash; } /** * @param e The {@link java.util.Map.Entry} to hash - may be <code>null</code> * @return The result of invoking {@link ObjectUtils#hashCode(Object)} on * the key and value respectively */ public static final int entryHashCode(Map.Entry<?, ?> e) { if (e == null) { return 0; } else { return 31 * ObjectUtils.hashCode(e.getKey()) + ObjectUtils.hashCode(e.getValue()); } } /** * @param map The {@link Map} to be hashed - may be <code>null</code> * @return The result of hashing each entry via {@link #entryDeepHash(Entry)} */ public static final int deepHash(Map<?, ?> map) { return entriesDeepHash((map == null) ? null : map.entrySet()); } /** * @param eSet The {@link Collection} of {@link java.util.Map.Entry}-ies to hash - * may be <code>null</code> * @return The result of hashing each entry via {@link #entryDeepHash(Entry)} */ public static final int entriesDeepHash(Collection<? extends Map.Entry<?, ?>> eSet) { if (ExtendedCollectionUtils.isEmpty(eSet)) { return 0; } int hash = 1; for (Map.Entry<?, ?> e : eSet) { hash = 31 * hash + entryDeepHash(e); } return hash; } /** * @param e The {@link java.util.Map.Entry} to hash - may be <code>null</code> * @return The result of invoking {@link ExtendedObjectUtils#deepHash(Object)} on * the key and value respectively */ public static final int entryDeepHash(Map.Entry<?, ?> e) { if (e == null) { return 0; } else { return 31 * ExtendedObjectUtils.deepHash(e.getKey()) + ExtendedObjectUtils.deepHash(e.getValue()); } } @SuppressWarnings("unchecked") public static final <K, V> SortedMap<K, V> emptySortedMap() { return EMPTY_SORTED_MAP; } /** * @param m1 The 1st {@link Map} to compare * @param m2 The 2nd {@link Map} to compare * @return <code>true</code> if both maps contain the same keys and values * @see #compareMaps(Map, Map, Comparator) */ public static final boolean compareMaps(Map<?, ?> m1, Map<?, ?> m2) { return compareMaps(m1, m2, null); } /** * @param m1 The 1st {@link Map} to compare * @param m2 The 2nd {@link Map} to compare * @param valuesComp The {@link Comparator} to use to check for mapped * values equality - if <code>null</code> then {@link Object#equals(Object)} * is used * @return <code>true</code> if both maps contain the same keys and values */ public static final <K, V> boolean compareMaps(Map<? extends K, ? extends V> m1, Map<? extends K, ? extends V> m2, Comparator<? super V> valuesComp) { if (m1 == m2) return true; if ((m1 == null) || (m2 == null)) return false; if (m1.size() != m2.size()) return false; return containsAll(m1, m2, valuesComp) && containsAll(m2, m1, valuesComp); } /** * @param m The "master" {@link Map} * @param subMap The {@link Map} to check if contained in the master one * @return <code>true</code> if the tested map is contained in the master * one both keys and value-wise * @see #containsAll(Map, Map, Comparator) */ public static final boolean containsAll(Map<?, ?> m, Map<?, ?> subMap) { return containsAll(m, subMap, null); } /** * @param m The "master" {@link Map} * @param subMap The {@link Map} to check if contained in the master one * @param valuesComp The {@link Comparator} to use to check for mapped * values equality - if <code>null</code> then {@link Object#equals(Object)} * is used * @return <code>true</code> if the tested map is contained in the master * one both keys and value-wise */ public static final <K, V> boolean containsAll(Map<? extends K, ? extends V> m, Map<? extends K, ? extends V> subMap, Comparator<? super V> valuesComp) { if (m == subMap) return true; if ((subMap == null) || subMap.isEmpty()) return true; if ((m == null) || m.isEmpty()) return false; for (Map.Entry<? extends K, ? extends V> subEntry : subMap.entrySet()) { K subKey = subEntry.getKey(); V subValue = subEntry.getValue(); /* * If the associated value is null we need to distinguish between * it and the fact that the key does not exist in the main map */ if (subValue == null) { if (!m.containsKey(subKey)) { return false; } if (m.get(subKey) != null) { return false; } } else { V value = m.get(subKey); if (valuesComp == null) { if (!subValue.equals(value)) { return false; } } else { if (valuesComp.compare(value, subValue) != 0) { return false; } } } } return true; } /** * @param map The {@link Map} to put the value into * @param key The mapping key * @param value The value * @return <code>true</code> if the value is non-<code>null</code> and has been * put in the map. <code>false</code> if the value is <code>null</code>, in which * case the value is <U>not</U> put in the map. */ public static <K, V> boolean putIfNonNull(Map<K, V> map, K key, V value) { if (value == null) { return false; } map.put(key, value); return true; } /** * @param comp The {@link Comparator} to use to create the {@link SortedMap} * @return A {@link SortedMap} that uses the provided comparator * @throws IllegalArgumentException If no comparator provided */ public static final <K, V> SortedMap<K, V> sortedMap(Comparator<? super K> comp) { if (comp == null) { throw new IllegalArgumentException("No comparator provided"); } return new TreeMap<K, V>(comp); } /** * Creates a {@link SortedMap} from a {@link Collection} of values using a * {@link Transformer} to generate keys for the mapped values, using the * "natural" {@link Comparator} to generate the sorted map * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate keys from * values * @param values The {@link Collection} of {@link Comparable} values to be mapped * - ignored if <code>null</code>/empty (i.e., an empty map is returned) * @return The generated (sorted) map * @throws IllegalArgumentException if no map, transformer or comparator provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> * @see #mapSortedCollectionValues(boolean, Transformer, Comparator, Collection) */ public static final <K extends Comparable<K>, V> SortedMap<K, V> mapSortedCollectionValues( boolean ignoreDuplicates, Transformer<? super V, ? extends K> transformer, Collection<? extends V> values) { return mapSortedCollectionValues(ignoreDuplicates, transformer, ExtendedComparatorUtils.<K>comparableComparator(), values); } /** * Creates a {@link SortedMap} from a {@link Collection} of values using a * {@link Transformer} to generate keys for the mapped values and a given * {@link Comparator} to generate the sorted map * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate keys from * values * @param comp The {@link Comparator} to use to generate the sorted map * @param values The {@link Collection} of values to be mapped - ignored * if <code>null</code>/empty (i.e., an empty map is returned) * @return The generated (sorted) map * @throws IllegalArgumentException if no map, transformer or comparator provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> * @see #updateCollectionValuesMap(Map, boolean, Transformer, Collection) */ public static final <K, V> SortedMap<K, V> mapSortedCollectionValues(boolean ignoreDuplicates, Transformer<? super V, ? extends K> transformer, Comparator<? super K> comp, Collection<? extends V> values) { if (ExtendedCollectionUtils.isEmpty(values)) { // need to check this only here since these are checked by the 'else' invoked code if ((transformer == null) || (comp == null)) { throw new IllegalArgumentException("Missing transformer or comparator"); } return emptySortedMap(); } else { return updateCollectionValuesMap(ExtendedMapUtils.<K, V>sortedMap(comp), ignoreDuplicates, transformer, values); } } /** * Creates a {@link Map} from a {@link Collection} of values using a * {@link Transformer} to generate keys for the mapped values * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate keys from * values * @param values The {@link Collection} of values to be mapped - ignored * if <code>null</code>/empty (i.e., an empty map is returned) * @return The generated map * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> * @see #updateCollectionValuesMap(Map, boolean, Transformer, Collection) */ public static final <K, V> Map<K, V> mapCollectionValues(boolean ignoreDuplicates, Transformer<? super V, ? extends K> transformer, Collection<? extends V> values) { if (ExtendedCollectionUtils.isEmpty(values)) { return Collections.emptyMap(); } else { return updateCollectionValuesMap(new HashMap<K, V>(values.size()), ignoreDuplicates, transformer, values); } } /** * @param map The {@link Map} to be updated * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate keys from * values * @param values The {@link Collection} of values to be mapped - ignored * if <code>null</code>/empty * @return The updated map * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, M extends Map<K, V>> M updateCollectionValuesMap(M map, boolean ignoreDuplicates, Transformer<? super V, ? extends K> transformer, Collection<? extends V> values) { if ((map == null) || (transformer == null)) { throw new IllegalArgumentException("Missing map or transformer"); } if (ExtendedCollectionUtils.isEmpty(values)) { return map; } for (V v : values) { K k = transformer.transform(v); V prev = map.put(k, v); if (prev == null) { continue; } if (!ignoreDuplicates) { throw new IllegalStateException("Multiple mapped value for key=" + k + ": " + v + ", " + prev); } } return map; } /** * @param transformer The {@link Transformer} to use to generate keys from * values * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param values The {@link Collection} of values to be mapped - ignored * if <code>null</code>/empty * @return A {@link SortedMap} where values mapped to the same key are collected * inside a {@link Collection} whose type is determined by the provided * factory instance * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K extends Comparable<K>, V, C extends Collection<V>> SortedMap<K, C> mapSortedCollectionMultiValues( Transformer<? super V, ? extends K> transformer, Factory<? extends C> factory, Collection<? extends V> values) { return mapSortedCollectionMultiValues(transformer, ExtendedComparatorUtils.<K>comparableComparator(), factory, values); } /** * @param transformer The {@link Transformer} to use to generate keys from * values * @param comp The {@link Comparator} used to determine if same key * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param values The {@link Collection} of values to be mapped - ignored * if <code>null</code>/empty * @return A {@link SortedMap} where values mapped to the same key are collected * inside a {@link Collection} whose type is determined by the provided * factory instance * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, C extends Collection<V>> SortedMap<K, C> mapSortedCollectionMultiValues( Transformer<? super V, ? extends K> transformer, Comparator<? super K> comp, Factory<? extends C> factory, Collection<? extends V> values) { if (ExtendedCollectionUtils.isEmpty(values)) { // need to check this only here since these are checked by the 'else' invoked code if ((transformer == null) || (comp == null) || (factory == null)) { throw new IllegalArgumentException("Missing transformer, comparator or factory"); } return emptySortedMap(); } else { return updateCollectionMultiValuesMap(ExtendedMapUtils.<K, C>sortedMap(comp), transformer, factory, values); } } /** * @param transformer The {@link Transformer} to use to generate keys from * values * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param values The {@link Collection} of values to be mapped - ignored * if <code>null</code>/empty * @return A {@link Map} where values mapped to the same key are collected * inside a {@link Collection} whose type is determined by the provided * factory instance * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, C extends Collection<V>> Map<K, C> mapCollectionMultiValues( Transformer<? super V, ? extends K> transformer, Factory<? extends C> factory, Collection<? extends V> values) { if (ExtendedCollectionUtils.isEmpty(values)) { if ((transformer == null) || (factory == null)) { throw new IllegalArgumentException("Missing transformer, or factory"); } return Collections.emptyMap(); } else { return updateCollectionMultiValuesMap(new HashMap<K, C>(values.size()), transformer, factory, values); } } /** * @param map The {@link Map} to be updated that contains keys mapped to * {@link Collection} of values that are mapped to the same key * @param transformer The {@link Transformer} to use to generate keys from * values * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param values The {@link Collection} of values to be mapped - ignored * if <code>null</code>/empty * @return The updated map * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, C extends Collection<V>, M extends Map<K, C>> M updateCollectionMultiValuesMap(M map, Transformer<? super V, ? extends K> transformer, Factory<? extends C> factory, Collection<? extends V> values) { if ((map == null) || (transformer == null) || (factory == null)) { throw new IllegalArgumentException("Missing map, transformer or factory"); } if (ExtendedCollectionUtils.isEmpty(values)) { return map; } for (V v : values) { K k = transformer.transform(v); C prev = map.get(k); if (prev == null) { prev = factory.create(); map.put(k, prev); } prev.add(v); } return map; } /** * @param map The {@link Map} whose entries are to be evaluated * @param predicate The {@link Predicate} to use for evaluation * @return The <U>first</U> {@link java.util.Map.Entry} whose {@link Predicate#evaluate(Object)} * result returned <code>true</code> - <code>null</code> if no match found * @see #findFirstEntry(Map, Predicate, boolean) */ public static final <K, V> Map.Entry<K, V> findFirstAcceptedEntry(Map<K, V> map, Predicate<Map.Entry<K, V>> predicate) { return findFirstEntry(map, predicate, true); } /** * @param map The {@link Map} whose entries are to be evaluated * @param predicate The {@link Predicate} to use for evaluation * @return The <U>first</U> {@link java.util.Map.Entry} whose {@link Predicate#evaluate(Object)} * result returned <code>false</code> - <code>null</code> if no match found * @see #findFirstEntry(Map, Predicate, boolean) */ public static final <K, V> Map.Entry<K, V> findFirstRejectedEntry(Map<K, V> map, Predicate<Map.Entry<K, V>> predicate) { return findFirstEntry(map, predicate, false); } /** * @param map The {@link Map} whose entries are to be evaluated * @param predicate The {@link Predicate} to use for evaluation * @param predicateValue The required evaluation result * @return The <U>first</U> {@link java.util.Map.Entry} whose {@link Predicate#evaluate(Object)} * result matches the required value - <code>null</code> if no match found */ public static final <K, V> Map.Entry<K, V> findFirstEntry(Map<K, V> map, Predicate<Map.Entry<K, V>> predicate, boolean predicateValue) { if (predicate == null) { throw new IllegalArgumentException("No predicate provided"); } if (isEmpty(map)) { return null; } for (Map.Entry<K, V> e : map.entrySet()) { if (predicate.evaluate(e) == predicateValue) { return e; } } return null; // no match found } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * <code>true</code> on the map keys * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the keys * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided * @see #filterKeys(Map, Predicate, boolean) */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterAcceptedValues(M map, Predicate<? super V> predicate) { return filterValues(map, predicate, true); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * <code>false</code> on the map keys * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the keys * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided * @see #filterKeys(Map, Predicate, boolean) */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterRejectedValues(M map, Predicate<? super V> predicate) { return filterValues(map, predicate, false); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * a required value on the mapped value * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the values * @param predicateValue The {@link Predicate#evaluate(Object)} result that * signals that an entry should be removed - <code>true</code>=remove all * entries accepted by the predicate, <code>false</code>=remove the rejected * ones * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterValues(M map, final Predicate<? super V> predicate, boolean predicateValue) { if (predicate == null) { throw new IllegalArgumentException("No predicate provided"); } return filterEntries(map, new Predicate<Map.Entry<K, V>>() { @Override public boolean evaluate(Entry<K, V> e) { return predicate.evaluate(e.getValue()); } }, predicateValue); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * <code>true</code> on the map keys * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the keys * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided * @see #filterKeys(Map, Predicate, boolean) */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterAcceptedKeys(M map, Predicate<? super K> predicate) { return filterKeys(map, predicate, true); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * <code>false</code> on the map keys * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the keys * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided * @see #filterKeys(Map, Predicate, boolean) */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterRejectedKeys(M map, Predicate<? super K> predicate) { return filterKeys(map, predicate, false); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * a required value on the mapped key * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the keys * @param predicateValue The {@link Predicate#evaluate(Object)} result that * signals that an entry should be removed - <code>true</code>=remove all * entries accepted by the predicate, <code>false</code>=remove the rejected * ones * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterKeys(M map, final Predicate<? super K> predicate, boolean predicateValue) { if (predicate == null) { throw new IllegalArgumentException("No predicate provided"); } return filterEntries(map, new Predicate<Map.Entry<K, V>>() { @Override public boolean evaluate(Entry<K, V> e) { return predicate.evaluate(e.getKey()); } }, predicateValue); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * <code>true</code> on the map entries * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the {@link java.util.Map.Entry}-ies * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided * @see #filterEntries(Map, Predicate, boolean) */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterAcceptedEntries(M map, Predicate<Map.Entry<K, V>> predicate) { return filterEntries(map, predicate, true); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * <code>false</code> on the map entries * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the {@link java.util.Map.Entry}-ies * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided * @see #filterEntries(Map, Predicate, boolean) */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterRejectedEntries(M map, Predicate<Map.Entry<K, V>> predicate) { return filterEntries(map, predicate, false); } /** * Removes all entries for which a {@link Predicate#evaluate(Object)} returned * a required value on the mapped entry * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param predicate The {@link Predicate} to use to evaluate the {@link java.util.Map.Entry}-ies * @param predicateValue The {@link Predicate#evaluate(Object)} result that * signals that an entry should be removed - <code>true</code>=remove all * entries accepted by the predicate, <code>false</code>=remove the rejected * ones * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed * @throws IllegalArgumentException if no predicate instance provided * @see #removeAll(Map, Collection) */ public static final <K, V, M extends Map<K, V>> List<Map.Entry<K, V>> filterEntries(M map, Predicate<Map.Entry<K, V>> predicate, boolean predicateValue) { if (predicate == null) { throw new IllegalArgumentException("No predicated provided"); } if (isEmpty(map)) { return Collections.emptyList(); } Collection<K> filteredKeys = null; for (Map.Entry<K, V> e : map.entrySet()) { if (predicate.evaluate(e) != predicateValue) { continue; } if (filteredKeys == null) { filteredKeys = new ArrayList<K>(map.size()); } filteredKeys.add(e.getKey()); } return removeAll(map, filteredKeys); } /** * Removes all entries mapped to the specified keys * @param map The {@link Map} to be updated - ignored if <code>null</code>/empty * @param keys The {@link Collection} of keys to be removed - ignored if * <code>null</code>/empty * @return A {@link List} of all the the {@link java.util.Map.Entry}-ies that have been * removed. <B>Note:</B> if the {@link Map#remove(Object)} call returns * <code>null</code> then it is not reported as a removed entry */ public static final <K, V> List<Map.Entry<K, V>> removeAll(Map<K, ? extends V> map, Collection<? extends K> keys) { if (isEmpty(map) || ExtendedCollectionUtils.isEmpty(keys)) { return Collections.emptyList(); } List<Map.Entry<K, V>> result = new ArrayList<Map.Entry<K, V>>(keys.size()); for (K key : keys) { V value = map.remove(key); if (value == null) { continue; } result.add(Pair.of(key, value)); } return result; } /** * Invokes a {@link Closure} for each key in a map * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param closure The {@link Closure} to be invoked for each {@link java.util.Map.Entry} * @throws IllegalArgumentException if no closure provided */ public static final <K> void forAllKeysDo(Map<? extends K, ?> map, Closure<? super K> closure) { if (closure == null) { throw new IllegalArgumentException("No closure provided"); } if (isEmpty(map)) { return; // debug breakpoint } CollectionUtils.forAllDo(map.keySet(), closure); } /** * Invokes a {@link Closure} for each value in a map * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param closure The {@link Closure} to be invoked for each value * @throws IllegalArgumentException if no closure provided */ public static final <V> void forAllValuesDo(Map<?, ? extends V> map, Closure<? super V> closure) { if (closure == null) { throw new IllegalArgumentException("No closure provided"); } if (isEmpty(map)) { return; // debug breakpoint } CollectionUtils.forAllDo(map.values(), closure); } /** * Invokes a {@link Closure} for each entry in a map * @param map The {@link Map} to be scanned - ignored if <code>null</code>/empty * @param closure The {@link Closure} to be invoked for each key * @throws IllegalArgumentException if no closure provided */ public static final <K, V> void forAllEntriesDo(Map<K, V> map, Closure<? super Map.Entry<K, V>> closure) { if (closure == null) { throw new IllegalArgumentException("No closure provided"); } if (isEmpty(map)) { return; // debug breakpoint } CollectionUtils.forAllDo(map.entrySet(), closure); } private static final Transformer<Map.Entry<?, ?>, Object> ENTRY_VALUE_EXTRACTOR = new Transformer<Map.Entry<?, ?>, Object>() { @Override public Object transform(Entry<?, ?> e) { if (e == null) { return null; } else { return e.getValue(); } } }; /** * @return A {@link Transformer} that "transforms" a * {@link java.util.Map.Entry} into its {@link java.util.Map.Entry#getValue()} invocation * result */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static final <K, V> Transformer<Map.Entry<? extends K, ? extends V>, V> entryValueExtractor() { return (Transformer) ENTRY_VALUE_EXTRACTOR; } private static final Transformer<Map.Entry<?, ?>, Object> ENTRY_KEY_EXTRACTOR = new Transformer<Map.Entry<?, ?>, Object>() { @Override public Object transform(Entry<?, ?> e) { if (e == null) { return null; } else { return e.getKey(); } } }; /** * @return A {@link Transformer} that "transforms" a * {@link java.util.Map.Entry} into its {@link java.util.Map.Entry#getKey()} invocation * result */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static final <K, V> Transformer<Map.Entry<? extends K, ? extends V>, K> entryKeyExtractor() { return (Transformer) ENTRY_KEY_EXTRACTOR; } /** * Reverses the keys and values from one {@link Map} to another - i.e., * the source map values become the keys, and its keys become the values * @param ignoreDuplicates If <code>false</code> and a mapped key/value * already exists in the destination {@link Map} then throws an {@link IllegalStateException} * @param src The source map * @param dst The destination map * @return Same instance as the destination map */ public static final <K, V, M extends Map<? super V, ? super K>> M flip(boolean ignoreDuplicates, final Map<? extends K, ? extends V> src, M dst) { if (dst == null) { throw new IllegalArgumentException("Missing destination map"); } if (isEmpty(src)) { return dst; } for (Map.Entry<? extends K, ? extends V> se : src.entrySet()) { K key = se.getKey(); V value = se.getValue(); Object prev = dst.put(value, key); if ((prev != null) && (!ignoreDuplicates)) { throw new IllegalStateException("Multiple mapped value for " + value + "=>" + key + " mapping: " + " current=" + key + ", previous=" + prev); } } return dst; } /** * Transforms a source map into a destination one * @param ignoreDuplicates If <code>false</code> and a mapped key/value * already exists in the destination {@link Map} then throws an {@link IllegalStateException} * @param src The source {@link Map} - may be <code>null</code>/empty * @param keyTransformer The {@link Transformer} for the keys * @param valueTransformer The {@link Transformer} for the values * @param dst The destination {@link Map} * @return The updated destination map * @throws IllegalArgumentException if no transformers or destination map provided * @throws IllegalStateException if duplicate mapping found in destination * map and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K1, V1, M1 extends Map<K1, V1>, K2, V2, M2 extends Map<K2, V2>> M2 map2mapTransform( final boolean ignoreDuplicates, final M1 src, final Transformer<? super K1, ? extends K2> keyTransformer, final Transformer<? super V1, ? extends V2> valueTransformer, final M2 dst) { if ((keyTransformer == null) || (valueTransformer == null)) { throw new IllegalArgumentException("Missing key/value transformer(s)"); } if (dst == null) { throw new IllegalArgumentException("Missing destination map"); } if (isEmpty(src)) { return dst; } forAllEntriesDo(src, new Closure<Map.Entry<K1, V1>>() { @Override public void execute(Entry<K1, V1> e) { K1 k1 = e.getKey(); V1 v1 = e.getValue(); K2 k2 = keyTransformer.transform(k1); V2 v2 = valueTransformer.transform(v1); V2 prev = dst.put(k2, v2); if ((prev != null) && (!ignoreDuplicates)) { throw new IllegalStateException("Multiple mapped value for " + k1 + "=>" + k2 + " mapping: " + " current=" + v2 + ", previous=" + prev); } } }); return dst; } /** * Creates a {@link Collection} of items out of a {@link Map} * @param ignoreDuplicates If <code>false</code> and the transformed item * already exists in the collection the throw an {@link IllegalStateException} * @param map The source {@link Map} to be transformed - ignored if * <code>null</code>/empty * @param transformer The {@link Transformer} to use in order to transform * the map entries into collected items. <B>Note:</B> if returns <code>null</code> * for a transformed entry then the item is not collected * @param targetCollection The target collection to the transformed items * are added * @return The (updated) input collection */ public static final <K, V, E, C extends Collection<? super E>> C collectMapEntries( final boolean ignoreDuplicates, Map<? extends K, ? extends V> map, final Transformer<Map.Entry<? extends K, ? extends V>, E> transformer, final C targetCollection) { if ((transformer == null) || (targetCollection == null)) { throw new IllegalArgumentException("Missing transformer or target collection"); } if (isEmpty(map)) { return targetCollection; } forAllEntriesDo(map, new Closure<Map.Entry<? extends K, ? extends V>>() { @Override public void execute(Entry<? extends K, ? extends V> entry) { E item = transformer.transform(entry); if (item == null) { return; } boolean modified = targetCollection.add(item); if (modified) { return; } if (!ignoreDuplicates) { // collection not modified ==> i.e., duplicate item throw new IllegalStateException("Duplicate item (" + item + ") found for key=" + entry.getKey() + "/value=" + entry.getValue()); } } }); return targetCollection; } /** * @param map The {@link Map} to be updated * @param ignoreDuplicates If <code>false</code> and an item is mapped to * an existing value then throws {@link IllegalStateException} * @param transformer The {@link Transformer} to use for converting the * collection items into map entries * @param values The {@link Collection} of values to be mapped * @return The (updated) input map */ public static final <K, V, M extends Map<K, V>, E> Map<K, V> updateCollectionEntries(M map, boolean ignoreDuplicates, Transformer<? super E, Map.Entry<? extends K, ? extends V>> transformer, Collection<? extends E> values) { if ((map == null) || (transformer == null)) { throw new IllegalArgumentException("Missing map or transformer"); } if (ExtendedCollectionUtils.isEmpty(values)) { return map; } for (E item : values) { Map.Entry<? extends K, ? extends V> entry = transformer.transform(item); if (entry == null) { continue; } K key = entry.getKey(); V value = entry.getValue(), prev = map.put(key, value); if (prev == null) { continue; } if (!ignoreDuplicates) { throw new IllegalStateException( "Duplicate value for key=" + key + " of item=" + item + ": " + prev + ", " + value); } } return map; } /** * Creates a {@link SortedMap} from a {@link Collection} of keys using a * {@link Transformer} to generate values for the mapped keys, using the * "natural" {@link Comparator} to generate the sorted map * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate values from * values * @param keys The {@link Collection} of {@link Comparable} keys to be mapped * - ignored if <code>null</code>/empty (i.e., an empty map is returned) * @return The generated (sorted) map * @throws IllegalArgumentException if no map, transformer or comparator provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> * @see #mapSortedCollectionKeys(boolean, Transformer, Comparator, Collection) */ public static final <K extends Comparable<K>, V> SortedMap<K, V> mapSortedCollectionKeys( boolean ignoreDuplicates, Transformer<? super K, ? extends V> transformer, Collection<? extends K> keys) { return mapSortedCollectionKeys(ignoreDuplicates, transformer, ExtendedComparatorUtils.<K>comparableComparator(), keys); } /** * Creates a {@link SortedMap} from a {@link Collection} of keys using a * {@link Transformer} to generate values for the mapped keys and a given * {@link Comparator} to generate the sorted map * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate values from * keys * @param comp The {@link Comparator} to use to generate the sorted map * @param keys The {@link Collection} of keys to be mapped - ignored * if <code>null</code>/empty (i.e., an empty map is returned) * @return The generated (sorted) map * @throws IllegalArgumentException if no map, transformer or comparator provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> * @see #updateCollectionKeysMap(Map, boolean, Transformer, Collection) */ public static final <K, V> SortedMap<K, V> mapSortedCollectionKeys(boolean ignoreDuplicates, Transformer<? super K, ? extends V> transformer, Comparator<? super K> comp, Collection<? extends K> keys) { if (ExtendedCollectionUtils.isEmpty(keys)) { // need to check this only here since these are checked by the 'else' invoked code if ((transformer == null) || (comp == null)) { throw new IllegalArgumentException("Missing transformer or comparator"); } return emptySortedMap(); } else { return updateCollectionKeysMap(ExtendedMapUtils.<K, V>sortedMap(comp), ignoreDuplicates, transformer, keys); } } /** * Creates a {@link Map} from a {@link Collection} of keys using a * {@link Transformer} to generate values for the mapped keys * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate values from * keys * @param keys The {@link Collection} of keys to be mapped - ignored * if <code>null</code>/empty (i.e., an empty map is returned) * @return The generated map * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> * @see #updateCollectionKeysMap(Map, boolean, Transformer, Collection) */ public static final <K, V> Map<K, V> mapCollectionKeys(boolean ignoreDuplicates, Transformer<? super K, ? extends V> transformer, Collection<? extends K> keys) { if (ExtendedCollectionUtils.isEmpty(keys)) { return Collections.emptyMap(); } else { return updateCollectionKeysMap(new HashMap<K, V>(keys.size()), ignoreDuplicates, transformer, keys); } } /** * @param map The {@link Map} to be updated * @param ignoreDuplicates If <code>false</code> and a value is mapped to * an existing entry then generates an exception * @param transformer The {@link Transformer} to use to generate values from * keys * @param keys The {@link Collection} of keys to be mapped - ignored * if <code>null</code>/empty * @return The updated map * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, M extends Map<K, V>> M updateCollectionKeysMap(M map, boolean ignoreDuplicates, Transformer<? super K, ? extends V> transformer, Collection<? extends K> keys) { if ((map == null) || (transformer == null)) { throw new IllegalArgumentException("Missing map or transformer"); } if (ExtendedCollectionUtils.isEmpty(keys)) { return map; } for (K k : keys) { V v = transformer.transform(k); V prev = map.put(k, v); if (prev == null) { continue; } if (!ignoreDuplicates) { throw new IllegalStateException("Multiple mapped value for key=" + k + ": " + v + ", " + prev); } } return map; } /** * @param transformer The {@link Transformer} to use to generate values from * keys * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param keys The {@link Collection} of values to be keys - ignored * if <code>null</code>/empty * @return A {@link SortedMap} where values mapped to the same key are collected * inside a {@link Collection} whose type is determined by the provided * factory instance * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K extends Comparable<K>, V, C extends Collection<V>> SortedMap<K, C> mapSortedCollectionMultiKeys( Transformer<? super K, ? extends V> transformer, Factory<? extends C> factory, Collection<? extends K> keys) { return mapSortedCollectionMultiKeys(transformer, ExtendedComparatorUtils.<K>comparableComparator(), factory, keys); } /** * @param transformer The {@link Transformer} to use to generate values from * keys * @param comp The {@link Comparator} used to determine if same key * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param keys The {@link Collection} of keys to be mapped - ignored * if <code>null</code>/empty * @return A {@link SortedMap} where values mapped to the same key are collected * inside a {@link Collection} whose type is determined by the provided * factory instance * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, C extends Collection<V>> SortedMap<K, C> mapSortedCollectionMultiKeys( Transformer<? super K, ? extends V> transformer, Comparator<? super K> comp, Factory<? extends C> factory, Collection<? extends K> keys) { if (ExtendedCollectionUtils.isEmpty(keys)) { // need to check this only here since these are checked by the 'else' invoked code if ((transformer == null) || (comp == null) || (factory == null)) { throw new IllegalArgumentException("Missing transformer, comparator or factory"); } return emptySortedMap(); } else { return updateCollectionMultiKeysMap(ExtendedMapUtils.<K, C>sortedMap(comp), transformer, factory, keys); } } /** * @param transformer The {@link Transformer} to use to generate values from * keys * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param keys The {@link Collection} of keys to be mapped - ignored * if <code>null</code>/empty * @return A {@link Map} where values mapped to the same key are collected * inside a {@link Collection} whose type is determined by the provided * factory instance * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, C extends Collection<V>> Map<K, C> mapCollectionMultiKeys( Transformer<? super K, ? extends V> transformer, Factory<? extends C> factory, Collection<? extends K> keys) { if (ExtendedCollectionUtils.isEmpty(keys)) { if ((transformer == null) || (factory == null)) { throw new IllegalArgumentException("Missing transformer, or factory"); } return Collections.emptyMap(); } else { return updateCollectionMultiKeysMap(new HashMap<K, C>(keys.size()), transformer, factory, keys); } } /** * @param map The {@link Map} to be updated that contains keys mapped to * {@link Collection} of values that are mapped to the same key * @param transformer The {@link Transformer} to use to generate values from * keys * @param factory The {@link Factory} instance that create an new (empty) * {@link Collection} for a mapped value that is encountered for the first * time * @param keys The {@link Collection} of keys to be mapped - ignored * if <code>null</code>/empty * @return The updated map * @throws IllegalArgumentException if no map or transformer provided * @throws IllegalStateException if a value is mapped to an existing entry * and <code>ignoreDuplicates</code> is <code>false</code> */ public static final <K, V, C extends Collection<V>, M extends Map<K, C>> M updateCollectionMultiKeysMap(M map, Transformer<? super K, ? extends V> transformer, Factory<? extends C> factory, Collection<? extends K> keys) { if ((map == null) || (transformer == null) || (factory == null)) { throw new IllegalArgumentException("Missing map, transformer or factory"); } if (ExtendedCollectionUtils.isEmpty(keys)) { return map; } for (K k : keys) { V v = transformer.transform(k); C prev = map.get(k); if (prev == null) { prev = factory.create(); map.put(k, prev); } prev.add(v); } return map; } }