/* * Copyright (C) 2013 The Guava Authors * * 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 com.google.common.collect; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.CollectPreconditions.checkNonnegative; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.base.Supplier; /** * A builder for a multimap implementation that allows customization of the * backing map and value collection implementations used in a particular * multimap. * *

* This can be used to easily configure multimap data structure implementations * not provided explicitly in {@code com.google.common.collect}, for example: * *

 * {
 * 	@code
 * 	ListMultimap treeListMultimap = MultimapBuilder.treeKeys().arrayListValues().build();
 * 	SetMultimap hashEnumMultimap = MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
 * }
 * 
* *

* {@code MultimapBuilder} instances are immutable. Invoking a configuration * method has no effect on the receiving instance; you must store and use the * new builder instance it returns instead. * *

* The generated multimaps are serializable if the key and value types are * serializable, unless stated otherwise in one of the configuration methods. * * @author Louis Wasserman * @param An upper bound on the key type of the generated multimap. * @param An upper bound on the value type of the generated multimap. * @since 16.0 */ @Beta @GwtCompatible public abstract class MultimapBuilder { /* * Leaving K and V as upper bounds rather than the actual key and value types * allows type parameters to be left implicit more often. CacheBuilder uses the * same technique. */ private MultimapBuilder() { } private static final int DEFAULT_EXPECTED_KEYS = 8; /** * Uses a {@link HashMap} to map keys to value collections. */ public static MultimapBuilderWithKeys hashKeys() { return hashKeys(DEFAULT_EXPECTED_KEYS); } /** * Uses a {@link HashMap} to map keys to value collections, initialized to * expect the specified number of keys. * * @throws IllegalArgumentException if {@code expectedKeys < 0} */ public static MultimapBuilderWithKeys hashKeys(final int expectedKeys) { checkNonnegative(expectedKeys, "expectedKeys"); return new MultimapBuilderWithKeys() { @Override Map> createMap() { return new HashMap>(expectedKeys); } }; } /** * Uses a {@link LinkedHashMap} to map keys to value collections. * *

* The collections returned by {@link Multimap#keySet()}, * {@link Multimap#keys()}, and {@link Multimap#asMap()} will iterate through * the keys in the order that they were first added to the multimap, save that * if all values associated with a key are removed and then the key is added * back into the multimap, that key will come last in the key iteration order. */ public static MultimapBuilderWithKeys linkedHashKeys() { return linkedHashKeys(DEFAULT_EXPECTED_KEYS); } /** * Uses a {@link LinkedHashMap} to map keys to value collections, initialized to * expect the specified number of keys. * *

* The collections returned by {@link Multimap#keySet()}, * {@link Multimap#keys()}, and {@link Multimap#asMap()} will iterate through * the keys in the order that they were first added to the multimap, save that * if all values associated with a key are removed and then the key is added * back into the multimap, that key will come last in the key iteration order. */ public static MultimapBuilderWithKeys linkedHashKeys(final int expectedKeys) { checkNonnegative(expectedKeys, "expectedKeys"); return new MultimapBuilderWithKeys() { @Override Map> createMap() { return new LinkedHashMap>(expectedKeys); } }; } /** * Uses a naturally-ordered {@link TreeMap} to map keys to value collections. * *

* The collections returned by {@link Multimap#keySet()}, * {@link Multimap#keys()}, and {@link Multimap#asMap()} will iterate through * the keys in sorted order. * *

* For all multimaps generated by the resulting builder, the * {@link Multimap#keySet()} can be safely cast to a * {@link java.util.SortedSet}, and the {@link Multimap#asMap()} can safely be * cast to a {@link java.util.SortedMap}. */ @SuppressWarnings("rawtypes") public static MultimapBuilderWithKeys treeKeys() { return treeKeys(Ordering.natural()); } /** * Uses a {@link TreeMap} sorted by the specified comparator to map keys to * value collections. * *

* The collections returned by {@link Multimap#keySet()}, * {@link Multimap#keys()}, and {@link Multimap#asMap()} will iterate through * the keys in sorted order. * *

* For all multimaps generated by the resulting builder, the * {@link Multimap#keySet()} can be safely cast to a * {@link java.util.SortedSet}, and the {@link Multimap#asMap()} can safely be * cast to a {@link java.util.SortedMap}. * *

* Multimaps generated by the resulting builder will not be serializable if * {@code comparator} is not serializable. */ public static MultimapBuilderWithKeys treeKeys(final Comparator comparator) { checkNotNull(comparator); return new MultimapBuilderWithKeys() { @Override Map> createMap() { return new TreeMap>(comparator); } }; } /** * Uses an {@link EnumMap} to map keys to value collections. */ public static > MultimapBuilderWithKeys enumKeys(final Class keyClass) { checkNotNull(keyClass); return new MultimapBuilderWithKeys() { @SuppressWarnings("unchecked") @Override Map> createMap() { // K must actually be K0, since enums are effectively final // (their subclasses are inaccessible) return (Map>) new EnumMap>(keyClass); } }; } private static final class ArrayListSupplier implements Supplier>, Serializable { private final int expectedValuesPerKey; ArrayListSupplier(int expectedValuesPerKey) { this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); } @Override public List get() { return new ArrayList(expectedValuesPerKey); } } private enum LinkedListSupplier implements Supplier> { INSTANCE; public static Supplier> instance() { // Each call generates a fresh LinkedList, which can serve as a List for any // V. @SuppressWarnings({ "rawtypes", "unchecked" }) Supplier> result = (Supplier) INSTANCE; return result; } @Override public List get() { return new LinkedList(); } } private static final class HashSetSupplier implements Supplier>, Serializable { private final int expectedValuesPerKey; HashSetSupplier(int expectedValuesPerKey) { this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); } @Override public Set get() { return new HashSet(expectedValuesPerKey); } } private static final class LinkedHashSetSupplier implements Supplier>, Serializable { private final int expectedValuesPerKey; LinkedHashSetSupplier(int expectedValuesPerKey) { this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); } @Override public Set get() { return new LinkedHashSet(expectedValuesPerKey); } } private static final class TreeSetSupplier implements Supplier>, Serializable { private final Comparator comparator; TreeSetSupplier(Comparator comparator) { this.comparator = checkNotNull(comparator); } @Override public SortedSet get() { return new TreeSet(comparator); } } private static final class EnumSetSupplier> implements Supplier>, Serializable { private final Class clazz; EnumSetSupplier(Class clazz) { this.clazz = checkNotNull(clazz); } @Override public Set get() { return EnumSet.noneOf(clazz); } } /** * An intermediate stage in a {@link MultimapBuilder} in which the key-value * collection map implementation has been specified, but the value collection * implementation has not. * * @param The upper bound on the key type of the generated multimap. */ public abstract static class MultimapBuilderWithKeys { private static final int DEFAULT_EXPECTED_VALUES_PER_KEY = 2; MultimapBuilderWithKeys() { } abstract Map> createMap(); /** * Uses an {@link ArrayList} to store value collections. */ public ListMultimapBuilder arrayListValues() { return arrayListValues(DEFAULT_EXPECTED_VALUES_PER_KEY); } /** * Uses an {@link ArrayList} to store value collections, initialized to expect * the specified number of values per key. * * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} */ public ListMultimapBuilder arrayListValues(final int expectedValuesPerKey) { checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); return new ListMultimapBuilder() { @Override public ListMultimap build() { return Multimaps.newListMultimap(MultimapBuilderWithKeys.this.createMap(), new ArrayListSupplier(expectedValuesPerKey)); } }; } /** * Uses a {@link LinkedList} to store value collections. */ public ListMultimapBuilder linkedListValues() { return new ListMultimapBuilder() { @Override public ListMultimap build() { return Multimaps.newListMultimap(MultimapBuilderWithKeys.this.createMap(), LinkedListSupplier.instance()); } }; } /** * Uses a {@link HashSet} to store value collections. */ public SetMultimapBuilder hashSetValues() { return hashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); } /** * Uses a {@link HashSet} to store value collections, initialized to expect the * specified number of values per key. * * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} */ public SetMultimapBuilder hashSetValues(final int expectedValuesPerKey) { checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); return new SetMultimapBuilder() { @Override public SetMultimap build() { return Multimaps.newSetMultimap(MultimapBuilderWithKeys.this.createMap(), new HashSetSupplier(expectedValuesPerKey)); } }; } /** * Uses a {@link LinkedHashSet} to store value collections. */ public SetMultimapBuilder linkedHashSetValues() { return linkedHashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); } /** * Uses a {@link LinkedHashSet} to store value collections, initialized to * expect the specified number of values per key. * * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} */ public SetMultimapBuilder linkedHashSetValues(final int expectedValuesPerKey) { checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); return new SetMultimapBuilder() { @Override public SetMultimap build() { return Multimaps.newSetMultimap(MultimapBuilderWithKeys.this.createMap(), new LinkedHashSetSupplier(expectedValuesPerKey)); } }; } /** * Uses a naturally-ordered {@link TreeSet} to store value collections. */ @SuppressWarnings("rawtypes") public SortedSetMultimapBuilder treeSetValues() { return treeSetValues(Ordering.natural()); } /** * Uses a {@link TreeSet} ordered by the specified comparator to store value * collections. * *

* Multimaps generated by the resulting builder will not be serializable if * {@code comparator} is not serializable. */ public SortedSetMultimapBuilder treeSetValues(final Comparator comparator) { checkNotNull(comparator, "comparator"); return new SortedSetMultimapBuilder() { @Override public SortedSetMultimap build() { return Multimaps.newSortedSetMultimap(MultimapBuilderWithKeys.this.createMap(), new TreeSetSupplier(comparator)); } }; } /** * Uses an {@link EnumSet} to store value collections. */ public > SetMultimapBuilder enumSetValues(final Class valueClass) { checkNotNull(valueClass, "valueClass"); return new SetMultimapBuilder() { @Override public SetMultimap build() { // V must actually be V0, since enums are effectively final // (their subclasses are inaccessible) @SuppressWarnings({ "unchecked", "rawtypes" }) Supplier> factory = (Supplier) new EnumSetSupplier(valueClass); return Multimaps.newSetMultimap(MultimapBuilderWithKeys.this.createMap(), factory); } }; } } /** * Returns a new, empty {@code Multimap} with the specified implementation. */ public abstract Multimap build(); /** * Returns a {@code Multimap} with the specified implementation, initialized * with the entries of {@code multimap}. */ public Multimap build(Multimap multimap) { Multimap result = build(); result.putAll(multimap); return result; } /** * A specialization of {@link MultimapBuilder} that generates * {@link ListMultimap} instances. */ public abstract static class ListMultimapBuilder extends MultimapBuilder { ListMultimapBuilder() { } @Override public abstract ListMultimap build(); @Override public ListMultimap build(Multimap multimap) { return (ListMultimap) super.build(multimap); } } /** * A specialization of {@link MultimapBuilder} that generates * {@link SetMultimap} instances. */ public abstract static class SetMultimapBuilder extends MultimapBuilder { SetMultimapBuilder() { } @Override public abstract SetMultimap build(); @Override public SetMultimap build(Multimap multimap) { return (SetMultimap) super.build(multimap); } } /** * A specialization of {@link MultimapBuilder} that generates * {@link SortedSetMultimap} instances. */ public abstract static class SortedSetMultimapBuilder extends SetMultimapBuilder { SortedSetMultimapBuilder() { } @Override public abstract SortedSetMultimap build(); @Override public SortedSetMultimap build(Multimap multimap) { return (SortedSetMultimap) super.build(multimap); } } }