From a79c8a8e9e60cc13be105869f02f92b57120c2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Prante?= Date: Wed, 6 Mar 2024 22:43:14 +0100 Subject: [PATCH] start work of immutable and multi subprojects --- .../org/xbib/datastructures/api/BiMap.java | 75 + .../xbib/datastructures/api/ListMultimap.java | 67 + .../org/xbib/datastructures/api/Multimap.java | 339 +++ .../org/xbib/datastructures/api/Multiset.java | 585 +++++ .../xbib/datastructures/api/SetMultimap.java | 92 + .../datastructures/api/SortedIterable.java | 23 + .../datastructures/api/SortedSetMultimap.java | 90 + datastructures-immutable/NOTICE.txt | 31 + datastructures-immutable/build.gradle | 3 + .../src/main/java/module-info.java | 5 + .../AbstractIndexedListIterator.java | 100 + .../immutable/AbstractMapEntry.java | 47 + .../immutable/ImmutableAsList.java | 31 + .../immutable/ImmutableBiMap.java | 574 +++++ .../immutable/ImmutableCollection.java | 635 +++++ .../immutable/ImmutableEntry.java | 68 + .../immutable/ImmutableEnumMap.java | 104 + .../immutable/ImmutableEnumSet.java | 99 + .../immutable/ImmutableList.java | 949 +++++++ .../immutable/ImmutableMap.java | 986 ++++++++ .../immutable/ImmutableMapEntry.java | 61 + .../immutable/ImmutableMapEntrySet.java | 43 + .../immutable/ImmutableMapKeySet.java | 52 + .../immutable/ImmutableMapValues.java | 97 + .../immutable/ImmutableSet.java | 1023 ++++++++ .../immutable/ImmutableSortedAsList.java | 59 + .../immutable/ImmutableSortedMap.java | 1054 ++++++++ .../immutable/ImmutableSortedSet.java | 784 ++++++ .../immutable/IndexedImmutableSet.java | 58 + .../immutable/IteratorBasedImmutableMap.java | 41 + .../immutable/JdkBackedImmutableBiMap.java | 100 + .../immutable/JdkBackedImmutableMap.java | 109 + .../immutable/JdkBackedImmutableSet.java | 37 + .../NonTerminalImmutableBiMapEntry.java | 19 + .../NonTerminalImmutableMapEntry.java | 26 + .../immutable/RegularEntrySet.java | 49 + .../immutable/RegularImmutableAsList.java | 66 + .../immutable/RegularImmutableBiMap.java | 289 +++ .../immutable/RegularImmutableList.java | 68 + .../immutable/RegularImmutableMap.java | 313 +++ .../immutable/RegularImmutableMapKeySet.java | 29 + .../immutable/RegularImmutableMapValues.java | 24 + .../immutable/RegularImmutableSet.java | 114 + .../immutable/RegularImmutableSortedSet.java | 313 +++ .../immutable/SingletonImmutableBiMap.java | 86 + .../immutable/SingletonImmutableList.java | 80 + .../immutable/SingletonImmutableSet.java | 59 + .../immutable/Spliterators.java | 269 ++ .../immutable/UnmodifiableIterator.java | 49 + .../immutable/UnmodifiableListIterator.java | 35 + .../immutable/order/AllEqualOrdering.java | 38 + .../immutable/order/ByFunctionOrdering.java | 44 + .../immutable/order/ComparatorOrdering.java | 41 + .../immutable/order/CompoundOrdering.java | 54 + .../immutable/order/ExplicitOrdering.java | 52 + .../order/LexicographicalOrdering.java | 55 + .../immutable/order/NaturalOrdering.java | 53 + .../immutable/order/NullsFirstOrdering.java | 66 + .../immutable/order/NullsLastOrdering.java | 65 + .../immutable/order/Ordering.java | 814 ++++++ .../order/ReverseNaturalOrdering.java | 77 + .../immutable/order/ReverseOrdering.java | 89 + .../immutable/order/TopKSelector.java | 342 +++ .../order/UsingToStringOrdering.java | 27 + .../immutable/test/CollectionTest.java | 29 + datastructures-multi/build.gradle | 3 + .../src/main/java/module-info.java | 5 + .../multi/AbstractMapBasedMultimap.java | 1689 +++++++++++++ .../multi/AbstractMapBasedMultiset.java | 321 +++ .../multi/AbstractMultimap.java | 295 +++ .../multi/AbstractMultiset.java | 234 ++ .../multi/AbstractSetMultimap.java | 126 + .../multi/BaseImmutableMultimap.java | 8 + .../multi/ImmutableListMultimap.java | 426 ++++ .../multi/ImmutableMultimap.java | 742 ++++++ .../multi/ImmutableMultiset.java | 592 +++++ .../multi/ImmutableSetMultimap.java | 619 +++++ .../multi/ImprovedAbstractSet.java | 63 + .../multi/JdkBackedImmutableMultiset.java | 80 + .../multi/LinkedHashMultimap.java | 567 +++++ .../multi/LinkedHashMultiset.java | 53 + .../multi/LinkedListMultimap.java | 866 +++++++ .../xbib/datastructures/multi/MapsKeySet.java | 69 + .../datastructures/multi/MultimapBuilder.java | 461 ++++ .../xbib/datastructures/multi/Multimaps.java | 2214 +++++++++++++++++ .../multi/MultimapsEntries.java | 39 + .../multi/MultisetsAbstractEntry.java | 47 + .../multi/MultisetsImmutableEntry.java | 33 + .../datastructures/multi/Serialization.java | 204 ++ .../multi/TransformedIterator.java | 33 + settings.gradle | 23 +- 91 files changed, 21957 insertions(+), 10 deletions(-) create mode 100644 datastructures-api/src/main/java/org/xbib/datastructures/api/BiMap.java create mode 100644 datastructures-api/src/main/java/org/xbib/datastructures/api/ListMultimap.java create mode 100644 datastructures-api/src/main/java/org/xbib/datastructures/api/Multimap.java create mode 100644 datastructures-api/src/main/java/org/xbib/datastructures/api/Multiset.java create mode 100644 datastructures-api/src/main/java/org/xbib/datastructures/api/SetMultimap.java create mode 100644 datastructures-api/src/main/java/org/xbib/datastructures/api/SortedIterable.java create mode 100644 datastructures-api/src/main/java/org/xbib/datastructures/api/SortedSetMultimap.java create mode 100644 datastructures-immutable/NOTICE.txt create mode 100644 datastructures-immutable/build.gradle create mode 100644 datastructures-immutable/src/main/java/module-info.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractIndexedListIterator.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractMapEntry.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableAsList.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableBiMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableCollection.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEntry.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableList.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntry.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntrySet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapKeySet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapValues.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedAsList.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IndexedImmutableSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IteratorBasedImmutableMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableBiMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableBiMapEntry.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableMapEntry.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularEntrySet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableAsList.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableBiMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableList.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapKeySet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapValues.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSortedSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableBiMap.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableList.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableSet.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/Spliterators.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableIterator.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableListIterator.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/AllEqualOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ByFunctionOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ComparatorOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/CompoundOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ExplicitOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/LexicographicalOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NaturalOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsFirstOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsLastOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/Ordering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseNaturalOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseOrdering.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/TopKSelector.java create mode 100644 datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/UsingToStringOrdering.java create mode 100644 datastructures-immutable/src/test/java/org/xbib/datastructures/immutable/test/CollectionTest.java create mode 100644 datastructures-multi/build.gradle create mode 100644 datastructures-multi/src/main/java/module-info.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultiset.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultiset.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractSetMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/BaseImmutableMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableListMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultiset.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableSetMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImprovedAbstractSet.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/JdkBackedImmutableMultiset.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultiset.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedListMultimap.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/MapsKeySet.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapBuilder.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/Multimaps.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapsEntries.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsAbstractEntry.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsImmutableEntry.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/Serialization.java create mode 100644 datastructures-multi/src/main/java/org/xbib/datastructures/multi/TransformedIterator.java diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/BiMap.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/BiMap.java new file mode 100644 index 0000000..893ee94 --- /dev/null +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/BiMap.java @@ -0,0 +1,75 @@ +package org.xbib.datastructures.api; + +import java.util.Map; +import java.util.Set; + +/** + * A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as + * that of its keys. This constraint enables bimaps to support an "inverse view", which is another + * bimap containing the same entries as this bimap but with reversed keys and values. + */ +public interface BiMap extends Map { + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the given value is already bound to a different key in this + * bimap. The bimap will remain unmodified in this event. To avoid this exception, call {@link + * #forcePut} instead. + */ + @Override + V put(K key, V value); + + /** + * An alternate form of {@code put} that silently removes any existing entry with the value {@code + * value} before proceeding with the {@link #put} operation. If the bimap previously contained the + * provided key-value mapping, this method has no effect. + * + *

Note that a successful call to this method could cause the size of the bimap to increase by + * one, stay the same, or even decrease by one. + * + *

Warning: If an existing entry with this value is removed, the key for that entry is + * discarded and not returned. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + * @return the value that was previously associated with the key, or {@code null} if there was no + * previous entry. (If the bimap contains null values, then {@code forcePut}, like {@code + * put}, returns {@code null} both if the key is absent and if it is present with a null + * value.) + */ + V forcePut(K key, V value); + + /** + * {@inheritDoc} + * + *

Warning: the results of calling this method may vary depending on the iteration order + * of {@code map}. + * + * @throws IllegalArgumentException if an attempt to {@code put} any entry fails. Note that some + * map entries may have been added to the bimap before the exception was thrown. + */ + @Override + void putAll(Map map); + + /** + * {@inheritDoc} + * + *

Because a bimap has unique values, this method returns a {@link Set}, instead of the {@link + * java.util.Collection} specified in the {@link Map} interface. + */ + @Override + Set values(); + + /** + * Returns the inverse view of this bimap, which maps each of this bimap's values to its + * associated key. The two bimaps are backed by the same data; any changes to one will appear in + * the other. + * + *

Note:There is no guaranteed correspondence between the iteration order of a bimap and + * that of its inverse. + * + * @return the inverse view of this bimap + */ + BiMap inverse(); +} diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/ListMultimap.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/ListMultimap.java new file mode 100644 index 0000000..babf907 --- /dev/null +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/ListMultimap.java @@ -0,0 +1,67 @@ +package org.xbib.datastructures.api; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * A {@code Multimap} that can hold duplicate key-value pairs and that maintains the insertion + * ordering of values for a given key. See the {@link Multimap} documentation for information common + * to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link + * List} of values. Though the method signature doesn't say so explicitly, the map returned by + * {@link #asMap} has {@code List} values. + */ +public interface ListMultimap + extends Multimap { + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link Collection} specified in + * the {@link Multimap} interface. + */ + @Override + List get(K key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link Collection} specified in + * the {@link Multimap} interface. + */ + @Override + List removeAll(Object key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link Collection} specified in + * the {@link Multimap} interface. + */ + @Override + List replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Note: The returned map's values are guaranteed to be of type {@link List}. + */ + @Override + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they contain the same values + * in the same order. If the value orderings disagree, the multimaps will not be considered equal. + * + *

An empty {@code ListMultimap} is equal to any other empty {@code Multimap}, including an + * empty {@code SetMultimap}. + */ + @Override + boolean equals(Object obj); +} diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/Multimap.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/Multimap.java new file mode 100644 index 0000000..1edd482 --- /dev/null +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/Multimap.java @@ -0,0 +1,339 @@ +package org.xbib.datastructures.api; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; + +/** + * A collection that maps keys to values, similar to {@link Map}, but in which each key may be + * associated with multiple values. You can visualize the contents of a multimap either as a + * map from keys to nonempty collections of values: + * + *

    + *
  • a → 1, 2 + *
  • b → 3 + *
+ *

+ * ... or as a single "flattened" collection of key-value pairs: + * + *

    + *
  • a → 1 + *
  • a → 2 + *
  • b → 3 + *
+ * + *

Important: although the first interpretation resembles how most multimaps are + * implemented, the design of the {@code Multimap} API is based on the second form. + * So, using the multimap shown above as an example, the {@link #size} is {@code 3}, not {@code 2}, + * and the {@link #values} collection is {@code [1, 2, 3]}, not {@code [[1, 2], [3]]}. For those + * times when the first style is more useful, use the multimap's {@link #asMap} view (or create a + * {@code Map>} in the first place). + * + *

Example

+ * + *

The following code: + * + *

{@code
+ * ListMultimap multimap = ArrayListMultimap.create();
+ * for (President pres : US_PRESIDENTS_IN_ORDER) {
+ *   multimap.put(pres.firstName(), pres.lastName());
+ * }
+ * for (String firstName : multimap.keySet()) {
+ *   List lastNames = multimap.get(firstName);
+ *   out.println(firstName + ": " + lastNames);
+ * }
+ * }
+ *

+ * ... produces output such as: + * + *

{@code
+ * Zachary: [Taylor]
+ * John: [Adams, Adams, Tyler, Kennedy]  // Remember, Quincy!
+ * George: [Washington, Bush, Bush]
+ * Grover: [Cleveland, Cleveland]        // Two, non-consecutive terms, rep'ing NJ!
+ * ...
+ * }
+ * + *

Views

+ * + *

Much of the power of the multimap API comes from the view collections it provides. + * These always reflect the latest state of the multimap itself. When they support modification, the + * changes are write-through (they automatically update the backing multimap). These view + * collections are: + * + *

    + *
  • {@link #asMap}, mentioned above + *
  • {@link #keys}, {@link #keySet}, {@link #values}, {@link #entries}, which are similar to the + * corresponding view collections of {@link Map} + *
  • and, notably, even the collection returned by {@link #get get(key)} is an active view of + * the values corresponding to {@code key} + *
+ * + *

The collections returned by the {@link #replaceValues replaceValues} and {@link #removeAll + * removeAll} methods, which contain values that have just been removed from the multimap, are + * naturally not views. + * + *

Subinterfaces

+ * + *

Instead of using the {@code Multimap} interface directly, prefer the subinterfaces {@link + * ListMultimap} and {@link SetMultimap}. These take their names from the fact that the collections + * they return from {@code get} behave like (and, of course, implement) {@link List} and {@link + * Set}, respectively. + * + *

For example, the "presidents" code snippet above used a {@code ListMultimap}; if it had used a + * {@code SetMultimap} instead, two presidents would have vanished, and last names might or might + * not appear in chronological order. + * + *

Warning: instances of type {@code Multimap} may not implement {@link Object#equals} in + * the way you expect. Multimaps containing the same key-value pairs, even in the same order, may or + * may not be equal and may or may not have the same {@code hashCode}. The recommended subinterfaces + * provide much stronger guarantees. + * + *

Comparison to a map of collections

+ * + *

Multimaps are commonly used in places where a {@code Map>} would otherwise + * have appeared. The differences include: + * + *

    + *
  • There is no need to populate an empty collection before adding an entry with {@link #put + * put}. + *
  • {@code get} never returns {@code null}, only an empty collection. + *
  • A key is contained in the multimap if and only if it maps to at least one value. Any + * operation that causes a key to have zero associated values has the effect of + * removing that key from the multimap. + *
  • The total entry count is available as {@link #size}. + *
  • Many complex operations become easier; for example, {@code + * Collections.min(multimap.values())} finds the smallest value across all keys. + *
+ * + *

Other Notes

+ * + *

As with {@code Map}, the behavior of a {@code Multimap} is not specified if key objects + * already present in the multimap change in a manner that affects {@code equals} comparisons. Use + * caution if mutable objects are used as keys in a {@code Multimap}. + * + *

All methods that modify the multimap are optional. The view collections returned by the + * multimap may or may not be modifiable. Any modification method that is not supported will throw + * {@link UnsupportedOperationException}. + */ +public interface Multimap { + // Query Operations + + /** + * Returns the number of key-value pairs in this multimap. + * + *

Note: this method does not return the number of distinct keys in the multimap, + * which is given by {@code keySet().size()} or {@code asMap().size()}. See the opening section of + * the {@link Multimap} class documentation for clarification. + */ + int size(); + + /** + * Returns {@code true} if this multimap contains no key-value pairs. Equivalent to {@code size() + * == 0}, but can in some cases be more efficient. + */ + boolean isEmpty(); + + /** + * Returns {@code true} if this multimap contains at least one key-value pair with the key {@code + * key}. + */ + boolean containsKey(Object key); + + /** + * Returns {@code true} if this multimap contains at least one key-value pair with the value + * {@code value}. + */ + boolean containsValue(Object value); + + /** + * Returns {@code true} if this multimap contains at least one key-value pair with the key {@code + * key} and the value {@code value}. + */ + boolean containsEntry(Object key, Object value); + + // Modification Operations + + /** + * Stores a key-value pair in this multimap. + * + *

Some multimap implementations allow duplicate key-value pairs, in which case {@code put} + * always adds a new key-value pair and increases the multimap size by 1. Other implementations + * prohibit duplicates, and storing a key-value pair that's already in the multimap has no effect. + * + * @return {@code true} if the method increased the size of the multimap, or {@code false} if the + * multimap already contained the key-value pair and doesn't allow duplicates + */ + boolean put(K key, V value); + + /** + * Removes a single key-value pair with the key {@code key} and the value {@code value} from this + * multimap, if such exists. If multiple key-value pairs in the multimap fit this description, + * which one is removed is unspecified. + * + * @return {@code true} if the multimap changed + */ + boolean remove(Object key, Object value); + + // Bulk Operations + + /** + * Stores a key-value pair in this multimap for each of {@code values}, all using the same key, + * {@code key}. Equivalent to (but expected to be more efficient than): + * + *

{@code
+     * for (V value : values) {
+     *   put(key, value);
+     * }
+     * }
+ * + *

In particular, this is a no-op if {@code values} is empty. + * + * @return {@code true} if the multimap changed + */ + boolean putAll(K key, Iterable values); + + /** + * Stores all key-value pairs of {@code multimap} in this multimap, in the order returned by + * {@code multimap.entries()}. + * + * @return {@code true} if the multimap changed + */ + boolean putAll(Multimap multimap); + + /** + * Stores a collection of values with the same key, replacing any existing values for that key. + * + *

If {@code values} is empty, this is equivalent to {@link #removeAll(Object) removeAll(key)}. + * + * @return the collection of replaced values, or an empty collection if no values were previously + * associated with the key. The collection may be modifiable, but updating it will have + * no effect on the multimap. + */ + Collection replaceValues(K key, Iterable values); + + /** + * Removes all values associated with the key {@code key}. + * + *

Once this method returns, {@code key} will not be mapped to any values, so it will not + * appear in {@link #keySet()}, {@link #asMap()}, or any other views. + * + * @return the values that were removed (possibly empty). The returned collection may be + * modifiable, but updating it will have no effect on the multimap. + */ + Collection removeAll(Object key); + + /** + * Removes all key-value pairs from the multimap, leaving it {@linkplain #isEmpty empty}. + */ + void clear(); + + // Views + + /** + * Returns a view collection of the values associated with {@code key} in this multimap, if any. + * Note that when {@code containsKey(key)} is false, this returns an empty collection, not {@code + * null}. + * + *

Changes to the returned collection will update the underlying multimap, and vice versa. + */ + Collection get(K key); + + /** + * Returns a view collection of all distinct keys contained in this multimap. Note that the + * key set contains a key if and only if this multimap maps that key to at least one value. + * + *

Changes to the returned set will update the underlying multimap, and vice versa. However, + * adding to the returned set is not possible. + */ + Set keySet(); + + /** + * Returns a view collection containing the key from each key-value pair in this multimap, + * without collapsing duplicates. This collection has the same size as this multimap, and + * {@code keys().count(k) == get(k).size()} for all {@code k}. + * + *

Changes to the returned multiset will update the underlying multimap, and vice versa. + * However, adding to the returned collection is not possible. + */ + Multiset keys(); + + /** + * Returns a view collection containing the value from each key-value pair contained in + * this multimap, without collapsing duplicates (so {@code values().size() == size()}). + * + *

Changes to the returned collection will update the underlying multimap, and vice versa. + * However, adding to the returned collection is not possible. + */ + Collection values(); + + /** + * Returns a view collection of all key-value pairs contained in this multimap, as {@link Entry} + * instances. + * + *

Changes to the returned collection or the entries it contains will update the underlying + * multimap, and vice versa. However, adding to the returned collection is not possible. + */ + Collection> entries(); + + /** + * Performs the given action for all key-value pairs contained in this multimap. If an ordering is + * specified by the {@code Multimap} implementation, actions will be performed in the order of + * iteration of {@link #entries()}. Exceptions thrown by the action are relayed to the caller. + * + *

To loop over all keys and their associated value collections, write {@code + * Multimaps.asMap(multimap).forEach((key, valueCollection) -> action())}. + */ + default void forEach(BiConsumer action) { + Objects.requireNonNull(action); + entries().forEach(entry -> action.accept(entry.getKey(), entry.getValue())); + } + + /** + * Returns a view of this multimap as a {@code Map} from each distinct key to the nonempty + * collection of that key's associated values. Note that {@code this.asMap().get(k)} is equivalent + * to {@code this.get(k)} only when {@code k} is a key contained in the multimap; otherwise it + * returns {@code null} as opposed to an empty collection. + * + *

Changes to the returned map or the collections that serve as its values will update the + * underlying multimap, and vice versa. The map does not support {@code put} or {@code putAll}, + * nor do its entries support {@link Entry#setValue setValue}. + */ + Map> asMap(); + + // Comparison and hashing + + /** + * Compares the specified object with this multimap for equality. Two multimaps are equal when + * their map views, as returned by {@link #asMap}, are also equal. + * + *

In general, two multimaps with identical key-value mappings may or may not be equal, + * depending on the implementation. For example, two {@link SetMultimap} instances with the same + * key-value mappings are equal, but equality of two {@link ListMultimap} instances depends on the + * ordering of the values for each key. + * + *

A non-empty {@link SetMultimap} cannot be equal to a non-empty {@link ListMultimap}, since + * their {@link #asMap} views contain unequal collections as values. However, any two empty + * multimaps are equal, because they both have empty {@link #asMap} views. + */ + @Override + boolean equals(Object obj); + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, as returned by + * {@link Multimap#asMap}. + * + *

In general, two multimaps with identical key-value mappings may or may not have the same + * hash codes, depending on the implementation. For example, two {@link SetMultimap} instances + * with the same key-value mappings will have the same {@code hashCode}, but the {@code hashCode} + * of {@link ListMultimap} instances depends on the ordering of the values for each key. + */ + @Override + int hashCode(); +} diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/Multiset.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/Multiset.java new file mode 100644 index 0000000..905b8bf --- /dev/null +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/Multiset.java @@ -0,0 +1,585 @@ +package org.xbib.datastructures.api; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.ObjIntConsumer; + +/** + * A collection that supports order-independent equality, like {@link Set}, but may have duplicate + * elements. A multiset is also sometimes called a bag. + * + *

Elements of a multiset that are equal to one another are referred to as occurrences of + * the same single element. The total number of occurrences of an element in a multiset is called + * the count of that element (the terms "frequency" and "multiplicity" are equivalent, but + * not used in this API). Since the count of an element is represented as an {@code int}, a multiset + * may never contain more than {@link Integer#MAX_VALUE} occurrences of any one element. + * + *

{@code Multiset} refines the specifications of several methods from {@code Collection}. It + * also defines an additional query operation, {@link #count}, which returns the count of an + * element. There are five new bulk-modification operations, for example {@link #add(Object, int)}, + * to add or remove multiple occurrences of an element at once, or to set the count of an element to + * a specific value. These modification operations are optional, but implementations which support + * the standard collection operations {@link #add(Object)} or {@link #remove(Object)} are encouraged + * to implement the related methods as well. Finally, two collection views are provided: {@link + * #elementSet} contains the distinct elements of the multiset "with duplicates collapsed", and + * {@link #entrySet} is similar but contains {@link Entry Multiset.Entry} instances, each providing + * both a distinct element and the count of that element. + * + *

In addition to these required methods, implementations of {@code Multiset} are expected to + * provide two {@code static} creation methods: {@code create()}, returning an empty multiset, and + * {@code create(Iterable)}, returning a multiset containing the given initial + * elements. This is simply a refinement of {@code Collection}'s constructor recommendations, + * reflecting the new developments of Java 5. + * + *

As with other collection types, the modification operations are optional, and should throw + * {@link UnsupportedOperationException} when they are not implemented. Most implementations should + * support either all add operations or none of them, all removal operations or none of them, and if + * and only if all of these are supported, the {@code setCount} methods as well. + * + *

A multiset uses {@link Object#equals} to determine whether two instances should be considered + * "the same," unless specified otherwise by the implementation. + * + *

Warning: as with normal {@link Set}s, it is almost always a bad idea to modify an + * element (in a way that affects its {@link Object#equals} behavior) while it is contained in a + * multiset. Undefined behavior and bugs will result. + */ +public interface Multiset extends Collection { + // Query Operations + + /** + * Returns the total number of all occurrences of all elements in this multiset. + * + *

Note: this method does not return the number of distinct elements in the + * multiset, which is given by {@code entrySet().size()}. + */ + @Override + int size(); + + /** + * Returns the number of occurrences of an element in this multiset (the count of the + * element). Note that for an {@link Object#equals}-based multiset, this gives the same result as + * {@link Collections#frequency} (which would presumably perform more poorly). + * + * @param element the element to count occurrences of + * @return the number of occurrences of the element in this multiset; possibly zero but never + * negative + */ + int count(Object element); + + // Bulk Operations + + /** + * Adds a number of occurrences of an element to this multiset. Note that if {@code occurrences == + * 1}, this method has the identical effect to {@link #add(Object)}. This method is functionally + * equivalent (except in the case of overflow) to the call {@code + * addAll(Collections.nCopies(element, occurrences))}, which would presumably perform much more + * poorly. + * + * @param element the element to add occurrences of; may be null only if explicitly allowed by the + * implementation + * @param occurrences the number of occurrences of the element to add. May be zero, in which case + * no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation would + * result in more than {@link Integer#MAX_VALUE} occurrences of the element + * @throws NullPointerException if {@code element} is null and this implementation does not permit + * null elements. Note that if {@code occurrences} is zero, the implementation may opt to + * return normally. + */ + int add(E element, int occurrences); + + /** + * Adds a single occurrence of the specified element to this multiset. + * + *

This method refines {@link Collection#add}, which only ensures the presence of the + * element, to further specify that a successful call must always increment the count of the + * element, and the overall size of the collection, by one. + * + *

To both add the element and obtain the previous count of that element, use {@link + * #add(Object, int) add}{@code (element, 1)} instead. + * + * @param element the element to add one occurrence of; may be null only if explicitly allowed by + * the implementation + * @return {@code true} always, since this call is required to modify the multiset, unlike other + * {@link Collection} types + * @throws NullPointerException if {@code element} is null and this implementation does not permit + * null elements + * @throws IllegalArgumentException if {@link Integer#MAX_VALUE} occurrences of {@code element} + * are already contained in this multiset + */ + @Override + boolean add(E element); + + /** + * Removes a number of occurrences of the specified element from this multiset. If the multiset + * contains fewer than this number of occurrences to begin with, all occurrences will be removed. + * Note that if {@code occurrences == 1}, this is functionally equivalent to the call {@code + * remove(element)}. + * + * @param element the element to conditionally remove occurrences of + * @param occurrences the number of occurrences of the element to remove. May be zero, in which + * case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + int remove(Object element, int occurrences); + + /** + * Removes a single occurrence of the specified element from this multiset, if present. + * + *

This method refines {@link Collection#remove} to further specify that it may not + * throw an exception in response to {@code element} being null or of the wrong type. + * + *

To both remove the element and obtain the previous count of that element, use {@link + * #remove(Object, int) remove}{@code (element, 1)} instead. + * + * @param element the element to remove one occurrence of + * @return {@code true} if an occurrence was found and removed + */ + @Override + boolean remove(Object element); + + /** + * Adds or removes the necessary occurrences of an element such that the element attains the + * desired count. + * + * @param element the element to add or remove occurrences of; may be null only if explicitly + * allowed by the implementation + * @param count the desired count of the element in this multiset + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code count} is negative + * @throws NullPointerException if {@code element} is null and this implementation does not permit + * null elements. Note that if {@code count} is zero, the implementor may optionally return + * zero instead. + */ + int setCount(E element, int count); + + /** + * Conditionally sets the count of an element to a new value, as described in {@link + * #setCount(Object, int)}, provided that the element has the expected current count. If the + * current count is not {@code oldCount}, no change is made. + * + * @param element the element to conditionally set the count of; may be null only if explicitly + * allowed by the implementation + * @param oldCount the expected present count of the element in this multiset + * @param newCount the desired count of the element in this multiset + * @return {@code true} if the condition for modification was met. This implies that the multiset + * was indeed modified, unless {@code oldCount == newCount}. + * @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is negative + * @throws NullPointerException if {@code element} is null and the implementation does not permit + * null elements. Note that if {@code oldCount} and {@code newCount} are both zero, the + * implementor may optionally return {@code true} instead. + */ + boolean setCount(E element, int oldCount, int newCount); + + // Views + + /** + * Returns the set of distinct elements contained in this multiset. The element set is backed by + * the same data as the multiset, so any change to either is immediately reflected in the other. + * The order of the elements in the element set is unspecified. + * + *

If the element set supports any removal operations, these necessarily cause all + * occurrences of the removed element(s) to be removed from the multiset. Implementations are not + * expected to support the add operations, although this is possible. + * + *

A common use for the element set is to find the number of distinct elements in the multiset: + * {@code elementSet().size()}. + * + * @return a view of the set of distinct elements in this multiset + */ + Set elementSet(); + + /** + * Returns a view of the contents of this multiset, grouped into {@code Multiset.Entry} instances, + * each providing an element of the multiset and the count of that element. This set contains + * exactly one entry for each distinct element in the multiset (thus it always has the same size + * as the {@link #elementSet}). The order of the elements in the element set is unspecified. + * + *

The entry set is backed by the same data as the multiset, so any change to either is + * immediately reflected in the other. However, multiset changes may or may not be reflected in + * any {@code Entry} instances already retrieved from the entry set (this is + * implementation-dependent). Furthermore, implementations are not required to support + * modifications to the entry set at all, and the {@code Entry} instances themselves don't even + * have methods for modification. See the specific implementation class for more details on how + * its entry set handles modifications. + * + * @return a set of entries representing the data of this multiset + */ + Set> entrySet(); + + /** + * An unmodifiable element-count pair for a multiset. The {@link Multiset#entrySet} method returns + * a view of the multiset whose elements are of this class. A multiset implementation may return + * Entry instances that are either live "read-through" views to the Multiset, or immutable + * snapshots. Note that this type is unrelated to the similarly-named type {@code Map.Entry}. + * + * @since 2.0 + */ + interface Entry { + + /** + * Returns the multiset element corresponding to this entry. Multiple calls to this method + * always return the same instance. + * + * @return the element corresponding to this entry + */ + E getElement(); + + /** + * Returns the count of the associated element in the underlying multiset. This count may either + * be an unchanging snapshot of the count at the time the entry was retrieved, or a live view of + * the current count of the element in the multiset, depending on the implementation. Note that + * in the former case, this method can never return zero, while in the latter, it will return + * zero if all occurrences of the element were since removed from the multiset. + * + * @return the count of the element; never negative + */ + int getCount(); + + /** + * {@inheritDoc} + * + *

Returns {@code true} if the given object is also a multiset entry and the two entries + * represent the same element and count. That is, two entries {@code a} and {@code b} are equal + * if: + * + *

{@code
+         * Objects.equal(a.getElement(), b.getElement())
+         *     && a.getCount() == b.getCount()
+         * }
+ */ + @Override + boolean equals(Object o); + + /** + * {@inheritDoc} + * + *

The hash code of a multiset entry for element {@code element} and count {@code count} is + * defined as: + * + *

{@code
+         * ((element == null) ? 0 : element.hashCode()) ^ count
+         * }
+ */ + @Override + int hashCode(); + + /** + * Returns the canonical string representation of this entry, defined as follows. If the count + * for this entry is one, this is simply the string representation of the corresponding element. + * Otherwise, it is the string representation of the element, followed by the three characters + * {@code " x "} (space, letter x, space), followed by the count. + */ + @Override + String toString(); + } + + /** + * Runs the specified action for each distinct element in this multiset, and the number of + * occurrences of that element. For some {@code Multiset} implementations, this may be more + * efficient than iterating over the {@link #entrySet()} either explicitly or with {@code + * entrySet().forEach(action)}. + */ + default void forEachEntry(ObjIntConsumer action) { + Objects.requireNonNull(action); + entrySet().forEach(entry -> action.accept(entry.getElement(), entry.getCount())); + } + + // Comparison and hashing + + /** + * Compares the specified object with this multiset for equality. Returns {@code true} if the + * given object is also a multiset and contains equal elements with equal counts, regardless of + * order. + */ + @Override + // TODO(kevinb): caveats about equivalence-relation? + boolean equals(Object object); + + /** + * Returns the hash code for this multiset. This is defined as the sum of + * + *
{@code
+     * ((element == null) ? 0 : element.hashCode()) ^ count(element)
+     * }
+ * + *

over all distinct elements in the multiset. It follows that a multiset and its entry set + * always have the same hash code. + */ + @Override + int hashCode(); + + /** + * {@inheritDoc} + * + *

It is recommended, though not mandatory, that this method return the result of invoking + * {@link #toString} on the {@link #entrySet}, yielding a result such as {@code [a x 3, c, d x 2, + * e]}. + */ + @Override + String toString(); + + // Refined Collection Methods + + /** + * {@inheritDoc} + * + *

Elements that occur multiple times in the multiset will appear multiple times in this + * iterator, though not necessarily sequentially. + */ + @Override + Iterator iterator(); + + /** + * Determines whether this multiset contains the specified element. + * + *

This method refines {@link Collection#contains} to further specify that it may not + * throw an exception in response to {@code element} being null or of the wrong type. + * + * @param element the element to check for + * @return {@code true} if this multiset contains at least one occurrence of the element + */ + @Override + boolean contains(Object element); + + /** + * Returns {@code true} if this multiset contains at least one occurrence of each element in the + * specified collection. + * + *

This method refines {@link Collection#containsAll} to further specify that it may not + * throw an exception in response to any of {@code elements} being null or of the wrong type. + * + *

Note: this method does not take into account the occurrence count of an element in + * the two collections; it may still return {@code true} even if {@code elements} contains several + * occurrences of an element and this multiset contains only one. This is no different than any + * other collection type like {@link List}, but it may be unexpected to the user of a multiset. + * + * @param elements the collection of elements to be checked for containment in this multiset + * @return {@code true} if this multiset contains at least one occurrence of each element + * contained in {@code elements} + * @throws NullPointerException if {@code elements} is null + */ + @Override + boolean containsAll(Collection elements); + + /** + * {@inheritDoc} + * + *

Note: This method ignores how often any element might appear in {@code c}, and only + * cares whether or not an element appears at all. If you wish to remove one occurrence in this + * multiset for every occurrence in {@code c}. + * + *

This method refines {@link Collection#removeAll} to further specify that it may not + * throw an exception in response to any of {@code elements} being null or of the wrong type. + */ + @Override + boolean removeAll(Collection c); + + /** + * {@inheritDoc} + * + *

Note: This method ignores how often any element might appear in {@code c}, and only + * cares whether or not an element appears at all. If you wish to remove one occurrence in this + * multiset for every occurrence in {@code c}. + * + *

This method refines {@link Collection#retainAll} to further specify that it may not + * throw an exception in response to any of {@code elements} being null or of the wrong type. + */ + @Override + boolean retainAll(Collection c); + + /** + * {@inheritDoc} + * + *

Elements that occur multiple times in the multiset will be passed to the {@code Consumer} + * correspondingly many times, though not necessarily sequentially. + */ + @Override + default void forEach(Consumer action) { + Objects.requireNonNull(action); + entrySet() + .forEach( + entry -> { + E elem = entry.getElement(); + int count = entry.getCount(); + for (int i = 0; i < count; i++) { + action.accept(elem); + } + }); + } + + @Override + default Spliterator spliterator() { + return spliteratorImpl(this); + } + + private static Spliterator spliteratorImpl(Multiset multiset) { + Spliterator> entrySpliterator = multiset.entrySet().spliterator(); + return flatMap( + entrySpliterator, + entry -> Collections.nCopies(entry.getCount(), entry.getElement()).spliterator(), + Spliterator.SIZED + | (entrySpliterator.characteristics() + & (Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE)), + multiset.size()); + } + + + /** + * Returns a {@code Spliterator} that iterates over the elements of the spliterators generated by + * applying {@code function} to the elements of {@code fromSpliterator}. + */ + private static Spliterator flatMap( + Spliterator fromSpliterator, + Function> function, + int topCharacteristics, + long topSize) { + if (!((topCharacteristics & Spliterator.SUBSIZED) == 0)) { + throw new IllegalArgumentException("flatMap does not support SUBSIZED characteristic"); + } + if (!((topCharacteristics & Spliterator.SORTED) == 0)) { + throw new IllegalArgumentException("flatMap does not support SORTED characteristic"); + } + Objects.requireNonNull(fromSpliterator); + Objects.requireNonNull(function); + return new FlatMapSpliteratorOfObject<>(null, fromSpliterator, function, topCharacteristics, topSize); + } + + public static class FlatMapSpliteratorOfObject + extends FlatMapSpliterator> { + + FlatMapSpliteratorOfObject(Spliterator prefix, + Spliterator from, + Function> function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfObject::new, characteristics, estimatedSize); + } + } + + + static abstract class FlatMapSpliterator< + InElementT extends Object, + OutElementT extends Object, + OutSpliteratorT extends Spliterator> + implements Spliterator { + /** + * Factory for constructing {@link FlatMapSpliterator} instances. + */ + @FunctionalInterface + interface Factory> { + OutSpliteratorT newFlatMapSpliterator(OutSpliteratorT prefix, + Spliterator fromSplit, + Function function, + int splitCharacteristics, + long estSplitSize); + } + + OutSpliteratorT prefix; + final Spliterator from; + final Function function; + final Factory factory; + int characteristics; + long estimatedSize; + + FlatMapSpliterator(OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + this.prefix = prefix; + this.from = from; + this.function = function; + this.factory = factory; + this.characteristics = characteristics; + this.estimatedSize = estimatedSize; + } + /* + * The tryAdvance and forEachRemaining in FlatMapSpliteratorOfPrimitive are overloads of these + * methods, not overrides. They are annotated @Override because they implement methods from + * Spliterator.OfPrimitive (and override default implementations from Spliterator.OfPrimitive or + * a subtype like Spliterator.OfInt). + */ + + @Override + public final boolean tryAdvance(Consumer action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public final void forEachRemaining(Consumer action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + Spliterator elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + + @Override + public final OutSpliteratorT trySplit() { + Spliterator fromSplit = from.trySplit(); + if (fromSplit != null) { + int splitCharacteristics = characteristics & ~Spliterator.SIZED; + long estSplitSize = estimateSize(); + if (estSplitSize < Long.MAX_VALUE) { + estSplitSize /= 2; + this.estimatedSize -= estSplitSize; + this.characteristics = splitCharacteristics; + } + OutSpliteratorT result = + factory.newFlatMapSpliterator( + this.prefix, fromSplit, function, splitCharacteristics, estSplitSize); + this.prefix = null; + return result; + } else if (prefix != null) { + OutSpliteratorT result = prefix; + this.prefix = null; + return result; + } else { + return null; + } + } + + @Override + public final long estimateSize() { + if (prefix != null) { + estimatedSize = Math.max(estimatedSize, prefix.estimateSize()); + } + return Math.max(estimatedSize, 0); + } + + @Override + public final int characteristics() { + return characteristics; + } + } + +} diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/SetMultimap.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/SetMultimap.java new file mode 100644 index 0000000..0e41144 --- /dev/null +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/SetMultimap.java @@ -0,0 +1,92 @@ +package org.xbib.datastructures.api; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * A {@code Multimap} that cannot hold duplicate key-value pairs. Adding a key-value pair that's + * already in the multimap has no effect. See the {@link Multimap} documentation for information + * common to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link + * Set} of values, while {@link #entries} returns a {@code Set} of map entries. Though the method + * signature doesn't say so explicitly, the map returned by {@link #asMap} has {@code Set} values. + * + *

If the values corresponding to a single key should be ordered according to a {@link + * java.util.Comparator} (or the natural order), see the {@link SortedSetMultimap} subinterface. + * + *

Since the value collections are sets, the behavior of a {@code SetMultimap} is not specified + * if key or value objects already present in the multimap change in a manner that affects + * {@code equals} comparisons. Use caution if mutable objects are used as keys or values in a {@code + * SetMultimap}. + * + *

Warning: Do not modify either a key or a value of a {@code SetMultimap} in a way + * that affects its {@link Object#equals} behavior. Undefined behavior and bugs will result. + * + */ +public interface SetMultimap + extends Multimap { + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} + * interface. + */ + @Override + Set get(K key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} + * interface. + */ + @Override + Set removeAll(Object key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} + * interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override + Set replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} + * interface. + */ + @Override + Set> entries(); + + /** + * {@inheritDoc} + * + *

Note: The returned map's values are guaranteed to be of type {@link Set}. + */ + @Override + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they contain the same values. + * Equality does not depend on the ordering of keys or values. + * + *

An empty {@code SetMultimap} is equal to any other empty {@code Multimap}, including an + * empty {@code ListMultimap}. + */ + @Override + boolean equals(Object obj); +} diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/SortedIterable.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/SortedIterable.java new file mode 100644 index 0000000..a80e6bd --- /dev/null +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/SortedIterable.java @@ -0,0 +1,23 @@ +package org.xbib.datastructures.api; + +import java.util.Comparator; +import java.util.Iterator; + +/** + * An {@code Iterable} whose elements are sorted relative to a {@code Comparator}, typically + * provided at creation time. + */ +public interface SortedIterable extends Iterable { + /** + * Returns the {@code Comparator} by which the elements of this iterable are ordered, or {@code + * Ordering.natural()} if the elements are ordered by their natural ordering. + */ + Comparator comparator(); + + /** + * Returns an iterator over elements of type {@code T}. The elements are returned in nondecreasing + * order according to the associated {@link #comparator}. + */ + @Override + Iterator iterator(); +} diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/SortedSetMultimap.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/SortedSetMultimap.java new file mode 100644 index 0000000..44c8499 --- /dev/null +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/SortedSetMultimap.java @@ -0,0 +1,90 @@ +package org.xbib.datastructures.api; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * A {@code SetMultimap} whose set of values for a given key are kept sorted; that is, they comprise + * a {@link SortedSet}. It cannot hold duplicate key-value pairs; adding a key-value pair that's + * already in the multimap has no effect. This interface does not specify the ordering of the + * multimap's keys. See the {@link Multimap} documentation for information common to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link + * SortedSet} of values, while {@link Multimap#entries()} returns a {@link Set} of map entries. + * Though the method signature doesn't say so explicitly, the map returned by {@link #asMap} has + * {@code SortedSet} values. + * + *

Warning: As in all {@link SetMultimap}s, do not modify either a key or a value + * of a {@code SortedSetMultimap} in a way that affects its {@link Object#equals} behavior (or its + * position in the order of the values). Undefined behavior and bugs will result. + * + */ +public interface SortedSetMultimap + extends SetMultimap { + // Following Javadoc copied from Multimap. + + /** + * Returns a collection view of all values associated with a key. If no mappings in the multimap + * have the provided key, an empty collection is returned. + * + *

Changes to the returned collection will update the underlying multimap, and vice versa. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link Collection} specified in the + * {@link Multimap} interface. + */ + @Override + SortedSet get(K key); + + /** + * Removes all values associated with a given key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link Collection} specified in the + * {@link Multimap} interface. + */ + @Override + SortedSet removeAll(Object key); + + /** + * Stores a collection of values with the same key, replacing any existing values for that key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link Collection} specified in the + * {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override + SortedSet replaceValues(K key, Iterable values); + + /** + * Returns a map view that associates each key with the corresponding values in the multimap. + * Changes to the returned map, such as element removal, will update the underlying multimap. The + * map does not support {@code setValue()} on its entries, {@code put}, or {@code putAll}. + * + *

When passed a key that is present in the map, {@code asMap().get(Object)} has the same + * behavior as {@link #get}, returning a live collection. When passed a key that is not present, + * however, {@code asMap().get(Object)} returns {@code null} instead of an empty collection. + * + *

Note: The returned map's values are guaranteed to be of type {@link SortedSet}. To + * obtain this map with the more specific generic type {@code Map>}, call {@link + * Multimaps#asMap(SortedSetMultimap)} instead. However, the returned map itself is + * not necessarily a {@link SortedMap}: A {@code SortedSetMultimap} must expose the values + * for a given key in sorted order, but it need not expose the keys in sorted order. + * Individual {@code SortedSetMultimap} implementations, like those built with {@link + * MultimapBuilder#treeKeys()}, may make additional guarantees. + */ + @Override + Map> asMap(); + + /** + * Returns the comparator that orders the multimap values, with {@code null} indicating that + * natural ordering is used. + */ + Comparator valueComparator(); +} diff --git a/datastructures-immutable/NOTICE.txt b/datastructures-immutable/NOTICE.txt new file mode 100644 index 0000000..db4e864 --- /dev/null +++ b/datastructures-immutable/NOTICE.txt @@ -0,0 +1,31 @@ +The subproject datastructures-immutable is based upon Google Guava + +https://github.com/google/guava + +License: Apache 2.0 + +as of 26 Dec 2023 (33.0.0) with thw following modifications: + +- use only classes that are required for ImmutableCollection, ImmutableList, ImmutableSet, ImmutableEnumSet, +ImmutableMap, ImmutableBiMap, ImmutableEnumMap, + +- javadocs cleaned up, no @since and no @author tags, no 'todos' to avoid confusion with the original project + +- all annotations are removed (except @Override) + +- all deprecations are removed + +- all code relating to java serialization is removed + +- all comments relating to Guava classes is renamed or removed + +- all comments realting to GWT is removed + +- all helper classes with static code (like Preconditions, Lists, Sets, Maps etc) are resolved and included into +existing classes, also from other Guava subpackages + +- indentation level changed to 4 + +- lift Java language level to java 21 + +- use sealed classes for contractual integrity diff --git a/datastructures-immutable/build.gradle b/datastructures-immutable/build.gradle new file mode 100644 index 0000000..6a1c6eb --- /dev/null +++ b/datastructures-immutable/build.gradle @@ -0,0 +1,3 @@ +dependencies { + api project(':datastructures-api') +} diff --git a/datastructures-immutable/src/main/java/module-info.java b/datastructures-immutable/src/main/java/module-info.java new file mode 100644 index 0000000..c863f02 --- /dev/null +++ b/datastructures-immutable/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.xbib.datastructures.immutable { + exports org.xbib.datastructures.immutable; + exports org.xbib.datastructures.immutable.order; + requires org.xbib.datastructures.api; +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractIndexedListIterator.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractIndexedListIterator.java new file mode 100644 index 0000000..18afc09 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractIndexedListIterator.java @@ -0,0 +1,100 @@ +package org.xbib.datastructures.immutable; + +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * This class provides a skeletal implementation of the {@link ListIterator} interface across a + * fixed number of elements that may be retrieved by position. It does not support {@link #remove}, + * {@link #set}, or {@link #add}. + */ +abstract class AbstractIndexedListIterator + extends UnmodifiableListIterator { + private final int size; + private int position; + + /** + * Returns the element with the specified index. This method is called by {@link #next()}. + */ + protected abstract E get(int index); + + /** + * Constructs an iterator across a sequence of the given size whose initial position is 0. That + * is, the first call to {@link #next()} will return the first element (or throw {@link + * NoSuchElementException} if {@code size} is zero). + * + * @throws IllegalArgumentException if {@code size} is negative + */ + protected AbstractIndexedListIterator(int size) { + this(size, 0); + } + + /** + * Constructs an iterator across a sequence of the given size with the given initial position. + * That is, the first call to {@link #nextIndex()} will return {@code position}, and the first + * call to {@link #next()} will return the element at that index, if available. Calls to {@link + * #previous()} can retrieve the preceding {@code position} elements. + * + * @throws IndexOutOfBoundsException if {@code position} is negative or is greater than {@code + * size} + * @throws IllegalArgumentException if {@code size} is negative + */ + protected AbstractIndexedListIterator(int size, int position) { + checkPositionIndex(position, size, "index"); + this.size = size; + this.position = position; + } + + @Override + public final boolean hasNext() { + return position < size; + } + + @Override + public final E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return get(position++); + } + + @Override + public final int nextIndex() { + return position; + } + + @Override + public final boolean hasPrevious() { + return position > 0; + } + + @Override + public final E previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + return get(--position); + } + + @Override + public final int previousIndex() { + return position - 1; + } + + private static int checkPositionIndex(int index, int size, String desc) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); + } + return index; + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return String.format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index > size + return String.format("%s (%s) must not be greater than size (%s)", desc, index, size); + } + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractMapEntry.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractMapEntry.java new file mode 100644 index 0000000..ca6d70a --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/AbstractMapEntry.java @@ -0,0 +1,47 @@ +package org.xbib.datastructures.immutable; + +import java.util.Map.Entry; +import java.util.Objects; + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} methods of {@code + * Entry}. + */ +abstract class AbstractMapEntry + implements Entry { + + @Override + public abstract K getKey(); + + @Override + public abstract V getValue(); + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof Entry that) { + return Objects.equals(this.getKey(), that.getKey()) + && Objects.equals(this.getValue(), that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * Returns a string representation of the form {@code {key}={value}}. + */ + @Override + public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableAsList.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableAsList.java new file mode 100644 index 0000000..f2e389f --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableAsList.java @@ -0,0 +1,31 @@ +package org.xbib.datastructures.immutable; + +/** + * List returned by {@link ImmutableCollection#asList} that delegates {@code contains} checks to the + * backing collection. + */ +abstract class ImmutableAsList extends ImmutableList { + abstract ImmutableCollection delegateCollection(); + + @Override + public boolean contains(Object target) { + // The collection's contains() is at least as fast as ImmutableList's + // and is often faster. + return delegateCollection().contains(target); + } + + @Override + public int size() { + return delegateCollection().size(); + } + + @Override + public boolean isEmpty() { + return delegateCollection().isEmpty(); + } + + @Override + boolean isPartialView() { + return delegateCollection().isPartialView(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableBiMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableBiMap.java new file mode 100644 index 0000000..62f9dd5 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableBiMap.java @@ -0,0 +1,574 @@ +package org.xbib.datastructures.immutable; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.xbib.datastructures.api.BiMap; +import org.xbib.datastructures.immutable.order.Ordering; +import static java.util.Objects.requireNonNull; +import static org.xbib.datastructures.immutable.ImmutableCollection.toArray; + +/** + * A BiMap whose contents will never change, with many other important properties detailed + * at {@link ImmutableCollection}. + */ +public abstract class ImmutableBiMap extends ImmutableMap + implements BiMap { + + /** + * Not supported. Use {@link ImmutableBiMap#toImmutableBiMap} instead. This method exists only to + * hide {@link ImmutableMap#toImmutableMap(Function, Function)} from consumers of {@code + * ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + */ + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. This method does not make sense for {@code BiMap}. This method exists only to + * hide {@link ImmutableMap#toImmutableMap(Function, Function, BinaryOperator)} from consumers of + * {@code ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + */ + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableBiMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableBiMap} in encounter order. + * + *

If the mapped keys or values contain duplicates (according to {@link + * Object#equals(Object)}), an {@code IllegalArgumentException} is thrown when the collection + * operation is performed. (This differs from the {@code Collector} returned by {@link + * Collectors#toMap(Function, Function)}, which throws an {@code IllegalStateException}.) + */ + public static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + Objects.requireNonNull(keyFunction); + Objects.requireNonNull(valueFunction); + return Collector.of( + ImmutableBiMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableBiMap.Builder::combine, + ImmutableBiMap.Builder::build, + new Collector.Characteristics[0]); + } + + /** + * Returns the empty bimap. + * + *

Performance note: the instance returned is a singleton. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableBiMap of() { + return (ImmutableBiMap) RegularImmutableBiMap.EMPTY; + } + + /** + * Returns an immutable bimap containing a single entry. + */ + public static ImmutableBiMap of(K k1, V v1) { + return new SingletonImmutableBiMap<>(k1, v1); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + @SuppressWarnings("unchecked") + public static ImmutableBiMap of(K k1, V v1, K k2, V v2) { + return RegularImmutableBiMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + @SuppressWarnings("unchecked") + public static ImmutableBiMap of(K k1, V v1, K k2, V v2, K k3, V v3) { + return RegularImmutableBiMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + @SuppressWarnings("unchecked") + public static ImmutableBiMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + @SuppressWarnings("unchecked") + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9, + K k10, + V v10) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9), + entryOf(k10, v10)); + } + + // looking for of() with > 10 entries? Use the builder or ofEntries instead. + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are provided + */ + @SafeVarargs + public static ImmutableBiMap ofEntries(Entry... entries) { + @SuppressWarnings("unchecked") // we will only ever read these + Entry[] entries2 = (Entry[]) entries; + return RegularImmutableBiMap.fromEntries(entries2); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Returns a new builder, expecting the specified number of entries to be added. + * + *

If {@code expectedSize} is exactly the number of entries added to the builder before {@link + * Builder#build()} is called, the builder is likely to perform better than an unsized {@link + * #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of entries added to the builder. + */ + public static Builder builderWithExpectedSize(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + return new Builder<>(expectedSize); + } + + /** + * A builder for creating immutable bimap instances, especially {@code public static final} bimaps + * ("constant bimaps"). Example: + * + *

{@code
+     * static final ImmutableBiMap WORD_TO_INT =
+     *     new ImmutableBiMap.Builder()
+     *         .put("one", 1)
+     *         .put("two", 2)
+     *         .put("three", 3)
+     *         .buildOrThrow();
+     * }
+ * + *

For small immutable bimaps, the {@code ImmutableBiMap.of()} methods are even more + * convenient. + * + *

By default, a {@code Builder} will generate bimaps that iterate over entries in the order + * they were inserted into the builder. For example, in the above example, {@code + * WORD_TO_INT.entrySet()} is guaranteed to iterate over the entries in the order {@code "one"=1, + * "two"=2, "three"=3}, and {@code keySet()} and {@code values()} respect the same order. If you + * want a different order, consider using {@link #orderEntriesByValue(Comparator)}, which changes + * this builder to sort entries by value. + * + *

Builder instances can be reused - it is safe to call {@link #buildOrThrow} multiple times to + * build multiple bimaps in series. Each bimap is a superset of the bimaps created before it. + */ + public static final class Builder extends ImmutableMap.Builder { + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableBiMap#builder}. + */ + public Builder() { + } + + Builder(int size) { + super(size); + } + + /** + * Associates {@code key} with {@code value} in the built bimap. Duplicate keys or values are + * not allowed, and will cause {@link #build()} to fail. + */ + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Adds the given {@code entry} to the bimap. Duplicate keys or values are not allowed, and will + * cause {@link #build()} to fail. + */ + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + /** + * Associates all of the given map's keys and values in the built bimap. Duplicate keys or + * values are not allowed, and will cause {@link #build()} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @Override + public Builder putAll(Map map) { + super.putAll(map); + return this; + } + + /** + * Adds all of the given entries to the built bimap. Duplicate keys or values are not allowed, + * and will cause {@link #build()} to fail. + * + * @throws NullPointerException if any key, value, or entry is null + */ + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + /** + * Configures this {@code Builder} to order entries by value according to the specified + * comparator. + * + *

The sort order is stable, that is, if two entries have values that compare as equivalent, + * the entry that was inserted first will be first in the built map's iteration order. + * + * @throws IllegalStateException if this method was already called + */ + @Override + public Builder orderEntriesByValue(Comparator valueComparator) { + super.orderEntriesByValue(valueComparator); + return this; + } + + @Override + Builder combine(ImmutableMap.Builder builder) { + super.combine(builder); + return this; + } + + /** + * Returns a newly-created immutable bimap. The iteration order of the returned bimap is the + * order in which entries were inserted into the builder, unless {@link #orderEntriesByValue} + * was called, in which case entries are sorted by value. + * + *

Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method + * will throw an exception if there are duplicate keys or values. The {@code build()} method + * will soon be deprecated. + * + * @throws IllegalArgumentException if duplicate keys or values were added + */ + @Override + public ImmutableBiMap build() { + return buildOrThrow(); + } + + /** + * Returns a newly-created immutable bimap, or throws an exception if any key or value was added + * more than once. The iteration order of the returned bimap is the order in which entries were + * inserted into the builder, unless {@link #orderEntriesByValue} was called, in which case + * entries are sorted by value. + * + * @throws IllegalArgumentException if duplicate keys or values were added + */ + @Override + public ImmutableBiMap buildOrThrow() { + switch (size) { + case 0: + return of(); + case 1: + // requireNonNull is safe because the first `size` elements have been filled in. + Entry onlyEntry = requireNonNull(entries[0]); + return of(onlyEntry.getKey(), onlyEntry.getValue()); + default: + /* + * If entries is full, or if hash flooding is detected, then this implementation may end + * up using the entries array directly and writing over the entry objects with + * non-terminal entries, but this is safe; if this Builder is used further, it will grow + * the entries array (so it can't affect the original array), and future build() calls + * will always copy any entry objects that cannot be safely reused. + */ + if (valueComparator != null) { + if (entriesUsed) { + entries = Arrays.copyOf(entries, size); + } + Arrays.sort( + entries, + 0, + size, + Ordering.from(valueComparator).onResultOf(valueFunction()) + ); + } + entriesUsed = true; + return RegularImmutableBiMap.fromEntryArray(size, entries); + } + } + + /** + * Throws {@link UnsupportedOperationException}. This method is inherited from {@link + * ImmutableMap.Builder}, but it does not make sense for bimaps. + * + * @throws UnsupportedOperationException always + */ + @Override + public ImmutableBiMap buildKeepingLast() { + throw new UnsupportedOperationException("Not supported for bimaps"); + } + } + + /** + * Returns an immutable bimap containing the same entries as {@code map}. If {@code map} somehow + * contains entries with duplicate keys (for example, if it is a {@code SortedMap} whose + * comparator is not consistent with equals), the results of this method are undefined. + * + *

The returned {@code BiMap} iterates over entries in the same order as the {@code entrySet} + * of the original map. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws IllegalArgumentException if two keys have the same value or two values have the same + * key + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableBiMap copyOf(Map map) { + if (map instanceof ImmutableBiMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableBiMap bimap = (ImmutableBiMap) map; + // TODO(lowasser): if we need to make a copy of a BiMap because the + // forward map is a view, don't make a copy of the non-view delegate map + if (!bimap.isPartialView()) { + return bimap; + } + } + return copyOf(map.entrySet()); + } + + /** + * Returns an immutable bimap containing the given entries. The returned bimap iterates over + * entries in the same order as the original iterable. + * + * @throws IllegalArgumentException if two keys have the same value or two values have the same + * key + * @throws NullPointerException if any key, value, or entry is null + */ + public static ImmutableBiMap copyOf(Iterable> entries) { + @SuppressWarnings("unchecked") // we'll only be using getKey and getValue, which are covariant + Entry[] entryArray = (Entry[]) toArray(entries, EMPTY_ENTRY_ARRAY); + switch (entryArray.length) { + case 0: + return of(); + case 1: + Entry entry = entryArray[0]; + return of(entry.getKey(), entry.getValue()); + default: + /* + * The current implementation will end up using entryArray directly, though it will write + * over the (arbitrary, potentially mutable) Entry objects actually stored in entryArray. + */ + return RegularImmutableBiMap.fromEntries(entryArray); + } + } + + ImmutableBiMap() { + } + + /** + * {@inheritDoc} + * + *

The inverse of an {@code ImmutableBiMap} is another {@code ImmutableBiMap}. + */ + @Override + public abstract ImmutableBiMap inverse(); + + /** + * Returns an immutable set of the values in this map, in the same order they appear in {@link + * #entrySet()}. + */ + @Override + public ImmutableSet values() { + return inverse().keySet(); + } + + @Override + final ImmutableSet createValues() { + throw new AssertionError("should never be called"); + } + + /** + * Guaranteed to throw an exception and leave the bimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableCollection.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableCollection.java new file mode 100644 index 0000000..6f371af --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableCollection.java @@ -0,0 +1,635 @@ +package org.xbib.datastructures.immutable; + +import java.lang.reflect.Array; +import java.util.AbstractCollection; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.stream.IntStream; +import static org.xbib.datastructures.immutable.ImmutableList.castOrCopyToCollection; + +/** + * A {@link Collection} whose contents will never change, and which offers a few additional + * guarantees detailed below. + * + *

Warning: avoid direct usage of {@link ImmutableCollection} as a type (just as + * with {@link Collection} itself). Prefer subtypes such as {@link ImmutableSet} or {@link + * ImmutableList}, which have well-defined {@link #equals} semantics, thus avoiding a common source + * of bugs and confusion. + * + *

About all {@code Immutable-} collections

+ * + *

The remainder of this documentation applies to every public {@code Immutable-} type in this + * package, whether it is a subtype of {@code ImmutableCollection} or not. + * + *

Guarantees

+ * + *

Each makes the following guarantees: + * + *

    + *
  • Shallow immutability. Elements can never be added, removed or replaced in this + * collection. This is a stronger guarantee than that of {@link + * Collections#unmodifiableCollection}, whose contents change whenever the wrapped collection + * is modified. + *
  • Null-hostility. This collection will never contain a null element. + *
  • Deterministic iteration. The iteration order is always well-defined, depending on + * how the collection was created. Typically this is insertion order unless an explicit + * ordering is otherwise specified (e.g. {@link ImmutableSortedSet#naturalOrder}). See the + * appropriate factory method for details. View collections iterate in the same order as + * the parent, except as noted. + *
  • Thread safety. It is safe to access this collection concurrently from multiple + * threads. + *
  • Integrity. This type cannot be subclassed outside this package (which would allow + * these guarantees to be violated). + *
+ * + *

"Interfaces", not implementations

+ * + *

These are classes instead of interfaces to prevent external subtyping, but should be thought + * of as interfaces in every important sense. Each public class such as {@link ImmutableSet} is a + * type offering meaningful behavioral guarantees. This is substantially different from the + * case of (say) {@link HashSet}, which is an implementation, with semantics that were + * largely defined by its supertype. + * + *

For field types and method return types, you should generally use the immutable type (such as + * {@link ImmutableList}) instead of the general collection interface type (such as {@link List}). + * This communicates to your callers all of the semantic guarantees listed above, which is almost + * always very useful information. + * + *

On the other hand, a parameter type of {@link ImmutableList} is generally a nuisance to + * callers. Instead, accept {@link Iterable} and have your method or constructor body pass it to the + * appropriate {@code copyOf} method itself. + * + *

Expressing the immutability guarantee directly in the type that user code references is a + * powerful advantage. Although Java offers certain immutable collection factory methods, such as + * {@link Collections#singleton(Object)} and {@code Set.of}, + * we recommend using these classes instead for this reason (as well as for consistency). + * + *

Creation

+ * + *

Except for logically "abstract" types like {@code ImmutableCollection} itself, each {@code + * Immutable} type provides the static operations you need to obtain instances of that type. These + * usually include: + * + *

    + *
  • Static methods named {@code of}, accepting an explicit list of elements or entries. + *
  • Static methods named {@code copyOf} (or {@code copyOfSorted}), accepting an existing + * collection whose contents should be copied. + *
  • A static nested {@code Builder} class which can be used to populate a new immutable + * instance. + *
+ * + *

Warnings

+ * + *
    + *
  • Warning: as with any collection, it is almost always a bad idea to modify an element + * (in a way that affects its {@link Object#equals} behavior) while it is contained in a + * collection. Undefined behavior and bugs will result. It's generally best to avoid using + * mutable objects as elements at all, as many users may expect your "immutable" object to be + * deeply immutable. + *
+ * + *

Performance notes

+ * + *
    + *
  • Implementations can be generally assumed to prioritize memory efficiency, then speed of + * access, and lastly speed of creation. + *
  • The {@code copyOf} methods will sometimes recognize that the actual copy operation is + * unnecessary; for example, {@code copyOf(copyOf(anArrayList))} should copy the data only + * once. This reduces the expense of habitually making defensive copies at API boundaries. + * However, the precise conditions for skipping the copy operation are undefined. + *
  • Warning: a view collection such as {@link ImmutableMap#keySet()} or {@link + * ImmutableList#subList} may retain a reference to the entire data set, preventing it from + * being garbage collected. If some of the data is no longer reachable through other means, + * this constitutes a memory leak. Pass the view collection to the appropriate {@code copyOf} + * method to obtain a correctly-sized copy. + *
  • The performance of using the associated {@code Builder} class can be assumed to be no + * worse, and possibly better, than creating a mutable collection and copying it. + *
  • Implementations generally do not cache hash codes. If your element or key type has a slow + * {@code hashCode} implementation, it should cache it itself. + *
+ * + *

Example usage

+ * + *
{@code
+ * class Foo {
+ *   private static final ImmutableSet RESERVED_CODES =
+ *       ImmutableSet.of("AZ", "CQ", "ZX");
+ *
+ *   private final ImmutableSet codes;
+ *
+ *   public Foo(Iterable codes) {
+ *     this.codes = ImmutableSet.copyOf(codes);
+ *     checkArgument(Collections.disjoint(this.codes, RESERVED_CODES));
+ *   }
+ * }
+ * }
+ */ +public abstract class ImmutableCollection extends AbstractCollection { + /* + * We expect SIZED (and SUBSIZED, if applicable) to be added by the spliterator factory methods. + * These are properties of the collection as a whole; SIZED and SUBSIZED are more properties of + * the spliterator implementation. + */ + static final int SPLITERATOR_CHARACTERISTICS = + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED; + + ImmutableCollection() { + } + + /** + * Returns an unmodifiable iterator across the elements in this collection. + */ + @Override + public abstract UnmodifiableIterator iterator(); + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(this, SPLITERATOR_CHARACTERISTICS); + } + + private static final Object[] EMPTY_ARRAY = {}; + + @Override + public final Object[] toArray() { + return toArray(EMPTY_ARRAY); + } + + @Override + /* + * This suppression is here for two reasons: + * + * 1. b/192354773 in our checker affects toArray declarations. + * + * 2. `other[size] = null` is unsound. We could "fix" this by requiring callers to pass in an + * array with a nullable element type. But probably they usually want an array with a non-nullable + * type. That said, we could *accept* a `T[]` (which, given that we treat arrays as + * covariant, would still permit a plain `T[]`) and return a plain `T[]`. But of course that would + * require its own suppression, since it is also unsound. toArray(T[]) is just a mess from a + * nullness perspective. The signature below at least has the virtue of being relatively simple. + */ + @SuppressWarnings("nullness") + public final T[] toArray(T[] other) { + Objects.requireNonNull(other); + int size = size(); + + if (other.length < size) { + Object[] internal = internalArray(); + if (internal != null) { + return copy(internal, internalArrayStart(), internalArrayEnd(), other); + } + other = newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + copyIntoArray(other, 0); + return other; + } + + /** + * If this collection is backed by an array of its elements in insertion order, returns it. + */ + Object[] internalArray() { + return null; + } + + /** + * If this collection is backed by an array of its elements in insertion order, returns the offset + * where this collection's elements start. + */ + int internalArrayStart() { + throw new UnsupportedOperationException(); + } + + /** + * If this collection is backed by an array of its elements in insertion order, returns the offset + * where this collection's elements end. + */ + int internalArrayEnd() { + throw new UnsupportedOperationException(); + } + + @Override + public abstract boolean contains(Object object); + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean addAll(Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean removeAll(Collection oldElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean removeIf(Predicate filter) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean retainAll(Collection elementsToKeep) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an {@code ImmutableList} containing the same elements, in the same order, as this + * collection. + * + *

Performance note: in most cases this method can return quickly without actually + * copying anything. The exact circumstances under which the copy is performed are undefined and + * subject to change. + */ + public ImmutableList asList() { + switch (size()) { + case 0: + return ImmutableList.of(); + case 1: + return ImmutableList.of(iterator().next()); + default: + return new RegularImmutableAsList(this, toArray()); + } + } + + /** + * Returns {@code true} if this immutable collection's implementation contains references to + * user-created objects that aren't accessible via this collection's methods. This is generally + * used to determine whether {@code copyOf} implementations should make an explicit copy to avoid + * memory leaks. + */ + abstract boolean isPartialView(); + + /** + * Copies the contents of this immutable collection into the specified array at the specified + * offset. Returns {@code offset + size()}. + */ + int copyIntoArray(Object[] dst, int offset) { + for (E e : this) { + dst[offset++] = e; + } + return offset; + } + + /** + * Abstract base class for builders of {@link ImmutableCollection} types. + */ + public abstract static class Builder { + static final int DEFAULT_INITIAL_CAPACITY = 4; + + static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; + // guaranteed to be >= newCapacity + } + return newCapacity; + } + + Builder() { + } + + /** + * Adds {@code element} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class covariantly returns its own type from this method. + * + * @param element the element to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code element} is null + */ + public abstract Builder add(E element); + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class overrides this method in order to covariantly return its own + * type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public Builder add(E... elements) { + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class overrides this method in order to covariantly return its own + * type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public Builder addAll(Iterable elements) { + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class overrides this method in order to covariantly return its own + * type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + public Builder addAll(Iterator elements) { + while (elements.hasNext()) { + add(elements.next()); + } + return this; + } + + /** + * Returns a newly-created {@code ImmutableCollection} of the appropriate type, containing the + * elements provided to this builder. + * + *

Note that each builder class covariantly returns the appropriate type of {@code + * ImmutableCollection} from this method. + */ + public abstract ImmutableCollection build(); + } + + /** + * Equivalent to Arrays.copyOfRange(source, from, to, arrayOfType.getClass()). + */ + /* + * Arrays are a mess from a nullness perspective, and Class instances for object-array types are + * even worse. For now, we just suppress and move on with our lives. + * + * - https://github.com/jspecify/jspecify/issues/65 + * + * - https://github.com/jspecify/jdk/commit/71d826792b8c7ef95d492c50a274deab938f2552 + */ + @SuppressWarnings("nullness") + private static T[] copy(Object[] source, int from, int to, T[] arrayOfType) { + return Arrays.copyOfRange(source, from, to, (Class) arrayOfType.getClass()); + } + + public static T[] newArray(Class type, int length) { + return (T[]) Array.newInstance(type, length); + } + + /** + * Returns a new array of the given length with the same type as a reference array. + * + * @param reference any array of the desired type + * @param length the length of the new array + */ + public static T[] newArray(T[] reference, int length) { + Class type = reference.getClass().getComponentType(); + // the cast is safe because + // result.getClass() == reference.getClass().getComponentType() + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(type, length); + return result; + } + + protected static Spliterator indexed(int size, + int extraCharacteristics, + IntFunction function) { + return indexed(size, extraCharacteristics, function, null); + } + + protected static Spliterator indexed(int size, + int extraCharacteristics, + IntFunction function, Comparator comparator) { + if (comparator != null) { + if ((extraCharacteristics & Spliterator.SORTED) != 0) { + throw new IllegalArgumentException(); + } + } + class WithCharacteristics implements Spliterator { + private final Spliterator.OfInt delegate; + + WithCharacteristics(Spliterator.OfInt delegate) { + this.delegate = delegate; + } + + @Override + public boolean tryAdvance(Consumer action) { + return delegate.tryAdvance((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public void forEachRemaining(Consumer action) { + delegate.forEachRemaining((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public Spliterator trySplit() { + Spliterator.OfInt split = delegate.trySplit(); + return (split == null) ? null : new WithCharacteristics(split); + } + + @Override + public long estimateSize() { + return delegate.estimateSize(); + } + + @Override + public int characteristics() { + return Spliterator.ORDERED + | Spliterator.SIZED + | Spliterator.SUBSIZED + | extraCharacteristics; + } + + @Override + public Comparator getComparator() { + if (hasCharacteristics(Spliterator.SORTED)) { + return comparator; + } else { + throw new IllegalStateException(); + } + } + } + return new WithCharacteristics(IntStream.range(0, size).spliterator()); + } + + /** + * Returns an iterator containing the elements of {@code array} in order. The returned iterator is + * a view of the array; subsequent changes to the array will be reflected in the iterator. + * + *

Note: It is often preferable to represent your data using a collection type, for + * example using {@link Arrays#asList(Object[])}, making this method unnecessary. + * + *

The {@code Iterable} equivalent of this method is either {@link Arrays#asList(Object[])}, + * {@link ImmutableList#copyOf(Object[])}}, or {@link ImmutableList#of}. + */ + @SafeVarargs + protected static UnmodifiableIterator forArray(T... array) { + return forArray(array, 0, array.length, 0); + } + + /** + * Returns a list iterator containing the elements in the specified range of {@code array} in + * order, starting at the specified index. + * + *

The {@code Iterable} equivalent of this method is {@code + * Arrays.asList(array).subList(offset, offset + length).listIterator(index)}. + */ + protected static UnmodifiableListIterator forArray(T[] array, + int offset, int length, int index) { + if (length < 0) { + throw new IllegalArgumentException(); + } + int end = offset + length; + + // Technically we should give a slightly more descriptive error on overflow + checkPositionIndexes(offset, end, array.length); + checkPositionIndex(index, length, "index"); + if (length == 0) { + return emptyListIterator(); + } + return new ArrayItr<>(array, offset, length, index); + } + + @SuppressWarnings("unchecked") + private static UnmodifiableListIterator emptyListIterator() { + return (UnmodifiableListIterator) ArrayItr.EMPTY; + } + + protected static int checkPositionIndex(int index, int size, String desc) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); + } + return index; + } + + protected static void checkPositionIndexes(int start, int end, int size) { + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); + } + } + + private static String badPositionIndexes(int start, int end, int size) { + if (start < 0 || start > size) { + return badPositionIndex(start, size, "start index"); + } + if (end < 0 || end > size) { + return badPositionIndex(end, size, "end index"); + } + // end < start + return String.format("end index (%s) must not be less than start index (%s)", end, start); + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return String.format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index > size + return String.format("%s (%s) must not be greater than size (%s)", desc, index, size); + } + } + + protected static final class ArrayItr + extends AbstractIndexedListIterator { + static final UnmodifiableListIterator EMPTY = new ArrayItr<>(new Object[0], 0, 0, 0); + + private final T[] array; + private final int offset; + + ArrayItr(T[] array, int offset, int length, int index) { + super(length, index); + this.array = array; + this.offset = offset; + } + + @Override + protected T get(int index) { + return array[offset + index]; + } + } + + public static T[] toArray(Iterable iterable, Class type) { + return toArray(iterable, newArray(type, 0)); + } + + public static T[] toArray(Iterable iterable, T[] array) { + Collection collection = castOrCopyToCollection(iterable); + return collection.toArray(array); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEntry.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEntry.java new file mode 100644 index 0000000..b67fde5 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEntry.java @@ -0,0 +1,68 @@ +package org.xbib.datastructures.immutable; + +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +public class ImmutableEntry extends AbstractMapEntry { + final K key; + final V value; + + public ImmutableEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public final K getKey() { + return key; + } + + @Override + public final V getValue() { + return value; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + /** + * Returns an unmodifiable view of the specified map entry. The {@link ImmutableEntry#setValue} operation + * throws an {@link UnsupportedOperationException}. This also has the side effect of redefining + * {@code equals} to comply with the Entry contract, to avoid a possible nefarious implementation + * of equals. + * + * @param entry the entry for which to return an unmodifiable view + * @return an unmodifiable view of the entry + */ + static Map.Entry unmodifiableEntry(final Map.Entry entry) { + Objects.requireNonNull(entry); + return new AbstractMapEntry() { + @Override + public K getKey() { + return entry.getKey(); + } + + @Override + public V getValue() { + return entry.getValue(); + } + }; + } + + static UnmodifiableIterator> unmodifiableEntryIterator(final Iterator> entryIterator) { + return new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public Map.Entry next() { + return unmodifiableEntry(entryIterator.next()); + } + }; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumMap.java new file mode 100644 index 0000000..f892ee4 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumMap.java @@ -0,0 +1,104 @@ +package org.xbib.datastructures.immutable; + +import java.util.EnumMap; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import static org.xbib.datastructures.immutable.UnmodifiableIterator.unmodifiableIterator; + +/** + * Implementation of {@link ImmutableMap} backed by a non-empty {@link EnumMap}. + */ +public final class ImmutableEnumMap, V> extends IteratorBasedImmutableMap { + static , V> ImmutableMap asImmutable(EnumMap map) { + return switch (map.size()) { + case 0 -> ImmutableMap.of(); + case 1 -> { + Entry entry = getOnlyElement(map.entrySet().iterator()); + yield ImmutableMap.of(entry.getKey(), entry.getValue()); + } + default -> new ImmutableEnumMap<>(map); + }; + } + + private final transient EnumMap delegate; + + private ImmutableEnumMap(EnumMap delegate) { + this.delegate = delegate; + if (delegate.isEmpty()) { + throw new IllegalArgumentException(); + } + } + + @Override + UnmodifiableIterator keyIterator() { + return unmodifiableIterator(delegate.keySet().iterator()); + } + + @Override + Spliterator keySpliterator() { + return delegate.keySet().spliterator(); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public V get(Object key) { + return delegate.get(key); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableEnumMap) { + object = ((ImmutableEnumMap) object).delegate; + } + return delegate.equals(object); + } + + @Override + UnmodifiableIterator> entryIterator() { + return ImmutableEntry.unmodifiableEntryIterator(delegate.entrySet().iterator()); + } + + @Override + Spliterator> entrySpliterator() { + return Spliterators.map(delegate.entrySet().spliterator(), ImmutableEntry::unmodifiableEntry); + } + + @Override + public void forEach(BiConsumer action) { + delegate.forEach(action); + } + + @Override + boolean isPartialView() { + return false; + } + + static T getOnlyElement(Iterator iterator) { + T first = iterator.next(); + if (!iterator.hasNext()) { + return first; + } + StringBuilder sb = new StringBuilder().append("expected one element but was: <").append(first); + for (int i = 0; i < 4 && iterator.hasNext(); i++) { + sb.append(", ").append(iterator.next()); + } + if (iterator.hasNext()) { + sb.append(", ..."); + } + sb.append('>'); + throw new IllegalArgumentException(sb.toString()); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumSet.java new file mode 100644 index 0000000..36d43ee --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableEnumSet.java @@ -0,0 +1,99 @@ +package org.xbib.datastructures.immutable; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Spliterator; +import java.util.function.Consumer; +import static org.xbib.datastructures.immutable.ImmutableEnumMap.getOnlyElement; +import static org.xbib.datastructures.immutable.UnmodifiableIterator.unmodifiableIterator; + +/** + * Implementation of {@link ImmutableSet} backed by a non-empty {@link EnumSet}. + */ +final class ImmutableEnumSet> extends ImmutableSet { + static ImmutableSet asImmutable(EnumSet set) { + return switch (set.size()) { + case 0 -> ImmutableSet.of(); + case 1 -> ImmutableSet.of(getOnlyElement(set.iterator())); + default -> new ImmutableEnumSet<>(set); + }; + } + + private final transient EnumSet delegate; + + private ImmutableEnumSet(EnumSet delegate) { + this.delegate = delegate; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public UnmodifiableIterator iterator() { + return unmodifiableIterator(delegate.iterator()); + } + + @Override + public Spliterator spliterator() { + return delegate.spliterator(); + } + + @Override + public void forEach(Consumer action) { + delegate.forEach(action); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override + public boolean containsAll(Collection collection) { + if (collection instanceof ImmutableEnumSet) { + collection = ((ImmutableEnumSet) collection).delegate; + } + return delegate.containsAll(collection); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableEnumSet) { + object = ((ImmutableEnumSet) object).delegate; + } + return delegate.equals(object); + } + + @Override + boolean isHashCodeFast() { + return true; + } + + private transient int hashCode; + + @Override + public int hashCode() { + int result = hashCode; + return (result == 0) ? hashCode = delegate.hashCode() : result; + } + + @Override + public String toString() { + return delegate.toString(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableList.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableList.java new file mode 100644 index 0000000..fdec9be --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableList.java @@ -0,0 +1,949 @@ +package org.xbib.datastructures.immutable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; +import java.util.stream.Collector; +import static java.util.Objects.requireNonNull; + +/** + * A {@link List} whose contents will never change, with many other important properties detailed at + * {@link ImmutableCollection}. + */ +public abstract class ImmutableList extends ImmutableCollection implements List, RandomAccess { + + private static final Collector> TO_IMMUTABLE_LIST = + Collector.of( + ImmutableList::builder, + ImmutableList.Builder::add, + ImmutableList.Builder::combine, + ImmutableList.Builder::build); + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableList}, in encounter order. + */ + public static Collector> toImmutableList() { + return (Collector) TO_IMMUTABLE_LIST; + } + + /** + * Returns the empty immutable list. This list behaves and performs comparably to {@link + * Collections#emptyList}, and is preferable mainly for consistency and maintainability of your + * code. + * + *

Performance note: the instance returned is a singleton. + */ + // Casting to any type is safe because the list will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableList of() { + return (ImmutableList) RegularImmutableList.EMPTY; + } + + /** + * Returns an immutable list containing a single element. This list behaves and performs + * comparably to {@link Collections#singletonList}, but will not accept a null element. It is + * preferable mainly for consistency and maintainability of your code. + * + * @throws NullPointerException if {@code element} is null + */ + public static ImmutableList of(E element) { + return new SingletonImmutableList(element); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2) { + return construct(e1, e2); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3) { + return construct(e1, e2, e3); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4) { + return construct(e1, e2, e3, e4); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5) { + return construct(e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6) { + return construct(e1, e2, e3, e4, e5, e6); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7) { + return construct(e1, e2, e3, e4, e5, e6, e7); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11); + } + + // These go up to eleven. After that, you just get the varargs form, and + // whatever warnings might come along with it. :( + + /** + * Returns an immutable list containing the given elements, in order. + * + *

The array {@code others} must not be longer than {@code Integer.MAX_VALUE - 12}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11, E e12, E... others) { + if (!(others.length <= Integer.MAX_VALUE - 12)) { + throw new IllegalArgumentException("the total number of elements must fit in an int"); + } + Object[] array = new Object[12 + others.length]; + array[0] = e1; + array[1] = e2; + array[2] = e3; + array[3] = e4; + array[4] = e5; + array[5] = e6; + array[6] = e7; + array[7] = e8; + array[8] = e9; + array[9] = e10; + array[10] = e11; + array[11] = e12; + System.arraycopy(others, 0, array, 12, others.length); + return construct(array); + } + + /** + * Returns an immutable list containing the given elements, in order. If {@code elements} is a + * {@link Collection}, this method behaves exactly as {@link #copyOf(Collection)}; otherwise, it + * behaves exactly as {@code copyOf(elements.iterator()}. + * + * @throws NullPointerException if {@code elements} contains a null element + */ + public static ImmutableList copyOf(Iterable elements) { + Objects.requireNonNull(elements); + return (elements instanceof Collection) + ? copyOf((Collection) elements) + : copyOf(elements.iterator()); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

Note that if {@code list} is a {@code List}, then {@code ImmutableList.copyOf(list)} + * returns an {@code ImmutableList} containing each of the strings in {@code list}, while + * ImmutableList.of(list)} returns an {@code ImmutableList>} containing one element + * (the given list itself). + * + *

This method is safe to use even when {@code elements} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code elements} contains a null element + */ + public static ImmutableList copyOf(Collection elements) { + if (elements instanceof ImmutableCollection) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableList list = ((ImmutableCollection) elements).asList(); + return list.isPartialView() ? ImmutableList.asImmutableList(list.toArray()) : list; + } + return construct(elements.toArray()); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if {@code elements} contains a null element + */ + public static ImmutableList copyOf(Iterator elements) { + // We special-case for 0 or 1 elements, but going further is madness. + if (!elements.hasNext()) { + return of(); + } + E first = elements.next(); + if (!elements.hasNext()) { + return of(first); + } else { + return new Builder().add(first).addAll(elements).build(); + } + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if {@code elements} contains a null element + */ + public static ImmutableList copyOf(E[] elements) { + switch (elements.length) { + case 0: + return of(); + case 1: + return of(elements[0]); + default: + return construct(elements.clone()); + } + } + + /** + * Returns an immutable list containing the given elements, sorted according to their natural + * order. The sorting algorithm used is stable, so elements that compare as equal will stay in the + * order in which they appear in the input. + * + *

If your data has no duplicates, or you wish to deduplicate elements, use {@code + * ImmutableSortedSet.copyOf(elements)}; if you want a {@code List} you can use its {@code + * asList()} view. + * + *

Java 8 users: If you want to convert a {@link java.util.stream.Stream} to a sorted + * {@code ImmutableList}, use {@code stream.sorted().collect(toImmutableList())}. + * + * @throws NullPointerException if any element in the input is null + */ + public static > ImmutableList sortedCopyOf(Iterable elements) { + Comparable[] array = toArray(elements, new Comparable[0]); + checkElementsNotNull((Object[]) array); + Arrays.sort(array); + return asImmutableList(array); + } + + /** + * Returns an immutable list containing the given elements, in sorted order relative to the + * specified comparator. The sorting algorithm used is stable, so elements that compare as equal + * will stay in the order in which they appear in the input. + * + *

If your data has no duplicates, or you wish to deduplicate elements, use {@code + * ImmutableSortedSet.copyOf(comparator, elements)}; if you want a {@code List} you can use its + * {@code asList()} view. + * + *

Java 8 users: If you want to convert a {@link java.util.stream.Stream} to a sorted + * {@code ImmutableList}, use {@code stream.sorted(comparator).collect(toImmutableList())}. + * + * @throws NullPointerException if any element in the input is null + */ + public static ImmutableList sortedCopyOf( + Comparator comparator, Iterable elements) { + Objects.requireNonNull(comparator); + @SuppressWarnings("unchecked") // all supported methods are covariant + E[] array = (E[]) toArray(elements); + checkElementsNotNull(array); + Arrays.sort(array, comparator); + return asImmutableList(array); + } + + /** + * Views the array as an immutable list. Checks for nulls; does not copy. + */ + private static ImmutableList construct(Object... elements) { + return asImmutableList(checkElementsNotNull(elements)); + } + + /** + * Views the array as an immutable list. Does not check for nulls; does not copy. + * + *

The array must be internally created. + */ + static ImmutableList asImmutableList(Object[] elements) { + return asImmutableList(elements, elements.length); + } + + /** + * Views the array as an immutable list. Copies if the specified range does not cover the complete + * array. Does not check for nulls. + */ + static ImmutableList asImmutableList(Object[] elements, int length) { + switch (length) { + case 0: + return of(); + case 1: + /* + * requireNonNull is safe because the callers promise to put non-null objects in the first + * `length` array elements. + */ + @SuppressWarnings("unchecked") // our callers put only E instances into the array + E onlyElement = (E) requireNonNull(elements[0]); + return of(onlyElement); + default: + /* + * The suppression is safe because the callers promise to put non-null objects in the first + * `length` array elements. + */ + @SuppressWarnings("nullness") + Object[] elementsWithoutTrailingNulls = + length < elements.length ? Arrays.copyOf(elements, length) : elements; + return new RegularImmutableList(elementsWithoutTrailingNulls); + } + } + + ImmutableList() { + } + + // This declaration is needed to make List.iterator() and + // ImmutableCollection.iterator() consistent. + @Override + public UnmodifiableIterator iterator() { + return listIterator(); + } + + @Override + public UnmodifiableListIterator listIterator() { + return listIterator(0); + } + + @Override + public UnmodifiableListIterator listIterator(int index) { + return new AbstractIndexedListIterator(size(), index) { + @Override + protected E get(int index) { + return ImmutableList.this.get(index); + } + }; + } + + @Override + public void forEach(Consumer consumer) { + Objects.requireNonNull(consumer); + int n = size(); + for (int i = 0; i < n; i++) { + consumer.accept(get(i)); + } + } + + @Override + public int indexOf(Object object) { + return (object == null) ? -1 : indexOfImpl(this, object); + } + + @Override + public int lastIndexOf(Object object) { + return (object == null) ? -1 : lastIndexOfImpl(this, object); + } + + @Override + public boolean contains(Object object) { + return indexOf(object) >= 0; + } + + // constrain the return type to ImmutableList + + /** + * Returns an immutable list of the elements between the specified {@code fromIndex}, inclusive, + * and {@code toIndex}, exclusive. (If {@code fromIndex} and {@code toIndex} are equal, the empty + * immutable list is returned.) + */ + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); + int length = toIndex - fromIndex; + if (length == size()) { + return this; + } else if (length == 0) { + return of(); + } else if (length == 1) { + return of(get(fromIndex)); + } else { + return subListUnchecked(fromIndex, toIndex); + } + } + + /** + * Called by the default implementation of {@link #subList} when {@code toIndex - fromIndex > 1}, + * after index validation has already been performed. + */ + ImmutableList subListUnchecked(int fromIndex, int toIndex) { + return new SubList(fromIndex, toIndex - fromIndex); + } + + class SubList extends ImmutableList { + final transient int offset; + final transient int length; + + SubList(int offset, int length) { + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @Override + public E get(int index) { + checkElementIndex(index, length, "index"); + return ImmutableList.this.get(index + offset); + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, length); + return ImmutableList.this.subList(fromIndex + offset, toIndex + offset); + } + + @Override + boolean isPartialView() { + return true; + } + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean addAll(int index, Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final E set(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void add(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final E remove(int index) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void replaceAll(UnaryOperator operator) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void sort(Comparator c) { + throw new UnsupportedOperationException(); + } + + @Override + public Spliterator spliterator() { + return indexed(size(), SPLITERATOR_CHARACTERISTICS, this::get); + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + // this loop is faster for RandomAccess instances, which ImmutableLists are + int size = size(); + for (int i = 0; i < size; i++) { + dst[offset + i] = get(i); + } + return offset + size; + } + + /** + * Returns a view of this immutable list in reverse order. For example, {@code ImmutableList.of(1, + * 2, 3).reverse()} is equivalent to {@code ImmutableList.of(3, 2, 1)}. + * + * @return a view of this immutable list in reverse order + */ + public ImmutableList reverse() { + return (size() <= 1) ? this : new ReverseImmutableList(this); + } + + private static class ReverseImmutableList extends ImmutableList { + private final transient ImmutableList forwardList; + + ReverseImmutableList(ImmutableList backingList) { + this.forwardList = backingList; + } + + private int reverseIndex(int index) { + return (size() - 1) - index; + } + + private int reversePosition(int index) { + return size() - index; + } + + @Override + public ImmutableList reverse() { + return forwardList; + } + + @Override + public boolean contains(Object object) { + return forwardList.contains(object); + } + + @Override + public int indexOf(Object object) { + int index = forwardList.lastIndexOf(object); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override + public int lastIndexOf(Object object) { + int index = forwardList.indexOf(object); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); + return forwardList.subList(reversePosition(toIndex), reversePosition(fromIndex)).reverse(); + } + + @Override + public E get(int index) { + checkElementIndex(index, size(), "index"); + return forwardList.get(reverseIndex(index)); + } + + @Override + public int size() { + return forwardList.size(); + } + + @Override + boolean isPartialView() { + return forwardList.isPartialView(); + } + } + + @Override + public boolean equals(Object obj) { + return equalsImpl(this, obj); + } + + @Override + public int hashCode() { + int hashCode = 1; + int n = size(); + for (int i = 0; i < n; i++) { + hashCode = 31 * hashCode + get(i).hashCode(); + hashCode = ~~hashCode; + } + return hashCode; + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a new builder, expecting the specified number of elements to be added. + * + *

If {@code expectedSize} is exactly the number of elements added to the builder before {@link + * Builder#build} is called, the builder is likely to perform better than an unsized {@link + * #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of elements added to the builder. + */ + public static Builder builderWithExpectedSize(int expectedSize) { + if (expectedSize < 0) { + throw new IllegalArgumentException("expectedSize"); + } + return new Builder(expectedSize); + } + + /** + * A builder for creating immutable list instances, especially {@code public static final} lists + * ("constant lists"). Example: + * + *

{@code
+     * public static final ImmutableList GOOGLE_COLORS
+     *     = new ImmutableList.Builder()
+     *         .addAll(WEBSAFE_COLORS)
+     *         .add(new Color(0, 191, 255))
+     *         .build();
+     * }
+ * + *

Elements appear in the resulting list in the same order they were added to the builder. + * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple lists in series. Each new list contains all the elements of the ones created before + * it. + */ + public static final class Builder extends ImmutableCollection.Builder { + // The first `size` elements are non-null. + Object[] contents; + private int size; + private boolean forceCopy; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableList#builder}. + */ + public Builder() { + this(DEFAULT_INITIAL_CAPACITY); + } + + Builder(int capacity) { + this.contents = new Object[capacity]; + this.size = 0; + } + + private void getReadyToExpandTo(int minCapacity) { + if (contents.length < minCapacity) { + this.contents = Arrays.copyOf(contents, expandedCapacity(contents.length, minCapacity)); + forceCopy = false; + } else if (forceCopy) { + contents = Arrays.copyOf(contents, contents.length); + forceCopy = false; + } + } + + /** + * Adds {@code element} to the {@code ImmutableList}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override + public Builder add(E element) { + Objects.requireNonNull(element); + getReadyToExpandTo(size + 1); + contents[size++] = element; + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder add(E... elements) { + checkElementsNotNull(elements); + add(elements, elements.length); + return this; + } + + private void add(Object[] elements, int n) { + getReadyToExpandTo(size + n); + /* + * The following call is not statically checked, since arraycopy accepts plain Object for its + * parameters. If it were statically checked, the checker would still be OK with it, since + * we're copying into a `contents` array whose type allows it to contain nulls. Still, it's + * worth noting that we promise not to put nulls into the array in the first `size` elements. + * We uphold that promise here because our callers promise that `elements` will not contain + * nulls in its first `n` elements. + */ + System.arraycopy(elements, 0, contents, size, n); + size += n; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder addAll(Iterable elements) { + Objects.requireNonNull(elements); + if (elements instanceof Collection collection) { + getReadyToExpandTo(size + collection.size()); + if (collection instanceof ImmutableCollection immutableCollection) { + size = immutableCollection.copyIntoArray(contents, size); + return this; + } + } + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterator} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + Builder combine(Builder builder) { + Objects.requireNonNull(builder); + add(builder.contents, builder.size); + return this; + } + + /** + * Returns a newly-created {@code ImmutableList} based on the contents of the {@code Builder}. + */ + @Override + public ImmutableList build() { + forceCopy = true; + return asImmutableList(contents, size); + } + } + + private static Object[] checkElementsNotNull(Object... array) { + return checkElementsNotNull(array, array.length); + } + + private static Object[] checkElementsNotNull(Object[] array, int length) { + for (int i = 0; i < length; i++) { + checkElementNotNull(array[i], i); + } + return array; + } + + private static Object checkElementNotNull(Object element, int index) { + if (element == null) { + throw new NullPointerException("at index " + index); + } + return element; + } + + public static int checkElementIndex(int index, int size, String desc) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); + } + return index; + } + + private static String badElementIndex(int index, int size, String desc) { + if (index < 0) { + return String.format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index >= size + return String.format("%s (%s) must be less than size (%s)", desc, index, size); + } + } + + public static Object[] toArray(Iterable iterable) { + return castOrCopyToCollection(iterable).toArray(); + } + + public static Collection castOrCopyToCollection(Iterable iterable) { + return (iterable instanceof Collection) + ? (Collection) iterable + : newArrayList(iterable.iterator()); + } + + public static ArrayList newArrayList(Iterator elements) { + ArrayList list = new ArrayList<>(); + addAll(list, elements); + return list; + } + + static boolean addAll(Collection addTo, Iterator iterator) { + Objects.requireNonNull(addTo); + Objects.requireNonNull(iterator); + boolean wasModified = false; + while (iterator.hasNext()) { + wasModified |= addTo.add(iterator.next()); + } + return wasModified; + } + + private static boolean equalsImpl(List thisList, Object other) { + if (other == Objects.requireNonNull(thisList)) { + return true; + } + if (!(other instanceof List otherList)) { + return false; + } + int size = thisList.size(); + if (size != otherList.size()) { + return false; + } + if (thisList instanceof RandomAccess && otherList instanceof RandomAccess) { + // avoid allocation and use the faster loop + for (int i = 0; i < size; i++) { + if (!Objects.equals(thisList.get(i), otherList.get(i))) { + return false; + } + } + return true; + } else { + return elementsEqual(thisList.iterator(), otherList.iterator()); + } + } + + private static boolean elementsEqual(Iterator iterator1, Iterator iterator2) { + while (iterator1.hasNext()) { + if (!iterator2.hasNext()) { + return false; + } + Object o1 = iterator1.next(); + Object o2 = iterator2.next(); + if (!Objects.equals(o1, o2)) { + return false; + } + } + return !iterator2.hasNext(); + } + + private static int indexOfImpl(List list, Object element) { + if (list instanceof RandomAccess) { + return indexOfRandomAccess(list, element); + } else { + ListIterator listIterator = list.listIterator(); + while (listIterator.hasNext()) { + if (Objects.equals(element, listIterator.next())) { + return listIterator.previousIndex(); + } + } + return -1; + } + } + + private static int indexOfRandomAccess(List list, Object element) { + int size = list.size(); + if (element == null) { + for (int i = 0; i < size; i++) { + if (list.get(i) == null) { + return i; + } + } + } else { + for (int i = 0; i < size; i++) { + if (element.equals(list.get(i))) { + return i; + } + } + } + return -1; + } + + static int lastIndexOfImpl(List list, Object element) { + if (list instanceof RandomAccess) { + return lastIndexOfRandomAccess(list, element); + } else { + ListIterator listIterator = list.listIterator(list.size()); + while (listIterator.hasPrevious()) { + if (Objects.equals(element, listIterator.previous())) { + return listIterator.nextIndex(); + } + } + return -1; + } + } + + private static int lastIndexOfRandomAccess(List list, Object element) { + if (element == null) { + for (int i = list.size() - 1; i >= 0; i--) { + if (list.get(i) == null) { + return i; + } + } + } else { + for (int i = list.size() - 1; i >= 0; i--) { + if (element.equals(list.get(i))) { + return i; + } + } + } + return -1; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMap.java new file mode 100644 index 0000000..a0d8db5 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMap.java @@ -0,0 +1,986 @@ +package org.xbib.datastructures.immutable; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.SortedMap; +import java.util.Spliterator; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.xbib.datastructures.immutable.order.Ordering; +import static java.util.Objects.requireNonNull; +import static org.xbib.datastructures.immutable.ImmutableCollection.toArray; +import static org.xbib.datastructures.immutable.ImmutableMapEntry.checkEntryNotNull; + +/** + * A {@link Map} whose contents will never change, with many other important properties detailed at + * {@link ImmutableCollection}. + */ +public abstract class ImmutableMap implements Map { + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableMap} in encounter order. + * + *

If the mapped keys contain duplicates (according to {@link Object#equals(Object)}, an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + */ + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + Objects.requireNonNull(keyFunction); + Objects.requireNonNull(valueFunction); + return Collector.of( + ImmutableMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableMap.Builder::combine, + ImmutableMap.Builder::build); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * + *

If the mapped keys contain duplicates (according to {@link Object#equals(Object)}), the + * values are merged using the specified merging function. Entries will appear in the encounter + * order of the first occurrence of the key. + */ + public static + Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + return toImmutableMap(keyFunction, valueFunction, mergeFunction); + } + + /** + * Returns the empty map. This map behaves and performs comparably to {@link + * Collections#emptyMap}, and is preferable mainly for consistency and maintainability of your + * code. + * + *

Performance note: the instance returned is a singleton. + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of() { + return (ImmutableMap) RegularImmutableMap.EMPTY; + } + + /** + * Returns an immutable map containing a single entry. This map behaves and performs comparably to + * {@link Collections#singletonMap} but will not accept a null key or value. It is preferable + * mainly for consistency and maintainability of your code. + */ + public static ImmutableMap of(K k1, V v1) { + return ImmutableBiMap.of(k1, v1); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + return RegularImmutableMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of(K k1, V v1, K k2, V v2, K k3, V v3) { + return RegularImmutableMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9, + K k10, + V v10) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9), + entryOf(k10, v10)); + } + + // looking for of() with > 10 entries? Use the builder or ofEntries instead. + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + @SafeVarargs + @SuppressWarnings("unchecked") + public static ImmutableMap ofEntries(Entry... entries) { + Entry[] entries2 = (Entry[]) entries; + return RegularImmutableMap.fromEntries(entries2); + } + + /** + * Verifies that {@code key} and {@code value} are non-null, and returns a new immutable entry + * with those values. + * + *

A call to {@link Entry#setValue} on the returned entry will always throw {@link + * UnsupportedOperationException}. + */ + static Entry entryOf(K key, V value) { + return new ImmutableMapEntry<>(key, value); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Returns a new builder, expecting the specified number of entries to be added. + * + *

If {@code expectedSize} is exactly the number of entries added to the builder before {@link + * Builder#build} is called, the builder is likely to perform better than an unsized {@link + * #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of entries added to the builder. + */ + public static Builder builderWithExpectedSize(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + return new Builder<>(expectedSize); + } + + static void checkNoConflict( + boolean safe, String conflictDescription, Object entry1, Object entry2) { + if (!safe) { + throw conflictException(conflictDescription, entry1, entry2); + } + } + + static IllegalArgumentException conflictException( + String conflictDescription, Object entry1, Object entry2) { + return new IllegalArgumentException( + "Multiple entries with same " + conflictDescription + ": " + entry1 + " and " + entry2); + } + + /** + * A builder for creating immutable map instances, especially {@code public static final} maps + * ("constant maps"). Example: + * + *

{@code
+     * static final ImmutableMap WORD_TO_INT =
+     *     new ImmutableMap.Builder()
+     *         .put("one", 1)
+     *         .put("two", 2)
+     *         .put("three", 3)
+     *         .buildOrThrow();
+     * }
+ * + *

For small immutable maps, the {@code ImmutableMap.of()} methods are even more + * convenient. + * + *

By default, a {@code Builder} will generate maps that iterate over entries in the order they + * were inserted into the builder, equivalently to {@code LinkedHashMap}. For example, in the + * above example, {@code WORD_TO_INT.entrySet()} is guaranteed to iterate over the entries in the + * order {@code "one"=1, "two"=2, "three"=3}, and {@code keySet()} and {@code values()} respect + * the same order. If you want a different order, consider using {@link ImmutableSortedMap} to + * sort by keys, or call {@link #orderEntriesByValue(Comparator)}, which changes this builder to + * sort entries by value. + * + *

Builder instances can be reused - it is safe to call {@link #buildOrThrow} multiple times to + * build multiple maps in series. Each map is a superset of the maps created before it. + */ + public static class Builder { + Comparator valueComparator; + Entry[] entries; + int size; + boolean entriesUsed; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableMap#builder}. + */ + public Builder() { + this(ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + Builder(int initialCapacity) { + this.entries = new Entry[initialCapacity]; + this.size = 0; + this.entriesUsed = false; + } + + private void ensureCapacity(int minCapacity) { + if (minCapacity > entries.length) { + entries = + Arrays.copyOf( + entries, ImmutableCollection.Builder.expandedCapacity(entries.length, minCapacity)); + entriesUsed = false; + } + } + + /** + * Associates {@code key} with {@code value} in the built map. If the same key is put more than + * once, {@link #buildOrThrow} will fail, while {@link #buildKeepingLast} will keep the last + * value put for that key. + */ + public Builder put(K key, V value) { + ensureCapacity(size + 1); + Entry entry = entryOf(key, value); + // don't inline this: we want to fail atomically if key or value is null + entries[size++] = entry; + return this; + } + + /** + * Adds the given {@code entry} to the map, making it immutable if necessary. If the same key is + * put more than once, {@link #buildOrThrow} will fail, while {@link #buildKeepingLast} will + * keep the last value put for that key. + */ + public Builder put(Entry entry) { + return put(entry.getKey(), entry.getValue()); + } + + /** + * Associates all of the given map's keys and values in the built map. If the same key is put + * more than once, {@link #buildOrThrow} will fail, while {@link #buildKeepingLast} will keep + * the last value put for that key. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public Builder putAll(Map map) { + return putAll(map.entrySet()); + } + + /** + * Adds all of the given entries to the built map. If the same key is put more than once, {@link + * #buildOrThrow} will fail, while {@link #buildKeepingLast} will keep the last value put for + * that key. + * + * @throws NullPointerException if any key, value, or entry is null + */ + public Builder putAll(Iterable> entries) { + if (entries instanceof Collection) { + ensureCapacity(size + ((Collection) entries).size()); + } + for (Entry entry : entries) { + put(entry); + } + return this; + } + + /** + * Configures this {@code Builder} to order entries by value according to the specified + * comparator. + * + *

The sort order is stable, that is, if two entries have values that compare as equivalent, + * the entry that was inserted first will be first in the built map's iteration order. + * + * @throws IllegalStateException if this method was already called + */ + public Builder orderEntriesByValue(Comparator valueComparator) { + if (this.valueComparator != null) { + throw new IllegalArgumentException("valueComparator was already set"); + } + this.valueComparator = Objects.requireNonNull(valueComparator, "valueComparator"); + return this; + } + + Builder combine(Builder other) { + Objects.requireNonNull(other); + ensureCapacity(this.size + other.size); + System.arraycopy(other.entries, 0, this.entries, this.size, other.size); + this.size += other.size; + return this; + } + + private ImmutableMap build(boolean throwIfDuplicateKeys) { + /* + * If entries is full, or if hash flooding is detected, then this implementation may end up + * using the entries array directly and writing over the entry objects with non-terminal + * entries, but this is safe; if this Builder is used further, it will grow the entries array + * (so it can't affect the original array), and future build() calls will always copy any + * entry objects that cannot be safely reused. + */ + switch (size) { + case 0: + return of(); + case 1: + // requireNonNull is safe because the first `size` elements have been filled in. + Entry onlyEntry = requireNonNull(entries[0]); + return of(onlyEntry.getKey(), onlyEntry.getValue()); + default: + break; + } + // localEntries is an alias for the entries field, except if we end up removing duplicates in + // a copy of the entries array. Likewise, localSize is the same as size except in that case. + // It's possible to keep using this Builder after calling buildKeepingLast(), so we need to + // ensure that its state is not corrupted by removing duplicates that should cause a later + // buildOrThrow() to fail, or by changing the size. + Entry[] localEntries; + int localSize = size; + if (valueComparator == null) { + localEntries = entries; + } else { + if (entriesUsed) { + entries = Arrays.copyOf(entries, size); + } + localEntries = entries; + if (!throwIfDuplicateKeys) { + // We want to retain only the last-put value for any given key, before sorting. + // This could be improved, but orderEntriesByValue is rather rarely used anyway. + @SuppressWarnings("nullness") // entries 0..size-1 are non-null + Entry[] nonNullEntries = localEntries; + localEntries = lastEntryForEachKey(nonNullEntries, size); + localSize = localEntries.length; + } + Arrays.sort( + localEntries, + 0, + localSize, + Ordering.from(valueComparator).onResultOf(valueFunction())); + } + entriesUsed = true; + return RegularImmutableMap.fromEntryArray(localSize, localEntries, throwIfDuplicateKeys); + } + + /** + * Returns a newly-created immutable map. The iteration order of the returned map is the order + * in which entries were inserted into the builder, unless {@link #orderEntriesByValue} was + * called, in which case entries are sorted by value. + * + *

Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method + * will throw an exception if there are duplicate keys. The {@code build()} method will soon be + * deprecated. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableMap build() { + return buildOrThrow(); + } + + /** + * Returns a newly-created immutable map, or throws an exception if any key was added more than + * once. The iteration order of the returned map is the order in which entries were inserted + * into the builder, unless {@link #orderEntriesByValue} was called, in which case entries are + * sorted by value. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableMap buildOrThrow() { + return build(true); + } + + /** + * Returns a newly-created immutable map, using the last value for any key that was added more + * than once. The iteration order of the returned map is the order in which entries were + * inserted into the builder, unless {@link #orderEntriesByValue} was called, in which case + * entries are sorted by value. If a key was added more than once, it appears in iteration order + * based on the first time it was added, again unless {@link #orderEntriesByValue} was called. + * + *

In the current implementation, all values associated with a given key are stored in the + * {@code Builder} object, even though only one of them will be used in the built map. If there + * can be many repeated keys, it may be more space-efficient to use a {@link + * java.util.LinkedHashMap LinkedHashMap} and {@link ImmutableMap#copyOf(Map)} rather than + * {@code ImmutableMap.Builder}. + */ + public ImmutableMap buildKeepingLast() { + return build(false); + } + + private static Entry[] lastEntryForEachKey(Entry[] entries, int size) { + Set seen = new HashSet<>(); + BitSet dups = new BitSet(); // slots that are overridden by a later duplicate key + for (int i = size - 1; i >= 0; i--) { + if (!seen.add(entries[i].getKey())) { + dups.set(i); + } + } + if (dups.isEmpty()) { + return entries; + } + @SuppressWarnings({"rawtypes", "unchecked"}) + Entry[] newEntries = new Entry[size - dups.cardinality()]; + for (int inI = 0, outI = 0; inI < size; inI++) { + if (!dups.get(inI)) { + newEntries[outI++] = entries[inI]; + } + } + return newEntries; + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. The returned map iterates + * over entries in the same order as the {@code entrySet} of the original map. If {@code map} + * somehow contains entries with duplicate keys (for example, if it is a {@code SortedMap} whose + * comparator is not consistent with equals), the results of this method are undefined. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableMap copyOf(Map map) { + if ((map instanceof ImmutableMap) && !(map instanceof SortedMap)) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableMap kvMap = (ImmutableMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } else if (map instanceof EnumMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableMap kvMap = (ImmutableMap) copyOfEnumMap((EnumMap) map); + return kvMap; + } + return copyOf(map.entrySet()); + } + + /** + * Returns an immutable map containing the specified entries. The returned map iterates over + * entries in the same order as the original iterable. + * + * @throws NullPointerException if any key, value, or entry is null + * @throws IllegalArgumentException if two entries have the same key + */ + public static ImmutableMap copyOf( + Iterable> entries) { + @SuppressWarnings("unchecked") // we'll only be using getKey and getValue, which are covariant + Entry[] entryArray = (Entry[]) toArray(entries, EMPTY_ENTRY_ARRAY); + switch (entryArray.length) { + case 0: + return of(); + case 1: + // requireNonNull is safe because the first `size` elements have been filled in. + Entry onlyEntry = requireNonNull(entryArray[0]); + return of(onlyEntry.getKey(), onlyEntry.getValue()); + default: + /* + * The current implementation will end up using entryArray directly, though it will write + * over the (arbitrary, potentially mutable) Entry objects actually stored in entryArray. + */ + return RegularImmutableMap.fromEntries(entryArray); + } + } + + private static , V> ImmutableMap copyOfEnumMap( + EnumMap original) { + EnumMap copy = new EnumMap<>(original); + for (Entry entry : copy.entrySet()) { + checkEntryNotNull(entry.getKey(), entry.getValue()); + } + return ImmutableEnumMap.asImmutable(copy); + } + + static final Entry[] EMPTY_ENTRY_ARRAY = new Entry[0]; + + ImmutableMap() { + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V put(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V putIfAbsent(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean replace(K key, V oldValue, V newValue) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V replace(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V computeIfAbsent(K key, Function mappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V computeIfPresent( + K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V compute( + K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V merge(K key, V value, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Override + public final void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void replaceAll(BiFunction function) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final V remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public boolean containsValue(Object value) { + return values().contains(value); + } + + // Overriding to mark it Nullable + @Override + public abstract V get(Object key); + + @Override + public final V getOrDefault(Object key, V defaultValue) { + /* + * Even though it's weird to pass a defaultValue that is null, some callers do so. Those who + * pass a literal "null" should probably just use `get`, but I would expect other callers to + * pass an expression that *might* be null. This could happen with: + * + * - a `getFooOrDefault(@CheckForNull Foo defaultValue)` method that returns + * `map.getOrDefault(FOO_KEY, defaultValue)` + * + * - a call that consults a chain of maps, as in `mapA.getOrDefault(key, mapB.getOrDefault(key, + * ...))` + * + * So it makes sense for the parameter (and thus the return type) to be @CheckForNull. + * + * Two other points: + * + * 1. We'll want to use something like @PolyNull once we can make that work for the various + * platforms we target. + * + * 2. Kotlin's Map type has a getOrDefault method that accepts and returns a "plain V," in + * contrast to the "V?" type that we're using. As a result, Kotlin sees a conflict between the + * nullness annotations in ImmutableMap and those in its own Map type. In response, it considers + * the parameter and return type both to be platform types. As a result, Kotlin permits calls + * that can lead to NullPointerException. That's unfortunate. But hopefully most Kotlin callers + * use `get(key) ?: defaultValue` instead of this method, anyway. + */ + V result = get(key); + // TODO(b/192579700): Use a ternary once it no longer confuses our nullness checker. + if (result != null) { + return result; + } else { + return defaultValue; + } + } + + private transient ImmutableSet> entrySet; + + /** + * Returns an immutable set of the mappings in this map. The iteration order is specified by the + * method used to create this map. Typically, this is insertion order. + */ + @Override + public ImmutableSet> entrySet() { + ImmutableSet> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + abstract ImmutableSet> createEntrySet(); + + private transient ImmutableSet keySet; + + /** + * Returns an immutable set of the keys in this map, in the same order that they appear in {@link + * #entrySet}. + */ + @Override + public ImmutableSet keySet() { + ImmutableSet result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + /* + * This could have a good default implementation of return new ImmutableKeySet(this), + * but ProGuard can't figure out how to eliminate that default when RegularImmutableMap + * overrides it. + */ + abstract ImmutableSet createKeySet(); + + UnmodifiableIterator keyIterator() { + final UnmodifiableIterator> entryIterator = entrySet().iterator(); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public K next() { + return entryIterator.next().getKey(); + } + }; + } + + Spliterator keySpliterator() { + return Spliterators.map(entrySet().spliterator(), Entry::getKey); + } + + private transient ImmutableCollection values; + + /** + * Returns an immutable collection of the values in this map, in the same order that they appear + * in {@link #entrySet}. + */ + @Override + public ImmutableCollection values() { + ImmutableCollection result = values; + return (result == null) ? values = createValues() : result; + } + + /* + * This could have a good default implementation of {@code return new + * ImmutableMapValues(this)}, but ProGuard can't figure out how to eliminate that default + * when RegularImmutableMap overrides it. + */ + abstract ImmutableCollection createValues(); + + @Override + public boolean equals(Object object) { + return equalsImpl(this, object); + } + + abstract boolean isPartialView(); + + @Override + public int hashCode() { + return hashCodeImpl(entrySet()); + } + + boolean isHashCodeFast() { + return false; + } + + @Override + public String toString() { + return toStringImpl(this); + } + + public static int checkNonnegative(int value, String name) { + if (value < 0) { + throw new IllegalArgumentException(name + " cannot be negative but was: " + value); + } + return value; + } + + static Function, V> valueFunction() { + return Entry::getValue; + } + + static boolean equalsImpl(Map map, Object object) { + if (map == object) { + return true; + } else if (object instanceof Map o) { + return map.entrySet().equals(o.entrySet()); + } + return false; + } + + static int hashCodeImpl(Set s) { + int hashCode = 0; + for (Object o : s) { + hashCode += o != null ? o.hashCode() : 0; + hashCode = ~~hashCode; + } + return hashCode; + } + + static String toStringImpl(Map map) { + StringBuilder sb = new StringBuilder(map.size()).append('{'); + boolean first = true; + for (Entry entry : map.entrySet()) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(entry.getKey()).append('=').append(entry.getValue()); + } + return sb.append('}').toString(); + } + + public static ImmutableMap indexMap(Collection list) { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(list.size()); + int i = 0; + for (E e : list) { + builder.put(e, i++); + } + return builder.buildOrThrow(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntry.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntry.java new file mode 100644 index 0000000..46ecf45 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntry.java @@ -0,0 +1,61 @@ +package org.xbib.datastructures.immutable; + +/** + * Implementation of {@code Entry} for {@link ImmutableMap} that adds extra methods to traverse hash + * buckets for the key and the value. This allows reuse in {@link RegularImmutableMap} and {@link + * RegularImmutableBiMap}, which don't have to recopy the entries created by their {@code Builder} + * implementations. + * + *

This base implementation has no key or value pointers, so instances of ImmutableMapEntry (but + * not its subclasses) can be reused when copied from one ImmutableMap to another. + */ +class ImmutableMapEntry extends ImmutableEntry { + + ImmutableMapEntry(K key, V value) { + super(key, value); + checkEntryNotNull(key, value); + } + + ImmutableMapEntry(ImmutableMapEntry contents) { + super(contents.getKey(), contents.getValue()); + // null check would be redundant + } + + ImmutableMapEntry getNextInKeyBucket() { + return null; + } + + ImmutableMapEntry getNextInValueBucket() { + return null; + } + + /** + * Returns true if this entry has no bucket links and can safely be reused as a terminal entry in + * a bucket in another map. + */ + boolean isReusable() { + return true; + } + + static void checkEntryNotNull(Object key, Object value) { + if (key == null) { + throw new NullPointerException("null key in entry: null=" + value); + } else if (value == null) { + throw new NullPointerException("null value in entry: " + key + "=null"); + } + } + + /** + * Creates an {@code ImmutableMapEntry} array to hold parameterized entries. The result must never + * be upcast back to ImmutableMapEntry[] (or Object[], etc.), or allowed to escape the class. + * + *

The returned array has all its elements set to their initial null values. However, we don't + * declare it as {@code @Nullable ImmutableMapEntry[]} because our checker doesn't require newly + * created arrays to have a {@code @Nullable} element type even when they're created directly with + * {@code new ImmutableMapEntry[...]}, so it seems silly to insist on that only here. + */ + @SuppressWarnings("unchecked") // Safe as long as the javadocs are followed + static ImmutableMapEntry[] createEntryArray(int size) { + return new ImmutableMapEntry[size]; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntrySet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntrySet.java new file mode 100644 index 0000000..1a36941 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapEntrySet.java @@ -0,0 +1,43 @@ +package org.xbib.datastructures.immutable; + +import java.util.Map.Entry; + +/** + * {@code entrySet()} implementation for {@link ImmutableMap}. + */ +abstract class ImmutableMapEntrySet extends ImmutableSet.CachingAsList> { + + ImmutableMapEntrySet() { + } + + abstract ImmutableMap map(); + + @Override + public int size() { + return map().size(); + } + + @Override + public boolean contains(Object object) { + if (object instanceof Entry entry) { + V value = map().get(entry.getKey()); + return value != null && value.equals(entry.getValue()); + } + return false; + } + + @Override + boolean isPartialView() { + return map().isPartialView(); + } + + @Override + boolean isHashCodeFast() { + return map().isHashCodeFast(); + } + + @Override + public int hashCode() { + return map().hashCode(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapKeySet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapKeySet.java new file mode 100644 index 0000000..2287402 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapKeySet.java @@ -0,0 +1,52 @@ +package org.xbib.datastructures.immutable; + +import java.util.Objects; +import java.util.Spliterator; +import java.util.function.Consumer; + +/** + * {@code keySet()} implementation for {@link ImmutableMap}. + */ +final class ImmutableMapKeySet extends IndexedImmutableSet { + private final ImmutableMap map; + + ImmutableMapKeySet(ImmutableMap map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public UnmodifiableIterator iterator() { + return map.keyIterator(); + } + + @Override + public Spliterator spliterator() { + return map.keySpliterator(); + } + + @Override + public boolean contains(Object object) { + return map.containsKey(object); + } + + @Override + K get(int index) { + return map.entrySet().asList().get(index).getKey(); + } + + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); + map.forEach((k, v) -> action.accept(k)); + } + + @Override + boolean isPartialView() { + return true; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapValues.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapValues.java new file mode 100644 index 0000000..1a1d479 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableMapValues.java @@ -0,0 +1,97 @@ +package org.xbib.datastructures.immutable; + +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Spliterator; +import java.util.function.Consumer; + +/** + * {@code values()} implementation for {@link ImmutableMap}. + */ +final class ImmutableMapValues extends ImmutableCollection { + private final ImmutableMap map; + + ImmutableMapValues(ImmutableMap map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public UnmodifiableIterator iterator() { + return new UnmodifiableIterator() { + final UnmodifiableIterator> entryItr = map.entrySet().iterator(); + + @Override + public boolean hasNext() { + return entryItr.hasNext(); + } + + @Override + public V next() { + return entryItr.next().getValue(); + } + }; + } + + @Override + public Spliterator spliterator() { + return Spliterators.map(map.entrySet().spliterator(), Entry::getValue); + } + + @Override + public boolean contains(Object object) { + return object != null && contains(iterator(), object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + public ImmutableList asList() { + final ImmutableList> entryList = map.entrySet().asList(); + return new ImmutableAsList() { + @Override + public V get(int index) { + return entryList.get(index).getValue(); + } + + @Override + ImmutableCollection delegateCollection() { + return ImmutableMapValues.this; + } + }; + } + + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); + map.forEach((k, v) -> action.accept(v)); + } + + /** + * Returns {@code true} if {@code iterator} contains {@code element}. + */ + static boolean contains(Iterator iterator, Object element) { + if (element == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + return true; + } + } + } else { + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + return true; + } + } + } + return false; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSet.java new file mode 100644 index 0000000..b501367 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSet.java @@ -0,0 +1,1023 @@ +package org.xbib.datastructures.immutable; + +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.stream.Collector; +import static java.util.Objects.requireNonNull; + +/** + * A {@link Set} whose contents will never change, with many other important properties detailed at + * {@link ImmutableCollection}. + */ +public abstract class ImmutableSet extends ImmutableCollection + implements Set { + static final int SPLITERATOR_CHARACTERISTICS = + ImmutableCollection.SPLITERATOR_CHARACTERISTICS | Spliterator.DISTINCT; + + private static final Collector> TO_IMMUTABLE_SET = + Collector.of( + ImmutableSet::builder, + ImmutableSet.Builder::add, + ImmutableSet.Builder::combine, + ImmutableSet.Builder::build); + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSet}. Elements appear in the resulting set in the encounter order of the stream; if + * the stream contains duplicates (according to {@link Object#equals(Object)}), only the first + * duplicate in encounter order will appear in the result. + */ + public static Collector> toImmutableSet() { + return (Collector) TO_IMMUTABLE_SET; + } + + /** + * Returns the empty immutable set. Preferred over {@link Collections#emptySet} for code + * consistency, and because the return type conveys the immutability guarantee. + * + *

Performance note: the instance returned is a singleton. + */ + @SuppressWarnings({"unchecked"}) // fully variant implementation (never actually produces any Es) + public static ImmutableSet of() { + return (ImmutableSet) RegularImmutableSet.EMPTY; + } + + /** + * Returns an immutable set containing {@code element}. Preferred over {@link + * Collections#singleton} for code consistency, {@code null} rejection, and because the return + * type conveys the immutability guarantee. + */ + public static ImmutableSet of(E element) { + return new SingletonImmutableSet(element); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2) { + return construct(2, 2, e1, e2); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2, E e3) { + return construct(3, 3, e1, e2, e3); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2, E e3, E e4) { + return construct(4, 4, e1, e2, e3, e4); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5) { + return construct(5, 5, e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + * + *

The array {@code others} must not be longer than {@code Integer.MAX_VALUE - 6}. + */ + @SafeVarargs // For Eclipse. For internal javac we have disabled this pointless type of warning. + public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { + if (!(others.length <= Integer.MAX_VALUE - 6)) { + throw new IllegalArgumentException("the total number of elements must fit in an int"); + } + final int paramCount = 6; + Object[] elements = new Object[paramCount + others.length]; + elements[0] = e1; + elements[1] = e2; + elements[2] = e3; + elements[3] = e4; + elements[4] = e5; + elements[5] = e6; + System.arraycopy(others, 0, elements, paramCount, others.length); + return construct(elements.length, elements.length, elements); + } + + /** + * Constructs an {@code ImmutableSet} from the first {@code n} elements of the specified array, + * which we have no particular reason to believe does or does not contain duplicates. If {@code k} + * is the size of the returned {@code ImmutableSet}, then the unique elements of {@code elements} + * will be in the first {@code k} positions, and {@code elements[i] == null} for {@code k <= i < + * n}. + * + *

This may modify {@code elements}. Additionally, if {@code n == elements.length} and {@code + * elements} contains no duplicates, {@code elements} may be used without copying in the returned + * {@code ImmutableSet}, in which case the caller must not modify it. + * + *

{@code elements} may contain only values of type {@code E}. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code elements} is null + */ + private static ImmutableSet constructUnknownDuplication(int n, Object... elements) { + // Guess the size is "halfway between" all duplicates and no duplicates, on a log scale. + return construct( + n, + Math.max( + ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY, + sqrt(n, RoundingMode.CEILING)), + elements); + } + + /** + * Constructs an {@code ImmutableSet} from the first {@code n} elements of the specified array. If + * {@code k} is the size of the returned {@code ImmutableSet}, then the unique elements of {@code + * elements} will be in the first {@code k} positions, and {@code elements[i] == null} for {@code + * k <= i < n}. + * + *

This may modify {@code elements}. Additionally, if {@code n == elements.length} and {@code + * elements} contains no duplicates, {@code elements} may be used without copying in the returned + * {@code ImmutableSet}, in which case it may no longer be modified. + * + *

{@code elements} may contain only values of type {@code E}. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code elements} is null + */ + private static ImmutableSet construct(int n, int expectedSize, Object... elements) { + switch (n) { + case 0: + return of(); + case 1: + @SuppressWarnings("unchecked") // safe; elements contains only E's + E elem = (E) elements[0]; + return of(elem); + default: + SetBuilderImpl builder = new RegularSetBuilderImpl(expectedSize); + for (int i = 0; i < n; i++) { + @SuppressWarnings("unchecked") + E e = (E) Objects.requireNonNull(elements[i]); + builder = builder.add(e); + } + return builder.review().build(); + } + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source collection. + * + *

Performance note: This method will sometimes recognize that the actual copy operation + * is unnecessary; for example, {@code copyOf(copyOf(anArrayList))} will copy the data only once. + * This reduces the expense of habitually making defensive copies at API boundaries. However, the + * precise conditions for skipping the copy operation are undefined. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Collection elements) { + /* + * TODO(lowasser): consider checking for ImmutableAsList here + * TODO(lowasser): consider checking for Multiset here + */ + // Don't refer to ImmutableSortedSet by name so it won't pull in all that code + if (elements instanceof ImmutableSet && !(elements instanceof SortedSet)) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableSet set = (ImmutableSet) elements; + if (!set.isPartialView()) { + return set; + } + } else if (elements instanceof EnumSet) { + return copyOfEnumSet((EnumSet) elements); + } + Object[] array = elements.toArray(); + if (elements instanceof Set) { + // assume probably no duplicates (though it might be using different equality semantics) + return construct(array.length, array.length, array); + } else { + return constructUnknownDuplication(array.length, array); + } + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source iterable. This method iterates over {@code elements} only + * once. + * + *

Performance note: This method will sometimes recognize that the actual copy operation + * is unnecessary; for example, {@code copyOf(copyOf(anArrayList))} should copy the data only + * once. This reduces the expense of habitually making defensive copies at API boundaries. + * However, the precise conditions for skipping the copy operation are undefined. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterable elements) { + return (elements instanceof Collection) + ? copyOf((Collection) elements) + : copyOf(elements.iterator()); + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source iterator. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterator elements) { + // We special-case for 0 or 1 elements, but anything further is madness. + if (!elements.hasNext()) { + return of(); + } + E first = elements.next(); + if (!elements.hasNext()) { + return of(first); + } else { + return new Builder().add(first).addAll(elements).build(); + } + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source array. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(E[] elements) { + return switch (elements.length) { + case 0 -> of(); + case 1 -> of(elements[0]); + default -> constructUnknownDuplication(elements.length, elements.clone()); + }; + } + + @SuppressWarnings("rawtypes") // necessary to compile against Java 8 + private static ImmutableSet copyOfEnumSet(EnumSet enumSet) { + return ImmutableEnumSet.asImmutable(EnumSet.copyOf(enumSet)); + } + + ImmutableSet() { + } + + /** + * Returns {@code true} if the {@code hashCode()} method runs quickly. + */ + boolean isHashCodeFast() { + return false; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableSet + && isHashCodeFast() + && ((ImmutableSet) object).isHashCodeFast() + && hashCode() != object.hashCode()) { + return false; + } + return equalsImpl(this, object); + } + + @Override + public int hashCode() { + return hashCodeImpl(this); + } + + // This declaration is needed to make Set.iterator() and + // ImmutableCollection.iterator() consistent. + @Override + public abstract UnmodifiableIterator iterator(); + + abstract static class CachingAsList extends ImmutableSet { + private transient ImmutableList asList; + + @Override + public ImmutableList asList() { + ImmutableList result = asList; + if (result == null) { + return asList = createAsList(); + } else { + return result; + } + } + + ImmutableList createAsList() { + return new RegularImmutableAsList(this, toArray()); + } + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a new builder, expecting the specified number of distinct elements to be added. + * + *

If {@code expectedSize} is exactly the number of distinct elements added to the builder + * before {@link Builder#build} is called, the builder is likely to perform better than an unsized + * {@link #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of distinct elements added to the builder. + */ + public static Builder builderWithExpectedSize(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + return new Builder(expectedSize); + } + + /** + * A builder for creating {@code ImmutableSet} instances. Example: + * + *

{@code
+     * static final ImmutableSet GOOGLE_COLORS =
+     *     ImmutableSet.builder()
+     *         .addAll(WEBSAFE_COLORS)
+     *         .add(new Color(0, 191, 255))
+     *         .build();
+     * }
+ * + *

Elements appear in the resulting set in the same order they were first added to the builder. + * + *

Building does not change the state of the builder, so it is still possible to add more + * elements and to build again. + */ + public static class Builder extends ImmutableCollection.Builder { + /* + * `impl` is null only for instances of the subclass, ImmutableSortedSet.Builder. That subclass + * overrides all the methods that access it here. Thus, all the methods here can safely assume + * that this field is non-null. + */ + private SetBuilderImpl impl; + boolean forceCopy; + + public Builder() { + this(0); + } + + Builder(int capacity) { + if (capacity > 0) { + impl = new RegularSetBuilderImpl(capacity); + } else { + impl = EmptySetBuilderImpl.instance(); + } + } + + Builder(@SuppressWarnings("unused") boolean subclass) { + this.impl = null; // unused + } + + void forceJdk() { + requireNonNull(impl); // see the comment on the field + this.impl = new JdkBackedSetBuilderImpl(impl); + } + + final void copyIfNecessary() { + if (forceCopy) { + copy(); + forceCopy = false; + } + } + + void copy() { + requireNonNull(impl); // see the comment on the field + impl = impl.copy(); + } + + @Override + public Builder add(E element) { + requireNonNull(impl); // see the comment on the field + requireNonNull(element); + copyIfNecessary(); + impl = impl.add(element); + return this; + } + + @Override + public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + Builder combine(Builder other) { + requireNonNull(impl); + requireNonNull(other.impl); + /* + * For discussion of requireNonNull, see the comment on the field. + * + * (And I don't believe there's any situation in which we call x.combine(y) when x is a plain + * ImmutableSet.Builder but y is an ImmutableSortedSet.Builder (or vice versa). Certainly + * ImmutableSortedSet.Builder.combine() is written as if its argument will never be a plain + * ImmutableSet.Builder: It casts immediately to ImmutableSortedSet.Builder.) + */ + copyIfNecessary(); + this.impl = this.impl.combine(other.impl); + return this; + } + + @Override + public ImmutableSet build() { + requireNonNull(impl); // see the comment on the field + forceCopy = true; + impl = impl.review(); + return impl.build(); + } + } + + /** + * Swappable internal implementation of an ImmutableSet.Builder. + */ + private abstract static class SetBuilderImpl { + // The first `distinct` elements are non-null. + // Since we can never access null elements, we don't mark this nullable. + E[] dedupedElements; + int distinct; + + @SuppressWarnings("unchecked") + SetBuilderImpl(int expectedCapacity) { + this.dedupedElements = (E[]) new Object[expectedCapacity]; + this.distinct = 0; + } + + /** + * Initializes this SetBuilderImpl with a copy of the deduped elements array from toCopy. + */ + SetBuilderImpl(SetBuilderImpl toCopy) { + this.dedupedElements = Arrays.copyOf(toCopy.dedupedElements, toCopy.dedupedElements.length); + this.distinct = toCopy.distinct; + } + + /** + * Resizes internal data structures if necessary to store the specified number of distinct + * elements. + */ + private void ensureCapacity(int minCapacity) { + if (minCapacity > dedupedElements.length) { + int newCapacity = + ImmutableCollection.Builder.expandedCapacity(dedupedElements.length, minCapacity); + dedupedElements = Arrays.copyOf(dedupedElements, newCapacity); + } + } + + /** + * Adds e to the insertion-order array of deduplicated elements. Calls ensureCapacity. + */ + final void addDedupedElement(E e) { + ensureCapacity(distinct + 1); + dedupedElements[distinct++] = e; + } + + /** + * Adds e to this SetBuilderImpl, returning the updated result. Only use the returned + * SetBuilderImpl, since we may switch implementations if e.g. hash flooding is detected. + */ + abstract SetBuilderImpl add(E e); + + /** + * Adds all the elements from the specified SetBuilderImpl to this SetBuilderImpl. + */ + final SetBuilderImpl combine(SetBuilderImpl other) { + SetBuilderImpl result = this; + for (int i = 0; i < other.distinct; i++) { + /* + * requireNonNull is safe because we ensure that the first `distinct` elements have been + * populated. + */ + result = result.add(requireNonNull(other.dedupedElements[i])); + } + return result; + } + + /** + * Creates a new copy of this SetBuilderImpl. Modifications to that SetBuilderImpl will not + * affect this SetBuilderImpl or sets constructed from this SetBuilderImpl via build(). + */ + abstract SetBuilderImpl copy(); + + /** + * Call this before build(). Does a final check on the internal data structures, e.g. shrinking + * unnecessarily large structures or detecting previously unnoticed hash flooding. + */ + SetBuilderImpl review() { + return this; + } + + abstract ImmutableSet build(); + } + + private static final class EmptySetBuilderImpl extends SetBuilderImpl { + private static final EmptySetBuilderImpl INSTANCE = new EmptySetBuilderImpl<>(); + + @SuppressWarnings("unchecked") + static SetBuilderImpl instance() { + return (SetBuilderImpl) INSTANCE; + } + + private EmptySetBuilderImpl() { + super(0); + } + + @Override + SetBuilderImpl add(E e) { + return new RegularSetBuilderImpl(Builder.DEFAULT_INITIAL_CAPACITY).add(e); + } + + @Override + SetBuilderImpl copy() { + return this; + } + + @Override + ImmutableSet build() { + return ImmutableSet.of(); + } + } + + private static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + // We use power-of-2 tables, and this is the highest int that's a power of 2 + private static final int MAX_TABLE_SIZE = MAX_POWER_OF_TWO; + + // Represents how tightly we can pack things, as a maximum. + private static final double DESIRED_LOAD_FACTOR = 0.7; + + // If the set has this many elements, it will "max out" the table size + private static final int CUTOFF = (int) (MAX_TABLE_SIZE * DESIRED_LOAD_FACTOR); + + /** + * Returns an array size suitable for the backing array of a hash table that uses open addressing + * with linear probing in its implementation. The returned size is the smallest power of two that + * can hold setSize elements with the desired load factor. Always returns at least setSize + 2. + */ + static int chooseTableSize(int setSize) { + setSize = Math.max(setSize, 2); + // Correct the size for open addressing to match desired load factor. + if (setSize < CUTOFF) { + // Round up to the next highest power of 2. + int tableSize = Integer.highestOneBit(setSize - 1) << 1; + while (tableSize * DESIRED_LOAD_FACTOR < setSize) { + tableSize <<= 1; + } + return tableSize; + } + + // The table can't be completely full or we'll get infinite reprobes + if (!(setSize < MAX_TABLE_SIZE)) { + throw new IllegalArgumentException("collection too large"); + } + return MAX_TABLE_SIZE; + } + + /** + * Default implementation of the guts of ImmutableSet.Builder, creating an open-addressed hash + * table and deduplicating elements as they come, so it only allocates O(max(distinct, + * expectedCapacity)) rather than O(calls to add). + * + *

This implementation attempts to detect hash flooding, and if it's identified, falls back to + * JdkBackedSetBuilderImpl. + */ + private static final class RegularSetBuilderImpl extends SetBuilderImpl { + // null until at least two elements are present + private Object[] hashTable; + private int maxRunBeforeFallback; + private int expandTableThreshold; + private int hashCode; + + RegularSetBuilderImpl(int expectedCapacity) { + super(expectedCapacity); + this.hashTable = null; + this.maxRunBeforeFallback = 0; + this.expandTableThreshold = 0; + } + + RegularSetBuilderImpl(RegularSetBuilderImpl toCopy) { + super(toCopy); + this.hashTable = (toCopy.hashTable == null) ? null : toCopy.hashTable.clone(); + this.maxRunBeforeFallback = toCopy.maxRunBeforeFallback; + this.expandTableThreshold = toCopy.expandTableThreshold; + this.hashCode = toCopy.hashCode; + } + + @Override + SetBuilderImpl add(E e) { + Objects.requireNonNull(e); + if (hashTable == null) { + if (distinct == 0) { + addDedupedElement(e); + return this; + } else { + ensureTableCapacity(dedupedElements.length); + E elem = dedupedElements[0]; + distinct--; + return insertInHashTable(elem).add(e); + } + } + return insertInHashTable(e); + } + + private SetBuilderImpl insertInHashTable(E e) { + requireNonNull(hashTable); + int eHash = e.hashCode(); + int i0 = smear(eHash); + int mask = hashTable.length - 1; + for (int i = i0; i - i0 < maxRunBeforeFallback; i++) { + int index = i & mask; + Object tableEntry = hashTable[index]; + if (tableEntry == null) { + addDedupedElement(e); + hashTable[index] = e; + hashCode += eHash; + ensureTableCapacity(distinct); // rebuilds table if necessary + return this; + } else if (tableEntry.equals(e)) { // not a new element, ignore + return this; + } + } + // we fell out of the loop due to a long run; fall back to JDK impl + return new JdkBackedSetBuilderImpl(this).add(e); + } + + @Override + SetBuilderImpl copy() { + return new RegularSetBuilderImpl(this); + } + + @Override + SetBuilderImpl review() { + if (hashTable == null) { + return this; + } + int targetTableSize = chooseTableSize(distinct); + if (targetTableSize * 2 < hashTable.length) { + hashTable = rebuildHashTable(targetTableSize, dedupedElements, distinct); + maxRunBeforeFallback = maxRunBeforeFallback(targetTableSize); + expandTableThreshold = (int) (DESIRED_LOAD_FACTOR * targetTableSize); + } + return hashFloodingDetected(hashTable) ? new JdkBackedSetBuilderImpl(this) : this; + } + + @Override + ImmutableSet build() { + switch (distinct) { + case 0: + return of(); + case 1: + /* + * requireNonNull is safe because we ensure that the first `distinct` elements have been + * populated. + */ + return of(requireNonNull(dedupedElements[0])); + default: + /* + * The suppression is safe because we ensure that the first `distinct` elements have been + * populated. + */ + @SuppressWarnings("nullness") + Object[] elements = + (distinct == dedupedElements.length) + ? dedupedElements + : Arrays.copyOf(dedupedElements, distinct); + return new RegularImmutableSet( + elements, hashCode, requireNonNull(hashTable), hashTable.length - 1); + } + } + + /** + * Builds a new open-addressed hash table from the first n objects in elements. + */ + static Object[] rebuildHashTable(int newTableSize, Object[] elements, int n) { + Object[] hashTable = new Object[newTableSize]; + int mask = hashTable.length - 1; + for (int i = 0; i < n; i++) { + // requireNonNull is safe because we ensure that the first n elements have been populated. + Object e = requireNonNull(elements[i]); + int j0 = smear(e.hashCode()); + for (int j = j0; ; j++) { + int index = j & mask; + if (hashTable[index] == null) { + hashTable[index] = e; + break; + } + } + } + return hashTable; + } + + void ensureTableCapacity(int minCapacity) { + int newTableSize; + if (hashTable == null) { + newTableSize = chooseTableSize(minCapacity); + hashTable = new Object[newTableSize]; + } else if (minCapacity > expandTableThreshold && hashTable.length < MAX_TABLE_SIZE) { + newTableSize = hashTable.length * 2; + hashTable = rebuildHashTable(newTableSize, dedupedElements, distinct); + } else { + return; + } + maxRunBeforeFallback = maxRunBeforeFallback(newTableSize); + expandTableThreshold = (int) (DESIRED_LOAD_FACTOR * newTableSize); + } + + /** + * We attempt to detect deliberate hash flooding attempts. If one is detected, we fall back to a + * wrapper around j.u.HashSet, which has built-in flooding protection. MAX_RUN_MULTIPLIER was + * determined experimentally to match our desired probability of false positives. + */ + // NB: yes, this is surprisingly high, but that's what the experiments said was necessary + // Raising this number slows the worst-case contains behavior, speeds up hashFloodingDetected, + // and reduces the false-positive probability. + static final int MAX_RUN_MULTIPLIER = 13; + + /** + * Checks the whole hash table for poor hash distribution. Takes O(n) in the worst case, O(n / + * log n) on average. + * + *

The online hash flooding detecting in RegularSetBuilderImpl.add can detect e.g. many + * exactly matching hash codes, which would cause construction to take O(n^2), but can't detect + * e.g. hash codes adversarially designed to go into ascending table locations, which keeps + * construction O(n) (as desired) but then can have O(n) queries later. + * + *

If this returns false, then no query can take more than O(log n). + * + *

Note that for a RegularImmutableSet with elements with truly random hash codes, contains + * operations take expected O(1) time but with high probability take O(log n) for at least some + * element. (https://en.wikipedia.org/wiki/Linear_probing#Analysis) + * + *

This method may return {@code true} even on truly random input, but {@code + * ImmutableSetTest} tests that the probability of that is low. + */ + static boolean hashFloodingDetected(Object[] hashTable) { + int maxRunBeforeFallback = maxRunBeforeFallback(hashTable.length); + int mask = hashTable.length - 1; + + // Invariant: all elements at indices in [knownRunStart, knownRunEnd) are nonnull. + // If knownRunStart == knownRunEnd, this is vacuously true. + // When knownRunEnd exceeds hashTable.length, it "wraps", detecting runs around the end + // of the table. + int knownRunStart = 0; + int knownRunEnd = 0; + + outerLoop: + while (knownRunStart < hashTable.length) { + if (knownRunStart == knownRunEnd && hashTable[knownRunStart] == null) { + if (hashTable[(knownRunStart + maxRunBeforeFallback - 1) & mask] == null) { + // There are only maxRunBeforeFallback - 1 elements between here and there, + // so even if they were all nonnull, we wouldn't detect a hash flood. Therefore, + // we can skip them all. + knownRunStart += maxRunBeforeFallback; + } else { + knownRunStart++; // the only case in which maxRunEnd doesn't increase by mRBF + // happens about f * (1-f) for f = DESIRED_LOAD_FACTOR, so around 21% of the time + } + knownRunEnd = knownRunStart; + } else { + for (int j = knownRunStart + maxRunBeforeFallback - 1; j >= knownRunEnd; j--) { + if (hashTable[j & mask] == null) { + knownRunEnd = knownRunStart + maxRunBeforeFallback; + knownRunStart = j + 1; + continue outerLoop; + } + } + return true; + } + } + return false; + } + + /** + * If more than this many consecutive positions are filled in a table of the specified size, + * report probable hash flooding. ({@link #hashFloodingDetected} may also report hash flooding + * if fewer consecutive positions are filled; see that method for details.) + */ + static int maxRunBeforeFallback(int tableSize) { + return MAX_RUN_MULTIPLIER * log2(tableSize, RoundingMode.UNNECESSARY); + } + } + + /** + * SetBuilderImpl version that uses a JDK HashSet, which has built in hash flooding protection. + */ + private static final class JdkBackedSetBuilderImpl extends SetBuilderImpl { + private final Set delegate; + + JdkBackedSetBuilderImpl(SetBuilderImpl toCopy) { + super(toCopy); // initializes dedupedElements and distinct + delegate = new HashSet<>(distinct); + for (int i = 0; i < distinct; i++) { + /* + * requireNonNull is safe because we ensure that the first `distinct` elements have been + * populated. + */ + delegate.add(requireNonNull(dedupedElements[i])); + } + } + + @Override + SetBuilderImpl add(E e) { + Objects.requireNonNull(e); + if (delegate.add(e)) { + addDedupedElement(e); + } + return this; + } + + @Override + SetBuilderImpl copy() { + return new JdkBackedSetBuilderImpl<>(this); + } + + @Override + ImmutableSet build() { + switch (distinct) { + case 0: + return of(); + case 1: + /* + * requireNonNull is safe because we ensure that the first `distinct` elements have been + * populated. + */ + return of(requireNonNull(dedupedElements[0])); + default: + return new JdkBackedImmutableSet( + delegate, ImmutableList.asImmutableList(dedupedElements, distinct)); + } + } + } + + private static int checkNonnegative(int value, String name) { + if (value < 0) { + throw new IllegalArgumentException(name + " cannot be negative but was: " + value); + } + return value; + } + + static int sqrt(int x, RoundingMode mode) { + checkNonnegative(x, "x"); + int sqrtFloor = sqrtFloor(x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(sqrtFloor * sqrtFloor == x); // fall through + case FLOOR: + case DOWN: + return sqrtFloor; + case CEILING: + case UP: + return sqrtFloor + lessThanBranchFree(sqrtFloor * sqrtFloor, x); + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + int halfSquare = sqrtFloor * sqrtFloor + sqrtFloor; + /* + * We wish to test whether or not x <= (sqrtFloor + 0.5)^2 = halfSquare + 0.25. Since both x + * and halfSquare are integers, this is equivalent to testing whether or not x <= + * halfSquare. (We have to deal with overflow, though.) + * + * If we treat halfSquare as an unsigned int, we know that + * sqrtFloor^2 <= x < (sqrtFloor + 1)^2 + * halfSquare - sqrtFloor <= x < halfSquare + sqrtFloor + 1 + * so |x - halfSquare| <= sqrtFloor. Therefore, it's safe to treat x - halfSquare as a + * signed int, so lessThanBranchFree is safe for use. + */ + return sqrtFloor + lessThanBranchFree(halfSquare, x); + default: + throw new AssertionError(); + } + } + + private static int sqrtFloor(int x) { + // There is no loss of precision in converting an int to a double, according to + // http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.1.2 + return (int) Math.sqrt(x); + } + + private static void checkRoundingUnnecessary(boolean condition) { + if (!condition) { + throw new ArithmeticException("mode was UNNECESSARY, but rounding was necessary"); + } + } + + static int lessThanBranchFree(int x, int y) { + return ~~(x - y) >>> (Integer.SIZE - 1); + } + + static int log2(int x, RoundingMode mode) { + checkPositive("x", x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case DOWN: + case FLOOR: + return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x); + + case UP: + case CEILING: + return Integer.SIZE - Integer.numberOfLeadingZeros(x - 1); + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + int leadingZeros = Integer.numberOfLeadingZeros(x); + int cmp = MAX_POWER_OF_SQRT2_UNSIGNED >>> leadingZeros; + // floor(2^(logFloor + 0.5)) + int logFloor = (Integer.SIZE - 1) - leadingZeros; + return logFloor + lessThanBranchFree(cmp, x); + + default: + throw new AssertionError(); + } + } + + private static int checkPositive(String role, int x) { + if (x <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + /** + * The biggest half power of two that can fit in an unsigned int. + */ + private static final int MAX_POWER_OF_SQRT2_UNSIGNED = 0xB504F333; + + private static boolean isPowerOfTwo(int x) { + return x > 0 & (x & (x - 1)) == 0; + } + + private static int smear(int hashCode) { + return (int) (C2 * Integer.rotateLeft((int) (hashCode * C1), 15)); + } + + private static final long C1 = 0xcc9e2d51; + + private static final long C2 = 0x1b873593; + + /** + * An implementation for {@link Set#hashCode()}. + */ + private static int hashCodeImpl(Set s) { + int hashCode = 0; + for (Object o : s) { + hashCode += o != null ? o.hashCode() : 0; + hashCode = ~~hashCode; + } + return hashCode; + } + + /** + * An implementation for {@link Set#equals(Object)}. + */ + private static boolean equalsImpl(Set s, Object object) { + if (s == object) { + return true; + } + if (object instanceof Set o) { + + try { + return s.size() == o.size() && s.containsAll(o); + } catch (NullPointerException | ClassCastException ignored) { + return false; + } + } + return false; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedAsList.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedAsList.java new file mode 100644 index 0000000..9ec3975 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedAsList.java @@ -0,0 +1,59 @@ +package org.xbib.datastructures.immutable; + +import java.util.Comparator; +import java.util.Spliterator; +import org.xbib.datastructures.api.SortedIterable; + +/** + * List returned by {@code ImmutableSortedSet.asList()} when the set isn't empty. + */ +final class ImmutableSortedAsList extends RegularImmutableAsList + implements SortedIterable { + ImmutableSortedAsList(ImmutableSortedSet backingSet, ImmutableList backingList) { + super(backingSet, backingList); + } + + @Override + ImmutableSortedSet delegateCollection() { + return (ImmutableSortedSet) super.delegateCollection(); + } + + @Override + public Comparator comparator() { + return delegateCollection().comparator(); + } + + // Override indexOf() and lastIndexOf() to be O(log N) instead of O(N). + + @Override + public int indexOf(Object target) { + int index = delegateCollection().indexOf(target); + return (index >= 0 && get(index).equals(target)) ? index : -1; + } + + @Override + public int lastIndexOf(Object target) { + return indexOf(target); + } + + @Override + public boolean contains(Object target) { + // Necessary for ISS's with comparators inconsistent with equals. + return indexOf(target) >= 0; + } + + @Override + ImmutableList subListUnchecked(int fromIndex, int toIndex) { + ImmutableList parentSubList = super.subListUnchecked(fromIndex, toIndex); + return new RegularImmutableSortedSet(parentSubList, comparator()).asList(); + } + + @Override + public Spliterator spliterator() { + return Spliterators.indexed( + size(), + ImmutableList.SPLITERATOR_CHARACTERISTICS | Spliterator.SORTED | Spliterator.DISTINCT, + delegateList()::get, + comparator()); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedMap.java new file mode 100644 index 0000000..f4ddd9e --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedMap.java @@ -0,0 +1,1054 @@ +package org.xbib.datastructures.immutable; + +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.SortedMap; +import java.util.Spliterator; +import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.xbib.datastructures.immutable.order.Ordering; +import static java.util.Objects.requireNonNull; +import static org.xbib.datastructures.immutable.ImmutableCollection.toArray; +import static org.xbib.datastructures.immutable.ImmutableMapEntry.checkEntryNotNull; + +/** + * A {@link NavigableMap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: as with any sorted collection, you are strongly advised not to use a {@link + * Comparator} or {@link Comparable} type whose comparison behavior is inconsistent with + * equals. That is, {@code a.compareTo(b)} or {@code comparator.compare(a, b)} should equal zero + * if and only if {@code a.equals(b)}. If this advice is not followed, the resulting map will + * not correctly obey its specification. + */ +public final class ImmutableSortedMap extends ImmutableMap implements NavigableMap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. The generated map is sorted by the specified comparator. + * + *

If the mapped keys contain duplicates (according to the specified comparator), an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + */ + public static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction) { + Objects.requireNonNull(comparator); + Objects.requireNonNull(keyFunction); + Objects.requireNonNull(valueFunction); + /* + * We will always fail if there are duplicate keys, and the keys are always sorted by + * the Comparator, so the entries can come in an arbitrary order -- so we report UNORDERED. + */ + return Collector.of( + () -> new ImmutableSortedMap.Builder(comparator), + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableSortedMap.Builder::combine, + ImmutableSortedMap.Builder::build, + Collector.Characteristics.UNORDERED); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

If the mapped keys contain duplicates (according to the comparator), the values are merged + * using the specified merging function. Entries will appear in the encounter order of the first + * occurrence of the key. + */ + public static + Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + Objects.requireNonNull(comparator); + Objects.requireNonNull(keyFunction); + Objects.requireNonNull(valueFunction); + Objects.requireNonNull(mergeFunction); + return Collectors.collectingAndThen( + Collectors.toMap( + keyFunction, valueFunction, mergeFunction, () -> new TreeMap(comparator)), + ImmutableSortedMap::copyOfSorted); + } + + private static final Comparator> NATURAL_ORDER = Ordering.natural(); + + private static final ImmutableSortedMap, Object> NATURAL_EMPTY_MAP = + new ImmutableSortedMap<>( + ImmutableSortedSet.emptySet(Ordering.natural()), ImmutableList.of()); + + static ImmutableSortedMap emptyMap(Comparator comparator) { + if (Ordering.natural().equals(comparator)) { + return of(); + } else { + return new ImmutableSortedMap<>( + ImmutableSortedSet.emptySet(comparator), ImmutableList.of()); + } + } + + /** + * Returns the empty sorted map. + * + *

Performance note: the instance returned is a singleton. + */ + @SuppressWarnings("unchecked") + // unsafe, comparator() returns a comparator on the specified type + public static ImmutableSortedMap of() { + return (ImmutableSortedMap) NATURAL_EMPTY_MAP; + } + + /** + * Returns an immutable map containing a single entry. + */ + public static , V> ImmutableSortedMap of(K k1, V v1) { + return of(Ordering.natural(), k1, v1); + } + + /** + * Returns an immutable map containing a single entry. + */ + private static ImmutableSortedMap of(Comparator comparator, K k1, V v1) { + return new ImmutableSortedMap<>( + new RegularImmutableSortedSet(ImmutableList.of(k1), Objects.requireNonNull(comparator)), + ImmutableList.of(v1)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if the two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2) { + return fromEntries(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, + V v1, + K k2, + V v2, + K k3, + V v3, + K k4, + V v4, + K k5, + V v5, + K k6, + V v6, + K k7, + V v7, + K k8, + V v8, + K k9, + V v9, + K k10, + V v10) { + return fromEntries( + entryOf(k1, v1), + entryOf(k2, v2), + entryOf(k3, v3), + entryOf(k4, v4), + entryOf(k5, v5), + entryOf(k6, v6), + entryOf(k7, v7), + entryOf(k8, v8), + entryOf(k9, v9), + entryOf(k10, v10)); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, sorted by the natural + * ordering of the keys. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on a map with keys that are not mutually + * comparable. + * + * @throws ClassCastException if the keys in {@code map} are not mutually comparable + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + public static ImmutableSortedMap copyOf(Map map) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) NATURAL_ORDER; + return copyOfInternal(map, naturalOrder); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, with keys sorted by the + * provided comparator. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to the comparator + */ + public static ImmutableSortedMap copyOf( + Map map, Comparator comparator) { + return copyOfInternal(map, Objects.requireNonNull(comparator)); + } + + /** + * Returns an immutable map containing the given entries, with keys sorted by their natural + * ordering. + * + *

This method is not type-safe, as it may be called on a map with keys that are not mutually + * comparable. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to the comparator + */ + public static ImmutableSortedMap copyOf( + Iterable> entries) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) NATURAL_ORDER; + return copyOf(entries, naturalOrder); + } + + /** + * Returns an immutable map containing the given entries, with keys sorted by the provided + * comparator. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to the comparator + */ + public static ImmutableSortedMap copyOf( + Iterable> entries, + Comparator comparator) { + return fromEntries(Objects.requireNonNull(comparator), false, entries); + } + + /** + * Returns an immutable map containing the same entries as the provided sorted map, with the same + * ordering. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSortedMap copyOfSorted(SortedMap map) { + Comparator comparator = map.comparator(); + if (comparator == null) { + // If map has a null comparator, the keys should have a natural ordering, + // even though K doesn't explicitly implement Comparable. + comparator = (Comparator) NATURAL_ORDER; + } + if (map instanceof ImmutableSortedMap) { + @SuppressWarnings("unchecked") + ImmutableSortedMap kvMap = (ImmutableSortedMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } + return fromEntries(comparator, true, map.entrySet()); + } + + private static ImmutableSortedMap copyOfInternal( + Map map, Comparator comparator) { + boolean sameComparator = false; + if (map instanceof SortedMap sortedMap) { + Comparator comparator2 = sortedMap.comparator(); + sameComparator = + (comparator2 == null) ? comparator == NATURAL_ORDER : comparator.equals(comparator2); + } + + if (sameComparator && (map instanceof ImmutableSortedMap)) { + @SuppressWarnings("unchecked") + ImmutableSortedMap kvMap = (ImmutableSortedMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } + return fromEntries(comparator, sameComparator, map.entrySet()); + } + + @SuppressWarnings("unchecked") + private static , V> ImmutableSortedMap fromEntries(Entry... entries) { + return fromEntries(Ordering.natural(), false, entries, entries.length); + } + + /** + * Accepts a collection of possibly-null entries. If {@code sameComparator}, then it is assumed + * that they do not need to be sorted or checked for dupes. + */ + private static ImmutableSortedMap fromEntries( + Comparator comparator, + boolean sameComparator, + Iterable> entries) { + // "adding" type params to an array of a raw type should be safe as + // long as no one can ever cast that same array instance back to a + // raw type. + @SuppressWarnings({"unchecked", "rawtypes"}) + Entry[] entryArray = (Entry[]) toArray(entries, EMPTY_ENTRY_ARRAY); + return fromEntries(comparator, sameComparator, entryArray, entryArray.length); + } + + private static ImmutableSortedMap fromEntries(final Comparator comparator, + boolean sameComparator, + Entry[] entryArray, + int size) { + switch (size) { + case 0: + return emptyMap(comparator); + case 1: + // requireNonNull is safe because the first `size` elements have been filled in. + Entry onlyEntry = requireNonNull(entryArray[0]); + return of(comparator, onlyEntry.getKey(), onlyEntry.getValue()); + default: + Object[] keys = new Object[size]; + Object[] values = new Object[size]; + if (sameComparator) { + // Need to check for nulls, but don't need to sort or validate. + for (int i = 0; i < size; i++) { + // requireNonNull is safe because the first `size` elements have been filled in. + Entry entry = requireNonNull(entryArray[i]); + Object key = entry.getKey(); + Object value = entry.getValue(); + checkEntryNotNull(key, value); + keys[i] = key; + values[i] = value; + } + } else { + // Need to sort and check for nulls and dupes. + // Inline the Comparator implementation rather than transforming with a Function + // to save code size. + Arrays.sort( + entryArray, + 0, + size, + new Comparator>() { + @Override + public int compare(Entry e1, Entry e2) { + // requireNonNull is safe because the first `size` elements have been filled in. + requireNonNull(e1); + requireNonNull(e2); + return comparator.compare(e1.getKey(), e2.getKey()); + } + }); + // requireNonNull is safe because the first `size` elements have been filled in. + Entry firstEntry = requireNonNull(entryArray[0]); + K prevKey = firstEntry.getKey(); + keys[0] = prevKey; + values[0] = firstEntry.getValue(); + checkEntryNotNull(keys[0], values[0]); + for (int i = 1; i < size; i++) { + // requireNonNull is safe because the first `size` elements have been filled in. + Entry prevEntry = requireNonNull(entryArray[i - 1]); + Entry entry = requireNonNull(entryArray[i]); + K key = entry.getKey(); + V value = entry.getValue(); + checkEntryNotNull(key, value); + keys[i] = key; + values[i] = value; + checkNoConflict(comparator.compare(prevKey, key) != 0, "key", prevEntry, entry); + prevKey = key; + } + } + return new ImmutableSortedMap<>( + new RegularImmutableSortedSet(new RegularImmutableList(keys), comparator), + new RegularImmutableList(values)); + } + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are ordered by their natural + * ordering. The sorted maps use {@link Ordering#natural()} as the comparator. + */ + public static , V> Builder naturalOrder() { + return new Builder<>(Ordering.natural()); + } + + /** + * Returns a builder that creates immutable sorted maps with an explicit comparator. If the + * comparator has a more general type than the map's keys, such as creating a {@code + * SortedMap} with a {@code Comparator}, use the {@link Builder} + * constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder<>(comparator); + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are ordered by the reverse of + * their natural ordering. + */ + public static , V> Builder reverseOrder() { + return new Builder<>(Ordering.natural().reverse()); + } + + /** + * A builder for creating immutable sorted map instances, especially {@code public static final} + * maps ("constant maps"). Example: + * + *

{@code
+     * static final ImmutableSortedMap INT_TO_WORD =
+     *     new ImmutableSortedMap.Builder(Ordering.natural())
+     *         .put(1, "one")
+     *         .put(2, "two")
+     *         .put(3, "three")
+     *         .buildOrThrow();
+     * }
+ * + *

For small immutable sorted maps, the {@code ImmutableSortedMap.of()} methods are even + * more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #buildOrThrow} multiple times to + * build multiple maps in series. Each map is a superset of the maps created before it. + */ + public static class Builder extends ImmutableMap.Builder { + private final Comparator comparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableSortedMap#orderedBy}. + */ + @SuppressWarnings("unchecked") + public Builder(Comparator comparator) { + this.comparator = Objects.requireNonNull(comparator); + } + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate keys, according to the + * comparator (which might be the keys' natural order), are not allowed, and will cause {@link + * #build()} to fail. + */ + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Adds the given {@code entry} to the map, making it immutable if necessary. Duplicate keys, + * according to the comparator (which might be the keys' natural order), are not allowed, and + * will cause {@link #build()} to fail. + */ + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + /** + * Associates all of the given map's keys and values in the built map. Duplicate keys, according + * to the comparator (which might be the keys' natural order), are not allowed, and will cause + * {@link #build()} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @Override + public Builder putAll(Map map) { + super.putAll(map); + return this; + } + + /** + * Adds all the given entries to the built map. Duplicate keys, according to the comparator + * (which might be the keys' natural order), are not allowed, and will cause {@link #build()} to + * fail. + * + * @throws NullPointerException if any key, value, or entry is null + */ + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + /** + * Throws an {@code UnsupportedOperationException}. + */ + @Override + public final Builder orderEntriesByValue(Comparator valueComparator) { + throw new UnsupportedOperationException("Not available on ImmutableSortedMap.Builder"); + } + + @Override + Builder combine(ImmutableMap.Builder other) { + super.combine(other); + return this; + } + + /** + * Returns a newly-created immutable sorted map. + * + *

Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method + * will throw an exception if there are duplicate keys. The {@code build()} method will soon be + * deprecated. + * + * @throws IllegalArgumentException if any two keys are equal according to the comparator (which + * might be the keys' natural order) + */ + @Override + public ImmutableSortedMap build() { + return buildOrThrow(); + } + + /** + * Returns a newly-created immutable sorted map, or throws an exception if any two keys are + * equal. + * + * @throws IllegalArgumentException if any two keys are equal according to the comparator (which + * might be the keys' natural order) + */ + @Override + public ImmutableSortedMap buildOrThrow() { + switch (size) { + case 0: + return emptyMap(comparator); + case 1: + // requireNonNull is safe because the first `size` elements have been filled in. + Entry onlyEntry = requireNonNull(entries[0]); + return of(comparator, onlyEntry.getKey(), onlyEntry.getValue()); + default: + return fromEntries(comparator, false, entries, size); + } + } + + /** + * Throws UnsupportedOperationException. A future version may support this operation. Then the + * value for any given key will be the one that was last supplied in a {@code put} operation for + * that key. + * + * @throws UnsupportedOperationException always + */ + @Override + public final ImmutableSortedMap buildKeepingLast() { + throw new UnsupportedOperationException("ImmutableSortedMap.Builder does not yet implement buildKeepingLast()"); + } + } + + private final transient RegularImmutableSortedSet keySet; + private final transient ImmutableList valueList; + private final transient ImmutableSortedMap descendingMap; + + ImmutableSortedMap(RegularImmutableSortedSet keySet, ImmutableList valueList) { + this(keySet, valueList, null); + } + + ImmutableSortedMap( + RegularImmutableSortedSet keySet, + ImmutableList valueList, + ImmutableSortedMap descendingMap) { + this.keySet = keySet; + this.valueList = valueList; + this.descendingMap = descendingMap; + } + + @Override + public int size() { + return valueList.size(); + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + ImmutableList keyList = keySet.asList(); + for (int i = 0; i < size(); i++) { + action.accept(keyList.get(i), valueList.get(i)); + } + } + + @Override + public V get(Object key) { + int index = keySet.indexOf(key); + return (index == -1) ? null : valueList.get(index); + } + + @Override + boolean isPartialView() { + return keySet.isPartialView() || valueList.isPartialView(); + } + + /** + * Returns an immutable set of the mappings in this map, sorted by the key ordering. + */ + @Override + public ImmutableSet> entrySet() { + return super.entrySet(); + } + + @Override + ImmutableSet> createEntrySet() { + return isEmpty() ? ImmutableSet.of() : new ImmutableSortedMapEntrySet(); + } + + /** + * Returns an immutable sorted set of the keys in this map. + */ + @Override + public ImmutableSortedSet keySet() { + return keySet; + } + + @Override + ImmutableSet createKeySet() { + throw new AssertionError("should never be called"); + } + + /** + * Returns an immutable collection of the values in this map, sorted by the ordering of the + * corresponding keys. + */ + @Override + public ImmutableCollection values() { + return valueList; + } + + @Override + ImmutableCollection createValues() { + throw new AssertionError("should never be called"); + } + + /** + * Returns the comparator that orders the keys, which is {@link Ordering#natural()} when the + * natural ordering of the keys is used. Note that its behavior is not consistent with {@link + * TreeMap#comparator()}, which returns {@code null} to indicate natural ordering. + */ + @Override + public Comparator comparator() { + return keySet().comparator(); + } + + @Override + public K firstKey() { + return keySet().first(); + } + + @Override + public K lastKey() { + return keySet().last(); + } + + private ImmutableSortedMap getSubMap(int fromIndex, int toIndex) { + if (fromIndex == 0 && toIndex == size()) { + return this; + } else if (fromIndex == toIndex) { + return emptyMap(comparator()); + } else { + return new ImmutableSortedMap<>( + keySet.getSubSet(fromIndex, toIndex), valueList.subList(fromIndex, toIndex)); + } + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are less + * than {@code toKey}. + * + *

The {@link SortedMap#headMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code toKey} greater than an earlier {@code + * toKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code toKey}. + */ + @Override + public ImmutableSortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are less + * than (or equal to, if {@code inclusive}) {@code toKey}. + * + *

The {@link SortedMap#headMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code toKey} greater than an earlier {@code + * toKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code toKey}. + */ + @Override + public ImmutableSortedMap headMap(K toKey, boolean inclusive) { + return getSubMap(0, keySet.headIndex(Objects.requireNonNull(toKey), inclusive)); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys ranges + * from {@code fromKey}, inclusive, to {@code toKey}, exclusive. + * + *

The {@link SortedMap#subMap} documentation states that a submap of a submap throws an {@link + * IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code fromKey}. + * However, this method doesn't throw an exception in that situation, but instead keeps the + * original {@code fromKey}. Similarly, this method keeps the original {@code toKey}, instead of + * throwing an exception, if passed a {@code toKey} greater than an earlier {@code toKey}. + */ + @Override + public ImmutableSortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys ranges + * from {@code fromKey} to {@code toKey}, inclusive or exclusive as indicated by the boolean + * flags. + * + *

The {@link SortedMap#subMap} documentation states that a submap of a submap throws an {@link + * IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code fromKey}. + * However, this method doesn't throw an exception in that situation, but instead keeps the + * original {@code fromKey}. Similarly, this method keeps the original {@code toKey}, instead of + * throwing an exception, if passed a {@code toKey} greater than an earlier {@code toKey}. + */ + @Override + public ImmutableSortedMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + Objects.requireNonNull(fromKey); + Objects.requireNonNull(toKey); + if (!(comparator().compare(fromKey, toKey) <= 0)) { + throw new IllegalArgumentException(String.format("expected fromKey <= toKey but %s > %s", fromKey, toKey)); + } + return headMap(toKey, toInclusive).tailMap(fromKey, fromInclusive); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are + * greater than or equals to {@code fromKey}. + * + *

The {@link SortedMap#tailMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code + * fromKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromKey}. + */ + @Override + public ImmutableSortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are + * greater than (or equal to, if {@code inclusive}) {@code fromKey}. + * + *

The {@link SortedMap#tailMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code + * fromKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromKey}. + */ + @Override + public ImmutableSortedMap tailMap(K fromKey, boolean inclusive) { + return getSubMap(keySet.tailIndex(Objects.requireNonNull(fromKey), inclusive), size()); + } + + @Override + public Entry lowerEntry(K key) { + return headMap(key, false).lastEntry(); + } + + @Override + public K lowerKey(K key) { + return keyOrNull(lowerEntry(key)); + } + + @Override + public Entry floorEntry(K key) { + return headMap(key, true).lastEntry(); + } + + @Override + public K floorKey(K key) { + return keyOrNull(floorEntry(key)); + } + + @Override + public Entry ceilingEntry(K key) { + return tailMap(key, true).firstEntry(); + } + + @Override + public K ceilingKey(K key) { + return keyOrNull(ceilingEntry(key)); + } + + @Override + public Entry higherEntry(K key) { + return tailMap(key, false).firstEntry(); + } + + @Override + public K higherKey(K key) { + return keyOrNull(higherEntry(key)); + } + + @Override + public Entry firstEntry() { + return isEmpty() ? null : entrySet().asList().get(0); + } + + @Override + public Entry lastEntry() { + return isEmpty() ? null : entrySet().asList().get(size() - 1); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public ImmutableSortedMap descendingMap() { + ImmutableSortedMap result = descendingMap; + if (result == null) { + if (isEmpty()) { + return result = emptyMap(Ordering.from(comparator()).reverse()); + } else { + return result = + new ImmutableSortedMap<>( + (RegularImmutableSortedSet) keySet.descendingSet(), valueList.reverse(), this); + } + } + return result; + } + + @Override + public ImmutableSortedSet navigableKeySet() { + return keySet; + } + + @Override + public ImmutableSortedSet descendingKeySet() { + return keySet.descendingSet(); + } + + private static K keyOrNull(Entry entry) { + return (entry == null) ? null : entry.getKey(); + } + + private class ImmutableSortedMapEntrySet extends ImmutableMapEntrySet { + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + public Spliterator> spliterator() { + return asList().spliterator(); + } + + @Override + public void forEach(Consumer> action) { + asList().forEach(action); + } + + @Override + ImmutableList> createAsList() { + return new ImmutableAsList>() { + @Override + public Entry get(int index) { + return new AbstractMap.SimpleImmutableEntry<>( + keySet.asList().get(index), valueList.get(index)); + } + + @Override + public Spliterator> spliterator() { + return Spliterators.indexed(size(), ImmutableSet.SPLITERATOR_CHARACTERISTICS, this::get); + } + + @Override + ImmutableCollection> delegateCollection() { + return ImmutableSortedMapEntrySet.this; + } + }; + } + + @Override + ImmutableMap map() { + return ImmutableSortedMap.this; + } + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedSet.java new file mode 100644 index 0000000..6b32b41 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/ImmutableSortedSet.java @@ -0,0 +1,784 @@ +package org.xbib.datastructures.immutable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Collector; +import org.xbib.datastructures.api.SortedIterable; +import org.xbib.datastructures.immutable.order.Ordering; +import static org.xbib.datastructures.immutable.ImmutableList.castOrCopyToCollection; + +/** + * A {@link NavigableSet} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: as with any sorted collection, you are strongly advised not to use a {@link + * Comparator} or {@link Comparable} type whose comparison behavior is inconsistent with + * equals. That is, {@code a.compareTo(b)} or {@code comparator.compare(a, b)} should equal zero + * if and only if {@code a.equals(b)}. If this advice is not followed, the resulting + * collection will not correctly obey its specification. + */ +public abstract class ImmutableSortedSet extends ImmutableSet.CachingAsList + implements NavigableSet, SortedIterable { + static final int SPLITERATOR_CHARACTERISTICS = + ImmutableSet.SPLITERATOR_CHARACTERISTICS | Spliterator.SORTED; + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSortedSet}, ordered by the specified comparator. + * + *

If the elements contain duplicates (according to the comparator), only the first duplicate + * in encounter order will appear in the result. + */ + public static Collector> toImmutableSortedSet( + Comparator comparator) { + Objects.requireNonNull(comparator); + return Collector.of( + () -> new ImmutableSortedSet.Builder(comparator), + ImmutableSortedSet.Builder::add, + ImmutableSortedSet.Builder::combine, + ImmutableSortedSet.Builder::build); + } + + static RegularImmutableSortedSet emptySet(Comparator comparator) { + if (Ordering.natural().equals(comparator)) { + return (RegularImmutableSortedSet) RegularImmutableSortedSet.NATURAL_EMPTY_SET; + } else { + return new RegularImmutableSortedSet(ImmutableList.of(), comparator); + } + } + + /** + * Returns the empty immutable sorted set. + * + *

Performance note: the instance returned is a singleton. + */ + public static ImmutableSortedSet of() { + return (ImmutableSortedSet) RegularImmutableSortedSet.NATURAL_EMPTY_SET; + } + + /** + * Returns an immutable sorted set containing a single element. + */ + public static > ImmutableSortedSet of(E element) { + return new RegularImmutableSortedSet(ImmutableList.of(element), Ordering.natural()); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of(E e1, E e2) { + return construct(Ordering.natural(), 2, e1, e2); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of(E e1, E e2, E e3) { + return construct(Ordering.natural(), 3, e1, e2, e3); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of(E e1, E e2, E e3, E e4) { + return construct(Ordering.natural(), 4, e1, e2, e3, e4); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5) { + return construct(Ordering.natural(), 5, e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + Comparable[] contents = new Comparable[6 + remaining.length]; + contents[0] = e1; + contents[1] = e2; + contents[2] = e3; + contents[3] = e4; + contents[4] = e5; + contents[5] = e6; + System.arraycopy(remaining, 0, contents, 6, remaining.length); + return construct(Ordering.natural(), contents.length, (E[]) contents); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static > ImmutableSortedSet copyOf(E[] elements) { + return construct(Ordering.natural(), elements.length, elements.clone()); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@code compareTo()}, only the first one + * specified is included. To create a copy of a {@code SortedSet} that preserves the comparator, + * call {@link #copyOfSorted} instead. This method iterates over {@code elements} at most once. + * + *

Note that if {@code s} is a {@code Set}, then {@code ImmutableSortedSet.copyOf(s)} + * returns an {@code ImmutableSortedSet} containing each of the strings in {@code s}, + * while {@code ImmutableSortedSet.of(s)} returns an {@code ImmutableSortedSet>} + * containing one element (the given set itself). + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf(Iterable elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@code compareTo()}, only the first one + * specified is included. To create a copy of a {@code SortedSet} that preserves the comparator, + * call {@link #copyOfSorted} instead. This method iterates over {@code elements} at most once. + * + *

Note that if {@code s} is a {@code Set}, then {@code ImmutableSortedSet.copyOf(s)} + * returns an {@code ImmutableSortedSet} containing each of the strings in {@code s}, + * while {@code ImmutableSortedSet.of(s)} returns an {@code ImmutableSortedSet>} + * containing one element (the given set itself). + * + *

Note: Despite what the method name suggests, if {@code elements} is an {@code + * ImmutableSortedSet}, it may be returned instead of a copy. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + *

This method is safe to use even when {@code elements} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf(Collection elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@code compareTo()}, only the first one + * specified is included. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf(Iterator elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by the given {@code + * Comparator}. When multiple elements are equivalent according to {@code compareTo()}, only the + * first one specified is included. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterator elements) { + return new Builder(comparator).addAll(elements).build(); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by the given {@code + * Comparator}. When multiple elements are equivalent according to {@code compare()}, only the + * first one specified is included. This method iterates over {@code elements} at most once. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterable elements) { + Objects.requireNonNull(comparator); + boolean hasSameComparator = hasSameComparator(comparator, elements); + + if (hasSameComparator && (elements instanceof ImmutableSortedSet)) { + @SuppressWarnings("unchecked") + ImmutableSortedSet original = (ImmutableSortedSet) elements; + if (!original.isPartialView()) { + return original; + } + } + @SuppressWarnings("unchecked") // elements only contains E's; it's safe. + E[] array = (E[]) castOrCopyToCollection(elements).toArray(); + return construct(comparator, array.length, array); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by the given {@code + * Comparator}. When multiple elements are equivalent according to {@code compareTo()}, only the + * first one specified is included. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is safe to use even when {@code elements} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Collection elements) { + return copyOf(comparator, (Iterable) elements); + } + + /** + * Returns an immutable sorted set containing the elements of a sorted set, sorted by the same + * {@code Comparator}. That behavior differs from {@link #copyOf(Iterable)}, which always uses the + * natural ordering of the elements. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is safe to use even when {@code sortedSet} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code sortedSet} or any of its elements is null + */ + public static ImmutableSortedSet copyOfSorted(SortedSet sortedSet) { + Comparator comparator = comparator(sortedSet); + ImmutableList list = ImmutableList.copyOf(sortedSet); + if (list.isEmpty()) { + return emptySet(comparator); + } else { + return new RegularImmutableSortedSet(list, comparator); + } + } + + /** + * Constructs an {@code ImmutableSortedSet} from the first {@code n} elements of {@code contents}. + * If {@code k} is the size of the returned {@code ImmutableSortedSet}, then the sorted unique + * elements are in the first {@code k} positions of {@code contents}, and {@code contents[i] == + * null} for {@code k <= i < n}. + * + *

If {@code k == contents.length}, then {@code contents} may no longer be safe for + * modification. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code contents} is null + */ + static ImmutableSortedSet construct( + Comparator comparator, int n, E... contents) { + if (n == 0) { + return emptySet(comparator); + } + checkElementsNotNull(contents, n); + Arrays.sort(contents, 0, n, comparator); + int uniques = 1; + for (int i = 1; i < n; i++) { + E cur = contents[i]; + E prev = contents[uniques - 1]; + if (comparator.compare(cur, prev) != 0) { + contents[uniques++] = cur; + } + } + Arrays.fill(contents, uniques, n, null); + return new RegularImmutableSortedSet( + ImmutableList.asImmutableList(contents, uniques), comparator); + } + + /** + * Returns a builder that creates immutable sorted sets with an explicit comparator. If the + * comparator has a more general type than the set being generated, such as creating a {@code + * SortedSet} with a {@code Comparator}, use the {@link Builder} constructor + * instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are ordered by the reverse + * of their natural ordering. + */ + public static > Builder reverseOrder() { + return new Builder(Collections.reverseOrder()); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are ordered by their + * natural ordering. The sorted sets use {@link Ordering#natural()} as the comparator. This method + * provides more type-safety than {@link #builder}, as it can be called only for classes that + * implement {@link Comparable}. + */ + public static > Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * A builder for creating immutable sorted set instances, especially {@code public static final} + * sets ("constant sets"), with a given comparator. Example: + * + *

{@code
+     * public static final ImmutableSortedSet LUCKY_NUMBERS =
+     *     new ImmutableSortedSet.Builder(ODDS_FIRST_COMPARATOR)
+     *         .addAll(SINGLE_DIGIT_PRIMES)
+     *         .add(42)
+     *         .build();
+     * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple sets in series. Each set is a superset of the set created before it. + */ + public static final class Builder extends ImmutableSet.Builder { + private final Comparator comparator; + private E[] elements; + private int n; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableSortedSet#orderedBy}. + */ + public Builder(Comparator comparator) { + super(true); // don't construct guts of hash-based set builder + this.comparator = Objects.requireNonNull(comparator); + this.elements = (E[]) new Object[ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY]; + this.n = 0; + } + + @Override + void copy() { + elements = Arrays.copyOf(elements, elements.length); + } + + private void sortAndDedup() { + if (n == 0) { + return; + } + Arrays.sort(elements, 0, n, comparator); + int unique = 1; + for (int i = 1; i < n; i++) { + int cmp = comparator.compare(elements[unique - 1], elements[i]); + if (cmp < 0) { + elements[unique++] = elements[i]; + } else if (cmp > 0) { + throw new AssertionError( + "Comparator " + comparator + " compare method violates its contract"); + } + } + Arrays.fill(elements, unique, n, null); + n = unique; + } + + /** + * Adds {@code element} to the {@code ImmutableSortedSet}. If the {@code ImmutableSortedSet} + * already contains {@code element}, then {@code add} has no effect. (only the previously added + * element is retained). + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override + public Builder add(E element) { + Objects.requireNonNull(element); + copyIfNecessary(); + if (n == elements.length) { + sortAndDedup(); + /* + * Sorting operations can only be allowed to occur once every O(n) operations to keep + * amortized O(n log n) performance. Therefore, ensure there are at least O(n) *unused* + * spaces in the builder array. + */ + int newLength = ImmutableCollection.Builder.expandedCapacity(n, n + 1); + if (newLength > elements.length) { + elements = Arrays.copyOf(elements, newLength); + } + } + elements[n++] = element; + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override + public Builder add(E... elements) { + if (elements == null) { + throw new IllegalArgumentException(); + } + for (E e : elements) { + add(e); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override + public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + @Override + Builder combine(ImmutableSet.Builder builder) { + copyIfNecessary(); + Builder other = (Builder) builder; + for (int i = 0; i < other.n; i++) { + add(other.elements[i]); + } + return this; + } + + /** + * Returns a newly-created {@code ImmutableSortedSet} based on the contents of the {@code + * Builder} and its comparator. + */ + @Override + public ImmutableSortedSet build() { + sortAndDedup(); + if (n == 0) { + return emptySet(comparator); + } else { + forceCopy = true; + return new RegularImmutableSortedSet( + ImmutableList.asImmutableList(elements, n), comparator); + } + } + } + + int unsafeCompare(Object a, Object b) { + return unsafeCompare(comparator, a, b); + } + + static int unsafeCompare(Comparator comparator, Object a, Object b) { + // Pretend the comparator can compare anything. If it turns out it can't + // compare a and b, we should get a CCE or NPE on the subsequent line. Only methods + // that are spec'd to throw CCE and NPE should call this. + @SuppressWarnings({"unchecked", "nullness"}) + Comparator unsafeComparator = (Comparator) comparator; + return unsafeComparator.compare(a, b); + } + + final transient Comparator comparator; + + ImmutableSortedSet(Comparator comparator) { + this.comparator = comparator; + } + + /** + * Returns the comparator that orders the elements, which is {@link Ordering#natural()} when the + * natural ordering of the elements is used. Note that its behavior is not consistent with {@link + * SortedSet#comparator()}, which returns {@code null} to indicate natural ordering. + */ + @Override + public Comparator comparator() { + return comparator; + } + + @Override // needed to unify the iterator() methods in Collection and SortedIterable + public abstract UnmodifiableIterator iterator(); + + /** + * {@inheritDoc} + * + *

This method returns a {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#headSet} documentation states that a subset of a subset throws an + * {@link IllegalArgumentException} if passed a {@code toElement} greater than an earlier {@code + * toElement}. However, this method doesn't throw an exception in that situation, but instead + * keeps the original {@code toElement}. + */ + @Override + public ImmutableSortedSet headSet(E toElement) { + return headSet(toElement, false); + } + + @Override + public ImmutableSortedSet headSet(E toElement, boolean inclusive) { + return headSetImpl(Objects.requireNonNull(toElement), inclusive); + } + + /** + * {@inheritDoc} + * + *

This method returns a {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#subSet} documentation states that a subset of a subset throws an {@link + * IllegalArgumentException} if passed a {@code fromElement} smaller than an earlier {@code + * fromElement}. However, this method doesn't throw an exception in that situation, but instead + * keeps the original {@code fromElement}. Similarly, this method keeps the original {@code + * toElement}, instead of throwing an exception, if passed a {@code toElement} greater than an + * earlier {@code toElement}. + */ + @Override + public ImmutableSortedSet subSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public ImmutableSortedSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + Objects.requireNonNull(fromElement); + Objects.requireNonNull(toElement); + if (!(comparator.compare(fromElement, toElement) <= 0)) { + throw new IllegalArgumentException(); + } + return subSetImpl(fromElement, fromInclusive, toElement, toInclusive); + } + + /** + * {@inheritDoc} + * + *

This method returns a {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#tailSet} documentation states that a subset of a subset throws an + * {@link IllegalArgumentException} if passed a {@code fromElement} smaller than an earlier {@code + * fromElement}. However, this method doesn't throw an exception in that situation, but instead + * keeps the original {@code fromElement}. + */ + @Override + public ImmutableSortedSet tailSet(E fromElement) { + return tailSet(fromElement, true); + } + + @Override + public ImmutableSortedSet tailSet(E fromElement, boolean inclusive) { + return tailSetImpl(Objects.requireNonNull(fromElement), inclusive); + } + + /* + * These methods perform most headSet, subSet, and tailSet logic, besides + * parameter validation. + */ + abstract ImmutableSortedSet headSetImpl(E toElement, boolean inclusive); + + abstract ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive); + + abstract ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive); + + @Override + public E lower(E e) { + return getNext(headSet(e, false).descendingIterator(), null); + } + + @Override + public E floor(E e) { + return getNext(headSet(e, true).descendingIterator(), null); + } + + @Override + public E ceiling(E e) { + return getNext(tailSet(e, true).iterator(), null); + } + + @Override + public E higher(E e) { + return getNext(tailSet(e, false).iterator(), null); + } + + @Override + public E first() { + return iterator().next(); + } + + @Override + public E last() { + return descendingIterator().next(); + } + + /** + * Guaranteed to throw an exception and leave the set unmodified. + */ + @Override + public final E pollFirst() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the set unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final E pollLast() { + throw new UnsupportedOperationException(); + } + + transient ImmutableSortedSet descendingSet; + + @Override + public ImmutableSortedSet descendingSet() { + // racy single-check idiom + ImmutableSortedSet result = descendingSet; + if (result == null) { + result = descendingSet = createDescendingSet(); + result.descendingSet = this; + } + return result; + } + + // Most classes should implement this as new DescendingImmutableSortedSet(this), + // but we push down that implementation because ProGuard can't eliminate it even when it's always + // overridden. + abstract ImmutableSortedSet createDescendingSet(); + + @Override + public Spliterator spliterator() { + return new Spliterators.AbstractSpliterator( + size(), SPLITERATOR_CHARACTERISTICS | Spliterator.SIZED) { + final UnmodifiableIterator iterator = iterator(); + + @Override + public boolean tryAdvance(Consumer action) { + if (iterator.hasNext()) { + action.accept(iterator.next()); + return true; + } else { + return false; + } + } + + @Override + public Comparator getComparator() { + return comparator; + } + }; + } + + @Override + public abstract UnmodifiableIterator descendingIterator(); + + /** + * Returns the position of an element within the set, or -1 if not present. + */ + abstract int indexOf(Object target); + + private static Object[] checkElementsNotNull(Object[] array, int length) { + for (int i = 0; i < length; i++) { + checkElementNotNull(array[i], i); + } + return array; + } + + private static Object checkElementNotNull(Object element, int index) { + if (element == null) { + throw new NullPointerException("at index " + index); + } + return element; + } + + private static T getNext(Iterator iterator, T defaultValue) { + return iterator.hasNext() ? iterator.next() : defaultValue; + } + + private static Comparator comparator(SortedSet sortedSet) { + Comparator result = sortedSet.comparator(); + if (result == null) { + result = (Comparator) Ordering.natural(); + } + return result; + } + + private static boolean hasSameComparator(Comparator comparator, Iterable elements) { + Objects.requireNonNull(comparator); + Objects.requireNonNull(elements); + Comparator comparator2; + if (elements instanceof SortedSet) { + comparator2 = comparator((SortedSet) elements); + } else if (elements instanceof SortedIterable) { + comparator2 = ((SortedIterable) elements).comparator(); + } else { + return false; + } + return comparator.equals(comparator2); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IndexedImmutableSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IndexedImmutableSet.java new file mode 100644 index 0000000..27098e8 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IndexedImmutableSet.java @@ -0,0 +1,58 @@ +package org.xbib.datastructures.immutable; + +import java.util.Objects; +import java.util.Spliterator; +import java.util.function.Consumer; + +abstract class IndexedImmutableSet extends ImmutableSet.CachingAsList { + abstract E get(int index); + + @Override + public UnmodifiableIterator iterator() { + return asList().iterator(); + } + + @Override + public Spliterator spliterator() { + return indexed(size(), SPLITERATOR_CHARACTERISTICS, this::get); + } + + @Override + public void forEach(Consumer consumer) { + Objects.requireNonNull(consumer); + int n = size(); + for (int i = 0; i < n; i++) { + consumer.accept(get(i)); + } + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + return asList().copyIntoArray(dst, offset); + } + + @Override + ImmutableList createAsList() { + return new ImmutableAsList() { + @Override + public E get(int index) { + return IndexedImmutableSet.this.get(index); + } + + @Override + boolean isPartialView() { + return IndexedImmutableSet.this.isPartialView(); + } + + @Override + public int size() { + return IndexedImmutableSet.this.size(); + } + + @Override + ImmutableCollection delegateCollection() { + return IndexedImmutableSet.this; + } + }; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IteratorBasedImmutableMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IteratorBasedImmutableMap.java new file mode 100644 index 0000000..5ab5ce5 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/IteratorBasedImmutableMap.java @@ -0,0 +1,41 @@ +package org.xbib.datastructures.immutable; + +import java.util.Spliterator; +import java.util.Spliterators; + +abstract class IteratorBasedImmutableMap extends ImmutableMap { + abstract UnmodifiableIterator> entryIterator(); + + Spliterator> entrySpliterator() { + return Spliterators.spliterator( + entryIterator(), + size(), + Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + ImmutableSet> createEntrySet() { + class EntrySetImpl extends ImmutableMapEntrySet { + @Override + ImmutableMap map() { + return IteratorBasedImmutableMap.this; + } + + @Override + public UnmodifiableIterator> iterator() { + return entryIterator(); + } + } + return new EntrySetImpl(); + } + + @Override + ImmutableCollection createValues() { + return new ImmutableMapValues<>(this); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableBiMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableBiMap.java new file mode 100644 index 0000000..2c7c840 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableBiMap.java @@ -0,0 +1,100 @@ +package org.xbib.datastructures.immutable; + +import java.util.HashMap; +import java.util.Map; +import static java.util.Objects.requireNonNull; + +/** + * Implementation of ImmutableBiMap backed by a pair of JDK HashMaps, which have smartness + * protecting against hash flooding. + */ +final class JdkBackedImmutableBiMap extends ImmutableBiMap { + static ImmutableBiMap create(int n, Entry[] entryArray) { + Map forwardDelegate = new HashMap<>(n); + Map backwardDelegate = new HashMap<>(n); + for (int i = 0; i < n; i++) { + // requireNonNull is safe because the first `n` elements have been filled in. + Entry e = RegularImmutableMap.makeImmutable(requireNonNull(entryArray[i])); + entryArray[i] = e; + V oldValue = forwardDelegate.putIfAbsent(e.getKey(), e.getValue()); + if (oldValue != null) { + throw conflictException("key", e.getKey() + "=" + oldValue, entryArray[i]); + } + K oldKey = backwardDelegate.putIfAbsent(e.getValue(), e.getKey()); + if (oldKey != null) { + throw conflictException("value", oldKey + "=" + e.getValue(), entryArray[i]); + } + } + ImmutableList> entryList = ImmutableList.asImmutableList(entryArray, n); + return new JdkBackedImmutableBiMap<>(entryList, forwardDelegate, backwardDelegate); + } + + private final transient ImmutableList> entries; + private final Map forwardDelegate; + private final Map backwardDelegate; + + private JdkBackedImmutableBiMap( + ImmutableList> entries, Map forwardDelegate, Map backwardDelegate) { + this.entries = entries; + this.forwardDelegate = forwardDelegate; + this.backwardDelegate = backwardDelegate; + } + + @Override + public int size() { + return entries.size(); + } + + private transient JdkBackedImmutableBiMap inverse; + + @Override + public ImmutableBiMap inverse() { + JdkBackedImmutableBiMap result = inverse; + if (result == null) { + inverse = + result = + new JdkBackedImmutableBiMap<>( + new InverseEntries(), backwardDelegate, forwardDelegate); + result.inverse = this; + } + return result; + } + + private final class InverseEntries extends ImmutableList> { + @Override + public Entry get(int index) { + Entry entry = entries.get(index); + return new ImmutableEntry<>(entry.getValue(), entry.getKey()); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return entries.size(); + } + } + + @Override + public V get(Object key) { + return forwardDelegate.get(key); + } + + @Override + ImmutableSet> createEntrySet() { + return new RegularEntrySet<>(this, entries); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + boolean isPartialView() { + return false; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableMap.java new file mode 100644 index 0000000..15a811a --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableMap.java @@ -0,0 +1,109 @@ +package org.xbib.datastructures.immutable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import static java.util.Objects.requireNonNull; + +/** + * Implementation of ImmutableMap backed by a JDK HashMap, which has smartness protecting against + * hash flooding. + */ +final class JdkBackedImmutableMap extends ImmutableMap { + /** + * Creates an {@code ImmutableMap} backed by a JDK HashMap. Used when probable hash flooding is + * detected. This implementation may replace the entries in entryArray with its own entry objects + * (though they will have the same key/value contents), and will take ownership of entryArray. + */ + static ImmutableMap create( + int n, Entry[] entryArray, boolean throwIfDuplicateKeys) { + Map delegateMap = new HashMap<>(n); + // If duplicates are allowed, this map will track the last value for each duplicated key. + // A second pass will retain only the first entry for that key, but with this last value. The + // value will then be replaced by null, signaling that later entries with the same key should + // be deleted. + Map duplicates = null; + int dupCount = 0; + for (int i = 0; i < n; i++) { + // requireNonNull is safe because the first `n` elements have been filled in. + entryArray[i] = RegularImmutableMap.makeImmutable(requireNonNull(entryArray[i])); + K key = entryArray[i].getKey(); + V value = entryArray[i].getValue(); + V oldValue = delegateMap.put(key, value); + if (oldValue != null) { + if (throwIfDuplicateKeys) { + throw conflictException("key", entryArray[i], entryArray[i].getKey() + "=" + oldValue); + } + if (duplicates == null) { + duplicates = new HashMap<>(); + } + duplicates.put(key, value); + dupCount++; + } + } + if (duplicates != null) { + @SuppressWarnings({"rawtypes", "unchecked"}) + Entry[] newEntryArray = new Entry[n - dupCount]; + for (int inI = 0, outI = 0; inI < n; inI++) { + Entry entry = requireNonNull(entryArray[inI]); + K key = entry.getKey(); + if (duplicates.containsKey(key)) { + V value = duplicates.get(key); + if (value == null) { + continue; // delete this duplicate + } + entry = new ImmutableMapEntry<>(key, value); + duplicates.put(key, null); + } + newEntryArray[outI++] = entry; + } + entryArray = newEntryArray; + } + return new JdkBackedImmutableMap<>(delegateMap, ImmutableList.asImmutableList(entryArray, n)); + } + + private final transient Map delegateMap; + private final transient ImmutableList> entries; + + JdkBackedImmutableMap(Map delegateMap, ImmutableList> entries) { + this.delegateMap = delegateMap; + this.entries = entries; + } + + @Override + public int size() { + return entries.size(); + } + + @Override + public V get(Object key) { + return delegateMap.get(key); + } + + @Override + ImmutableSet> createEntrySet() { + return new RegularEntrySet<>(this, entries); + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + entries.forEach(e -> action.accept(e.getKey(), e.getValue())); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + ImmutableCollection createValues() { + return new ImmutableMapValues<>(this); + } + + @Override + boolean isPartialView() { + return false; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableSet.java new file mode 100644 index 0000000..fef4096 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/JdkBackedImmutableSet.java @@ -0,0 +1,37 @@ +package org.xbib.datastructures.immutable; + +import java.util.Set; + +/** + * ImmutableSet implementation backed by a JDK HashSet, used to defend against apparent hash + * flooding. + */ +final class JdkBackedImmutableSet extends IndexedImmutableSet { + private final Set delegate; + private final ImmutableList delegateList; + + JdkBackedImmutableSet(Set delegate, ImmutableList delegateList) { + this.delegate = delegate; + this.delegateList = delegateList; + } + + @Override + E get(int index) { + return delegateList.get(index); + } + + @Override + public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return delegateList.size(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableBiMapEntry.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableBiMapEntry.java new file mode 100644 index 0000000..6b5b3e4 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableBiMapEntry.java @@ -0,0 +1,19 @@ +package org.xbib.datastructures.immutable; + +class NonTerminalImmutableBiMapEntry + extends NonTerminalImmutableMapEntry { + private final transient ImmutableMapEntry nextInValueBucket; + + NonTerminalImmutableBiMapEntry(K key, + V value, + ImmutableMapEntry nextInKeyBucket, + ImmutableMapEntry nextInValueBucket) { + super(key, value, nextInKeyBucket); + this.nextInValueBucket = nextInValueBucket; + } + + @Override + ImmutableMapEntry getNextInValueBucket() { + return nextInValueBucket; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableMapEntry.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableMapEntry.java new file mode 100644 index 0000000..175ef9a --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/NonTerminalImmutableMapEntry.java @@ -0,0 +1,26 @@ +package org.xbib.datastructures.immutable; + +class NonTerminalImmutableMapEntry extends ImmutableMapEntry { + /* + * Yes, we sometimes set nextInKeyBucket to null, even for this "non-terminal" entry. We don't + * do that with a plain NonTerminalImmutableMapEntry, but we do it with the BiMap-specific + * subclass below. That's because the Entry might be non-terminal in the key bucket but terminal + * in the value bucket (or vice versa). + */ + private final transient ImmutableMapEntry nextInKeyBucket; + + NonTerminalImmutableMapEntry(K key, V value, ImmutableMapEntry nextInKeyBucket) { + super(key, value); + this.nextInKeyBucket = nextInKeyBucket; + } + + @Override + final ImmutableMapEntry getNextInKeyBucket() { + return nextInKeyBucket; + } + + @Override + final boolean isReusable() { + return false; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularEntrySet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularEntrySet.java new file mode 100644 index 0000000..28a66b2 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularEntrySet.java @@ -0,0 +1,49 @@ +package org.xbib.datastructures.immutable; + +import java.util.Map; +import java.util.Spliterator; +import java.util.function.Consumer; + +final class RegularEntrySet extends ImmutableMapEntrySet { + private final transient ImmutableMap map; + private final transient ImmutableList> entries; + + RegularEntrySet(ImmutableMap map, Map.Entry[] entries) { + this(map, ImmutableList.asImmutableList(entries)); + } + + RegularEntrySet(ImmutableMap map, ImmutableList> entries) { + this.map = map; + this.entries = entries; + } + + @Override + ImmutableMap map() { + return map; + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + return entries.copyIntoArray(dst, offset); + } + + @Override + public UnmodifiableIterator> iterator() { + return entries.iterator(); + } + + @Override + public Spliterator> spliterator() { + return entries.spliterator(); + } + + @Override + public void forEach(Consumer> action) { + entries.forEach(action); + } + + @Override + ImmutableList> createAsList() { + return new RegularImmutableAsList<>(this, entries); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableAsList.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableAsList.java new file mode 100644 index 0000000..bdd40f0 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableAsList.java @@ -0,0 +1,66 @@ +package org.xbib.datastructures.immutable; + +import java.util.function.Consumer; + +/** + * An {@link ImmutableAsList} implementation specialized for when the delegate collection is already + * backed by an {@code ImmutableList} or array. + */ +class RegularImmutableAsList extends ImmutableAsList { + private final ImmutableCollection delegate; + private final ImmutableList delegateList; + + RegularImmutableAsList(ImmutableCollection delegate, ImmutableList delegateList) { + this.delegate = delegate; + this.delegateList = delegateList; + } + + RegularImmutableAsList(ImmutableCollection delegate, Object[] array) { + this(delegate, ImmutableList.asImmutableList(array)); + } + + @Override + ImmutableCollection delegateCollection() { + return delegate; + } + + ImmutableList delegateList() { + return delegateList; + } + + @SuppressWarnings("unchecked") // safe covariant cast! + @Override + public UnmodifiableListIterator listIterator(int index) { + return (UnmodifiableListIterator) delegateList.listIterator(index); + } + + @Override + public void forEach(Consumer action) { + delegateList.forEach(action); + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + return delegateList.copyIntoArray(dst, offset); + } + + @Override + Object[] internalArray() { + return delegateList.internalArray(); + } + + @Override + int internalArrayStart() { + return delegateList.internalArrayStart(); + } + + @Override + int internalArrayEnd() { + return delegateList.internalArrayEnd(); + } + + @Override + public E get(int index) { + return delegateList.get(index); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableBiMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableBiMap.java new file mode 100644 index 0000000..6da4813 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableBiMap.java @@ -0,0 +1,289 @@ +package org.xbib.datastructures.immutable; + +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import static java.util.Objects.requireNonNull; +import static org.xbib.datastructures.immutable.ImmutableCollection.checkPositionIndex; +import static org.xbib.datastructures.immutable.ImmutableMapEntry.checkEntryNotNull; +import static org.xbib.datastructures.immutable.ImmutableMapEntry.createEntryArray; +import static org.xbib.datastructures.immutable.RegularImmutableMap.checkNoConflictInKeyBucket; + +/** + * Bimap with zero or more mappings. + */ +final class RegularImmutableBiMap extends ImmutableBiMap { + @SuppressWarnings("unchecked") + static final RegularImmutableBiMap EMPTY = + new RegularImmutableBiMap<>( + null, null, (Entry[]) EMPTY_ENTRY_ARRAY, 0, 0); + + static final double MAX_LOAD_FACTOR = 1.2; + + private final transient ImmutableMapEntry[] keyTable; + + private final transient ImmutableMapEntry[] valueTable; + + final transient Entry[] entries; + + private final transient int mask; + + private final transient int hashCode; + + @SuppressWarnings("unchecked") + static ImmutableBiMap fromEntries(Entry... entries) { + return fromEntryArray(entries.length, entries); + } + + static ImmutableBiMap fromEntryArray(int n, Entry[] entryArray) { + checkPositionIndex(n, entryArray.length, "n"); + int tableSize = closedTableSize(n, MAX_LOAD_FACTOR); + int mask = tableSize - 1; + ImmutableMapEntry[] keyTable = createEntryArray(tableSize); + ImmutableMapEntry[] valueTable = createEntryArray(tableSize); + /* + * The cast is safe: n==entryArray.length means that we have filled the whole array with Entry + * instances, in which case it is safe to cast it from an array of nullable entries to an array + * of non-null entries. + */ + @SuppressWarnings("nullness") + Entry[] entries = + (n == entryArray.length) ? entryArray : createEntryArray(n); + int hashCode = 0; + + for (int i = 0; i < n; i++) { + // requireNonNull is safe because the first `n` elements have been filled in. + Entry entry = requireNonNull(entryArray[i]); + K key = entry.getKey(); + V value = entry.getValue(); + checkEntryNotNull(key, value); + int keyHash = key.hashCode(); + int valueHash = value.hashCode(); + int keyBucket = smear(keyHash) & mask; + int valueBucket = smear(valueHash) & mask; + + ImmutableMapEntry nextInKeyBucket = keyTable[keyBucket]; + ImmutableMapEntry nextInValueBucket = valueTable[valueBucket]; + try { + checkNoConflictInKeyBucket(key, value, nextInKeyBucket, /* throwIfDuplicateKeys= */ true); + checkNoConflictInValueBucket(value, entry, nextInValueBucket); + } catch (RegularImmutableMap.BucketOverflowException e) { + return JdkBackedImmutableBiMap.create(n, entryArray); + } + ImmutableMapEntry newEntry = + (nextInValueBucket == null && nextInKeyBucket == null) + ? RegularImmutableMap.makeImmutable(entry, key, value) + : new NonTerminalImmutableBiMapEntry<>( + key, value, nextInKeyBucket, nextInValueBucket); + keyTable[keyBucket] = newEntry; + valueTable[valueBucket] = newEntry; + entries[i] = newEntry; + hashCode += keyHash ^ valueHash; + } + return new RegularImmutableBiMap<>(keyTable, valueTable, entries, mask, hashCode); + } + + private RegularImmutableBiMap(ImmutableMapEntry[] keyTable, ImmutableMapEntry[] valueTable, + Entry[] entries, + int mask, + int hashCode) { + this.keyTable = keyTable; + this.valueTable = valueTable; + this.entries = entries; + this.mask = mask; + this.hashCode = hashCode; + } + + // checkNoConflictInKeyBucket is static imported from RegularImmutableMap + + /** + * @throws IllegalArgumentException if another entry in the bucket has the same key + * @throws RegularImmutableMap.BucketOverflowException if this bucket has too many entries, which may indicate a hash + * flooding attack + */ + private static void checkNoConflictInValueBucket( + Object value, Entry entry, ImmutableMapEntry valueBucketHead) + throws RegularImmutableMap.BucketOverflowException { + int bucketSize = 0; + for (; valueBucketHead != null; valueBucketHead = valueBucketHead.getNextInValueBucket()) { + checkNoConflict(!value.equals(valueBucketHead.getValue()), "value", entry, valueBucketHead); + if (++bucketSize > RegularImmutableMap.MAX_HASH_BUCKET_LENGTH) { + throw new RegularImmutableMap.BucketOverflowException(); + } + } + } + + @Override + public V get(Object key) { + return RegularImmutableMap.get(key, keyTable, mask); + } + + @Override + ImmutableSet> createEntrySet() { + return isEmpty() + ? ImmutableSet.of() + : new RegularEntrySet(this, entries); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + for (Entry entry : entries) { + action.accept(entry.getKey(), entry.getValue()); + } + } + + @Override + boolean isHashCodeFast() { + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return entries.length; + } + + private transient ImmutableBiMap inverse; + + @Override + public ImmutableBiMap inverse() { + if (isEmpty()) { + return of(); + } + ImmutableBiMap result = inverse; + return (result == null) ? inverse = new Inverse() : result; + } + + private final class Inverse extends ImmutableBiMap { + + @Override + public int size() { + return inverse().size(); + } + + @Override + public ImmutableBiMap inverse() { + return RegularImmutableBiMap.this; + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + RegularImmutableBiMap.this.forEach((k, v) -> action.accept(v, k)); + } + + @Override + public K get(Object value) { + if (value == null || valueTable == null) { + return null; + } + int bucket = smear(value.hashCode()) & mask; + for (ImmutableMapEntry entry = valueTable[bucket]; + entry != null; + entry = entry.getNextInValueBucket()) { + if (value.equals(entry.getValue())) { + return entry.getKey(); + } + } + return null; + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + ImmutableSet> createEntrySet() { + return new InverseEntrySet(); + } + + final class InverseEntrySet extends ImmutableMapEntrySet { + @Override + ImmutableMap map() { + return Inverse.this; + } + + @Override + boolean isHashCodeFast() { + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + public void forEach(Consumer> action) { + asList().forEach(action); + } + + @Override + ImmutableList> createAsList() { + return new ImmutableAsList>() { + @Override + public Entry get(int index) { + Entry entry = entries[index]; + return new ImmutableMapEntry<>(entry.getValue(), entry.getKey()); + } + + @Override + ImmutableCollection> delegateCollection() { + return InverseEntrySet.this; + } + }; + } + } + + @Override + boolean isPartialView() { + return false; + } + } + + private static int closedTableSize(int expectedEntries, double loadFactor) { + // Get the recommended table size. + // Round down to the nearest power of 2. + expectedEntries = Math.max(expectedEntries, 2); + int tableSize = Integer.highestOneBit(expectedEntries); + // Check to make sure that we will not exceed the maximum load factor. + if (expectedEntries > (int) (loadFactor * tableSize)) { + tableSize <<= 1; + return (tableSize > 0) ? tableSize : MAX_TABLE_SIZE; + } + return tableSize; + } + + private static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + private static final int MAX_TABLE_SIZE = MAX_POWER_OF_TWO; + + private static int smear(int hashCode) { + return (int) (C2 * Integer.rotateLeft((int) (hashCode * C1), 15)); + } + + private static final long C1 = 0xcc9e2d51; + + private static final long C2 = 0x1b873593; +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableList.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableList.java new file mode 100644 index 0000000..eccb974 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableList.java @@ -0,0 +1,68 @@ +package org.xbib.datastructures.immutable; + +import java.util.Spliterator; +import java.util.Spliterators; + +/** + * Implementation of {@link ImmutableList} backed by a simple array. + */ +class RegularImmutableList extends ImmutableList { + static final ImmutableList EMPTY = new RegularImmutableList<>(new Object[0]); + + final transient Object[] array; + + RegularImmutableList(Object[] array) { + this.array = array; + } + + @Override + public int size() { + return array.length; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + Object[] internalArray() { + return array; + } + + @Override + int internalArrayStart() { + return 0; + } + + @Override + int internalArrayEnd() { + return array.length; + } + + @Override + int copyIntoArray(Object[] dst, int dstOff) { + System.arraycopy(array, 0, dst, dstOff, array.length); + return dstOff + array.length; + } + + // The fake cast to E is safe because the creation methods only allow E's + @Override + @SuppressWarnings("unchecked") + public E get(int index) { + return (E) array[index]; + } + + @SuppressWarnings("unchecked") + @Override + public UnmodifiableListIterator listIterator(int index) { + // for performance + // The fake cast to E is safe because the creation methods only allow E's + return (UnmodifiableListIterator) forArray(array, 0, array.length, index); + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(array, SPLITERATOR_CHARACTERISTICS); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMap.java new file mode 100644 index 0000000..aebd63e --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMap.java @@ -0,0 +1,313 @@ +package org.xbib.datastructures.immutable; + +import java.util.IdentityHashMap; +import java.util.Objects; +import java.util.function.BiConsumer; +import static java.util.Objects.requireNonNull; +import static org.xbib.datastructures.immutable.ImmutableCollection.checkPositionIndex; +import static org.xbib.datastructures.immutable.ImmutableMapEntry.checkEntryNotNull; +import static org.xbib.datastructures.immutable.ImmutableMapEntry.createEntryArray; + +/** + * Implementation of {@link ImmutableMap} with two or more entries. + */ +final class RegularImmutableMap extends ImmutableMap { + @SuppressWarnings("unchecked") + static final ImmutableMap EMPTY = + new RegularImmutableMap<>((Entry[]) EMPTY_ENTRY_ARRAY, null, 0); + + /** + * Closed addressing tends to perform well even with high load factors. Being conservative here + * ensures that the table is still likely to be relatively sparse (hence it misses fast) while + * saving space. + */ + static final double MAX_LOAD_FACTOR = 1.2; + + /** + * Maximum allowed false positive probability of detecting a hash flooding attack given random + * input. + */ + static final double HASH_FLOODING_FPP = 0.001; + + /** + * Maximum allowed length of a hash table bucket before falling back to a j.u.HashMap based + * implementation. Experimentally determined. + */ + static final int MAX_HASH_BUCKET_LENGTH = 8; + + // entries in insertion order + final transient Entry[] entries; + // array of linked lists of entries + private final transient ImmutableMapEntry[] table; + // 'and' with an int to get a table index + private final transient int mask; + + static ImmutableMap fromEntries(Entry... entries) { + return fromEntryArray(entries.length, entries, /* throwIfDuplicateKeys= */ true); + } + + /** + * Creates an ImmutableMap from the first n entries in entryArray. This implementation may replace + * the entries in entryArray with its own entry objects (though they will have the same key/value + * contents), and may take ownership of entryArray. + */ + static ImmutableMap fromEntryArray( + int n, Entry[] entryArray, boolean throwIfDuplicateKeys) { + checkPositionIndex(n, entryArray.length, "n"); + if (n == 0) { + @SuppressWarnings("unchecked") // it has no entries so the type variables don't matter + ImmutableMap empty = (ImmutableMap) EMPTY; + return empty; + } + try { + return fromEntryArrayCheckingBucketOverflow(n, entryArray, throwIfDuplicateKeys); + } catch (BucketOverflowException e) { + // probable hash flooding attack, fall back to j.u.HM based implementation and use its + // implementation of hash flooding protection + return JdkBackedImmutableMap.create(n, entryArray, throwIfDuplicateKeys); + } + } + + private static ImmutableMap fromEntryArrayCheckingBucketOverflow( + int n, Entry[] entryArray, boolean throwIfDuplicateKeys) + throws BucketOverflowException { + /* + * The cast is safe: n==entryArray.length means that we have filled the whole array with Entry + * instances, in which case it is safe to cast it from an array of nullable entries to an array + * of non-null entries. + */ + @SuppressWarnings("nullness") + Entry[] entries = + (n == entryArray.length) ? entryArray : createEntryArray(n); + int tableSize = closedTableSize(n, MAX_LOAD_FACTOR); + ImmutableMapEntry[] table = createEntryArray(tableSize); + int mask = tableSize - 1; + // If duplicates are allowed, this IdentityHashMap will record the final Entry for each + // duplicated key. We will use this final Entry to overwrite earlier slots in the entries array + // that have the same key. Then a second pass will remove all but the first of the slots that + // have this Entry. The value in the map becomes false when this first entry has been copied, so + // we know not to copy the remaining ones. + IdentityHashMap, Boolean> duplicates = null; + int dupCount = 0; + for (int entryIndex = n - 1; entryIndex >= 0; entryIndex--) { + // requireNonNull is safe because the first `n` elements have been filled in. + Entry entry = requireNonNull(entryArray[entryIndex]); + K key = entry.getKey(); + V value = entry.getValue(); + checkEntryNotNull(key, value); + int tableIndex = smear(key.hashCode()) & mask; + ImmutableMapEntry keyBucketHead = table[tableIndex]; + ImmutableMapEntry effectiveEntry = + checkNoConflictInKeyBucket(key, value, keyBucketHead, throwIfDuplicateKeys); + if (effectiveEntry == null) { + // prepend, not append, so the entries can be immutable + effectiveEntry = + (keyBucketHead == null) + ? makeImmutable(entry, key, value) + : new NonTerminalImmutableMapEntry(key, value, keyBucketHead); + table[tableIndex] = effectiveEntry; + } else { + // We already saw this key, and the first value we saw (going backwards) is the one we are + // keeping. So we won't touch table[], but we do still want to add the existing entry that + // we found to entries[] so that we will see this key in the right place when iterating. + if (duplicates == null) { + duplicates = new IdentityHashMap<>(); + } + duplicates.put(effectiveEntry, true); + dupCount++; + // Make sure we are not overwriting the original entries array, in case we later do + // buildOrThrow(). We would want an exception to include two values for the duplicate key. + if (entries == entryArray) { + entries = entries.clone(); + } + } + entries[entryIndex] = effectiveEntry; + } + if (duplicates != null) { + // Explicit type parameters needed here to avoid a problem with nullness inference. + entries = RegularImmutableMap.removeDuplicates(entries, n, n - dupCount, duplicates); + int newTableSize = closedTableSize(entries.length, MAX_LOAD_FACTOR); + if (newTableSize != tableSize) { + return fromEntryArrayCheckingBucketOverflow( + entries.length, entries, /* throwIfDuplicateKeys= */ true); + } + } + return new RegularImmutableMap<>(entries, table, mask); + } + + /** + * Constructs a new entry array where each duplicated key from the original appears only once, at + * its first position but with its final value. The {@code duplicates} map is modified. + * + * @param entries the original array of entries including duplicates + * @param n the number of valid entries in {@code entries} + * @param newN the expected number of entries once duplicates are removed + * @param duplicates a map of canonical {@link Entry} objects for each duplicate key. This map + * will be updated by the method, setting each value to false as soon as the {@link Entry} has + * been included in the new entry array. + * @return an array of {@code newN} entries where no key appears more than once. + */ + static Entry[] removeDuplicates( + Entry[] entries, int n, int newN, IdentityHashMap, Boolean> duplicates) { + Entry[] newEntries = createEntryArray(newN); + for (int in = 0, out = 0; in < n; in++) { + Entry entry = entries[in]; + Boolean status = duplicates.get(entry); + // null=>not dup'd; true=>dup'd, first; false=>dup'd, not first + if (status != null) { + if (status) { + duplicates.put(entry, false); + } else { + continue; // delete this entry; we already copied an earlier one for the same key + } + } + newEntries[out++] = entry; + } + return newEntries; + } + + /** + * Makes an entry usable internally by a new ImmutableMap without rereading its contents. + */ + static ImmutableMapEntry makeImmutable(Entry entry, K key, V value) { + boolean reusable = + entry instanceof ImmutableMapEntry && ((ImmutableMapEntry) entry).isReusable(); + return reusable ? (ImmutableMapEntry) entry : new ImmutableMapEntry(key, value); + } + + /** + * Makes an entry usable internally by a new ImmutableMap. + */ + static ImmutableMapEntry makeImmutable(Entry entry) { + return makeImmutable(entry, entry.getKey(), entry.getValue()); + } + + private RegularImmutableMap(Entry[] entries, ImmutableMapEntry[] table, int mask) { + this.entries = entries; + this.table = table; + this.mask = mask; + } + + /** + * Checks if the given key already appears in the hash chain starting at {@code keyBucketHead}. If + * it does not, then null is returned. If it does, then if {@code throwIfDuplicateKeys} is true an + * {@code IllegalArgumentException} is thrown, and otherwise the existing {@link Entry} is + * returned. + * + * @throws IllegalArgumentException if another entry in the bucket has the same key and {@code + * throwIfDuplicateKeys} is true + * @throws BucketOverflowException if this bucket has too many entries, which may indicate a hash + * flooding attack + */ + static ImmutableMapEntry checkNoConflictInKeyBucket( + Object key, + Object newValue, + ImmutableMapEntry keyBucketHead, + boolean throwIfDuplicateKeys) + throws BucketOverflowException { + int bucketSize = 0; + for (; keyBucketHead != null; keyBucketHead = keyBucketHead.getNextInKeyBucket()) { + if (keyBucketHead.getKey().equals(key)) { + if (throwIfDuplicateKeys) { + checkNoConflict(/* safe= */ false, "key", keyBucketHead, key + "=" + newValue); + } else { + return keyBucketHead; + } + } + if (++bucketSize > MAX_HASH_BUCKET_LENGTH) { + throw new BucketOverflowException(); + } + } + return null; + } + + static class BucketOverflowException extends Exception { + } + + @Override + public V get(Object key) { + return get(key, table, mask); + } + + static V get(Object key, ImmutableMapEntry[] keyTable, + int mask) { + if (key == null || keyTable == null) { + return null; + } + int index = smear(key.hashCode()) & mask; + for (ImmutableMapEntry entry = keyTable[index]; + entry != null; + entry = entry.getNextInKeyBucket()) { + Object candidateKey = entry.getKey(); + + /* + * Assume that equals uses the == optimization when appropriate, and that + * it would check hash codes as an optimization when appropriate. If we + * did these things, it would just make things worse for the most + * performance-conscious users. + */ + if (key.equals(candidateKey)) { + return entry.getValue(); + } + } + return null; + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + for (Entry entry : entries) { + action.accept(entry.getKey(), entry.getValue()); + } + } + + @Override + public int size() { + return entries.length; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + ImmutableSet> createEntrySet() { + return new RegularEntrySet<>(this, entries); + } + + @Override + ImmutableSet createKeySet() { + return new RegularImmutableMapKeySet<>(this); + } + + @Override + ImmutableCollection createValues() { + return new RegularImmutableMapValues<>(this); + } + + private static int closedTableSize(int expectedEntries, double loadFactor) { + // Get the recommended table size. + // Round down to the nearest power of 2. + expectedEntries = Math.max(expectedEntries, 2); + int tableSize = Integer.highestOneBit(expectedEntries); + // Check to make sure that we will not exceed the maximum load factor. + if (expectedEntries > (int) (loadFactor * tableSize)) { + tableSize <<= 1; + return (tableSize > 0) ? tableSize : MAX_TABLE_SIZE; + } + return tableSize; + } + + private static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + private static final int MAX_TABLE_SIZE = MAX_POWER_OF_TWO; + + private static int smear(int hashCode) { + return (int) (C2 * Integer.rotateLeft((int) (hashCode * C1), 15)); + } + + private static final long C1 = 0xcc9e2d51; + + private static final long C2 = 0x1b873593; +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapKeySet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapKeySet.java new file mode 100644 index 0000000..d249469 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapKeySet.java @@ -0,0 +1,29 @@ +package org.xbib.datastructures.immutable; + +final class RegularImmutableMapKeySet extends IndexedImmutableSet { + private final RegularImmutableMap map; + + RegularImmutableMapKeySet(RegularImmutableMap map) { + this.map = map; + } + + @Override + K get(int index) { + return map.entries[index].getKey(); + } + + @Override + public boolean contains(Object object) { + return map.containsKey(object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + public int size() { + return map.size(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapValues.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapValues.java new file mode 100644 index 0000000..c4ad518 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableMapValues.java @@ -0,0 +1,24 @@ +package org.xbib.datastructures.immutable; + +final class RegularImmutableMapValues extends ImmutableList { + private final RegularImmutableMap map; + + RegularImmutableMapValues(RegularImmutableMap map) { + this.map = map; + } + + @Override + public V get(int index) { + return map.entries[index].getValue(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + boolean isPartialView() { + return true; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSet.java new file mode 100644 index 0000000..4817d88 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSet.java @@ -0,0 +1,114 @@ +package org.xbib.datastructures.immutable; + +import java.util.Spliterator; +import java.util.Spliterators; + +/** + * Implementation of {@link ImmutableSet} with two or more elements. + */ +final class RegularImmutableSet extends ImmutableSet.CachingAsList { + private static final Object[] EMPTY_ARRAY = new Object[0]; + static final RegularImmutableSet EMPTY = + new RegularImmutableSet<>(EMPTY_ARRAY, 0, EMPTY_ARRAY, 0); + + private final transient Object[] elements; + private final transient int hashCode; + // the same values as `elements` in hashed positions (plus nulls) + final transient Object[] table; + // 'and' with an int to get a valid table index. + private final transient int mask; + + RegularImmutableSet(Object[] elements, int hashCode, Object[] table, int mask) { + this.elements = elements; + this.hashCode = hashCode; + this.table = table; + this.mask = mask; + } + + @Override + public boolean contains(Object target) { + Object[] table = this.table; + if (target == null || table.length == 0) { + return false; + } + for (int i = smearedHash(target); ; i++) { + i &= mask; + Object candidate = table[i]; + if (candidate == null) { + return false; + } else if (candidate.equals(target)) { + return true; + } + } + } + + @Override + public int size() { + return elements.length; + } + + @Override + public UnmodifiableIterator iterator() { + return (UnmodifiableIterator) forArray(elements); + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(elements, SPLITERATOR_CHARACTERISTICS); + } + + @Override + Object[] internalArray() { + return elements; + } + + @Override + int internalArrayStart() { + return 0; + } + + @Override + int internalArrayEnd() { + return elements.length; + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + System.arraycopy(elements, 0, dst, offset, elements.length); + return offset + elements.length; + } + + @Override + ImmutableList createAsList() { + return (table.length == 0) + ? ImmutableList.of() + : new RegularImmutableAsList(this, elements); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + boolean isHashCodeFast() { + return true; + } + + private static int smearedHash(Object o) { + return smear((o == null) ? 0 : o.hashCode()); + } + + private static int smear(int hashCode) { + return (int) (C2 * Integer.rotateLeft((int) (hashCode * C1), 15)); + } + + private static final long C1 = 0xcc9e2d51; + + private static final long C2 = 0x1b873593; +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSortedSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSortedSet.java new file mode 100644 index 0000000..395b45a --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/RegularImmutableSortedSet.java @@ -0,0 +1,313 @@ +package org.xbib.datastructures.immutable; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.Consumer; +import org.xbib.datastructures.api.SortedIterable; +import org.xbib.datastructures.immutable.order.Ordering; + +/** + * An immutable sorted set with one or more elements. + */ +final class RegularImmutableSortedSet extends ImmutableSortedSet { + + static final RegularImmutableSortedSet> NATURAL_EMPTY_SET = + new RegularImmutableSortedSet<>(ImmutableList.of(), Ordering.natural()); + + private final transient ImmutableList elements; + + RegularImmutableSortedSet(ImmutableList elements, Comparator comparator) { + super(comparator); + this.elements = elements; + } + + @Override + Object[] internalArray() { + return elements.internalArray(); + } + + @Override + int internalArrayStart() { + return elements.internalArrayStart(); + } + + @Override + int internalArrayEnd() { + return elements.internalArrayEnd(); + } + + @Override + public UnmodifiableIterator iterator() { + return elements.iterator(); + } + + @Override + public UnmodifiableIterator descendingIterator() { + return elements.reverse().iterator(); + } + + @Override + public Spliterator spliterator() { + return asList().spliterator(); + } + + @Override + public void forEach(Consumer action) { + elements.forEach(action); + } + + @Override + public int size() { + return elements.size(); + } + + @Override + public boolean contains(Object o) { + try { + return o != null && unsafeBinarySearch(o) >= 0; + } catch (ClassCastException e) { + return false; + } + } + + @Override + public boolean containsAll(Collection targets) { + if (!hasSameComparator(comparator(), targets) || (targets.size() <= 1)) { + return super.containsAll(targets); + } + /* + * If targets is a sorted set with the same comparator, containsAll can run + * in O(n) time stepping through the two collections. + */ + Iterator thisIterator = iterator(); + + Iterator thatIterator = targets.iterator(); + // known nonempty since we checked targets.size() > 1 + + if (!thisIterator.hasNext()) { + return false; + } + + Object target = thatIterator.next(); + E current = thisIterator.next(); + try { + while (true) { + int cmp = unsafeCompare(current, target); + + if (cmp < 0) { + if (!thisIterator.hasNext()) { + return false; + } + current = thisIterator.next(); + } else if (cmp == 0) { + if (!thatIterator.hasNext()) { + return true; + } + target = thatIterator.next(); + + } else { + return false; + } + } + } catch (NullPointerException | ClassCastException e) { + return false; + } + } + + private int unsafeBinarySearch(Object key) throws ClassCastException { + return Collections.binarySearch(elements, key, unsafeComparator()); + } + + @Override + boolean isPartialView() { + return elements.isPartialView(); + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + return elements.copyIntoArray(dst, offset); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Set that)) { + return false; + } + if (size() != that.size()) { + return false; + } else if (isEmpty()) { + return true; + } + if (hasSameComparator(comparator, that)) { + Iterator otherIterator = that.iterator(); + try { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + Object element = iterator.next(); + Object otherElement = otherIterator.next(); + if (otherElement == null || unsafeCompare(element, otherElement) != 0) { + return false; + } + } + return true; + } catch (ClassCastException e) { + return false; + } catch (NoSuchElementException e) { + return false; // concurrent change to other set + } + } + return containsAll(that); + } + + @Override + public E first() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return elements.getFirst(); + } + + @Override + public E last() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return elements.get(size() - 1); + } + + @Override + public E lower(E element) { + int index = headIndex(element, false) - 1; + return (index == -1) ? null : elements.get(index); + } + + @Override + public E floor(E element) { + int index = headIndex(element, true) - 1; + return (index == -1) ? null : elements.get(index); + } + + @Override + public E ceiling(E element) { + int index = tailIndex(element, true); + return (index == size()) ? null : elements.get(index); + } + + @Override + public E higher(E element) { + int index = tailIndex(element, false); + return (index == size()) ? null : elements.get(index); + } + + @Override + ImmutableSortedSet headSetImpl(E toElement, boolean inclusive) { + return getSubSet(0, headIndex(toElement, inclusive)); + } + + int headIndex(E toElement, boolean inclusive) { + int index = Collections.binarySearch(elements, Objects.requireNonNull(toElement), comparator()); + if (index >= 0) { + return inclusive ? index + 1 : index; + } else { + return ~index; + } + } + + @Override + ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return tailSetImpl(fromElement, fromInclusive).headSetImpl(toElement, toInclusive); + } + + @Override + ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive) { + return getSubSet(tailIndex(fromElement, inclusive), size()); + } + + int tailIndex(E fromElement, boolean inclusive) { + int index = Collections.binarySearch(elements, Objects.requireNonNull(fromElement), comparator()); + if (index >= 0) { + return inclusive ? index : index + 1; + } else { + return ~index; + } + } + + // Pretend the comparator can compare anything. If it turns out it can't + // compare two elements, it'll throw a CCE. Only methods that are specified to + // throw CCE should call this. + @SuppressWarnings("unchecked") + Comparator unsafeComparator() { + return (Comparator) comparator; + } + + RegularImmutableSortedSet getSubSet(int newFromIndex, int newToIndex) { + if (newFromIndex == 0 && newToIndex == size()) { + return this; + } else if (newFromIndex < newToIndex) { + return new RegularImmutableSortedSet( + elements.subList(newFromIndex, newToIndex), comparator); + } else { + return emptySet(comparator); + } + } + + @Override + int indexOf(Object target) { + if (target == null) { + return -1; + } + int position; + try { + position = Collections.binarySearch(elements, target, unsafeComparator()); + } catch (ClassCastException e) { + return -1; + } + return (position >= 0) ? position : -1; + } + + @Override + ImmutableList createAsList() { + return (size() <= 1) ? elements : new ImmutableSortedAsList(this, elements); + } + + @Override + ImmutableSortedSet createDescendingSet() { + Comparator reversedOrder = Collections.reverseOrder(comparator); + return isEmpty() + ? emptySet(reversedOrder) + : new RegularImmutableSortedSet(elements.reverse(), reversedOrder); + } + + private static boolean hasSameComparator(Comparator comparator, Iterable elements) { + Objects.requireNonNull(comparator); + Objects.requireNonNull(elements); + Comparator comparator2; + if (elements instanceof SortedSet) { + comparator2 = comparator((SortedSet) elements); + } else if (elements instanceof SortedIterable) { + comparator2 = ((SortedIterable) elements).comparator(); + } else { + return false; + } + return comparator.equals(comparator2); + } + + private static Comparator comparator(SortedSet sortedSet) { + Comparator result = sortedSet.comparator(); + if (result == null) { + result = (Comparator) Ordering.natural(); + } + return result; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableBiMap.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableBiMap.java new file mode 100644 index 0000000..12c606b --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableBiMap.java @@ -0,0 +1,86 @@ +package org.xbib.datastructures.immutable; + +import java.util.Objects; +import java.util.function.BiConsumer; +import static org.xbib.datastructures.immutable.ImmutableMapEntry.checkEntryNotNull; + +/** + * Implementation of {@link ImmutableMap} with exactly one entry. + */ +final class SingletonImmutableBiMap extends ImmutableBiMap { + + final transient K singleKey; + final transient V singleValue; + + SingletonImmutableBiMap(K singleKey, V singleValue) { + checkEntryNotNull(singleKey, singleValue); + this.singleKey = singleKey; + this.singleValue = singleValue; + this.inverse = null; + } + + private SingletonImmutableBiMap(K singleKey, V singleValue, ImmutableBiMap inverse) { + this.singleKey = singleKey; + this.singleValue = singleValue; + this.inverse = inverse; + } + + @Override + public V get(Object key) { + return singleKey.equals(key) ? singleValue : null; + } + + @Override + public int size() { + return 1; + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action).accept(singleKey, singleValue); + } + + @Override + public boolean containsKey(Object key) { + return singleKey.equals(key); + } + + @Override + public boolean containsValue(Object value) { + return singleValue.equals(value); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + ImmutableSet> createEntrySet() { + return ImmutableSet.of(new ImmutableMapEntry<>(singleKey, singleValue)); + } + + @Override + ImmutableSet createKeySet() { + return ImmutableSet.of(singleKey); + } + + private final transient ImmutableBiMap inverse; + + private transient ImmutableBiMap lazyInverse; + + @Override + public ImmutableBiMap inverse() { + if (inverse != null) { + return inverse; + } else { + // racy single-check idiom + ImmutableBiMap result = lazyInverse; + if (result == null) { + return lazyInverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this); + } else { + return result; + } + } + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableList.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableList.java new file mode 100644 index 0000000..afbbb64 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableList.java @@ -0,0 +1,80 @@ +package org.xbib.datastructures.immutable; + +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Spliterator; + +/** + * Implementation of {@link ImmutableList} with exactly one element. + */ +final class SingletonImmutableList extends ImmutableList { + + final transient E element; + + SingletonImmutableList(E element) { + this.element = Objects.requireNonNull(element); + } + + @Override + public E get(int index) { + checkElementIndex(index, 1, "index"); + return element; + } + + @Override + public UnmodifiableIterator iterator() { + return singletonIterator(element); + } + + @Override + public Spliterator spliterator() { + return Collections.singleton(element).spliterator(); + } + + @Override + public int size() { + return 1; + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, 1); + return (fromIndex == toIndex) ? ImmutableList.of() : this; + } + + @Override + public String toString() { + return '[' + element.toString() + ']'; + } + + @Override + boolean isPartialView() { + return false; + } + + /** + * Returns an iterator containing only {@code value}. + * + *

The {@link Iterable} equivalent of this method is {@link Collections#singleton}. + */ + public static UnmodifiableIterator singletonIterator(T value) { + return new UnmodifiableIterator() { + boolean done; + + @Override + public boolean hasNext() { + return !done; + } + + @Override + public T next() { + if (done) { + throw new NoSuchElementException(); + } + done = true; + return value; + } + }; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableSet.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableSet.java new file mode 100644 index 0000000..424cc0a --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/SingletonImmutableSet.java @@ -0,0 +1,59 @@ +package org.xbib.datastructures.immutable; + +import java.util.Objects; +import static org.xbib.datastructures.immutable.SingletonImmutableList.singletonIterator; + +/** + * Implementation of {@link ImmutableSet} with exactly one element. + */ +final class SingletonImmutableSet extends ImmutableSet { + // We deliberately avoid caching the asList and hashCode here, to ensure that with + // compressed oops, a SingletonImmutableSet packs all the way down to the optimal 16 bytes. + + final transient E element; + + SingletonImmutableSet(E element) { + this.element = Objects.requireNonNull(element); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean contains(Object target) { + return element.equals(target); + } + + @Override + public UnmodifiableIterator iterator() { + return singletonIterator(element); + } + + @Override + public ImmutableList asList() { + return ImmutableList.of(element); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + dst[offset] = element; + return offset + 1; + } + + @Override + public int hashCode() { + return element.hashCode(); + } + + @Override + public String toString() { + return '[' + element.toString() + ']'; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/Spliterators.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/Spliterators.java new file mode 100644 index 0000000..39d17e5 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/Spliterators.java @@ -0,0 +1,269 @@ +package org.xbib.datastructures.immutable; + +import java.util.Comparator; +import java.util.Objects; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.stream.IntStream; + +public class Spliterators { + + private Spliterators() { + } + + /** + * Returns a {@code Spliterator} over the elements of {@code fromSpliterator} mapped by {@code + * function}. + */ + public static Spliterator map( + Spliterator fromSpliterator, + Function function) { + Objects.requireNonNull(fromSpliterator); + Objects.requireNonNull(function); + return new Spliterator() { + + @Override + public boolean tryAdvance(Consumer action) { + return fromSpliterator.tryAdvance( + fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public void forEachRemaining(Consumer action) { + fromSpliterator.forEachRemaining(fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit != null) ? map(fromSplit, function) : null; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & ~(Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SORTED); + } + }; + } + + + /** + * Returns a {@code Spliterator} that iterates over the elements of the spliterators generated by + * applying {@code function} to the elements of {@code fromSpliterator}. + */ + public static Spliterator flatMap( + Spliterator fromSpliterator, + Function> function, + int topCharacteristics, + long topSize) { + if (!((topCharacteristics & Spliterator.SUBSIZED) == 0)) { + throw new IllegalArgumentException("flatMap does not support SUBSIZED characteristic"); + } + if (!((topCharacteristics & Spliterator.SORTED) == 0)) { + throw new IllegalArgumentException("flatMap does not support SORTED characteristic"); + } + Objects.requireNonNull(fromSpliterator); + Objects.requireNonNull(function); + return new FlatMapSpliteratorOfObject<>(null, fromSpliterator, function, topCharacteristics, topSize); + } + + public static class FlatMapSpliteratorOfObject + extends FlatMapSpliterator> { + + FlatMapSpliteratorOfObject(Spliterator prefix, + Spliterator from, + Function> function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfObject::new, characteristics, estimatedSize); + } + } + + static abstract class FlatMapSpliterator< + InElementT extends Object, + OutElementT extends Object, + OutSpliteratorT extends Spliterator> + implements Spliterator { + /** + * Factory for constructing {@link FlatMapSpliterator} instances. + */ + @FunctionalInterface + interface Factory> { + OutSpliteratorT newFlatMapSpliterator(OutSpliteratorT prefix, + Spliterator fromSplit, + Function function, + int splitCharacteristics, + long estSplitSize); + } + + OutSpliteratorT prefix; + final Spliterator from; + final Function function; + final Factory factory; + int characteristics; + long estimatedSize; + + FlatMapSpliterator(OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + this.prefix = prefix; + this.from = from; + this.function = function; + this.factory = factory; + this.characteristics = characteristics; + this.estimatedSize = estimatedSize; + } + /* + * The tryAdvance and forEachRemaining in FlatMapSpliteratorOfPrimitive are overloads of these + * methods, not overrides. They are annotated @Override because they implement methods from + * Spliterator.OfPrimitive (and override default implementations from Spliterator.OfPrimitive or + * a subtype like Spliterator.OfInt). + */ + + @Override + public final boolean tryAdvance(Consumer action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public final void forEachRemaining(Consumer action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + Spliterator elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + + @Override + public final OutSpliteratorT trySplit() { + Spliterator fromSplit = from.trySplit(); + if (fromSplit != null) { + int splitCharacteristics = characteristics & ~Spliterator.SIZED; + long estSplitSize = estimateSize(); + if (estSplitSize < Long.MAX_VALUE) { + estSplitSize /= 2; + this.estimatedSize -= estSplitSize; + this.characteristics = splitCharacteristics; + } + OutSpliteratorT result = + factory.newFlatMapSpliterator( + this.prefix, fromSplit, function, splitCharacteristics, estSplitSize); + this.prefix = null; + return result; + } else if (prefix != null) { + OutSpliteratorT result = prefix; + this.prefix = null; + return result; + } else { + return null; + } + } + + @Override + public final long estimateSize() { + if (prefix != null) { + estimatedSize = Math.max(estimatedSize, prefix.estimateSize()); + } + return Math.max(estimatedSize, 0); + } + + @Override + public final int characteristics() { + return characteristics; + } + } + + static Spliterator indexed( + int size, int extraCharacteristics, IntFunction function) { + return indexed(size, extraCharacteristics, function, null); + } + + static Spliterator indexed( + int size, + int extraCharacteristics, + IntFunction function, + Comparator comparator) { + if (comparator != null) { + if ((extraCharacteristics & Spliterator.SORTED) == 0) { + throw new IllegalArgumentException(); + } + } + class WithCharacteristics implements Spliterator { + private final Spliterator.OfInt delegate; + + WithCharacteristics(Spliterator.OfInt delegate) { + this.delegate = delegate; + } + + @Override + public boolean tryAdvance(Consumer action) { + return delegate.tryAdvance((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public void forEachRemaining(Consumer action) { + delegate.forEachRemaining((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public Spliterator trySplit() { + Spliterator.OfInt split = delegate.trySplit(); + return (split == null) ? null : new WithCharacteristics(split); + } + + @Override + public long estimateSize() { + return delegate.estimateSize(); + } + + @Override + public int characteristics() { + return Spliterator.ORDERED + | Spliterator.SIZED + | Spliterator.SUBSIZED + | extraCharacteristics; + } + + @Override + public Comparator getComparator() { + if (hasCharacteristics(Spliterator.SORTED)) { + return comparator; + } else { + throw new IllegalStateException(); + } + } + } + return new WithCharacteristics(IntStream.range(0, size).spliterator()); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableIterator.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableIterator.java new file mode 100644 index 0000000..680bfe4 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableIterator.java @@ -0,0 +1,49 @@ +package org.xbib.datastructures.immutable; + +import java.util.Iterator; +import java.util.Objects; + +/** + * An iterator that does not support {@link #remove}. + * + *

{@code UnmodifiableIterator} is used primarily in conjunction with implementations of {@link + * ImmutableCollection}, such as {@link ImmutableList}. You can, however, convert an existing + * iterator to an {@code UnmodifiableIterator} using {@link #unmodifiableIterator}. + */ +public abstract class UnmodifiableIterator implements Iterator { + /** + * Constructor for use by subclasses. + */ + protected UnmodifiableIterator() { + } + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void remove() { + throw new UnsupportedOperationException(); + } + + public static UnmodifiableIterator unmodifiableIterator(Iterator iterator) { + Objects.requireNonNull(iterator); + if (iterator instanceof UnmodifiableIterator) { + @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe + UnmodifiableIterator result = (UnmodifiableIterator) iterator; + return result; + } + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + return iterator.next(); + } + }; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableListIterator.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableListIterator.java new file mode 100644 index 0000000..37ba10c --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/UnmodifiableListIterator.java @@ -0,0 +1,35 @@ +package org.xbib.datastructures.immutable; + +import java.util.ListIterator; + +/** + * A list iterator that does not support {@link #remove}, {@link #add}, or {@link #set}. + */ +public abstract class UnmodifiableListIterator + extends UnmodifiableIterator implements ListIterator { + /** + * Constructor for use by subclasses. + */ + protected UnmodifiableListIterator() { + } + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void set(E e) { + throw new UnsupportedOperationException(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/AllEqualOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/AllEqualOrdering.java new file mode 100644 index 0000000..bf9694b --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/AllEqualOrdering.java @@ -0,0 +1,38 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.List; +import org.xbib.datastructures.immutable.ImmutableList; + +/** + * An ordering that treats all references as equals, even nulls. + */ +final class AllEqualOrdering extends Ordering { + static final AllEqualOrdering INSTANCE = new AllEqualOrdering(); + + @Override + public int compare(Object left, Object right) { + return 0; + } + + @Override + public List sortedCopy(Iterable iterable) { + return ImmutableList.newArrayList(iterable.iterator()); + } + + @Override + @SuppressWarnings("nullness") // unsafe: see supertype + public ImmutableList immutableSortedCopy(Iterable iterable) { + return ImmutableList.copyOf(iterable); + } + + @SuppressWarnings("unchecked") + @Override + public Ordering reverse() { + return (Ordering) this; + } + + @Override + public String toString() { + return "Ordering.allEqual()"; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ByFunctionOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ByFunctionOrdering.java new file mode 100644 index 0000000..0747999 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ByFunctionOrdering.java @@ -0,0 +1,44 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.Objects; +import java.util.function.Function; + +/** + * An ordering that orders elements by applying an order to the result of a function on those + * elements. + */ +final class ByFunctionOrdering extends Ordering { + final Function function; + final Ordering ordering; + + ByFunctionOrdering(Function function, Ordering ordering) { + this.function = Objects.requireNonNull(function); + this.ordering = Objects.requireNonNull(ordering); + } + + @Override + public int compare(F left, F right) { + return ordering.compare(function.apply(left), function.apply(right)); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ByFunctionOrdering that) { + return this.function.equals(that.function) && this.ordering.equals(that.ordering); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(function, ordering); + } + + @Override + public String toString() { + return ordering + ".onResultOf(" + function + ")"; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ComparatorOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ComparatorOrdering.java new file mode 100644 index 0000000..8c7ba4a --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ComparatorOrdering.java @@ -0,0 +1,41 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.Comparator; +import java.util.Objects; + +/** + * An ordering for a pre-existing comparator. + */ +final class ComparatorOrdering extends Ordering { + final Comparator comparator; + + ComparatorOrdering(Comparator comparator) { + this.comparator = Objects.requireNonNull(comparator); + } + + @Override + public int compare(T a, T b) { + return comparator.compare(a, b); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ComparatorOrdering that) { + return this.comparator.equals(that.comparator); + } + return false; + } + + @Override + public int hashCode() { + return comparator.hashCode(); + } + + @Override + public String toString() { + return comparator.toString(); + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/CompoundOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/CompoundOrdering.java new file mode 100644 index 0000000..7fd8de6 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/CompoundOrdering.java @@ -0,0 +1,54 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.Arrays; +import java.util.Comparator; +import static org.xbib.datastructures.immutable.ImmutableCollection.toArray; + +/** + * An ordering that tries several comparators in order. + */ +final class CompoundOrdering extends Ordering { + final Comparator[] comparators; + + @SuppressWarnings("unchecked") + CompoundOrdering(Comparator primary, Comparator secondary) { + this.comparators = (Comparator[]) new Comparator[]{primary, secondary}; + } + + @SuppressWarnings("unchecked") + CompoundOrdering(Iterable> comparators) { + this.comparators = toArray(comparators, new Comparator[0]); + } + + @Override + public int compare(T left, T right) { + for (Comparator comparator : comparators) { + int result = comparator.compare(left, right); + if (result != 0) { + return result; + } + } + return 0; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof CompoundOrdering that) { + return Arrays.equals(this.comparators, that.comparators); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(comparators); + } + + @Override + public String toString() { + return "Ordering.compound(" + Arrays.toString(comparators) + ")"; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ExplicitOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ExplicitOrdering.java new file mode 100644 index 0000000..b1a1698 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ExplicitOrdering.java @@ -0,0 +1,52 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.List; +import org.xbib.datastructures.immutable.ImmutableMap; +import static org.xbib.datastructures.immutable.ImmutableMap.indexMap; + +/** + * An ordering that compares objects according to a given order. + */ +final class ExplicitOrdering extends Ordering { + final ImmutableMap rankMap; + + ExplicitOrdering(List valuesInOrder) { + this(indexMap(valuesInOrder)); + } + + ExplicitOrdering(ImmutableMap rankMap) { + this.rankMap = rankMap; + } + + @Override + public int compare(T left, T right) { + return rank(left) - rank(right); // safe because both are nonnegative + } + + private int rank(T value) { + Integer rank = rankMap.get(value); + if (rank == null) { + throw new IncomparableValueException(value); + } + return rank; + } + + @Override + public boolean equals(Object object) { + if (object instanceof ExplicitOrdering that) { + return this.rankMap.equals(that.rankMap); + } + return false; + } + + @Override + public int hashCode() { + return rankMap.hashCode(); + } + + @Override + public String toString() { + return "Ordering.explicit(" + rankMap.keySet() + ")"; + } + +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/LexicographicalOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/LexicographicalOrdering.java new file mode 100644 index 0000000..f1706ac --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/LexicographicalOrdering.java @@ -0,0 +1,55 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.Comparator; +import java.util.Iterator; + +/** + * An ordering which sorts iterables by comparing corresponding elements pairwise. + */ +final class LexicographicalOrdering extends Ordering> { + final Comparator elementOrder; + + LexicographicalOrdering(Comparator elementOrder) { + this.elementOrder = elementOrder; + } + + @Override + public int compare(Iterable leftIterable, Iterable rightIterable) { + Iterator left = leftIterable.iterator(); + Iterator right = rightIterable.iterator(); + while (left.hasNext()) { + if (!right.hasNext()) { + return LEFT_IS_GREATER; // because it's longer + } + int result = elementOrder.compare(left.next(), right.next()); + if (result != 0) { + return result; + } + } + if (right.hasNext()) { + return RIGHT_IS_GREATER; // because it's longer + } + return 0; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof LexicographicalOrdering that) { + return this.elementOrder.equals(that.elementOrder); + } + return false; + } + + @Override + public int hashCode() { + return elementOrder.hashCode() ^ 2075626741; // meaningless + } + + @Override + public String toString() { + return elementOrder + ".lexicographical()"; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NaturalOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NaturalOrdering.java new file mode 100644 index 0000000..b370267 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NaturalOrdering.java @@ -0,0 +1,53 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.Objects; + +/** + * An ordering that uses the natural order of the values. + */ +@SuppressWarnings("unchecked") +final class NaturalOrdering extends Ordering> { + static final NaturalOrdering INSTANCE = new NaturalOrdering(); + + private transient Ordering> nullsFirst; + private transient Ordering> nullsLast; + + private NaturalOrdering() { + } + + @Override + public int compare(Comparable left, Comparable right) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); + return ((Comparable) left).compareTo(right); + } + + @Override + public > Ordering nullsFirst() { + Ordering> result = nullsFirst; + if (result == null) { + result = nullsFirst = super.nullsFirst(); + } + return (Ordering) result; + } + + @Override + public > Ordering nullsLast() { + Ordering> result = nullsLast; + if (result == null) { + result = nullsLast = super.nullsLast(); + } + return (Ordering) result; + } + + @Override + public > Ordering reverse() { + return (Ordering) ReverseNaturalOrdering.INSTANCE; + } + + @Override + public String toString() { + return "Ordering.natural()"; + } + +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsFirstOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsFirstOrdering.java new file mode 100644 index 0000000..279b588 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsFirstOrdering.java @@ -0,0 +1,66 @@ +package org.xbib.datastructures.immutable.order; + +/** + * An ordering that treats {@code null} as less than all other values. + */ +final class NullsFirstOrdering extends Ordering { + final Ordering ordering; + + NullsFirstOrdering(Ordering ordering) { + this.ordering = ordering; + } + + @Override + public int compare(T left, T right) { + if (left == right) { + return 0; + } + if (left == null) { + return RIGHT_IS_GREATER; + } + if (right == null) { + return LEFT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override + @SuppressWarnings("nullness") // should be safe, but not sure if we can avoid the warning + public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsLast(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override + public Ordering nullsFirst() { + return (Ordering) this; + } + + @Override + public Ordering nullsLast() { + return ordering.nullsLast(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsFirstOrdering that) { + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override + public int hashCode() { + return ordering.hashCode() ^ 957692532; // meaningless + } + + @Override + public String toString() { + return ordering + ".nullsFirst()"; + } + +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsLastOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsLastOrdering.java new file mode 100644 index 0000000..e98b323 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/NullsLastOrdering.java @@ -0,0 +1,65 @@ +package org.xbib.datastructures.immutable.order; + +/** + * An ordering that treats {@code null} as greater than all other values. + */ +final class NullsLastOrdering extends Ordering { + final Ordering ordering; + + NullsLastOrdering(Ordering ordering) { + this.ordering = ordering; + } + + @Override + public int compare(T left, T right) { + if (left == right) { + return 0; + } + if (left == null) { + return LEFT_IS_GREATER; + } + if (right == null) { + return RIGHT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override + @SuppressWarnings("nullness") // should be safe, but not sure if we can avoid the warning + public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsFirst(); + } + + @Override + public Ordering nullsFirst() { + return ordering.nullsFirst(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override + public Ordering nullsLast() { + return (Ordering) this; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsLastOrdering that) { + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override + public int hashCode() { + return ordering.hashCode() ^ -921210296; // meaningless + } + + @Override + public String toString() { + return ordering + ".nullsLast()"; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/Ordering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/Ordering.java new file mode 100644 index 0000000..93e2f3a --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/Ordering.java @@ -0,0 +1,814 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.RandomAccess; +import java.util.TreeSet; +import java.util.function.Function; +import org.xbib.datastructures.immutable.ImmutableList; +import static org.xbib.datastructures.immutable.ImmutableList.checkElementIndex; +import static org.xbib.datastructures.immutable.ImmutableList.newArrayList; +import static org.xbib.datastructures.immutable.ImmutableMap.checkNonnegative; + +/** + * A comparator, with additional methods to support common operations. + * + *

Three types of methods

+ *

+ * Like other fluent types, there are three types of methods present: methods for acquiring, + * chaining, and using. + * + *

Acquiring

+ * + *

The common ways to get an instance of {@code Ordering} are: + * + *

    + *
  • Subclass it and implement {@link #compare} instead of implementing {@link Comparator} + * directly + *
  • Pass a pre-existing {@link Comparator} instance to {@link #from(Comparator)} + *
  • Use the natural ordering, {@link Ordering#natural} + *
+ * + *

Chaining

+ * + *

Then you can use the chaining methods to get an altered version of that {@code + * Ordering}, including: + * + *

    + *
  • {@link #reverse} + *
  • {@link #compound(Comparator)} + *
  • {@link #onResultOf(Function)} + *
  • {@link #nullsFirst} / {@link #nullsLast} + *
+ * + *

Using

+ * + *

Finally, use the resulting {@code Ordering} anywhere a {@link Comparator} is required, or use + * any of its special operations, such as: + * + *

    + *
  • {@link #immutableSortedCopy} + *
  • {@link #isOrdered} / {@link #isStrictlyOrdered} + *
  • {@link #min} / {@link #max} + *
+ * + *

Understanding complex orderings

+ * + *

Complex chained orderings like the following example can be challenging to understand. + * + *

{@code
+ * Ordering ordering =
+ *     Ordering.natural()
+ *         .nullsFirst()
+ *         .onResultOf(getBarFunction)
+ *         .nullsLast();
+ * }
+ *

+ * Note that each chaining method returns a new ordering instance which is backed by the previous + * instance, but has the chance to act on values before handing off to that backing instance. + * As a result, it usually helps to read chained ordering expressions backwards. For example, + * when {@code compare} is called on the above ordering: + * + *

    + *
  1. First, if only one {@code Foo} is null, that null value is treated as greater + *
  2. Next, non-null {@code Foo} values are passed to {@code getBarFunction} (we will be + * comparing {@code Bar} values from now on) + *
  3. Next, if only one {@code Bar} is null, that null value is treated as lesser + *
  4. Finally, natural ordering is used (i.e. the result of {@code Bar.compareTo(Bar)} is + * returned) + *
+ * + *

Alas, {@link #reverse} is a little different. As you read backwards through a chain and + * encounter a call to {@code reverse}, continue working backwards until a result is determined, and + * then reverse that result. + * + *

For Java 8 users

+ * + *

If you are using Java 8, this class is now obsolete. Most of its functionality is now provided + * by {@link java.util.stream.Stream Stream} and by {@link Comparator} itself, and the rest can now + * be found as static methods in our new {@link Comparators} class. See each method below for + * further instructions. Whenever possible, you should change any references of type {@code + * Ordering} to be of type {@code Comparator} instead. However, at this time we have no plan to + * deprecate this class. + * + *

Many replacements involve adopting {@code Stream}, and these changes can sometimes make your + * code verbose. Whenever following this advice, you should check whether {@code Stream} could be + * adopted more comprehensively in your code; the end result may be quite a bit simpler. + */ +public abstract class Ordering implements Comparator { + // Natural order + + /** + * Returns a ordering that uses the natural order of the values. The ordering throws + * a {@link NullPointerException} when passed a null parameter. + * + *

The type specification is {@code }, instead of the technically correct + * {@code >}, to support legacy types from before Java 5. + * + *

Java 8 users: use {@link Comparator#naturalOrder} instead. + */ + @SuppressWarnings("unchecked") + public static Ordering natural() { + return (Ordering) NaturalOrdering.INSTANCE; + } + + // Static factories + + /** + * Returns an ordering based on an existing comparator instance. Note that it is + * unnecessary to create a new anonymous inner class implementing {@code Comparator} just + * to pass it in here. Instead, simply subclass {@code Ordering} and implement its {@code compare} + * method directly. + * + *

Java 8 users: this class is now obsolete as explained in the class documentation, so + * there is no need to use this method. + * + * @param comparator the comparator that defines the order + * @return comparator itself if it is already an {@code Ordering}; otherwise an ordering that + * wraps that comparator + */ + public static Ordering from(Comparator comparator) { + return (comparator instanceof Ordering) + ? (Ordering) comparator + : new ComparatorOrdering(comparator); + } + + /** + * Simply returns its argument. + */ + public static Ordering from(Ordering ordering) { + return Objects.requireNonNull(ordering); + } + + /** + * Returns an ordering that compares objects according to the order in which they appear in the + * given list. Only objects present in the list (according to {@link Object#equals}) may be + * compared. This comparator imposes a "partial ordering" over the type {@code T}. Subsequent + * changes to the {@code valuesInOrder} list will have no effect on the returned comparator. Null + * values in the list are not supported. + * + *

The returned comparator throws a {@link ClassCastException} when it receives an input + * parameter that isn't among the provided values. + * + * @param valuesInOrder the values that the returned comparator will be able to compare, in the + * order the comparator should induce + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if {@code valuesInOrder} contains any duplicate values + * (according to {@link Object#equals}) + */ + public static Ordering explicit(List valuesInOrder) { + return new ExplicitOrdering(valuesInOrder); + } + + /** + * Returns an ordering that compares objects according to the order in which they are given to + * this method. Only objects present in the argument list (according to {@link Object#equals}) may + * be compared. This comparator imposes a "partial ordering" over the type {@code T}. Null values + * in the argument list are not supported. + * + *

The returned comparator throws a {@link ClassCastException} when it receives an input + * parameter that isn't among the provided values. + * + * @param leastValue the value which the returned comparator should consider the "least" of all + * values + * @param remainingValuesInOrder the rest of the values that the returned comparator will be able + * to compare, in the order the comparator should follow + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if any duplicate values (according to {@link + * Object#equals(Object)}) are present among the method arguments + */ + public static Ordering explicit(T leastValue, T... remainingValuesInOrder) { + return explicit(asList(leastValue, remainingValuesInOrder)); + } + + public static List asList(E first, E[] rest) { + return new OnePlusArrayList<>(first, rest); + } + + private static class OnePlusArrayList extends AbstractList implements RandomAccess { + final E first; + final E[] rest; + + OnePlusArrayList(E first, E[] rest) { + this.first = first; + this.rest = Objects.requireNonNull(rest); + } + + @Override + public int size() { + return saturatedAdd(rest.length, 1); + } + + @Override + public E get(int index) { + // check explicitly so the IOOBE will have the right message + checkElementIndex(index, size(), "index"); + return (index == 0) ? first : rest[index - 1]; + } + + private static int saturatedAdd(int a, int b) { + return saturatedCast((long) a + b); + } + + private static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } + } + + + // Ordering singletons + + /** + * Returns an ordering which treats all values as equal, indicating "no ordering." Passing this + * ordering to any stable sort algorithm results in no change to the order of elements. + * Note especially that {@link #sortedCopy} and {@link #immutableSortedCopy} are stable, and in + * the returned instance these are implemented by simply copying the source list. + * + *

Example: + * + *

{@code
+     * Ordering.allEqual().nullsLast().sortedCopy(
+     *     asList(t, null, e, s, null, t, null))
+     * }
+ * + *

Assuming {@code t}, {@code e} and {@code s} are non-null, this returns {@code [t, e, s, t, + * null, null, null]} regardless of the true comparison order of those three values (which might + * not even implement {@link Comparable} at all). + * + *

Warning: by definition, this comparator is not consistent with equals (as + * defined {@linkplain Comparator here}). Avoid its use in APIs, such as {@link + * TreeSet#TreeSet(Comparator)}, where such consistency is expected. + * + *

Java 8 users: Use the lambda expression {@code (a, b) -> 0} instead (in certain cases + * you may need to cast that to {@code Comparator}). + */ + public static Ordering allEqual() { + return AllEqualOrdering.INSTANCE; + } + + /** + * Returns an ordering that compares objects by the natural ordering of their string + * representations as returned by {@code toString()}. It does not support null values. + * + *

Java 8 users: Use {@code Comparator.comparing(Object::toString)} instead. + */ + public static Ordering usingToString() { + return UsingToStringOrdering.INSTANCE; + } + + // Constructor + + /** + * Constructs a new instance of this class (only invokable by the subclass constructor, typically + * implicit). + */ + protected Ordering() { + } + + // Instance-based factories (and any static equivalents) + + /** + * Returns the reverse of this ordering; the {@code Ordering} equivalent to {@link + * Collections#reverseOrder(Comparator)}. + * + *

Java 8 users: Use {@code thisComparator.reversed()} instead. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().reverse(); + public Ordering reverse() { + return new ReverseOrdering(this); + } + + /** + * Returns an ordering that treats {@code null} as less than all other values and uses {@code + * this} to compare non-null values. + * + *

Java 8 users: Use {@code Comparator.nullsFirst(thisComparator)} instead. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsFirst(); + public Ordering nullsFirst() { + return new NullsFirstOrdering(this); + } + + /** + * Returns an ordering that treats {@code null} as greater than all other values and uses this + * ordering to compare non-null values. + * + *

Java 8 users: Use {@code Comparator.nullsLast(thisComparator)} instead. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsLast(); + public Ordering nullsLast() { + return new NullsLastOrdering(this); + } + + /** + * Returns a new ordering on {@code F} which orders elements by first applying a function to them, + * then comparing those results using {@code this}. For example, to compare objects by their + * string forms, in a case-insensitive manner, use: + * + *

{@code
+     * Ordering.from(String.CASE_INSENSITIVE_ORDER)
+     *     .onResultOf(Functions.toStringFunction())
+     * }
+ * + *

Java 8 users: Use {@code Comparator.comparing(function, thisComparator)} instead (you + * can omit the comparator if it is the natural order). + */ + public Ordering onResultOf(Function function) { + return new ByFunctionOrdering<>(function, this); + } + + Ordering> onKeys() { + return onResultOf(Entry::getKey); + } + + /** + * Returns an ordering which first uses the ordering {@code this}, but which in the event of a + * "tie", then delegates to {@code secondaryComparator}. For example, to sort a bug list first by + * status and second by priority, you might use {@code byStatus.compound(byPriority)}. For a + * compound ordering with three or more components, simply chain multiple calls to this method. + * + *

An ordering produced by this method, or a chain of calls to this method, is equivalent to + * one created using {@link Ordering#compound(Iterable)} on the same component comparators. + * + *

Java 8 users: Use {@code thisComparator.thenComparing(secondaryComparator)} instead. + * Depending on what {@code secondaryComparator} is, one of the other overloads of {@code + * thenComparing} may be even more useful. + */ + public Ordering compound(Comparator secondaryComparator) { + return new CompoundOrdering(this, Objects.requireNonNull(secondaryComparator)); + } + + /** + * Returns an ordering which tries each given comparator in order until a non-zero result is + * found, returning that result, and returning zero only if all comparators return zero. The + * returned ordering is based on the state of the {@code comparators} iterable at the time it was + * provided to this method. + * + *

The returned ordering is equivalent to that produced using {@code + * Ordering.from(comp1).compound(comp2).compound(comp3) . . .}. + * + *

Warning: Supplying an argument with undefined iteration order, such as a {@link + * HashSet}, will produce non-deterministic results. + * + *

Java 8 users: Use a chain of calls to {@link Comparator#thenComparing(Comparator)}, + * or {@code comparatorCollection.stream().reduce(Comparator::thenComparing).get()} (if the + * collection might be empty, also provide a default comparator as the {@code identity} parameter + * to {@code reduce}). + * + * @param comparators the comparators to try in order + */ + public static Ordering compound( + Iterable> comparators) { + return new CompoundOrdering(comparators); + } + + /** + * Returns a new ordering which sorts iterables by comparing corresponding elements pairwise until + * a nonzero result is found; imposes "dictionary order". If the end of one iterable is reached, + * but not the other, the shorter iterable is considered to be less than the longer one. For + * example, a lexicographical natural ordering over integers considers {@code [] < [1] < [1, 1] < + * [1, 2] < [2]}. + * + *

Note that {@code ordering.lexicographical().reverse()} is not equivalent to {@code + * ordering.reverse().lexicographical()} (consider how each would order {@code [1]} and {@code [1, + * 1]}). + * + *

Java 8 users: Use {@link Comparators#lexicographical(Comparator)} instead. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering> o = + // Ordering.natural().lexicographical(); + public Ordering> lexicographical() { + /* + * Note that technically the returned ordering should be capable of + * handling not just {@code Iterable} instances, but also any {@code + * Iterable}. However, the need for this comes up so rarely + * that it doesn't justify making everyone else deal with the very ugly + * wildcard. + */ + return new LexicographicalOrdering(this); + } + + // Regular instance methods + + @Override + public abstract int compare(T left, T right); + + /** + * Returns the least of the specified values according to this ordering. If there are multiple + * least values, the first of those is returned. The iterator will be left exhausted: its {@code + * hasNext()} method will return {@code false}. + * + *

Java 8 users: Use {@code Streams.stream(iterator).min(thisComparator).get()} instead + * (but note that it does not guarantee which tied minimum element is returned). + * + * @param iterator the iterator whose minimum element is to be determined + * @throws NoSuchElementException if {@code iterator} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E min(Iterator iterator) { + // let this throw NoSuchElementException as necessary + E minSoFar = iterator.next(); + + while (iterator.hasNext()) { + minSoFar = min(minSoFar, iterator.next()); + } + + return minSoFar; + } + + /** + * Returns the least of the specified values according to this ordering. If there are multiple + * least values, the first of those is returned. + * + *

Java 8 users: If {@code iterable} is a {@link Collection}, use {@code + * Collections.min(collection, thisComparator)} instead. Otherwise, use {@code + * Streams.stream(iterable).min(thisComparator).get()} instead. Note that these alternatives do + * not guarantee which tied minimum element is returned. + * + * @param iterable the iterable whose minimum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E min(Iterable iterable) { + return min(iterable.iterator()); + } + + /** + * Returns the lesser of the two values according to this ordering. If the values compare as 0, + * the first is returned. + * + *

Implementation note: this method is invoked by the default implementations of the + * other {@code min} overloads, so overriding it will affect their behavior. + * + *

Note: Consider using {@code Comparators.min(a, b, thisComparator)} instead. If {@code + * thisComparator} is {@link Ordering#natural}, then use {@code Comparators.min(a, b)}. + * + * @param a value to compare, returned if less than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E min(E a, E b) { + return (compare(a, b) <= 0) ? a : b; + } + + /** + * Returns the least of the specified values according to this ordering. If there are multiple + * least values, the first of those is returned. + * + *

Java 8 users: Use {@code Collections.min(Arrays.asList(a, b, c...), thisComparator)} + * instead (but note that it does not guarantee which tied minimum element is returned). + * + * @param a value to compare, returned if less than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E min(E a, E b, E c, E... rest) { + E minSoFar = min(min(a, b), c); + + for (E r : rest) { + minSoFar = min(minSoFar, r); + } + + return minSoFar; + } + + /** + * Returns the greatest of the specified values according to this ordering. If there are multiple + * greatest values, the first of those is returned. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + *

Java 8 users: Use {@code Streams.stream(iterator).max(thisComparator).get()} instead + * (but note that it does not guarantee which tied maximum element is returned). + * + * @param iterator the iterator whose maximum element is to be determined + * @throws NoSuchElementException if {@code iterator} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E max(Iterator iterator) { + // let this throw NoSuchElementException as necessary + E maxSoFar = iterator.next(); + + while (iterator.hasNext()) { + maxSoFar = max(maxSoFar, iterator.next()); + } + + return maxSoFar; + } + + /** + * Returns the greatest of the specified values according to this ordering. If there are multiple + * greatest values, the first of those is returned. + * + *

Java 8 users: If {@code iterable} is a {@link Collection}, use {@code + * Collections.max(collection, thisComparator)} instead. Otherwise, use {@code + * Streams.stream(iterable).max(thisComparator).get()} instead. Note that these alternatives do + * not guarantee which tied maximum element is returned. + * + * @param iterable the iterable whose maximum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E max(Iterable iterable) { + return max(iterable.iterator()); + } + + /** + * Returns the greater of the two values according to this ordering. If the values compare as 0, + * the first is returned. + * + *

Implementation note: this method is invoked by the default implementations of the + * other {@code max} overloads, so overriding it will affect their behavior. + * + *

Note: Consider using {@code Comparators.max(a, b, thisComparator)} instead. If {@code + * thisComparator} is {@link Ordering#natural}, then use {@code Comparators.max(a, b)}. + * + * @param a value to compare, returned if greater than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E max(E a, E b) { + return (compare(a, b) >= 0) ? a : b; + } + + /** + * Returns the greatest of the specified values according to this ordering. If there are multiple + * greatest values, the first of those is returned. + * + *

Java 8 users: Use {@code Collections.max(Arrays.asList(a, b, c...), thisComparator)} + * instead (but note that it does not guarantee which tied maximum element is returned). + * + * @param a value to compare, returned if greater than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E max(E a, E b, E c, E... rest) { + E maxSoFar = max(max(a, b), c); + + for (E r : rest) { + maxSoFar = max(maxSoFar, r); + } + + return maxSoFar; + } + + /** + * Returns the {@code k} least elements of the given iterable according to this ordering, in order + * from least to greatest. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterable).collect(Comparators.least(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} least elements in ascending + * order + * @throws IllegalArgumentException if {@code k} is negative + */ + public List leastOf(Iterable iterable, int k) { + if (iterable instanceof Collection collection) { + if (collection.size() <= 2L * k) { + // In this case, just dumping the collection to an array and sorting is + // faster than using the implementation for Iterator, which is + // specialized for k much smaller than n. + + @SuppressWarnings("unchecked") // c only contains E's and doesn't escape + E[] array = (E[]) collection.toArray(); + Arrays.sort(array, this); + if (array.length > k) { + array = Arrays.copyOf(array, k); + } + return Collections.unmodifiableList(Arrays.asList(array)); + } + } + return leastOf(iterable.iterator(), k); + } + + /** + * Returns the {@code k} least elements from the given iterator according to this ordering, in + * order from least to greatest. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterator).collect(Comparators.least(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} least elements in ascending + * order + * @throws IllegalArgumentException if {@code k} is negative + */ + public List leastOf(Iterator iterator, int k) { + Objects.requireNonNull(iterator); + checkNonnegative(k, "k"); + if (k == 0 || !iterator.hasNext()) { + return Collections.emptyList(); + } else if (k >= Integer.MAX_VALUE / 2) { + // k is really large; just do a straightforward sorted-copy-and-sublist + ArrayList list = newArrayList(iterator); + Collections.sort(list, this); + if (list.size() > k) { + list.subList(k, list.size()).clear(); + } + list.trimToSize(); + return Collections.unmodifiableList(list); + } else { + TopKSelector selector = TopKSelector.least(k, this); + selector.offerAll(iterator); + return selector.topK(); + } + } + + /** + * Returns the {@code k} greatest elements of the given iterable according to this ordering, in + * order from greatest to least. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterable).collect(Comparators.greatest(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in + * descending order + * @throws IllegalArgumentException if {@code k} is negative + */ + public List greatestOf(Iterable iterable, int k) { + return reverse().leastOf(iterable, k); + } + + /** + * Returns the {@code k} greatest elements from the given iterator according to this ordering, in + * order from greatest to least. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterator).collect(Comparators.greatest(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in + * descending order + * @throws IllegalArgumentException if {@code k} is negative + */ + public List greatestOf(Iterator iterator, int k) { + return reverse().leastOf(iterator, k); + } + + /** + * Returns a mutable list containing {@code elements} sorted by this ordering; use this + * only when the resulting list may need further modification, or may contain {@code null}. The + * input is not modified. The returned list has random access. + * + *

Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard elements that are + * duplicates according to the comparator. The sort performed is stable, meaning that such + * elements will appear in the returned list in the same order they appeared in {@code elements}. + * + *

Performance note: According to our + * benchmarking + * on Open JDK 7, {@link #immutableSortedCopy} generally performs better (in both time and space) + * than this method, and this method in turn generally performs better than copying the list and + * calling {@link Collections#sort(List)}. + */ + public List sortedCopy(Iterable elements) { + @SuppressWarnings("unchecked") // does not escape, and contains only E's + E[] array = (E[]) ImmutableList.castOrCopyToCollection(elements).toArray(); + Arrays.sort(array, this); + return newArrayList(Arrays.asList(array).iterator()); + } + + /** + * Returns an immutable list containing {@code elements} sorted by this ordering. The input + * is not modified. + * + *

Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard elements that are + * duplicates according to the comparator. The sort performed is stable, meaning that such + * elements will appear in the returned list in the same order they appeared in {@code elements}. + * + *

Performance note: According to our + * benchmarking + * on Open JDK 7, this method is the most efficient way to make a sorted copy of a collection. + * + * @throws NullPointerException if any element of {@code elements} is {@code null} + */ + @SuppressWarnings("nullness") // unsafe, but there's not much we can do about it now + public ImmutableList immutableSortedCopy(Iterable elements) { + return ImmutableList.sortedCopyOf(this, elements); + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is greater than or + * equal to the element that preceded it, according to this ordering. Note that this is always + * true when the iterable has fewer than two elements. + * + *

Java 8 users: Use the equivalent {@link Comparators#isInOrder(Iterable, Comparator)} + * instead, since the rest of {@code Ordering} is mostly obsolete (as explained in the class + * documentation). + */ + public boolean isOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) > 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is strictly + * greater than the element that preceded it, according to this ordering. Note that this is always + * true when the iterable has fewer than two elements. + * + *

Java 8 users: Use the equivalent {@link Comparators#isInStrictOrder(Iterable, + * Comparator)} instead, since the rest of {@code Ordering} is mostly obsolete (as explained in + * the class documentation). + */ + public boolean isStrictlyOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) >= 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * {@link Collections#binarySearch(List, Object, Comparator) Searches} {@code sortedList} for + * {@code key} using the binary search algorithm. The list must be sorted using this ordering. + * + * @param sortedList the list to be searched + * @param key the key to be searched for + */ + public int binarySearch( + List sortedList, T key) { + return Collections.binarySearch(sortedList, key, this); + } + + /** + * Exception thrown by a {@link Ordering#explicit(List)} or {@link Ordering#explicit(Object, + * Object[])} comparator when comparing a value outside the set of values it can compare. + * Extending {@link ClassCastException} may seem odd, but it is required. + */ + static class IncomparableValueException extends ClassCastException { + final Object value; + + IncomparableValueException(Object value) { + super("Cannot compare value: " + value); + this.value = value; + } + } + + // Never make these public + static final int LEFT_IS_GREATER = 1; + static final int RIGHT_IS_GREATER = -1; +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseNaturalOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseNaturalOrdering.java new file mode 100644 index 0000000..b22a4e6 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseNaturalOrdering.java @@ -0,0 +1,77 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.Iterator; +import java.util.Objects; + +/** + * An ordering that uses the reverse of the natural order of the values. + */ +final class ReverseNaturalOrdering extends Ordering> { + + static final ReverseNaturalOrdering INSTANCE = new ReverseNaturalOrdering(); + + private ReverseNaturalOrdering() { + } + + @SuppressWarnings("unchecked") + @Override + public int compare(Comparable left, Comparable right) { + Objects.requireNonNull(left); // right null is caught later + if (left == right) { + return 0; + } + return ((Comparable) right).compareTo(left); + } + + @Override + public > Ordering reverse() { + return Ordering.natural(); + } + + // Override the min/max methods to "hoist" delegation outside loops + + @Override + public > E min(E a, E b) { + return NaturalOrdering.INSTANCE.max(a, b); + } + + @Override + public > E min(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.max(a, b, c, rest); + } + + @Override + public > E min(Iterator iterator) { + return NaturalOrdering.INSTANCE.max(iterator); + } + + @Override + public > E min(Iterable iterable) { + return NaturalOrdering.INSTANCE.max(iterable); + } + + @Override + public > E max(E a, E b) { + return NaturalOrdering.INSTANCE.min(a, b); + } + + @Override + public > E max(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.min(a, b, c, rest); + } + + @Override + public > E max(Iterator iterator) { + return NaturalOrdering.INSTANCE.min(iterator); + } + + @Override + public > E max(Iterable iterable) { + return NaturalOrdering.INSTANCE.min(iterable); + } + + @Override + public String toString() { + return "Ordering.natural().reverse()"; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseOrdering.java new file mode 100644 index 0000000..364fbe3 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/ReverseOrdering.java @@ -0,0 +1,89 @@ +package org.xbib.datastructures.immutable.order; + +import java.util.Iterator; +import java.util.Objects; + +/** + * An ordering that uses the reverse of a given order. + */ +final class ReverseOrdering extends Ordering { + final Ordering forwardOrder; + + ReverseOrdering(Ordering forwardOrder) { + this.forwardOrder = Objects.requireNonNull(forwardOrder); + } + + @Override + public int compare(T a, T b) { + return forwardOrder.compare(b, a); + } + + @SuppressWarnings("unchecked") + @Override + public Ordering reverse() { + return (Ordering) forwardOrder; + } + + // Override the min/max methods to "hoist" delegation outside loops + + @Override + public E min(E a, E b) { + return forwardOrder.max(a, b); + } + + @Override + public E min(E a, E b, E c, E... rest) { + return forwardOrder.max(a, b, c, rest); + } + + @Override + public E min(Iterator iterator) { + return forwardOrder.max(iterator); + } + + @Override + public E min(Iterable iterable) { + return forwardOrder.max(iterable); + } + + @Override + public E max(E a, E b) { + return forwardOrder.min(a, b); + } + + @Override + public E max(E a, E b, E c, E... rest) { + return forwardOrder.min(a, b, c, rest); + } + + @Override + public E max(Iterator iterator) { + return forwardOrder.min(iterator); + } + + @Override + public E max(Iterable iterable) { + return forwardOrder.min(iterable); + } + + @Override + public int hashCode() { + return -forwardOrder.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ReverseOrdering that) { + return this.forwardOrder.equals(that.forwardOrder); + } + return false; + } + + @Override + public String toString() { + return forwardOrder + ".reverse()"; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/TopKSelector.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/TopKSelector.java new file mode 100644 index 0000000..6128ef0 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/TopKSelector.java @@ -0,0 +1,342 @@ +package org.xbib.datastructures.immutable.order; + +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * An accumulator that selects the "top" {@code k} elements added to it, relative to a provided + * comparator. "Top" can mean the greatest or the lowest elements, specified in the factory used to + * create the {@code TopKSelector} instance. + * + *

If your input data is available as a {@link Stream}, prefer passing {@link + * Comparators#least(int)} to {@link Stream#collect(java.util.stream.Collector)}. If it is available + * as an {@link Iterable} or {@link Iterator}, prefer {@link Ordering#leastOf(Iterable, int)}. + * + *

This uses the same efficient implementation as {@link Ordering#leastOf(Iterable, int)}, + * offering expected O(n + k log k) performance (worst case O(n log k)) for n calls to {@link + * #offer} and a call to {@link #topK}, with O(k) memory. In comparison, quickselect has the same + * asymptotics but requires O(n) memory, and a {@code PriorityQueue} implementation takes O(n log + * k). In benchmarks, this implementation performs at least as well as either implementation, and + * degrades more gracefully for worst-case input. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * equivalent elements are added to it, it is undefined which will come first in the output. + */ +final class TopKSelector { + + /** + * Returns a {@code TopKSelector} that collects the lowest {@code k} elements added to it, + * relative to the natural ordering of the elements, and returns them via {@link #topK} in + * ascending order. + * + * @throws IllegalArgumentException if {@code k < 0} or {@code k > Integer.MAX_VALUE / 2} + */ + public static > TopKSelector least(int k) { + return least(k, Ordering.natural()); + } + + /** + * Returns a {@code TopKSelector} that collects the lowest {@code k} elements added to it, + * relative to the specified comparator, and returns them via {@link #topK} in ascending order. + * + * @throws IllegalArgumentException if {@code k < 0} or {@code k > Integer.MAX_VALUE / 2} + */ + public static TopKSelector least( + int k, Comparator comparator) { + return new TopKSelector(comparator, k); + } + + /** + * Returns a {@code TopKSelector} that collects the greatest {@code k} elements added to it, + * relative to the natural ordering of the elements, and returns them via {@link #topK} in + * descending order. + * + * @throws IllegalArgumentException if {@code k < 0} or {@code k > Integer.MAX_VALUE / 2} + */ + public static > TopKSelector greatest(int k) { + return greatest(k, Ordering.natural()); + } + + /** + * Returns a {@code TopKSelector} that collects the greatest {@code k} elements added to it, + * relative to the specified comparator, and returns them via {@link #topK} in descending order. + * + * @throws IllegalArgumentException if {@code k < 0} or {@code k > Integer.MAX_VALUE / 2} + */ + public static TopKSelector greatest( + int k, Comparator comparator) { + return new TopKSelector(Ordering.from(comparator).reverse(), k); + } + + private final int k; + private final Comparator comparator; + + /* + * We are currently considering the elements in buffer in the range [0, bufferSize) as candidates + * for the top k elements. Whenever the buffer is filled, we quickselect the top k elements to the + * range [0, k) and ignore the remaining elements. + */ + private final T[] buffer; + private int bufferSize; + + /** + * The largest of the lowest k elements we've seen so far relative to this comparator. If + * bufferSize ≥ k, then we can ignore any elements greater than this value. + */ + private T threshold; + + private TopKSelector(Comparator comparator, int k) { + this.comparator = Objects.requireNonNull(comparator, "comparator"); + this.k = k; + if (!(k >= 0)) { + throw new IllegalArgumentException(String.format("k (%s) must be >= 0", k)); + } + if (!(k <= Integer.MAX_VALUE / 2)) { + throw new IllegalArgumentException(String.format("k (%s) must be <= Integer.MAX_VALUE / 2", k)); + } + this.buffer = (T[]) new Object[checkedMultiply(k, 2)]; + this.bufferSize = 0; + this.threshold = null; + } + + /** + * Adds {@code elem} as a candidate for the top {@code k} elements. This operation takes amortized + * O(1) time. + */ + public void offer(T elem) { + if (k == 0) { + } else if (bufferSize == 0) { + buffer[0] = elem; + threshold = elem; + bufferSize = 1; + } else if (bufferSize < k) { + buffer[bufferSize++] = elem; + // uncheckedCastNullableTToT is safe because bufferSize > 0. + if (comparator.compare(elem, uncheckedCastNullableTToT(threshold)) > 0) { + threshold = elem; + } + // uncheckedCastNullableTToT is safe because bufferSize > 0. + } else if (comparator.compare(elem, uncheckedCastNullableTToT(threshold)) < 0) { + // Otherwise, we can ignore elem; we've seen k better elements. + buffer[bufferSize++] = elem; + if (bufferSize == 2 * k) { + trim(); + } + } + } + + /** + * Quickselects the top k elements from the 2k elements in the buffer. O(k) expected time, O(k log + * k) worst case. + */ + private void trim() { + int left = 0; + int right = 2 * k - 1; + + int minThresholdPosition = 0; + // The leftmost position at which the greatest of the k lower elements + // -- the new value of threshold -- might be found. + + int iterations = 0; + int maxIterations = log2(right - left, RoundingMode.CEILING) * 3; + while (left < right) { + int pivotIndex = (left + right + 1) >>> 1; + + int pivotNewIndex = partition(left, right, pivotIndex); + + if (pivotNewIndex > k) { + right = pivotNewIndex - 1; + } else if (pivotNewIndex < k) { + left = Math.max(pivotNewIndex, left + 1); + minThresholdPosition = pivotNewIndex; + } else { + break; + } + iterations++; + if (iterations >= maxIterations) { + @SuppressWarnings("nullness") // safe because we pass sort() a range that contains real Ts + T[] castBuffer = buffer; + // We've already taken O(k log k), let's make sure we don't take longer than O(k log k). + Arrays.sort(castBuffer, left, right + 1, comparator); + break; + } + } + bufferSize = k; + + threshold = uncheckedCastNullableTToT(buffer[minThresholdPosition]); + for (int i = minThresholdPosition + 1; i < k; i++) { + if (comparator.compare( + uncheckedCastNullableTToT(buffer[i]), uncheckedCastNullableTToT(threshold)) + > 0) { + threshold = buffer[i]; + } + } + } + + /** + * Partitions the contents of buffer in the range [left, right] around the pivot element + * previously stored in buffer[pivotValue]. Returns the new index of the pivot element, + * pivotNewIndex, so that everything in [left, pivotNewIndex] is ≤ pivotValue and everything in + * (pivotNewIndex, right] is greater than pivotValue. + */ + private int partition(int left, int right, int pivotIndex) { + T pivotValue = uncheckedCastNullableTToT(buffer[pivotIndex]); + buffer[pivotIndex] = buffer[right]; + + int pivotNewIndex = left; + for (int i = left; i < right; i++) { + if (comparator.compare(uncheckedCastNullableTToT(buffer[i]), pivotValue) < 0) { + swap(pivotNewIndex, i); + pivotNewIndex++; + } + } + buffer[right] = buffer[pivotNewIndex]; + buffer[pivotNewIndex] = pivotValue; + return pivotNewIndex; + } + + private void swap(int i, int j) { + T tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + } + + TopKSelector combine(TopKSelector other) { + for (int i = 0; i < other.bufferSize; i++) { + this.offer(uncheckedCastNullableTToT(other.buffer[i])); + } + return this; + } + + /** + * Adds each member of {@code elements} as a candidate for the top {@code k} elements. This + * operation takes amortized linear time in the length of {@code elements}. + * + *

If all input data to this {@code TopKSelector} is in a single {@code Iterable}, prefer + * {@link Ordering#leastOf(Iterable, int)}, which provides a simpler API for that use case. + */ + public void offerAll(Iterable elements) { + offerAll(elements.iterator()); + } + + /** + * Adds each member of {@code elements} as a candidate for the top {@code k} elements. This + * operation takes amortized linear time in the length of {@code elements}. The iterator is + * consumed after this operation completes. + * + *

If all input data to this {@code TopKSelector} is in a single {@code Iterator}, prefer + * {@link Ordering#leastOf(Iterator, int)}, which provides a simpler API for that use case. + */ + public void offerAll(Iterator elements) { + while (elements.hasNext()) { + offer(elements.next()); + } + } + + /** + * Returns the top {@code k} elements offered to this {@code TopKSelector}, or all elements if + * fewer than {@code k} have been offered, in the order specified by the factory used to create + * this {@code TopKSelector}. + * + *

The returned list is an unmodifiable copy and will not be affected by further changes to + * this {@code TopKSelector}. This method returns in O(k log k) time. + */ + public List topK() { + @SuppressWarnings("nullness") // safe because we pass sort() a range that contains real Ts + T[] castBuffer = buffer; + Arrays.sort(castBuffer, 0, bufferSize, comparator); + if (bufferSize > k) { + Arrays.fill(buffer, k, buffer.length, null); + bufferSize = k; + threshold = buffer[k - 1]; + } + // we have to support null elements, so no ImmutableList for us + return Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(buffer, bufferSize))); + } + + @SuppressWarnings("nullness") + static T uncheckedCastNullableTToT(T t) { + return t; + } + + private static int checkedMultiply(int a, int b) { + long result = (long) a * b; + checkNoOverflow(result == (int) result, "checkedMultiply", a, b); + return (int) result; + } + + /** + * Returns the base-2 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of two + */ + @SuppressWarnings("fallthrough") + private static int log2(int x, RoundingMode mode) { + checkPositive("x", x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case DOWN: + case FLOOR: + return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x); + + case UP: + case CEILING: + return Integer.SIZE - Integer.numberOfLeadingZeros(x - 1); + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + int leadingZeros = Integer.numberOfLeadingZeros(x); + int cmp = MAX_POWER_OF_SQRT2_UNSIGNED >>> leadingZeros; + // floor(2^(logFloor + 0.5)) + int logFloor = (Integer.SIZE - 1) - leadingZeros; + return logFloor + lessThanBranchFree(cmp, x); + + default: + throw new AssertionError(); + } + } + + private static void checkNoOverflow(boolean condition, String methodName, int a, int b) { + if (!condition) { + throw new ArithmeticException("overflow: " + methodName + "(" + a + ", " + b + ")"); + } + } + + private static int checkPositive(String role, int x) { + if (x <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + private static void checkRoundingUnnecessary(boolean condition) { + if (!condition) { + throw new ArithmeticException("mode was UNNECESSARY, but rounding was necessary"); + } + } + + private static int lessThanBranchFree(int x, int y) { + return ~~(x - y) >>> (Integer.SIZE - 1); + } + + /** + * The biggest half power of two that can fit in an unsigned int. + */ + private static final int MAX_POWER_OF_SQRT2_UNSIGNED = 0xB504F333; + + private static boolean isPowerOfTwo(int x) { + return x > 0 & (x & (x - 1)) == 0; + } +} diff --git a/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/UsingToStringOrdering.java b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/UsingToStringOrdering.java new file mode 100644 index 0000000..d7f4d40 --- /dev/null +++ b/datastructures-immutable/src/main/java/org/xbib/datastructures/immutable/order/UsingToStringOrdering.java @@ -0,0 +1,27 @@ +package org.xbib.datastructures.immutable.order; + +/** + * An ordering that uses the natural order of the string representation of the values. + */ +final class UsingToStringOrdering extends Ordering { + static final UsingToStringOrdering INSTANCE = new UsingToStringOrdering(); + + private UsingToStringOrdering() { + } + + @Override + public int compare(Object left, Object right) { + return left.toString().compareTo(right.toString()); + } + + // preserve singleton-ness, so equals() and hashCode() work correctly + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "Ordering.usingToString()"; + } + +} diff --git a/datastructures-immutable/src/test/java/org/xbib/datastructures/immutable/test/CollectionTest.java b/datastructures-immutable/src/test/java/org/xbib/datastructures/immutable/test/CollectionTest.java new file mode 100644 index 0000000..6961d3f --- /dev/null +++ b/datastructures-immutable/src/test/java/org/xbib/datastructures/immutable/test/CollectionTest.java @@ -0,0 +1,29 @@ +package org.xbib.datastructures.immutable.test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.immutable.ImmutableList; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CollectionTest { + + @Test + public void givenUsingWhenUnmodifiableListIsCreatedThenNotModifiable() { + assertThrows(UnsupportedOperationException.class, () -> { + List list = new ArrayList<>(Arrays.asList("one", "two", "three")); + List unmodifiableList = ImmutableList.copyOf(list); + unmodifiableList.add("four"); + }); + } + + @Test + public void givenUsingBuilderWhenUnmodifiableListIsCreatedThenNoLongerModifiable() { + assertThrows(UnsupportedOperationException.class, () -> { + List list = new ArrayList<>(Arrays.asList("one", "two", "three")); + ImmutableList unmodifiableList = ImmutableList.builder().addAll(list).build(); + unmodifiableList.add("four"); + }); + } +} diff --git a/datastructures-multi/build.gradle b/datastructures-multi/build.gradle new file mode 100644 index 0000000..4c5fd3a --- /dev/null +++ b/datastructures-multi/build.gradle @@ -0,0 +1,3 @@ +dependencies { + api project(':datastructures-immutable') +} diff --git a/datastructures-multi/src/main/java/module-info.java b/datastructures-multi/src/main/java/module-info.java new file mode 100644 index 0000000..f871cdd --- /dev/null +++ b/datastructures-multi/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.xbib.datastructures.multi { + exports org.xbib.datastructures.multi; + requires org.xbib.datastructures.api; + requires org.xbib.datastructures.immutable; +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultimap.java new file mode 100644 index 0000000..809daa4 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultimap.java @@ -0,0 +1,1689 @@ +package org.xbib.datastructures.multi; + +import static java.util.Objects.requireNonNull; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.Multiset; +import org.xbib.datastructures.api.SetMultimap; + +/** + * Basic implementation of the {@link Multimap} interface. This class represents a multimap as a map + * that associates each key with a collection of values. All methods of {@link Multimap} are + * supported, including those specified as optional in the interface. + * + *

To implement a multimap, a subclass must define the method {@link #createCollection()}, which + * creates an empty collection of values for a key. + * + *

The multimap constructor takes a map that has a single entry for each distinct key. When you + * insert a key-value pair with a key that isn't already in the multimap, {@code + * AbstractMapBasedMultimap} calls {@link #createCollection()} to create the collection of values + * for that key. The subclass should not call {@link #createCollection()} directly, and a new + * instance should be created every time the method is called. + * + *

For example, the subclass could pass a {@link java.util.TreeMap} during construction, and + * {@link #createCollection()} could return a {@link java.util.TreeSet}, in which case the + * multimap's iterators would propagate through the keys and values in sorted order. + * + *

Keys and values may be null, as long as the underlying collection classes support null + * elements. + * + *

The collections created by {@link #createCollection()} may or may not allow duplicates. If the + * collection, such as a {@link Set}, does not support duplicates, an added key-value pair will + * replace an existing pair with the same key and value, if such a pair is present. With collections + * like {@link List} that allow duplicates, the collection will keep the existing key-value pairs + * while adding a new pair. + * + *

This class is not threadsafe when any concurrent operations update the multimap, even if the + * underlying map and {@link #createCollection()} method return threadsafe classes. Concurrent read + * operations will work correctly. To allow concurrent update operations, wrap your multimap with a + * call to {@link Multimaps#synchronizedMultimap}. + * + *

For serialization to work, the subclass must specify explicit {@code readObject} and {@code + * writeObject} methods. + */ +abstract class AbstractMapBasedMultimap + extends AbstractMultimap { + /* + * Here's an outline of the overall design. + * + * The map variable contains the collection of values associated with each + * key. When a key-value pair is added to a multimap that didn't previously + * contain any values for that key, a new collection generated by + * createCollection is added to the map. That same collection instance + * remains in the map as long as the multimap has any values for the key. If + * all values for the key are removed, the key and collection are removed + * from the map. + * + * The get method returns a WrappedCollection, which decorates the collection + * in the map (if the key is present) or an empty collection (if the key is + * not present). When the collection delegate in the WrappedCollection is + * empty, the multimap may contain subsequently added values for that key. To + * handle that situation, the WrappedCollection checks whether map contains + * an entry for the provided key, and if so replaces the delegate. + */ + + private transient Map> map; + private transient int totalSize; + + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding values + * @throws IllegalArgumentException if {@code map} is not empty + */ + protected AbstractMapBasedMultimap(Map> map) { + checkArgument(map.isEmpty()); + this.map = map; + } + + /** Used during deserialization only. */ + final void setMap(Map> map) { + this.map = map; + totalSize = 0; + for (Collection values : map.values()) { + checkArgument(!values.isEmpty()); + totalSize += values.size(); + } + } + + /** + * Creates an unmodifiable, empty collection of values. + * + *

This is used in {@link #removeAll} on an empty key. + */ + Collection createUnmodifiableEmptyCollection() { + return unmodifiableCollectionSubclass(createCollection()); + } + + /** + * Creates the collection of values for a single key. + * + *

Collections with weak, soft, or phantom references are not supported. Each call to {@code + * createCollection} should create a new instance. + * + *

The returned collection class determines whether duplicate key-value pairs are allowed. + * + * @return an empty collection of values + */ + abstract Collection createCollection(); + + /** + * Creates the collection of values for an explicitly provided key. By default, it simply calls + * {@link #createCollection()}, which is the correct behavior for most implementations. The {@link + * LinkedHashMultimap} class overrides it. + * + * @param key key to associate with values in the collection + * @return an empty collection of values + */ + Collection createCollection(K key) { + return createCollection(); + } + + Map> backingMap() { + return map; + } + + // Query Operations + + @Override + public int size() { + return totalSize; + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + // Modification Operations + + @Override + public boolean put(K key, V value) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + if (collection.add(value)) { + totalSize++; + map.put(key, collection); + return true; + } else { + throw new AssertionError("New Collection violated the Collection spec"); + } + } else if (collection.add(value)) { + totalSize++; + return true; + } else { + return false; + } + } + + private Collection getOrCreateCollection(K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + map.put(key, collection); + } + return collection; + } + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + @Override + public Collection replaceValues(K key, Iterable values) { + Iterator iterator = values.iterator(); + if (!iterator.hasNext()) { + return removeAll(key); + } + + // TODO(lowasser): investigate atomic failure? + Collection collection = getOrCreateCollection(key); + Collection oldValues = createCollection(); + oldValues.addAll(collection); + + totalSize -= collection.size(); + collection.clear(); + + while (iterator.hasNext()) { + if (collection.add(iterator.next())) { + totalSize++; + } + } + + return unmodifiableCollectionSubclass(oldValues); + } + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + @Override + public Collection removeAll(Object key) { + Collection collection = map.remove(key); + + if (collection == null) { + return createUnmodifiableEmptyCollection(); + } + + Collection output = createCollection(); + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + + return unmodifiableCollectionSubclass(output); + } + + Collection unmodifiableCollectionSubclass( + Collection collection) { + return Collections.unmodifiableCollection(collection); + } + + @Override + public void clear() { + // Clear each collection, to make previously returned collections empty. + for (Collection collection : map.values()) { + collection.clear(); + } + map.clear(); + totalSize = 0; + } + + // Views + + /** + * {@inheritDoc} + * + *

The returned collection is not serializable. + */ + @Override + public Collection get(K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + } + return wrapCollection(key, collection); + } + + /** + * Generates a decorated collection that remains consistent with the values in the multimap for + * the provided key. Changes to the multimap may alter the returned collection, and vice versa. + */ + Collection wrapCollection(K key, Collection collection) { + return new WrappedCollection(key, collection, null); + } + + final List wrapList( + K key, List list, WrappedCollection ancestor) { + return (list instanceof RandomAccess) + ? new RandomAccessWrappedList(key, list, ancestor) + : new WrappedList(key, list, ancestor); + } + + /** + * Collection decorator that stays in sync with the multimap values for a key. There are two kinds + * of wrapped collections: full and subcollections. Both have a delegate pointing to the + * underlying collection class. + * + *

Full collections, identified by a null ancestor field, contain all multimap values for a + * given key. Its delegate is a value in {@link AbstractMapBasedMultimap#map} whenever the + * delegate is non-empty. The {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} + * methods ensure that the {@code WrappedCollection} and map remain consistent. + * + *

A subcollection, such as a sublist, contains some of the values for a given key. Its + * ancestor field points to the full wrapped collection with all values for the key. The + * subcollection {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} methods call + * the corresponding methods of the full wrapped collection. + */ + + class WrappedCollection extends AbstractCollection { + final K key; + Collection delegate; + final WrappedCollection ancestor; + final Collection ancestorDelegate; + + WrappedCollection( + K key, + Collection delegate, + WrappedCollection ancestor) { + this.key = key; + this.delegate = delegate; + this.ancestor = ancestor; + this.ancestorDelegate = (ancestor == null) ? null : ancestor.getDelegate(); + } + + /** + * If the delegate collection is empty, but the multimap has values for the key, replace the + * delegate with the new collection for the key. + * + *

For a subcollection, refresh its ancestor and validate that the ancestor delegate hasn't + * changed. + */ + void refreshIfEmpty() { + if (ancestor != null) { + ancestor.refreshIfEmpty(); + if (ancestor.getDelegate() != ancestorDelegate) { + throw new ConcurrentModificationException(); + } + } else if (delegate.isEmpty()) { + Collection newDelegate = map.get(key); + if (newDelegate != null) { + delegate = newDelegate; + } + } + } + + /** + * If collection is empty, remove it from {@code AbstractMapBasedMultimap.this.map}. For + * subcollections, check whether the ancestor collection is empty. + */ + void removeIfEmpty() { + if (ancestor != null) { + ancestor.removeIfEmpty(); + } else if (delegate.isEmpty()) { + map.remove(key); + } + } + + + K getKey() { + return key; + } + + /** + * Add the delegate to the map. Other {@code WrappedCollection} methods should call this method + * after adding elements to a previously empty collection. + * + *

Subcollection add the ancestor's delegate instead. + */ + void addToMap() { + if (ancestor != null) { + ancestor.addToMap(); + } else { + map.put(key, delegate); + } + } + + @Override + public int size() { + refreshIfEmpty(); + return delegate.size(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + refreshIfEmpty(); + return delegate.equals(object); + } + + @Override + public int hashCode() { + refreshIfEmpty(); + return delegate.hashCode(); + } + + @Override + public String toString() { + refreshIfEmpty(); + return delegate.toString(); + } + + Collection getDelegate() { + return delegate; + } + + @Override + public Iterator iterator() { + refreshIfEmpty(); + return new WrappedIterator(); + } + + @Override + public Spliterator spliterator() { + refreshIfEmpty(); + return delegate.spliterator(); + } + + /** Collection iterator for {@code WrappedCollection}. */ + class WrappedIterator implements Iterator { + final Iterator delegateIterator; + final Collection originalDelegate = delegate; + + WrappedIterator() { + delegateIterator = iteratorOrListIterator(delegate); + } + + WrappedIterator(Iterator delegateIterator) { + this.delegateIterator = delegateIterator; + } + + /** + * If the delegate changed since the iterator was created, the iterator is no longer valid. + */ + void validateIterator() { + refreshIfEmpty(); + if (delegate != originalDelegate) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + validateIterator(); + return delegateIterator.hasNext(); + } + + @Override + + public V next() { + validateIterator(); + return delegateIterator.next(); + } + + @Override + public void remove() { + delegateIterator.remove(); + totalSize--; + removeIfEmpty(); + } + + Iterator getDelegateIterator() { + validateIterator(); + return delegateIterator; + } + } + + @Override + public boolean add(V value) { + refreshIfEmpty(); + boolean wasEmpty = delegate.isEmpty(); + boolean changed = delegate.add(value); + if (changed) { + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + return changed; + } + + + WrappedCollection getAncestor() { + return ancestor; + } + + // The following methods are provided for better performance. + + @Override + public boolean addAll(Collection collection) { + if (collection.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.addAll(collection); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + @Override + public boolean contains(Object o) { + refreshIfEmpty(); + return delegate.contains(o); + } + + @Override + public boolean containsAll(Collection c) { + refreshIfEmpty(); + return delegate.containsAll(c); + } + + @Override + public void clear() { + int oldSize = size(); // calls refreshIfEmpty + if (oldSize == 0) { + return; + } + delegate.clear(); + totalSize -= oldSize; + removeIfEmpty(); // maybe shouldn't be removed if this is a sublist + } + + @Override + public boolean remove(Object o) { + refreshIfEmpty(); + boolean changed = delegate.remove(o); + if (changed) { + totalSize--; + removeIfEmpty(); + } + return changed; + } + + @Override + public boolean removeAll(Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.removeAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + Objects.requireNonNull(c); + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.retainAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + private static Iterator iteratorOrListIterator( + Collection collection) { + return (collection instanceof List) + ? ((List) collection).listIterator() + : collection.iterator(); + } + + /** Set decorator that stays in sync with the multimap values for a key. */ + + class WrappedSet extends WrappedCollection implements Set { + WrappedSet(K key, Set delegate) { + super(key, delegate, null); + } + + @Override + public boolean removeAll(Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + + // Guava issue 1013: AbstractSet and most JDK set implementations are + // susceptible to quadratic removeAll performance on lists; + // use a slightly smarter implementation here + boolean changed = Sets.removeAllImpl((Set) delegate, c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + /** SortedSet decorator that stays in sync with the multimap values for a key. */ + + class WrappedSortedSet extends WrappedCollection implements SortedSet { + WrappedSortedSet( + K key, + SortedSet delegate, + WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + SortedSet getSortedSetDelegate() { + return (SortedSet) getDelegate(); + } + + @Override + + public Comparator comparator() { + return getSortedSetDelegate().comparator(); + } + + @Override + + public V first() { + refreshIfEmpty(); + return getSortedSetDelegate().first(); + } + + @Override + + public V last() { + refreshIfEmpty(); + return getSortedSetDelegate().last(); + } + + @Override + public SortedSet headSet(V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), + getSortedSetDelegate().headSet(toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public SortedSet subSet(V fromElement, V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), + getSortedSetDelegate().subSet(fromElement, toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public SortedSet tailSet(V fromElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), + getSortedSetDelegate().tailSet(fromElement), + (getAncestor() == null) ? this : getAncestor()); + } + } + + + class WrappedNavigableSet extends WrappedSortedSet implements NavigableSet { + WrappedNavigableSet( + K key, + NavigableSet delegate, + WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + @Override + NavigableSet getSortedSetDelegate() { + return (NavigableSet) super.getSortedSetDelegate(); + } + + @Override + + public V lower(V v) { + return getSortedSetDelegate().lower(v); + } + + @Override + + public V floor(V v) { + return getSortedSetDelegate().floor(v); + } + + @Override + + public V ceiling(V v) { + return getSortedSetDelegate().ceiling(v); + } + + @Override + + public V higher(V v) { + return getSortedSetDelegate().higher(v); + } + + @Override + + public V pollFirst() { + return Iterators.pollNext(iterator()); + } + + @Override + + public V pollLast() { + return Iterators.pollNext(descendingIterator()); + } + + private NavigableSet wrap(NavigableSet wrapped) { + return new WrappedNavigableSet(key, wrapped, (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public NavigableSet descendingSet() { + return wrap(getSortedSetDelegate().descendingSet()); + } + + @Override + public Iterator descendingIterator() { + return new WrappedIterator(getSortedSetDelegate().descendingIterator()); + } + + @Override + public NavigableSet subSet( + V fromElement, + boolean fromInclusive, + V toElement, + boolean toInclusive) { + return wrap( + getSortedSetDelegate().subSet(fromElement, fromInclusive, toElement, toInclusive)); + } + + @Override + public NavigableSet headSet(V toElement, boolean inclusive) { + return wrap(getSortedSetDelegate().headSet(toElement, inclusive)); + } + + @Override + public NavigableSet tailSet(V fromElement, boolean inclusive) { + return wrap(getSortedSetDelegate().tailSet(fromElement, inclusive)); + } + } + + /** List decorator that stays in sync with the multimap values for a key. */ + + class WrappedList extends WrappedCollection implements List { + WrappedList( + K key, List delegate, WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + List getListDelegate() { + return (List) getDelegate(); + } + + @Override + public boolean addAll(int index, Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = getListDelegate().addAll(index, c); + if (changed) { + int newSize = getDelegate().size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + @Override + + public V get(int index) { + refreshIfEmpty(); + return getListDelegate().get(index); + } + + @Override + + public V set(int index, V element) { + refreshIfEmpty(); + return getListDelegate().set(index, element); + } + + @Override + public void add(int index, V element) { + refreshIfEmpty(); + boolean wasEmpty = getDelegate().isEmpty(); + getListDelegate().add(index, element); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + + @Override + + public V remove(int index) { + refreshIfEmpty(); + V value = getListDelegate().remove(index); + totalSize--; + removeIfEmpty(); + return value; + } + + @Override + public int indexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + refreshIfEmpty(); + return new WrappedListIterator(); + } + + @Override + public ListIterator listIterator(int index) { + refreshIfEmpty(); + return new WrappedListIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + refreshIfEmpty(); + return wrapList( + getKey(), + getListDelegate().subList(fromIndex, toIndex), + (getAncestor() == null) ? this : getAncestor()); + } + + /** ListIterator decorator. */ + private class WrappedListIterator extends WrappedIterator implements ListIterator { + WrappedListIterator() {} + + public WrappedListIterator(int index) { + super(getListDelegate().listIterator(index)); + } + + private ListIterator getDelegateListIterator() { + return (ListIterator) getDelegateIterator(); + } + + @Override + public boolean hasPrevious() { + return getDelegateListIterator().hasPrevious(); + } + + @Override + + public V previous() { + return getDelegateListIterator().previous(); + } + + @Override + public int nextIndex() { + return getDelegateListIterator().nextIndex(); + } + + @Override + public int previousIndex() { + return getDelegateListIterator().previousIndex(); + } + + @Override + public void set(V value) { + getDelegateListIterator().set(value); + } + + @Override + public void add(V value) { + boolean wasEmpty = isEmpty(); + getDelegateListIterator().add(value); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + } + } + + /** + * List decorator that stays in sync with the multimap values for a key and supports rapid random + * access. + */ + private class RandomAccessWrappedList extends WrappedList implements RandomAccess { + RandomAccessWrappedList( + K key, List delegate, WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + } + + @Override + Set createKeySet() { + return new KeySet(map); + } + + final Set createMaybeNavigableKeySet() { + if (map instanceof NavigableMap) { + return new NavigableKeySet((NavigableMap>) map); + } else if (map instanceof SortedMap) { + return new SortedKeySet((SortedMap>) map); + } else { + return new KeySet(map); + } + } + + + private class KeySet extends MapsKeySet> { + KeySet(final Map> subMap) { + super(subMap); + } + + @Override + public Iterator iterator() { + final Iterator>> entryIterator = map().entrySet().iterator(); + return new Iterator() { + Entry> entry; + + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + + public K next() { + entry = entryIterator.next(); + return entry.getKey(); + } + + @Override + public void remove() { + checkState(entry != null, "no calls to next() since the last call to remove()"); + Collection collection = entry.getValue(); + entryIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + entry = null; + } + }; + } + + // The following methods are included for better performance. + + @Override + public Spliterator spliterator() { + return map().keySet().spliterator(); + } + + @Override + public boolean remove(Object key) { + int count = 0; + Collection collection = map().remove(key); + if (collection != null) { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count > 0; + } + + @Override + public void clear() { + Iterators.clear(iterator()); + } + + @Override + public boolean containsAll(Collection c) { + return map().keySet().containsAll(c); + } + + @Override + public boolean equals(Object object) { + return this == object || this.map().keySet().equals(object); + } + + @Override + public int hashCode() { + return map().keySet().hashCode(); + } + } + + + private class SortedKeySet extends KeySet implements SortedSet { + + SortedKeySet(SortedMap> subMap) { + super(subMap); + } + + SortedMap> sortedMap() { + return (SortedMap>) super.map(); + } + + @Override + + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + + public K first() { + return sortedMap().firstKey(); + } + + @Override + public SortedSet headSet(K toElement) { + return new SortedKeySet(sortedMap().headMap(toElement)); + } + + @Override + + public K last() { + return sortedMap().lastKey(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return new SortedKeySet(sortedMap().subMap(fromElement, toElement)); + } + + @Override + public SortedSet tailSet(K fromElement) { + return new SortedKeySet(sortedMap().tailMap(fromElement)); + } + } + + + class NavigableKeySet extends SortedKeySet implements NavigableSet { + NavigableKeySet(NavigableMap> subMap) { + super(subMap); + } + + @Override + NavigableMap> sortedMap() { + return (NavigableMap>) super.sortedMap(); + } + + @Override + + public K lower(K k) { + return sortedMap().lowerKey(k); + } + + @Override + + public K floor(K k) { + return sortedMap().floorKey(k); + } + + @Override + + public K ceiling(K k) { + return sortedMap().ceilingKey(k); + } + + @Override + + public K higher(K k) { + return sortedMap().higherKey(k); + } + + @Override + + public K pollFirst() { + return Iterators.pollNext(iterator()); + } + + @Override + + public K pollLast() { + return Iterators.pollNext(descendingIterator()); + } + + @Override + public NavigableSet descendingSet() { + return new NavigableKeySet(sortedMap().descendingMap()); + } + + @Override + public Iterator descendingIterator() { + return descendingSet().iterator(); + } + + @Override + public NavigableSet headSet(K toElement) { + return headSet(toElement, false); + } + + @Override + public NavigableSet headSet(K toElement, boolean inclusive) { + return new NavigableKeySet(sortedMap().headMap(toElement, inclusive)); + } + + @Override + public NavigableSet subSet( + K fromElement, K toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public NavigableSet subSet( + K fromElement, + boolean fromInclusive, + K toElement, + boolean toInclusive) { + return new NavigableKeySet( + sortedMap().subMap(fromElement, fromInclusive, toElement, toInclusive)); + } + + @Override + public NavigableSet tailSet(K fromElement) { + return tailSet(fromElement, true); + } + + @Override + public NavigableSet tailSet(K fromElement, boolean inclusive) { + return new NavigableKeySet(sortedMap().tailMap(fromElement, inclusive)); + } + } + + /** Removes all values for the provided key. */ + private void removeValuesForKey(Object key) { + Collection collection = Maps.safeRemove(map, key); + + if (collection != null) { + int count = collection.size(); + collection.clear(); + totalSize -= count; + } + } + + private abstract class Itr implements Iterator { + final Iterator>> keyIterator; + K key; + Collection collection; + Iterator valueIterator; + + Itr() { + keyIterator = map.entrySet().iterator(); + key = null; + collection = null; + valueIterator = Iterators.emptyModifiableIterator(); + } + + abstract T output(K key, V value); + + @Override + public boolean hasNext() { + return keyIterator.hasNext() || valueIterator.hasNext(); + } + + @Override + public T next() { + if (!valueIterator.hasNext()) { + Entry> mapEntry = keyIterator.next(); + key = mapEntry.getKey(); + collection = mapEntry.getValue(); + valueIterator = collection.iterator(); + } + /* + * uncheckedCastNullableTToT is safe: The first call to this method always enters the !hasNext() case and + * populates key, after which it's never cleared. + */ + return output(uncheckedCastNullableTToT(key), valueIterator.next()); + } + + @Override + public void remove() { + valueIterator.remove(); + /* + * requireNonNull is safe because we've already initialized `collection`. If we hadn't, then + * valueIterator.remove() would have failed. + */ + if (requireNonNull(collection).isEmpty()) { + keyIterator.remove(); + } + totalSize--; + } + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values for one key, followed + * by the values of a second key, and so on. + */ + @Override + public Collection values() { + return super.values(); + } + + @Override + Collection createValues() { + return new Values(); + } + + @Override + Iterator valueIterator() { + return new Itr() { + @Override + + V output(K key, V value) { + return value; + } + }; + } + + @Override + Spliterator valueSpliterator() { + return CollectSpliterators.flatMap( + map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size()); + } + + /* + * TODO(kevinb): should we copy this javadoc to each concrete class, so that + * classes like LinkedHashMultimap that need to say something different are + * still able to {@inheritDoc} all the way from Multimap? + */ + + @Override + Multiset createKeys() { + return new Multimaps.Keys(this); + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values for one key, followed + * by the values of a second key, and so on. + * + *

Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the + * time the entry is returned by a method call to the collection or its iterator. + */ + @Override + public Collection> entries() { + return super.entries(); + } + + @Override + Collection> createEntries() { + if (this instanceof SetMultimap) { + return new EntrySet(); + } else { + return new Entries(); + } + } + + /** + * Returns an iterator across all key-value map entries, used by {@code entries().iterator()} and + * {@code values().iterator()}. The default behavior, which traverses the values for one key, the + * values for a second key, and so on, suffices for most {@code AbstractMapBasedMultimap} + * implementations. + * + * @return an iterator across map entries + */ + @Override + Iterator> entryIterator() { + return new Itr>() { + @Override + Entry output(K key, V value) { + return Maps.immutableEntry(key, value); + } + }; + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.flatMap( + map.entrySet().spliterator(), + keyToValueCollectionEntry -> { + K key = keyToValueCollectionEntry.getKey(); + Collection valueCollection = keyToValueCollectionEntry.getValue(); + return CollectSpliterators.map( + valueCollection.spliterator(), (V value) -> Maps.immutableEntry(key, value)); + }, + Spliterator.SIZED, + size()); + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + map.forEach( + (key, valueCollection) -> valueCollection.forEach(value -> action.accept(key, value))); + } + + @Override + Map> createAsMap() { + return new AsMap(map); + } + + final Map> createMaybeNavigableAsMap() { + if (map instanceof NavigableMap) { + return new NavigableAsMap((NavigableMap>) map); + } else if (map instanceof SortedMap) { + return new SortedAsMap((SortedMap>) map); + } else { + return new AsMap(map); + } + } + + + private class AsMap extends ViewCachingAbstractMap> { + /** + * Usually the same as map, but smaller for the headMap(), tailMap(), or subMap() of a + * SortedAsMap. + */ + final transient Map> submap; + + AsMap(Map> submap) { + this.submap = submap; + } + + @Override + protected Set>> createEntrySet() { + return new AsMapEntries(); + } + + // The following methods are included for performance. + + @Override + public boolean containsKey(Object key) { + return Maps.safeContainsKey(submap, key); + } + + @Override + + public Collection get(Object key) { + Collection collection = Maps.safeGet(submap, key); + if (collection == null) { + return null; + } + @SuppressWarnings("unchecked") + K k = (K) key; + return wrapCollection(k, collection); + } + + @Override + public Set keySet() { + return AbstractMapBasedMultimap.this.keySet(); + } + + @Override + public int size() { + return submap.size(); + } + + @Override + + public Collection remove(Object key) { + Collection collection = submap.remove(key); + if (collection == null) { + return null; + } + + Collection output = createCollection(); + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + return output; + } + + @Override + public boolean equals(Object object) { + return this == object || submap.equals(object); + } + + @Override + public int hashCode() { + return submap.hashCode(); + } + + @Override + public String toString() { + return submap.toString(); + } + + @Override + public void clear() { + if (submap == map) { + AbstractMapBasedMultimap.this.clear(); + } else { + Iterators.clear(new AsMapIterator()); + } + } + + Entry> wrapEntry(Entry> entry) { + K key = entry.getKey(); + return Maps.immutableEntry(key, wrapCollection(key, entry.getValue())); + } + + + class AsMapEntries extends Maps.EntrySet> { + @Override + Map> map() { + return AsMap.this; + } + + @Override + public Iterator>> iterator() { + return new AsMapIterator(); + } + + @Override + public Spliterator>> spliterator() { + return CollectSpliterators.map(submap.entrySet().spliterator(), AsMap.this::wrapEntry); + } + + // The following methods are included for performance. + + @Override + public boolean contains(Object o) { + return Collections2.safeContains(submap.entrySet(), o); + } + + @Override + public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + // requireNonNull is safe because of the contains check. + Entry entry = requireNonNull((Entry) o); + removeValuesForKey(entry.getKey()); + return true; + } + } + + /** Iterator across all keys and value collections. */ + class AsMapIterator implements Iterator>> { + final Iterator>> delegateIterator = submap.entrySet().iterator(); + Collection collection; + + @Override + public boolean hasNext() { + return delegateIterator.hasNext(); + } + + @Override + public Entry> next() { + Entry> entry = delegateIterator.next(); + collection = entry.getValue(); + return wrapEntry(entry); + } + + @Override + public void remove() { + checkState(collection != null, "no calls to next() since the last call to remove()"); + delegateIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + collection = null; + } + } + } + + + private class SortedAsMap extends AsMap implements SortedMap> { + SortedAsMap(SortedMap> submap) { + super(submap); + } + + SortedMap> sortedMap() { + return (SortedMap>) submap; + } + + @Override + + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + + public K firstKey() { + return sortedMap().firstKey(); + } + + @Override + + public K lastKey() { + return sortedMap().lastKey(); + } + + @Override + public SortedMap> headMap(K toKey) { + return new SortedAsMap(sortedMap().headMap(toKey)); + } + + @Override + public SortedMap> subMap( + K fromKey, K toKey) { + return new SortedAsMap(sortedMap().subMap(fromKey, toKey)); + } + + @Override + public SortedMap> tailMap(K fromKey) { + return new SortedAsMap(sortedMap().tailMap(fromKey)); + } + + SortedSet sortedKeySet; + + // returns a SortedSet, even though returning a Set would be sufficient to + // satisfy the SortedMap.keySet() interface + @Override + public SortedSet keySet() { + SortedSet result = sortedKeySet; + return (result == null) ? sortedKeySet = createKeySet() : result; + } + + @Override + SortedSet createKeySet() { + return new SortedKeySet(sortedMap()); + } + } + + class NavigableAsMap extends SortedAsMap implements NavigableMap> { + + NavigableAsMap(NavigableMap> submap) { + super(submap); + } + + @Override + NavigableMap> sortedMap() { + return (NavigableMap>) super.sortedMap(); + } + + @Override + + public Entry> lowerEntry(K key) { + Entry> entry = sortedMap().lowerEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + + public K lowerKey(K key) { + return sortedMap().lowerKey(key); + } + + @Override + + public Entry> floorEntry(K key) { + Entry> entry = sortedMap().floorEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + + public K floorKey(K key) { + return sortedMap().floorKey(key); + } + + @Override + + public Entry> ceilingEntry(K key) { + Entry> entry = sortedMap().ceilingEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + + public K ceilingKey(K key) { + return sortedMap().ceilingKey(key); + } + + @Override + + public Entry> higherEntry(K key) { + Entry> entry = sortedMap().higherEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + + public K higherKey(K key) { + return sortedMap().higherKey(key); + } + + @Override + + public Entry> firstEntry() { + Entry> entry = sortedMap().firstEntry(); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + + public Entry> lastEntry() { + Entry> entry = sortedMap().lastEntry(); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + + public Entry> pollFirstEntry() { + return pollAsMapEntry(entrySet().iterator()); + } + + @Override + + public Entry> pollLastEntry() { + return pollAsMapEntry(descendingMap().entrySet().iterator()); + } + + + Entry> pollAsMapEntry(Iterator>> entryIterator) { + if (!entryIterator.hasNext()) { + return null; + } + Entry> entry = entryIterator.next(); + Collection output = createCollection(); + output.addAll(entry.getValue()); + entryIterator.remove(); + return Maps.immutableEntry(entry.getKey(), unmodifiableCollectionSubclass(output)); + } + + @Override + public NavigableMap> descendingMap() { + return new NavigableAsMap(sortedMap().descendingMap()); + } + + @Override + public NavigableSet keySet() { + return (NavigableSet) super.keySet(); + } + + @Override + NavigableSet createKeySet() { + return new NavigableKeySet(sortedMap()); + } + + @Override + public NavigableSet navigableKeySet() { + return keySet(); + } + + @Override + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + @Override + public NavigableMap> subMap( + K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap> subMap( + K fromKey, + boolean fromInclusive, + K toKey, + boolean toInclusive) { + return new NavigableAsMap(sortedMap().subMap(fromKey, fromInclusive, toKey, toInclusive)); + } + + @Override + public NavigableMap> headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public NavigableMap> headMap(K toKey, boolean inclusive) { + return new NavigableAsMap(sortedMap().headMap(toKey, inclusive)); + } + + @Override + public NavigableMap> tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public NavigableMap> tailMap( + K fromKey, boolean inclusive) { + return new NavigableAsMap(sortedMap().tailMap(fromKey, inclusive)); + } + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultiset.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultiset.java new file mode 100644 index 0000000..2fa62e4 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMapBasedMultiset.java @@ -0,0 +1,321 @@ +package org.xbib.datastructures.multi; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.function.ObjIntConsumer; +import static java.util.Objects.requireNonNull; + +/** + * Basic implementation of {@code Multiset} backed by an instance of {@code Map}. + * + *

For serialization to work, the subclass must specify explicit {@code readObject} and {@code + * writeObject} methods. + */ +abstract class AbstractMapBasedMultiset extends AbstractMultiset + implements Serializable { + // TODO(lowasser): consider overhauling this back to Map + private transient Map backingMap; + + /* + * Cache the size for efficiency. Using a long lets us avoid the need for + * overflow checking and ensures that size() will function correctly even if + * the multiset had once been larger than Integer.MAX_VALUE. + */ + private transient long size; + + /** Standard constructor. */ + protected AbstractMapBasedMultiset(Map backingMap) { + checkArgument(backingMap.isEmpty()); + this.backingMap = backingMap; + } + + /** Used during deserialization only. The backing map must be empty. */ + void setBackingMap(Map backingMap) { + this.backingMap = backingMap; + } + + // Required Implementations + + /** + * {@inheritDoc} + * + *

Invoking {@link Entry#getCount} on an entry in the returned set always returns the + * current count of that element in the multiset, as opposed to the count at the time the entry + * was retrieved. + */ + @Override + public Set> entrySet() { + return super.entrySet(); + } + + @Override + Iterator elementIterator() { + final Iterator> backingEntries = backingMap.entrySet().iterator(); + return new Iterator() { + @CheckForNull Map.Entry toRemove; + + @Override + public boolean hasNext() { + return backingEntries.hasNext(); + } + + @Override + @ParametricNullness + public E next() { + final Map.Entry mapEntry = backingEntries.next(); + toRemove = mapEntry; + return mapEntry.getKey(); + } + + @Override + public void remove() { + checkState(toRemove != null, "no calls to next() since the last call to remove()"); + size -= toRemove.getValue().getAndSet(0); + backingEntries.remove(); + toRemove = null; + } + }; + } + + @Override + Iterator> entryIterator() { + final Iterator> backingEntries = backingMap.entrySet().iterator(); + return new Iterator>() { + @CheckForNull Map.Entry toRemove; + + @Override + public boolean hasNext() { + return backingEntries.hasNext(); + } + + @Override + public Entry next() { + final Map.Entry mapEntry = backingEntries.next(); + toRemove = mapEntry; + return new Multisets.AbstractEntry() { + @Override + @ParametricNullness + public E getElement() { + return mapEntry.getKey(); + } + + @Override + public int getCount() { + Count count = mapEntry.getValue(); + if (count == null || count.get() == 0) { + Count frequency = backingMap.get(getElement()); + if (frequency != null) { + return frequency.get(); + } + } + return (count == null) ? 0 : count.get(); + } + }; + } + + @Override + public void remove() { + checkState(toRemove != null, "no calls to next() since the last call to remove()"); + size -= toRemove.getValue().getAndSet(0); + backingEntries.remove(); + toRemove = null; + } + }; + } + + @Override + public void forEachEntry(ObjIntConsumer action) { + checkNotNull(action); + backingMap.forEach((element, count) -> action.accept(element, count.get())); + } + + @Override + public void clear() { + for (Count frequency : backingMap.values()) { + frequency.set(0); + } + backingMap.clear(); + size = 0L; + } + + @Override + int distinctElements() { + return backingMap.size(); + } + + // Optimizations - Query Operations + + @Override + public int size() { + return Ints.saturatedCast(size); + } + + @Override + public Iterator iterator() { + return new MapBasedMultisetIterator(); + } + + /* + * Not subclassing AbstractMultiset$MultisetIterator because next() needs to + * retrieve the Map.Entry entry, which can then be used for + * a more efficient remove() call. + */ + private class MapBasedMultisetIterator implements Iterator { + final Iterator> entryIterator; + @CheckForNull Map.Entry currentEntry; + int occurrencesLeft; + boolean canRemove; + + MapBasedMultisetIterator() { + this.entryIterator = backingMap.entrySet().iterator(); + } + + @Override + public boolean hasNext() { + return occurrencesLeft > 0 || entryIterator.hasNext(); + } + + @Override + @ParametricNullness + public E next() { + if (occurrencesLeft == 0) { + currentEntry = entryIterator.next(); + occurrencesLeft = currentEntry.getValue().get(); + } + occurrencesLeft--; + canRemove = true; + /* + * requireNonNull is safe because occurrencesLeft starts at 0, forcing us to initialize + * currentEntry above. After that, we never clear it. + */ + return requireNonNull(currentEntry).getKey(); + } + + @Override + public void remove() { + checkRemove(canRemove); + /* + * requireNonNull is safe because canRemove is set to true only after we initialize + * currentEntry (which we never subsequently clear). + */ + int frequency = requireNonNull(currentEntry).getValue().get(); + if (frequency <= 0) { + throw new ConcurrentModificationException(); + } + if (currentEntry.getValue().addAndGet(-1) == 0) { + entryIterator.remove(); + } + size--; + canRemove = false; + } + } + + @Override + public int count(@CheckForNull Object element) { + Count frequency = Maps.safeGet(backingMap, element); + return (frequency == null) ? 0 : frequency.get(); + } + + // Optional Operations - Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the call would result in more than {@link + * Integer#MAX_VALUE} occurrences of {@code element} in this multiset. + */ + @CanIgnoreReturnValue + @Override + public int add(@ParametricNullness E element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences); + Count frequency = backingMap.get(element); + int oldCount; + if (frequency == null) { + oldCount = 0; + backingMap.put(element, new Count(occurrences)); + } else { + oldCount = frequency.get(); + long newCount = (long) oldCount + (long) occurrences; + checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount); + frequency.add(occurrences); + } + size += occurrences; + return oldCount; + } + + @CanIgnoreReturnValue + @Override + public int remove(@CheckForNull Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences); + Count frequency = backingMap.get(element); + if (frequency == null) { + return 0; + } + + int oldCount = frequency.get(); + + int numberRemoved; + if (oldCount > occurrences) { + numberRemoved = occurrences; + } else { + numberRemoved = oldCount; + backingMap.remove(element); + } + + frequency.add(-numberRemoved); + size -= numberRemoved; + return oldCount; + } + + // Roughly a 33% performance improvement over AbstractMultiset.setCount(). + @CanIgnoreReturnValue + @Override + public int setCount(@ParametricNullness E element, int count) { + checkNonnegative(count, "count"); + + Count existingCounter; + int oldCount; + if (count == 0) { + existingCounter = backingMap.remove(element); + oldCount = getAndSet(existingCounter, count); + } else { + existingCounter = backingMap.get(element); + oldCount = getAndSet(existingCounter, count); + + if (existingCounter == null) { + backingMap.put(element, new Count(count)); + } + } + + size += (count - oldCount); + return oldCount; + } + + private static int getAndSet(@CheckForNull Count i, int count) { + if (i == null) { + return 0; + } + + return i.getAndSet(count); + } + + // Don't allow default serialization. + @GwtIncompatible // java.io.ObjectStreamException + private void readObjectNoData() throws ObjectStreamException { + throw new InvalidObjectException("Stream data required"); + } + + @GwtIncompatible // not needed in emulated source. + private static final long serialVersionUID = -2250766705698539974L; +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultimap.java new file mode 100644 index 0000000..f48a6d9 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultimap.java @@ -0,0 +1,295 @@ +package org.xbib.datastructures.multi; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.Multiset; +import org.xbib.datastructures.api.SetMultimap; + +/** + * A skeleton {@code Multimap} implementation, not necessarily in terms of a {@code Map}. + */ +abstract class AbstractMultimap + implements Multimap { + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsValue(Object value) { + for (Collection collection : asMap().values()) { + if (collection.contains(value)) { + return true; + } + } + + return false; + } + + @Override + public boolean containsEntry(Object key, Object value) { + Collection collection = asMap().get(key); + return collection != null && collection.contains(value); + } + + @Override + public boolean remove(Object key, Object value) { + Collection collection = asMap().get(key); + return collection != null && collection.remove(value); + } + + @Override + public boolean put(K key, V value) { + return get(key).add(value); + } + + @Override + public boolean putAll(K key, Iterable values) { + Objects.requireNonNull(values); + // make sure we only call values.iterator() once + // and we only call get(key) if values is nonempty + if (values instanceof Collection valueCollection) { + return !valueCollection.isEmpty() && get(key).addAll(valueCollection); + } else { + Iterator valueItr = values.iterator(); + return valueItr.hasNext() && addAll(get(key), valueItr); + } + } + + @Override + public boolean putAll(Multimap multimap) { + boolean changed = false; + for (Entry entry : multimap.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + @Override + public Collection replaceValues(K key, Iterable values) { + Objects.requireNonNull(values); + Collection result = removeAll(key); + putAll(key, values); + return result; + } + + private transient Collection> entries; + + @Override + public Collection> entries() { + Collection> result = entries; + return (result == null) ? entries = createEntries() : result; + } + + abstract Collection> createEntries(); + + class Entries extends MultimapsEntries { + @Override + Multimap multimap() { + return AbstractMultimap.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + + @Override + public Spliterator> spliterator() { + return entrySpliterator(); + } + } + + class EntrySet extends Entries implements Set> { + @Override + public int hashCode() { + return hashCodeImpl(this); + } + + @Override + public boolean equals(Object obj) { + return equalsImpl(this, obj); + } + } + + abstract Iterator> entryIterator(); + + Spliterator> entrySpliterator() { + return Spliterators.spliterator( + entryIterator(), size(), (this instanceof SetMultimap) ? Spliterator.DISTINCT : 0); + } + + private transient Set keySet; + + @Override + public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + abstract Set createKeySet(); + + private transient Multiset keys; + + @Override + public Multiset keys() { + Multiset result = keys; + return (result == null) ? keys = createKeys() : result; + } + + abstract Multiset createKeys(); + + private transient Collection values; + + @Override + public Collection values() { + Collection result = values; + return (result == null) ? values = createValues() : result; + } + + abstract Collection createValues(); + + class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return valueIterator(); + } + + @Override + public Spliterator spliterator() { + return valueSpliterator(); + } + + @Override + public int size() { + return AbstractMultimap.this.size(); + } + + @Override + public boolean contains(Object o) { + return AbstractMultimap.this.containsValue(o); + } + + @Override + public void clear() { + AbstractMultimap.this.clear(); + } + } + + Iterator valueIterator() { + return valueIterator(entries().iterator()); + } + + Spliterator valueSpliterator() { + return Spliterators.spliterator(valueIterator(), size(), 0); + } + + private transient Map> asMap; + + @Override + public Map> asMap() { + Map> result = asMap; + return (result == null) ? asMap = createAsMap() : result; + } + + abstract Map> createAsMap(); + + // Comparison and hashing + + @Override + public boolean equals(Object object) { + return equalsImpl(this, object); + } + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, as returned by + * {@link Multimap#asMap}. + * + * @see Map#hashCode + */ + @Override + public int hashCode() { + return asMap().hashCode(); + } + + /** + * Returns a string representation of the multimap, generated by calling {@code toString} on the + * map returned by {@link Multimap#asMap}. + * + * @return a string representation of the multimap + */ + @Override + public String toString() { + return asMap().toString(); + } + + private static Iterator valueIterator(Iterator> entryIterator) { + return new TransformedIterator, V>(entryIterator) { + @Override + V transform(Entry entry) { + return entry.getValue(); + } + }; + } + + private static boolean addAll( + Collection addTo, Iterator iterator) { + Objects.requireNonNull(addTo); + Objects.requireNonNull(iterator); + boolean wasModified = false; + while (iterator.hasNext()) { + wasModified |= addTo.add(iterator.next()); + } + return wasModified; + } + + /** An implementation for {@link Set#hashCode()}. */ + static int hashCodeImpl(Set s) { + int hashCode = 0; + for (Object o : s) { + hashCode += o != null ? o.hashCode() : 0; + + hashCode = ~~hashCode; + // Needed to deal with unusual integer overflow in GWT. + } + return hashCode; + } + + /** An implementation for {@link Set#equals(Object)}. */ + private static boolean equalsImpl(Set s, Object object) { + if (s == object) { + return true; + } + if (object instanceof Set) { + Set o = (Set) object; + + try { + return s.size() == o.size() && s.containsAll(o); + } catch (NullPointerException | ClassCastException ignored) { + return false; + } + } + return false; + } + + static boolean equalsImpl(Multimap multimap, Object object) { + if (object == multimap) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return multimap.asMap().equals(that.asMap()); + } + return false; + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultiset.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultiset.java new file mode 100644 index 0000000..45814f8 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractMultiset.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2007 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 org.xbib.datastructures.multi; + +import com.google.common.annotations.GwtCompatible; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.concurrent.LazyInit; +import com.google.j2objc.annotations.WeakOuter; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import javax.annotation.CheckForNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.xbib.datastructures.api.Multiset; +import static com.google.common.collect.Multisets.setCountImpl; + +/** + * This class provides a skeletal implementation of the {@link Multiset} interface. A new multiset + * implementation can be created easily by extending this class and implementing the {@link + * Multiset#entrySet()} method, plus optionally overriding {@link #add(Object, int)} and {@link + * #remove(Object, int)} to enable modifications to the multiset. + * + *

The {@link #count} and {@link #size} implementations all iterate across the set returned by + * {@link Multiset#entrySet()}, as do many methods acting on the set returned by {@link + * #elementSet()}. Override those methods for better performance. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + */ +@GwtCompatible +@ElementTypesAreNonnullByDefault +abstract class AbstractMultiset extends AbstractCollection + implements Multiset { + // Query Operations + + @Override + public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override + public boolean contains(@CheckForNull Object element) { + return count(element) > 0; + } + + // Modification Operations + @CanIgnoreReturnValue + @Override + public final boolean add(@ParametricNullness E element) { + add(element, 1); + return true; + } + + @CanIgnoreReturnValue + @Override + public int add(@ParametricNullness E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @CanIgnoreReturnValue + @Override + public final boolean remove(@CheckForNull Object element) { + return remove(element, 1) > 0; + } + + @CanIgnoreReturnValue + @Override + public int remove(@CheckForNull Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @CanIgnoreReturnValue + @Override + public int setCount(@ParametricNullness E element, int count) { + return setCountImpl(this, element, count); + } + + @CanIgnoreReturnValue + @Override + public boolean setCount(@ParametricNullness E element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

This implementation is highly efficient when {@code elementsToAdd} is itself a {@link + * Multiset}. + */ + @CanIgnoreReturnValue + @Override + public final boolean addAll(Collection elementsToAdd) { + return Multisets.addAllImpl(this, elementsToAdd); + } + + @CanIgnoreReturnValue + @Override + public final boolean removeAll(Collection elementsToRemove) { + return Multisets.removeAllImpl(this, elementsToRemove); + } + + @CanIgnoreReturnValue + @Override + public final boolean retainAll(Collection elementsToRetain) { + return Multisets.retainAllImpl(this, elementsToRetain); + } + + @Override + public abstract void clear(); + + // Views + + @LazyInit @CheckForNull private transient Set elementSet; + + @Override + public Set elementSet() { + Set result = elementSet; + if (result == null) { + elementSet = result = createElementSet(); + } + return result; + } + + /** + * Creates a new instance of this multiset's element set, which will be returned by {@link + * #elementSet()}. + */ + Set createElementSet() { + return new ElementSet(); + } + + @WeakOuter + class ElementSet extends Multisets.ElementSet { + @Override + Multiset multiset() { + return AbstractMultiset.this; + } + + @Override + public Iterator iterator() { + return elementIterator(); + } + } + + abstract Iterator elementIterator(); + + @LazyInit @CheckForNull private transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + if (result == null) { + entrySet = result = createEntrySet(); + } + return result; + } + + @WeakOuter + class EntrySet extends Multisets.EntrySet { + @Override + Multiset multiset() { + return AbstractMultiset.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + + @Override + public int size() { + return distinctElements(); + } + } + + Set> createEntrySet() { + return new EntrySet(); + } + + abstract Iterator> entryIterator(); + + abstract int distinctElements(); + + // Object methods + + /** + * {@inheritDoc} + * + *

This implementation returns {@code true} if {@code object} is a multiset of the same size + * and if, for each element, the two multisets have the same count. + */ + @Override + public final boolean equals(@CheckForNull Object object) { + return Multisets.equalsImpl(this, object); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the hash code of {@link Multiset#entrySet()}. + */ + @Override + public final int hashCode() { + return entrySet().hashCode(); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the result of invoking {@code toString} on {@link + * Multiset#entrySet()}. + */ + @Override + public final String toString() { + return entrySet().toString(); + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractSetMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractSetMultimap.java new file mode 100644 index 0000000..1f3f20b --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/AbstractSetMultimap.java @@ -0,0 +1,126 @@ +package org.xbib.datastructures.multi; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.SetMultimap; + +/** + * Basic implementation of the {@link SetMultimap} interface. It's a wrapper around {@link + * AbstractMapBasedMultimap} that converts the returned collections into {@code Sets}. The {@link + * #createCollection} method must return a {@code Set}. + */ +abstract class AbstractSetMultimap + extends AbstractMapBasedMultimap implements SetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding values + */ + protected AbstractSetMultimap(Map> map) { + super(map); + } + + @Override + abstract Set createCollection(); + + @Override + Set createUnmodifiableEmptyCollection() { + return Collections.emptySet(); + } + + @Override + Collection unmodifiableCollectionSubclass( + Collection collection) { + return Collections.unmodifiableSet((Set) collection); + } + + @Override + Collection wrapCollection(K key, Collection collection) { + return new WrappedSet(key, (Set) collection); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + */ + @Override + public Set get(K key) { + return (Set) super.get(key); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + */ + @Override + public Set> entries() { + return (Set>) super.entries(); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + */ + @Override + public Set removeAll(Object key) { + return (Set) super.removeAll(key); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override + public Set replaceValues(K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map has {@link Set} + * values. + */ + @Override + public Map> asMap() { + return super.asMap(); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or {@code false} if the + * multimap already contained the key-value pair + */ + @Override + public boolean put(K key, V value) { + return super.put(key, value); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they contain the same values. + * Equality does not depend on the ordering of keys or values. + */ + @Override + public boolean equals(Object object) { + return super.equals(object); + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/BaseImmutableMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/BaseImmutableMultimap.java new file mode 100644 index 0000000..9866c4b --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/BaseImmutableMultimap.java @@ -0,0 +1,8 @@ +package org.xbib.datastructures.multi; + +/** + * A dummy superclass of {@link ImmutableMultimap} that can be instanceof'd without ProGuard + * retaining additional implementation details of {@link ImmutableMultimap}. + */ +abstract class BaseImmutableMultimap extends AbstractMultimap { +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableListMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableListMultimap.java new file mode 100644 index 0000000..c1327d2 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableListMultimap.java @@ -0,0 +1,426 @@ +package org.xbib.datastructures.multi; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; +import org.xbib.datastructures.api.ListMultimap; +import org.xbib.datastructures.api.Multimap; + +/** + * A {@link ListMultimap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + */ +public class ImmutableListMultimap extends ImmutableMultimap + implements ListMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableListMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

Example: + * + *

{@code
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(toImmutableListMultimap(str -> str.charAt(0), str -> str.substring(1)));
+   *
+   * // is equivalent to
+   *
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     new ImmutableListMultimap.Builder()
+   *         .put('b', "anana")
+   *         .putAll('a', "pple", "sparagus")
+   *         .putAll('c', "arrot", "herry")
+   *         .build();
+   * }
+ * + * @since 21.0 + */ + public static + Collector> toImmutableListMultimap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableListMultimap(keyFunction, valueFunction); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableListMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

Example: + * + *

{@code
+   * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToImmutableListMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c));
+   *
+   * // is equivalent to
+   *
+   * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP =
+   *     ImmutableListMultimap.builder()
+   *         .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
+   *         .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
+   *         .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
+   *         .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
+   *         .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
+   *         .build();
+   * }
+   * }
+ * + * @since 21.0 + */ + public static + Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction); + } + + /** + * Returns the empty multimap. + * + *

Performance note: the instance returned is a singleton. + */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableListMultimap of() { + return (ImmutableListMultimap) EmptyImmutableListMultimap.INSTANCE; + } + + /** Returns an immutable multimap containing a single entry. */ + public static ImmutableListMultimap of(K k1, V v1) { + Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of(K k1, V v1, K k2, V v2) { + Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of(K k1, V v1, K k2, V v2, K k3, V v3) { + Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * A builder for creating immutable {@code ListMultimap} instances, especially {@code public + * static final} multimaps ("constant multimaps"). Example: + * + *

{@code
+   * static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *     new ImmutableListMultimap.Builder()
+   *         .put("one", 1)
+   *         .putAll("several", 1, 2, 3)
+   *         .putAll("many", 1, 2, 3, 4, 5)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multimaps in series. Each multimap contains the key-value mappings in the previously + * created multimaps. + */ + public static final class Builder extends ImmutableMultimap.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableListMultimap#builder}. + */ + public Builder() {} + + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + @Override + public Builder putAll(K key, Iterable values) { + super.putAll(key, values); + return this; + } + + @Override + public Builder putAll(K key, V... values) { + super.putAll(key, values); + return this; + } + + @Override + public Builder putAll(Multimap multimap) { + super.putAll(multimap); + return this; + } + + @Override + Builder combine(ImmutableMultimap.Builder other) { + super.combine(other); + return this; + } + + @Override + public Builder orderKeysBy(Comparator keyComparator) { + super.orderKeysBy(keyComparator); + return this; + } + + @Override + public Builder orderValuesBy(Comparator valueComparator) { + super.orderValuesBy(valueComparator); + return this; + } + + /** Returns a newly-created immutable list multimap. */ + @Override + public ImmutableListMultimap build() { + return (ImmutableListMultimap) super.build(); + } + } + + /** + * Returns an immutable multimap containing the same mappings as {@code multimap}. The generated + * multimap's key and value orderings correspond to the iteration ordering of the {@code + * multimap.asMap()} view. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is null + */ + public static ImmutableListMultimap copyOf( + Multimap multimap) { + if (multimap.isEmpty()) { + return of(); + } + + // TODO(lowasser): copy ImmutableSetMultimap by using asList() on the sets + if (multimap instanceof ImmutableListMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableListMultimap kvMultimap = (ImmutableListMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + + return fromMapEntries(multimap.asMap().entrySet(), null); + } + + /** + * Returns an immutable multimap containing the specified entries. The returned multimap iterates + * over keys in the order they were first encountered in the input, and the values for each key + * are iterated in the order they were encountered. + * + * @throws NullPointerException if any key, value, or entry is null + */ + public static ImmutableListMultimap copyOf( + Iterable> entries) { + return new Builder().putAll(entries).build(); + } + + /** Creates an ImmutableListMultimap from an asMap.entrySet. */ + static ImmutableListMultimap fromMapEntries( + Collection>> mapEntries, + Comparator valueComparator) { + if (mapEntries.isEmpty()) { + return of(); + } + ImmutableMap.Builder> builder = + new ImmutableMap.Builder<>(mapEntries.size()); + int size = 0; + + for (Entry> entry : mapEntries) { + K key = entry.getKey(); + Collection values = entry.getValue(); + ImmutableList list = + (valueComparator == null) + ? ImmutableList.copyOf(values) + : ImmutableList.sortedCopyOf(valueComparator, values); + if (!list.isEmpty()) { + builder.put(key, list); + size += list.size(); + } + } + + return new ImmutableListMultimap<>(builder.buildOrThrow(), size); + } + + ImmutableListMultimap(ImmutableMap> map, int size) { + super(map, size); + } + + // views + + /** + * Returns an immutable list of the values for the given key. If no mappings in the multimap have + * the provided key, an empty immutable list is returned. The values are in the same order as the + * parameters used to build this multimap. + */ + @Override + public ImmutableList get(K key) { + // This cast is safe as its type is known in constructor. + ImmutableList list = (ImmutableList) map.get(key); + return (list == null) ? ImmutableList.of() : list; + } + + private transient ImmutableListMultimap inverse; + + /** + * {@inheritDoc} + * + *

Because an inverse of a list multimap can contain multiple pairs with the same key and + * value, this method returns an {@code ImmutableListMultimap} rather than the {@code + * ImmutableMultimap} specified in the {@code ImmutableMultimap} class. + * + */ + @Override + public ImmutableListMultimap inverse() { + ImmutableListMultimap result = inverse; + return (result == null) ? (inverse = invert()) : result; + } + + private ImmutableListMultimap invert() { + Builder builder = builder(); + for (Entry entry : entries()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableListMultimap invertedMultimap = builder.build(); + invertedMultimap.inverse = this; + return invertedMultimap; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final ImmutableList removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final ImmutableList replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the key, the number of + * values for that key, and the key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + ImmutableList.Builder valuesBuilder = ImmutableList.builder(); + for (int j = 0; j < valueCount; j++) { + valuesBuilder.add(stream.readObject()); + } + builder.put(key, valuesBuilder.build()); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.buildOrThrow(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) new InvalidObjectException(e.getMessage()).initCause(e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + } + + private static final long serialVersionUID = 0; +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultimap.java new file mode 100644 index 0000000..6c65433 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultimap.java @@ -0,0 +1,742 @@ +package org.xbib.datastructures.multi; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.SetMultimap; +import org.xbib.datastructures.immutable.order.Ordering; +import static java.util.Objects.requireNonNull; + +/** + * A {@link Multimap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: avoid direct usage of {@link ImmutableMultimap} as a type (as with + * {@link Multimap} itself). Prefer subtypes such as {@link ImmutableSetMultimap} or {@link + * ImmutableListMultimap}, which have well-defined {@link #equals} semantics, thus avoiding a common + * source of bugs and confusion. + * + *

Note: every {@link ImmutableMultimap} offers an {@link #inverse} view, so there is no + * need for a distinct {@code ImmutableBiMultimap} type. + * + *

+ * + *

Key-grouped iteration. All view collections follow the same iteration order. In all + * current implementations, the iteration order always keeps multiple entries with the same key + * together. Any creation method that would customarily respect insertion order (such as {@link + * #copyOf(Multimap)}) instead preserves key-grouped order by inserting entries for an existing key + * immediately after the last entry having that key. + */ +public abstract class ImmutableMultimap extends BaseImmutableMultimap + implements Serializable { + + /** + * Returns an empty multimap. + * + *

Performance note: the instance returned is a singleton. + */ + public static ImmutableMultimap of() { + return ImmutableListMultimap.of(); + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableMultimap of(K k1, V v1) { + return ImmutableListMultimap.of(k1, v1); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2) { + return ImmutableListMultimap.of(k1, v1, k2, v2); + } + + /** + * Returns an immutable multimap containing the given entries, in the "key-grouped" insertion + * order described in the class documentation. + */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2, K k3, V v3) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3); + } + + /** + * Returns an immutable multimap containing the given entries, in the "key-grouped" insertion + * order described in the class documentation. + */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4); + } + + /** + * Returns an immutable multimap containing the given entries, in the "key-grouped" insertion + * order described in the class documentation. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * A builder for creating immutable multimap instances, especially {@code public static final} + * multimaps ("constant multimaps"). Example: + * + *

{@code
+     * static final Multimap STRING_TO_INTEGER_MULTIMAP =
+     *     new ImmutableMultimap.Builder()
+     *         .put("one", 1)
+     *         .putAll("several", 1, 2, 3)
+     *         .putAll("many", 1, 2, 3, 4, 5)
+     *         .build();
+     * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multimaps in series. Each multimap contains the key-value mappings in the previously + * created multimaps. + * + */ + public static class Builder { + final Map> builderMap; + Comparator keyComparator; + Comparator valueComparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableMultimap#builder}. + */ + public Builder() { + this.builderMap = new LinkedHashMap<>(); + } + + Collection newMutableValueCollection() { + return new ArrayList<>(); + } + + /** + * Adds a key-value mapping to the built multimap. + */ + public Builder put(K key, V value) { + checkEntryNotNull(key, value); + Collection valueCollection = builderMap.get(key); + if (valueCollection == null) { + builderMap.put(key, valueCollection = newMutableValueCollection()); + } + valueCollection.add(value); + return this; + } + + /** + * Adds an entry to the built multimap. + */ + public Builder put(Entry entry) { + return put(entry.getKey(), entry.getValue()); + } + + /** + * Adds entries to the built multimap. + */ + public Builder putAll(Iterable> entries) { + for (Entry entry : entries) { + put(entry); + } + return this; + } + + /** + * Stores a collection of values with the same key in the built multimap. + * + * @throws NullPointerException if {@code key}, {@code values}, or any element in {@code values} + * is null. The builder is left in an invalid state. + */ + public Builder putAll(K key, Iterable values) { + if (key == null) { + throw new NullPointerException("null key in entry: null=" + toString(values.iterator())); + } + Collection valueCollection = builderMap.get(key); + if (valueCollection != null) { + for (V value : values) { + checkEntryNotNull(key, value); + valueCollection.add(value); + } + return this; + } + Iterator valuesItr = values.iterator(); + if (!valuesItr.hasNext()) { + return this; + } + valueCollection = newMutableValueCollection(); + while (valuesItr.hasNext()) { + V value = valuesItr.next(); + checkEntryNotNull(key, value); + valueCollection.add(value); + } + builderMap.put(key, valueCollection); + return this; + } + + /** + * Stores an array of values with the same key in the built multimap. + * + * @throws NullPointerException if the key or any value is null. The builder is left in an + * invalid state. + */ + public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + /** + * Stores another multimap's entries in the built multimap. The generated multimap's key and + * value orderings correspond to the iteration ordering of the {@code multimap.asMap()} view, + * with new keys and values following any existing keys and values. + * + * @throws NullPointerException if any key or value in {@code multimap} is null. The builder is + * left in an invalid state. + */ + public Builder putAll(Multimap multimap) { + for (Entry> entry : + multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Specifies the ordering of the generated multimap's keys. + */ + public Builder orderKeysBy(Comparator keyComparator) { + this.keyComparator = Objects.requireNonNull(keyComparator); + return this; + } + + /** + * Specifies the ordering of the generated multimap's values for each key. + */ + public Builder orderValuesBy(Comparator valueComparator) { + this.valueComparator = Objects.requireNonNull(valueComparator); + return this; + } + + Builder combine(Builder other) { + for (Entry> entry : other.builderMap.entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Returns a newly-created immutable multimap. + */ + public ImmutableMultimap build() { + Collection>> mapEntries = builderMap.entrySet(); + if (keyComparator != null) { + mapEntries = Ordering.from(keyComparator).onKeys().immutableSortedCopy(mapEntries); + } + return ImmutableListMultimap.fromMapEntries(mapEntries, valueComparator); + } + + + private static String toString(Iterator iterator) { + StringBuilder sb = new StringBuilder().append('['); + boolean first = true; + while (iterator.hasNext()) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(iterator.next()); + } + return sb.append(']').toString(); + } + } + + /** + * Returns an immutable multimap containing the same mappings as {@code multimap}, in the + * "key-grouped" iteration order described in the class documentation. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is null + */ + public static ImmutableMultimap copyOf(Multimap multimap) { + if (multimap instanceof ImmutableMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableMultimap kvMultimap = (ImmutableMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + return ImmutableListMultimap.copyOf(multimap); + } + + /** + * Returns an immutable multimap containing the specified entries. The returned multimap iterates + * over keys in the order they were first encountered in the input, and the values for each key + * are iterated in the order they were encountered. + * + * @throws NullPointerException if any key, value, or entry is null + */ + public static ImmutableMultimap copyOf( + Iterable> entries) { + return ImmutableListMultimap.copyOf(entries); + } + + final transient ImmutableMap> map; + final transient int size; + + // These constants allow the deserialization code to set final fields. This + // holder class makes sure they are not initialized unless an instance is + // deserialized. + static class FieldSettersHolder { + static final Serialization.FieldSetter MAP_FIELD_SETTER = + Serialization.getFieldSetter(ImmutableMultimap.class, "map"); + static final Serialization.FieldSetter SIZE_FIELD_SETTER = + Serialization.getFieldSetter(ImmutableMultimap.class, "size"); + } + + ImmutableMultimap(ImmutableMap> map, int size) { + this.map = map; + this.size = size; + } + + // mutators (not supported) + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + // DoNotCall wants this to be final, but we want to override it to return more specific types. + // Inheritance is closed, and all subtypes are @DoNotCall, so this is safe to suppress. + @SuppressWarnings("DoNotCall") + public ImmutableCollection removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + // DoNotCall wants this to be final, but we want to override it to return more specific types. + // Inheritance is closed, and all subtypes are @DoNotCall, so this is safe to suppress. + @SuppressWarnings("DoNotCall") + public ImmutableCollection replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an immutable collection of the values for the given key. If no mappings in the multimap + * have the provided key, an empty immutable collection is returned. The values are in the same + * order as the parameters used to build this multimap. + */ + @Override + public abstract ImmutableCollection get(K key); + + /** + * Returns an immutable multimap which is the inverse of this one. For every key-value mapping in + * the original, the result will have a mapping with key and value reversed. + */ + public abstract ImmutableMultimap inverse(); + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Returns {@code true} if this immutable multimap's implementation contains references to + * user-created objects that aren't accessible via this multimap's methods. This is generally used + * to determine whether {@code copyOf} implementations should make an explicit copy to avoid + * memory leaks. + */ + boolean isPartialView() { + return map.isPartialView(); + } + + // accessors + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return value != null && super.containsValue(value); + } + + @Override + public int size() { + return size; + } + + // views + + /** + * Returns an immutable set of the distinct keys in this multimap, in the same order as they + * appear in this multimap. + */ + @Override + public ImmutableSet keySet() { + return map.keySet(); + } + + @Override + Set createKeySet() { + throw new AssertionError("unreachable"); + } + + /** + * Returns an immutable map that associates each key with its corresponding values in the + * multimap. Keys and values appear in the same order as in this multimap. + */ + @Override + @SuppressWarnings("unchecked") // a widening cast + public ImmutableMap> asMap() { + return (ImmutableMap) map; + } + + @Override + Map> createAsMap() { + throw new AssertionError("should never be called"); + } + + /** + * Returns an immutable collection of all key-value pairs in the multimap. + */ + @Override + public ImmutableCollection> entries() { + return (ImmutableCollection>) super.entries(); + } + + @Override + ImmutableCollection> createEntries() { + return new EntryCollection<>(this); + } + + private static class EntryCollection extends ImmutableCollection> { + final ImmutableMultimap multimap; + + EntryCollection(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + @Override + public UnmodifiableIterator> iterator() { + return multimap.entryIterator(); + } + + @Override + boolean isPartialView() { + return multimap.isPartialView(); + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + public boolean contains(Object object) { + if (object instanceof Entry entry) { + return multimap.containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + private static final long serialVersionUID = 0; + } + + @Override + UnmodifiableIterator> entryIterator() { + return new UnmodifiableIterator>() { + final Iterator>> asMapItr = + map.entrySet().iterator(); + K currentKey = null; + Iterator valueItr = emptyIterator(); + + @Override + public boolean hasNext() { + return valueItr.hasNext() || asMapItr.hasNext(); + } + + @Override + public Entry next() { + if (!valueItr.hasNext()) { + Entry> entry = asMapItr.next(); + currentKey = entry.getKey(); + valueItr = entry.getValue().iterator(); + } + /* + * requireNonNull is safe: The first call to this method always enters the !hasNext() case + * and populates currentKey, after which it's never cleared. + */ + return new ImmutableEntry<>(requireNonNull(currentKey), valueItr.next()); + } + }; + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.flatMap( + asMap().entrySet().spliterator(), + keyToValueCollectionEntry -> { + K key = keyToValueCollectionEntry.getKey(); + Collection valueCollection = keyToValueCollectionEntry.getValue(); + return CollectSpliterators.map( + valueCollection.spliterator(), (V value) -> Maps.immutableEntry(key, value)); + }, + Spliterator.SIZED | (this instanceof SetMultimap ? Spliterator.DISTINCT : 0), + size()); + } + + @Override + public void forEach(BiConsumer action) { + Objects.requireNonNull(action); + asMap() + .forEach( + (key, valueCollection) -> valueCollection.forEach(value -> action.accept(key, value))); + } + + /** + * Returns an immutable multiset containing all the keys in this multimap, in the same order and + * with the same frequencies as they appear in this multimap; to get only a single occurrence of + * each key, use {@link #keySet}. + */ + @Override + public ImmutableMultiset keys() { + return (ImmutableMultiset) super.keys(); + } + + @Override + ImmutableMultiset createKeys() { + return new Keys(); + } + + @SuppressWarnings("serial") // Uses writeReplace, not default serialization + class Keys extends ImmutableMultiset { + @Override + public boolean contains(Object object) { + return containsKey(object); + } + + @Override + public int count(Object element) { + Collection values = map.get(element); + return (values == null) ? 0 : values.size(); + } + + @Override + public ImmutableSet elementSet() { + return keySet(); + } + + @Override + public int size() { + return ImmutableMultimap.this.size(); + } + + @Override + ImmutableMultiset.Entry getEntry(int index) { + Entry> entry = map.entrySet().asList().get(index); + return Multisets.immutableEntry(entry.getKey(), entry.getValue().size()); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + Object writeReplace() { + return new KeysSerializedForm(ImmutableMultimap.this); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use KeysSerializedForm"); + } + } + + private static final class KeysSerializedForm implements Serializable { + final ImmutableMultimap multimap; + + KeysSerializedForm(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + Object readResolve() { + return multimap.keys(); + } + } + + /** + * Returns an immutable collection of the values in this multimap. Its iterator traverses the + * values for the first key, the values for the second key, and so on. + */ + @Override + public ImmutableCollection values() { + return (ImmutableCollection) super.values(); + } + + @Override + ImmutableCollection createValues() { + return new Values<>(this); + } + + @Override + UnmodifiableIterator valueIterator() { + return new UnmodifiableIterator() { + final Iterator> valueCollectionItr = map.values().iterator(); + Iterator valueItr = Iterators.emptyIterator(); + + @Override + public boolean hasNext() { + return valueItr.hasNext() || valueCollectionItr.hasNext(); + } + + @Override + public V next() { + if (!valueItr.hasNext()) { + valueItr = valueCollectionItr.next().iterator(); + } + return valueItr.next(); + } + }; + } + + private static final class Values extends ImmutableCollection { + private final transient ImmutableMultimap multimap; + + Values(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + @Override + public boolean contains(Object object) { + return multimap.containsValue(object); + } + + @Override + public UnmodifiableIterator iterator() { + return multimap.valueIterator(); + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + for (ImmutableCollection valueCollection : multimap.map.values()) { + offset = valueCollection.copyIntoArray(dst, offset); + } + return offset; + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + boolean isPartialView() { + return true; + } + + private static final long serialVersionUID = 0; + } + + private static final long serialVersionUID = 0; + + private static void checkEntryNotNull(Object key, Object value) { + if (key == null) { + throw new NullPointerException("null key in entry: null=" + value); + } else if (value == null) { + throw new NullPointerException("null value in entry: " + key + "=null"); + } + } + + private static UnmodifiableIterator emptyIterator() { + return emptyListIterator(); + } + + /** + * Returns the empty iterator. + * + *

The {@link Iterable} equivalent of this method is {@link ImmutableSet#of()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static UnmodifiableListIterator emptyListIterator() { + return (UnmodifiableListIterator) ImmutableCollection.ArrayItr.EMPTY; + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultiset.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultiset.java new file mode 100644 index 0000000..d43f8d2 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableMultiset.java @@ -0,0 +1,592 @@ +package org.xbib.datastructures.multi; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; +import org.xbib.datastructures.api.Multiset; +import static java.util.Objects.requireNonNull; + +/** + * A {@link Multiset} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Grouped iteration. In all current implementations, duplicate elements always appear + * consecutively when iterating. Elements iterate in order by the first appearance of that + * element when the multiset was created. + * + */ +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableMultiset extends ImmutableCollection + implements Multiset { + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableMultiset}. Elements iterate in order by the first appearance of that element in + * encounter order. + * + * @since 21.0 + */ + public static Collector> toImmutableMultiset() { + return CollectCollectors.toImmutableMultiset(Function.identity(), e -> 1); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableMultiset} whose + * elements are the result of applying {@code elementFunction} to the inputs, with counts equal to + * the result of applying {@code countFunction} to the inputs. + * + *

If the mapped elements contain duplicates (according to {@link Object#equals}), the first + * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of + * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. + * + * @since 22.0 + */ + public static + Collector> toImmutableMultiset( + Function elementFunction, + ToIntFunction countFunction) { + return CollectCollectors.toImmutableMultiset(elementFunction, countFunction); + } + + /** + * Returns the empty immutable multiset. + * + *

Performance note: the instance returned is a singleton. + */ + @SuppressWarnings("unchecked") // all supported methods are covariant + public static ImmutableMultiset of() { + return (ImmutableMultiset) RegularImmutableMultiset.EMPTY; + } + + /** + * Returns an immutable multiset containing a single element. + * + * @throws NullPointerException if {@code element} is null + * @since 6.0 (source-compatible since 2.0) + */ + public static ImmutableMultiset of(E element) { + return copyFromElements(element); + } + + /** + * Returns an immutable multiset containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + public static ImmutableMultiset of(E e1, E e2) { + return copyFromElements(e1, e2); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + public static ImmutableMultiset of(E e1, E e2, E e3) { + return copyFromElements(e1, e2, e3); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + public static ImmutableMultiset of(E e1, E e2, E e3, E e4) { + return copyFromElements(e1, e2, e3, e4); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5) { + return copyFromElements(e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { + return new Builder().add(e1).add(e2).add(e3).add(e4).add(e5).add(e6).add(others).build(); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 6.0 + */ + public static ImmutableMultiset copyOf(E[] elements) { + return copyFromElements(elements); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf(Iterable elements) { + if (elements instanceof ImmutableMultiset) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableMultiset result = (ImmutableMultiset) elements; + if (!result.isPartialView()) { + return result; + } + } + + Multiset multiset = + (elements instanceof Multiset) + ? Multisets.cast(elements) + : LinkedHashMultiset.create(elements); + + return copyFromEntries(multiset.entrySet()); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf(Iterator elements) { + Multiset multiset = LinkedHashMultiset.create(); + Iterators.addAll(multiset, elements); + return copyFromEntries(multiset.entrySet()); + } + + private static ImmutableMultiset copyFromElements(E... elements) { + Multiset multiset = LinkedHashMultiset.create(); + Collections.addAll(multiset, elements); + return copyFromEntries(multiset.entrySet()); + } + + static ImmutableMultiset copyFromEntries( + Collection> entries) { + if (entries.isEmpty()) { + return of(); + } else { + return RegularImmutableMultiset.create(entries); + } + } + + ImmutableMultiset() {} + + @Override + public UnmodifiableIterator iterator() { + final Iterator> entryIterator = entrySet().iterator(); + return new UnmodifiableIterator() { + int remaining; + E element; + + @Override + public boolean hasNext() { + return (remaining > 0) || entryIterator.hasNext(); + } + + @Override + public E next() { + if (remaining <= 0) { + Entry entry = entryIterator.next(); + element = entry.getElement(); + remaining = entry.getCount(); + } + remaining--; + /* + * requireNonNull is safe because `remaining` starts at 0, forcing us to initialize + * `element` above. After that, we never clear it. + */ + return requireNonNull(element); + } + }; + } + + private transient ImmutableList asList; + + @Override + public ImmutableList asList() { + ImmutableList result = asList; + return (result == null) ? asList = super.asList() : result; + } + + @Override + public boolean contains(Object object) { + return count(object) > 0; + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final int setCount(E element, int count) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final boolean setCount(E element, int oldCount, int newCount) { + throw new UnsupportedOperationException(); + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + for (Multiset.Entry entry : entrySet()) { + Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement()); + offset += entry.getCount(); + } + return offset; + } + + @Override + public boolean equals(Object object) { + return Multisets.equalsImpl(this, object); + } + + @Override + public int hashCode() { + return Sets.hashCodeImpl(entrySet()); + } + + @Override + public String toString() { + return entrySet().toString(); + } + + @Override + public abstract ImmutableSet elementSet(); + + private transient ImmutableSet> entrySet; + + @Override + public ImmutableSet> entrySet() { + ImmutableSet> es = entrySet; + return (es == null) ? (entrySet = createEntrySet()) : es; + } + + private ImmutableSet> createEntrySet() { + return isEmpty() ? ImmutableSet.>of() : new EntrySet(); + } + + abstract Entry getEntry(int index); + + private final class EntrySet extends IndexedImmutableSet> { + @Override + boolean isPartialView() { + return ImmutableMultiset.this.isPartialView(); + } + + @Override + Entry get(int index) { + return getEntry(index); + } + + @Override + public int size() { + return elementSet().size(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + if (entry.getCount() <= 0) { + return false; + } + int count = count(entry.getElement()); + return count == entry.getCount(); + } + return false; + } + + @Override + public int hashCode() { + return ImmutableMultiset.this.hashCode(); + } + + @Override + Object writeReplace() { + return new EntrySetSerializedForm(ImmutableMultiset.this); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use EntrySetSerializedForm"); + } + + private static final long serialVersionUID = 0; + } + + static class EntrySetSerializedForm implements Serializable { + final ImmutableMultiset multiset; + + EntrySetSerializedForm(ImmutableMultiset multiset) { + this.multiset = multiset; + } + + Object readResolve() { + return multiset.entrySet(); + } + } + + @Override + Object writeReplace() { + return new SerializedForm(this); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable multiset instances, especially {@code public static final} + * multisets ("constant multisets"). Example: + * + *

{@code
+     * public static final ImmutableMultiset BEANS =
+     *     new ImmutableMultiset.Builder()
+     *         .addCopies(Bean.COCOA, 4)
+     *         .addCopies(Bean.GARDEN, 6)
+     *         .addCopies(Bean.RED, 8)
+     *         .addCopies(Bean.BLACK_EYED, 10)
+     *         .build();
+     * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multisets in series. + * + * @since 2.0 + */ + public static class Builder extends ImmutableCollection.Builder { + final Multiset contents; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableMultiset#builder}. + */ + public Builder() { + this(LinkedHashMultiset.create()); + } + + Builder(Multiset contents) { + this.contents = contents; + } + + /** + * Adds {@code element} to the {@code ImmutableMultiset}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override + public Builder add(E element) { + contents.add(Objects.requireNonNull(element)); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds a number of occurrences of an element to this {@code ImmutableMultiset}. + * + * @param element the element to add + * @param occurrences the number of occurrences of the element to add. May be zero, in which + * case no change will be made. + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation + * would result in more than {@link Integer#MAX_VALUE} occurrences of the element + */ + public Builder addCopies(E element, int occurrences) { + contents.add(Objects.requireNonNull(element), occurrences); + return this; + } + + /** + * Adds or removes the necessary occurrences of an element such that the element attains the + * desired count. + * + * @param element the element to add or remove occurrences of + * @param count the desired count of the element in this multiset + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code count} is negative + */ + public Builder setCount(E element, int count) { + contents.setCount(Objects.requireNonNull(element), count); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder addAll(Iterable elements) { + if (elements instanceof Multiset) { + Multiset multiset = Multisets.cast(elements); + multiset.forEachEntry((e, n) -> contents.add(Objects.requireNonNull(e), n)); + } else { + super.addAll(elements); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add to the {@code ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableMultiset} based on the contents of the {@code + * Builder}. + */ + @Override + public ImmutableMultiset build() { + return copyOf(contents); + } + + ImmutableMultiset buildJdkBacked() { + if (contents.isEmpty()) { + return of(); + } + return JdkBackedImmutableMultiset.create(contents.entrySet()); + } + } + + static final class ElementSet extends ImmutableSet.Indexed { + private final List> entries; + // TODO(cpovirk): @Weak? + private final Multiset delegate; + + ElementSet(List> entries, Multiset delegate) { + this.entries = entries; + this.delegate = delegate; + } + + @Override + E get(int index) { + return entries.get(index).getElement(); + } + + @Override + public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + public int size() { + return entries.size(); + } + } + + static final class SerializedForm implements Serializable { + final Object[] elements; + final int[] counts; + + // "extends Object" works around https://github.com/typetools/checker-framework/issues/3013 + SerializedForm(Multiset multiset) { + int distinct = multiset.entrySet().size(); + elements = new Object[distinct]; + counts = new int[distinct]; + int i = 0; + for (Entry entry : multiset.entrySet()) { + elements[i] = entry.getElement(); + counts[i] = entry.getCount(); + i++; + } + } + + Object readResolve() { + LinkedHashMultiset multiset = LinkedHashMultiset.create(elements.length); + for (int i = 0; i < elements.length; i++) { + multiset.add(elements[i], counts[i]); + } + return ImmutableMultiset.copyOf(multiset); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableSetMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableSetMultimap.java new file mode 100644 index 0000000..7cfce74 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImmutableSetMultimap.java @@ -0,0 +1,619 @@ +package org.xbib.datastructures.multi; + +//import com.google.common.base.MoreObjects; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.SetMultimap; +import org.xbib.datastructures.immutable.order.Ordering; + +/** + * A {@link SetMultimap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: As in all {@link SetMultimap}s, do not modify either a key or a value + * of a {@code ImmutableSetMultimap} in a way that affects its {@link Object#equals} behavior. + * Undefined behavior and bugs will result. + * + *

See the Guava User Guide article on immutable collections. + */ +public class ImmutableSetMultimap extends ImmutableMultimap + implements SetMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSetMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

Example: + * + *

{@code
+     * static final Multimap FIRST_LETTER_MULTIMAP =
+     *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+     *         .collect(toImmutableSetMultimap(str -> str.charAt(0), str -> str.substring(1)));
+     *
+     * // is equivalent to
+     *
+     * static final Multimap FIRST_LETTER_MULTIMAP =
+     *     new ImmutableSetMultimap.Builder()
+     *         .put('b', "anana")
+     *         .putAll('a', "pple", "sparagus")
+     *         .putAll('c', "arrot", "herry")
+     *         .build();
+     * }
+ */ + public static + Collector> toImmutableSetMultimap( + Function keyFunction, + Function valueFunction) { + Objects.requireNonNull(keyFunction, "keyFunction"); + Objects.requireNonNull(valueFunction, "valueFunction"); + return Collector.of( + ImmutableSetMultimap::builder, + (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), + Builder::combine, + Builder::build); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableSetMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

Example: + * + *

{@code
+     * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+     *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+     *         .collect(
+     *             flatteningToImmutableSetMultimap(
+     *                  str -> str.charAt(0),
+     *                  str -> str.substring(1).chars().mapToObj(c -> (char) c));
+     *
+     * // is equivalent to
+     *
+     * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+     *     ImmutableSetMultimap.builder()
+     *         .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
+     *         .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
+     *         .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
+     *         .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
+     *         .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
+     *         .build();
+     *
+     * // after deduplication, the resulting multimap is equivalent to
+     *
+     * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+     *     ImmutableSetMultimap.builder()
+     *         .putAll('b', Arrays.asList('a', 'n'))
+     *         .putAll('a', Arrays.asList('p', 'l', 'e', 's', 'a', 'r', 'g', 'u'))
+     *         .putAll('c', Arrays.asList('a', 'r', 'o', 't', 'h', 'e', 'y'))
+     *         .build();
+     * }
+     * }
+ * + */ + public static + Collector> flatteningToImmutableSetMultimap( + Function keyFunction, + Function> valuesFunction) { + Objects.requireNonNull(keyFunction); + Objects.requireNonNull(valuesFunction); + return Collectors.collectingAndThen( + flatteningToMultimap( + input -> Objects.requireNonNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(Objects::requireNonNull), + MultimapBuilder.linkedHashKeys().linkedHashSetValues()::build), + ImmutableSetMultimap::copyOf); + } + + private static < + T extends Object, + K extends Object, + V extends Object, + M extends Multimap> + Collector flatteningToMultimap( + Function keyFunction, + Function> valueFunction, + Supplier multimapSupplier) { + Objects.requireNonNull(keyFunction); + Objects.requireNonNull(valueFunction); + Objects.requireNonNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> { + K key = keyFunction.apply(input); + Collection valuesForKey = multimap.get(key); + valueFunction.apply(input).forEachOrdered(valuesForKey::add); + }, + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); + } + + /** + * Returns the empty multimap. + * + *

Performance note: the instance returned is a singleton. + */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableSetMultimap of() { + return (ImmutableSetMultimap) EmptyImmutableSetMultimap.INSTANCE; + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableSetMultimap of(K k1, V v1) { + Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of(K k1, V v1, K k2, V v2) { + Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of(K k1, V v1, K k2, V v2, K k3, V v3) { + Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new {@link Builder}. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * A builder for creating immutable {@code SetMultimap} instances, especially {@code public static + * final} multimaps ("constant multimaps"). Example: + * + *

{@code
+     * static final Multimap STRING_TO_INTEGER_MULTIMAP =
+     *     new ImmutableSetMultimap.Builder()
+     *         .put("one", 1)
+     *         .putAll("several", 1, 2, 3)
+     *         .putAll("many", 1, 2, 3, 4, 5)
+     *         .build();
+     * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multimaps in series. Each multimap contains the key-value mappings in the previously + * created multimaps. + * + * @since 2.0 + */ + public static final class Builder extends ImmutableMultimap.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableSetMultimap#builder}. + */ + public Builder() { + super(); + } + + @Override + Collection newMutableValueCollection() { + return Platform.preservesInsertionOrderOnAddsSet(); + } + + /** + * Adds a key-value mapping to the built multimap if it is not already present. + */ + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Adds an entry to the built multimap if it is not already present. + */ + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + @Override + public Builder putAll(K key, Iterable values) { + super.putAll(key, values); + return this; + } + + @Override + public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + @Override + public Builder putAll(Multimap multimap) { + for (Entry> entry : + multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + @Override + Builder combine(ImmutableMultimap.Builder other) { + super.combine(other); + return this; + } + + @Override + public Builder orderKeysBy(Comparator keyComparator) { + super.orderKeysBy(keyComparator); + return this; + } + + /** + * Specifies the ordering of the generated multimap's values for each key. + * + *

If this method is called, the sets returned by the {@code get()} method of the generated + * multimap and its {@link Multimap#asMap()} view are {@link ImmutableSortedSet} instances. + * However, serialization does not preserve that property, though it does maintain the key and + * value ordering. + */ + @Override + public Builder orderValuesBy(Comparator valueComparator) { + super.orderValuesBy(valueComparator); + return this; + } + + /** + * Returns a newly-created immutable set multimap. + */ + @Override + public ImmutableSetMultimap build() { + Collection>> mapEntries = builderMap.entrySet(); + if (keyComparator != null) { + mapEntries = Ordering.from(keyComparator).onKeys().immutableSortedCopy(mapEntries); + } + return fromMapEntries(mapEntries, valueComparator); + } + } + + /** + * Returns an immutable set multimap containing the same mappings as {@code multimap}. The + * generated multimap's key and value orderings correspond to the iteration ordering of the {@code + * multimap.asMap()} view. Repeated occurrences of an entry in the multimap after the first are + * ignored. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is null + */ + public static ImmutableSetMultimap copyOf( + Multimap multimap) { + return copyOf(multimap, null); + } + + private static ImmutableSetMultimap copyOf( + Multimap multimap, + Comparator valueComparator) { + checkNotNull(multimap); // eager for GWT + if (multimap.isEmpty() && valueComparator == null) { + return of(); + } + + if (multimap instanceof ImmutableSetMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableSetMultimap kvMultimap = (ImmutableSetMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + + return fromMapEntries(multimap.asMap().entrySet(), valueComparator); + } + + /** + * Returns an immutable multimap containing the specified entries. The returned multimap iterates + * over keys in the order they were first encountered in the input, and the values for each key + * are iterated in the order they were encountered. If two values for the same key are {@linkplain + * Object#equals equal}, the first value encountered is used. + * + * @throws NullPointerException if any key, value, or entry is null + */ + public static ImmutableSetMultimap copyOf( + Iterable> entries) { + return new Builder().putAll(entries).build(); + } + + /** + * Creates an ImmutableSetMultimap from an asMap.entrySet. + */ + static ImmutableSetMultimap fromMapEntries( + Collection>> mapEntries, + Comparator valueComparator) { + if (mapEntries.isEmpty()) { + return of(); + } + ImmutableMap.Builder> builder = + new ImmutableMap.Builder<>(mapEntries.size()); + int size = 0; + + for (Entry> entry : mapEntries) { + K key = entry.getKey(); + Collection values = entry.getValue(); + ImmutableSet set = valueSet(valueComparator, values); + if (!set.isEmpty()) { + builder.put(key, set); + size += set.size(); + } + } + + return new ImmutableSetMultimap<>(builder.buildOrThrow(), size, valueComparator); + } + + /** + * Returned by get() when a missing key is provided. Also holds the comparator, if any, used for + * values. + */ + private final transient ImmutableSet emptySet; + + ImmutableSetMultimap( + ImmutableMap> map, + int size, + Comparator valueComparator) { + super(map, size); + this.emptySet = emptySet(valueComparator); + } + + // views + + /** + * Returns an immutable set of the values for the given key. If no mappings in the multimap have + * the provided key, an empty immutable set is returned. The values are in the same order as the + * parameters used to build this multimap. + */ + @Override + public ImmutableSet get(K key) { + // This cast is safe as its type is known in constructor. + ImmutableSet set = (ImmutableSet) map.get(key); + return MoreObjects.firstNonNull(set, emptySet); + } + + private transient ImmutableSetMultimap inverse; + + /** + * {@inheritDoc} + * + *

Because an inverse of a set multimap cannot contain multiple pairs with the same key and + * value, this method returns an {@code ImmutableSetMultimap} rather than the {@code + * ImmutableMultimap} specified in the {@code ImmutableMultimap} class. + */ + @Override + public ImmutableSetMultimap inverse() { + ImmutableSetMultimap result = inverse; + return (result == null) ? (inverse = invert()) : result; + } + + private ImmutableSetMultimap invert() { + Builder builder = builder(); + for (Entry entry : entries()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableSetMultimap invertedMultimap = builder.build(); + invertedMultimap.inverse = this; + return invertedMultimap; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final ImmutableSet removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override + public final ImmutableSet replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private transient ImmutableSet> entries; + + /** + * Returns an immutable collection of all key-value pairs in the multimap. Its iterator traverses + * the values for the first key, the values for the second key, and so on. + */ + @Override + public ImmutableSet> entries() { + ImmutableSet> result = entries; + return result == null ? (entries = new EntrySet<>(this)) : result; + } + + private static final class EntrySet extends ImmutableSet> { + private final transient ImmutableSetMultimap multimap; + + EntrySet(ImmutableSetMultimap multimap) { + this.multimap = multimap; + } + + @Override + public boolean contains(Object object) { + if (object instanceof Entry entry) { + return multimap.containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + public UnmodifiableIterator> iterator() { + return multimap.entryIterator(); + } + + @Override + boolean isPartialView() { + return false; + } + } + + private static ImmutableSet valueSet(Comparator valueComparator, Collection values) { + return (valueComparator == null) + ? ImmutableSet.copyOf(values) + : ImmutableSortedSet.copyOf(valueComparator, values); + } + + private static ImmutableSet emptySet(Comparator valueComparator) { + return (valueComparator == null) + ? ImmutableSet.of() + : ImmutableSortedSet.emptySet(valueComparator); + } + + private static ImmutableSet.Builder valuesBuilder(Comparator valueComparator) { + return (valueComparator == null) + ? new ImmutableSet.Builder() + : new ImmutableSortedSet.Builder(valueComparator); + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(valueComparator()); + Serialization.writeMultimap(this, stream); + } + + Comparator valueComparator() { + return emptySet instanceof ImmutableSortedSet + ? ((ImmutableSortedSet) emptySet).comparator() + : null; + } + + private static final class SetFieldSettersHolder { + static final Serialization.FieldSetter EMPTY_SET_FIELD_SETTER = + Serialization.getFieldSetter(ImmutableSetMultimap.class, "emptySet"); + } + + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + Comparator valueComparator = (Comparator) stream.readObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + ImmutableSet.Builder valuesBuilder = valuesBuilder(valueComparator); + for (int j = 0; j < valueCount; j++) { + valuesBuilder.add(stream.readObject()); + } + ImmutableSet valueSet = valuesBuilder.build(); + if (valueSet.size() != valueCount) { + throw new InvalidObjectException("Duplicate key-value pairs exist for key " + key); + } + builder.put(key, valueSet); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.buildOrThrow(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) new InvalidObjectException(e.getMessage(), e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + SetFieldSettersHolder.EMPTY_SET_FIELD_SETTER.set(this, emptySet(valueComparator)); + } + + private static final long serialVersionUID = 0; +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImprovedAbstractSet.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImprovedAbstractSet.java new file mode 100644 index 0000000..b94a9e8 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/ImprovedAbstractSet.java @@ -0,0 +1,63 @@ +package org.xbib.datastructures.multi; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import org.xbib.datastructures.api.Multiset; + +/** + * {@link AbstractSet} substitute without the potentially-quadratic {@code removeAll} + * implementation. + */ +abstract class ImprovedAbstractSet extends AbstractSet { + @Override + public boolean removeAll(Collection c) { + return removeAllImpl(this, c); + } + + @Override + public boolean retainAll(Collection c) { + return super.retainAll(Objects.requireNonNull(c)); + } + + private static boolean removeAllImpl(Set set, Collection collection) { + Objects.requireNonNull(collection); // for GWT + if (collection instanceof Multiset) { + collection = ((Multiset) collection).elementSet(); + } + /* + * AbstractSet.removeAll(List) has quadratic behavior if the list size + * is just more than the set's size. We augment the test by + * assuming that sets have fast contains() performance, and other + * collections don't. + */ + if (collection instanceof Set && collection.size() > set.size()) { + return removeAll(set.iterator(), collection); + } else { + return removeAllImpl(set, collection.iterator()); + } + } + + /** Remove each element in an iterable from a set. */ + private static boolean removeAllImpl(Set set, Iterator iterator) { + boolean changed = false; + while (iterator.hasNext()) { + changed |= set.remove(iterator.next()); + } + return changed; + } + + private static boolean removeAll(Iterator removeFrom, Collection elementsToRemove) { + Objects.requireNonNull(elementsToRemove); + boolean result = false; + while (removeFrom.hasNext()) { + if (elementsToRemove.contains(removeFrom.next())) { + removeFrom.remove(); + result = true; + } + } + return result; + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/JdkBackedImmutableMultiset.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/JdkBackedImmutableMultiset.java new file mode 100644 index 0000000..6c2730f --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/JdkBackedImmutableMultiset.java @@ -0,0 +1,80 @@ +package org.xbib.datastructures.multi; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * An implementation of ImmutableMultiset backed by a JDK Map and a list of entries. Used to protect + * against hash flooding attacks. + */ +final class JdkBackedImmutableMultiset extends ImmutableMultiset { + private final Map delegateMap; + private final ImmutableList> entries; + private final long size; + + static ImmutableMultiset create(Collection> entries) { + @SuppressWarnings("unchecked") + Entry[] entriesArray = entries.toArray(new Entry[0]); + Map delegateMap = new HashMap<>(entriesArray.length); + long size = 0; + for (int i = 0; i < entriesArray.length; i++) { + Entry entry = entriesArray[i]; + int count = entry.getCount(); + size += count; + E element = Objects.requireNonNull(entry.getElement()); + delegateMap.put(element, count); + if (!(entry instanceof MultisetsImmutableEntry)) { + entriesArray[i] = new MultisetsImmutableEntry<>(element, count); + } + } + return new JdkBackedImmutableMultiset<>( + delegateMap, ImmutableList.asImmutableList(entriesArray), size); + } + + private JdkBackedImmutableMultiset( + Map delegateMap, ImmutableList> entries, long size) { + this.delegateMap = delegateMap; + this.entries = entries; + this.size = size; + } + + @Override + public int count(Object element) { + return delegateMap.getOrDefault(element, 0); + } + + private transient ImmutableSet elementSet; + + @Override + public ImmutableSet elementSet() { + ImmutableSet result = elementSet; + return (result == null) ? elementSet = new ElementSet<>(entries, this) : result; + } + + @Override + Entry getEntry(int index) { + return entries.get(index); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return saturatedCast(size); + } + + private static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultimap.java new file mode 100644 index 0000000..0be503f --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultimap.java @@ -0,0 +1,567 @@ +package org.xbib.datastructures.multi; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.immutable.ImmutableEntry; + +/** + * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that + * returns collections whose iterators follow the ordering in which the data was added to the + * multimap. + * + *

The collections returned by {@code keySet}, {@code keys}, and {@code asMap} iterate through + * the keys in the order they were first added to the multimap. Similarly, {@code get}, {@code + * removeAll}, and {@code replaceValues} return collections that iterate through the values in the + * order they were added. The collections generated by {@code entries} and {@code values} iterate + * across the key-value mappings in the order they were added to the multimap. + * + *

The iteration ordering of the collections generated by {@code keySet}, {@code keys}, and + * {@code asMap} has a few subtleties. As long as the set of keys remains unchanged, adding or + * removing mappings does not affect the key iteration order. However, if you remove all values + * associated with a key and then add the key back to the multimap, that key will come last in the + * key iteration order. + * + *

The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an + * existing key-value pair has no effect. + * + *

Keys and values may be null. All optional multimap methods are supported, and all returned + * views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the multimap. Concurrent + * read operations will work correctly. To allow concurrent update operations, wrap your multimap + * with a call to {@link Multimaps#synchronizedSetMultimap}. + * + *

Warning: Do not modify either a key or a value of a {@code LinkedHashMultimap} + * in a way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will + * result. + * + */ +public final class LinkedHashMultimap + extends AbstractSetMultimap { + + /** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */ + public static + LinkedHashMultimap create() { + return new LinkedHashMultimap<>(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY); + } + + /** + * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold the specified + * numbers of keys and values without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is + * negative + */ + public static + LinkedHashMultimap create(int expectedKeys, int expectedValuesPerKey) { + return new LinkedHashMultimap<>( + Maps.capacity(expectedKeys), Maps.capacity(expectedValuesPerKey)); + } + + /** + * Constructs a {@code LinkedHashMultimap} with the same mappings as the specified multimap. If a + * key-value mapping appears multiple times in the input multimap, it only appears once in the + * constructed multimap. The new multimap has the same {@link Multimap#entries()} iteration order + * as the input multimap, except for excluding duplicate mappings. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static + LinkedHashMultimap create(Multimap multimap) { + LinkedHashMultimap result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY); + result.putAll(multimap); + return result; + } + + private interface ValueSetLink { + ValueSetLink getPredecessorInValueSet(); + + ValueSetLink getSuccessorInValueSet(); + + void setPredecessorInValueSet(ValueSetLink entry); + + void setSuccessorInValueSet(ValueSetLink entry); + } + + private static void succeedsInValueSet( + ValueSetLink pred, ValueSetLink succ) { + pred.setSuccessorInValueSet(succ); + succ.setPredecessorInValueSet(pred); + } + + private static void succeedsInMultimap( + ValueEntry pred, ValueEntry succ) { + pred.setSuccessorInMultimap(succ); + succ.setPredecessorInMultimap(pred); + } + + private static void deleteFromValueSet( + ValueSetLink entry) { + succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet()); + } + + private static void deleteFromMultimap( + ValueEntry entry) { + succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap()); + } + + /** + * LinkedHashMultimap entries are in no less than three coexisting linked lists: a bucket in the + * hash table for a {@code Set} associated with a key, the linked list of insertion-ordered + * entries in that {@code Set}, and the linked list of entries in the LinkedHashMultimap as a + * whole. + */ + static final class ValueEntry + extends ImmutableEntry implements ValueSetLink { + final int smearedValueHash; + + ValueEntry nextInValueBucket; + /* + * The *InValueSet and *InMultimap fields below are null after construction, but we almost + * always call succeedsIn*() to initialize them immediately thereafter. + * + * The exception is the *InValueSet fields of multimapHeaderEntry, which are never set. (That + * works out fine as long as we continue to be careful not to try to delete them or iterate + * past them.) + * + * We could consider "lying" and omitting @CheckNotNull from all these fields. Normally, I'm not + * a fan of that: What if we someday implement (presumably to be enabled during tests only) + * bytecode rewriting that checks for any null value that passes through an API with a + * known-non-null type? But that particular problem might not arise here, since we're not + * actually reading from the fields in any case in which they might be null (as proven by the + * requireNonNull checks below). Plus, we're *already* lying here, since newHeader passes a null + * key and value, which we pass to the superconstructor, even though the key and value type for + * a given entry might not include null. The right fix for the header problems is probably to + * define a separate MultimapLink interface with a separate "header" implementation, which + * hopefully could avoid implementing Entry or ValueSetLink at all. (But note that that approach + * requires us to define extra classes -- unfortunate under Android.) *Then* we could consider + * lying about the fields below on the grounds that we always initialize them just after the + * constructor -- an example of the kind of lying that our hypothetical bytecode rewriter would + * already have to deal with, thanks to DI frameworks that perform field and method injection, + * frameworks like Android that define post-construct hooks like Activity.onCreate, etc. + */ + + ValueSetLink predecessorInValueSet; + ValueSetLink successorInValueSet; + + ValueEntry predecessorInMultimap; + ValueEntry successorInMultimap; + + ValueEntry(K key, V value, + int smearedValueHash, + ValueEntry nextInValueBucket) { + super(key, value); + this.smearedValueHash = smearedValueHash; + this.nextInValueBucket = nextInValueBucket; + } + + @SuppressWarnings("nullness") // see the comment on the class fields, especially about newHeader + static ValueEntry newHeader() { + return new ValueEntry<>(null, null, 0, null); + } + + boolean matchesValue(Object v, int smearedVHash) { + return smearedValueHash == smearedVHash && Objects.equal(getValue(), v); + } + + @Override + public ValueSetLink getPredecessorInValueSet() { + return requireNonNull(predecessorInValueSet); // see the comment on the class fields + } + + @Override + public ValueSetLink getSuccessorInValueSet() { + return requireNonNull(successorInValueSet); // see the comment on the class fields + } + + @Override + public void setPredecessorInValueSet(ValueSetLink entry) { + predecessorInValueSet = entry; + } + + @Override + public void setSuccessorInValueSet(ValueSetLink entry) { + successorInValueSet = entry; + } + + public ValueEntry getPredecessorInMultimap() { + return requireNonNull(predecessorInMultimap); // see the comment on the class fields + } + + public ValueEntry getSuccessorInMultimap() { + return requireNonNull(successorInMultimap); // see the comment on the class fields + } + + public void setSuccessorInMultimap(ValueEntry multimapSuccessor) { + this.successorInMultimap = multimapSuccessor; + } + + public void setPredecessorInMultimap(ValueEntry multimapPredecessor) { + this.predecessorInMultimap = multimapPredecessor; + } + } + + private static final int DEFAULT_KEY_CAPACITY = 16; + private static final int DEFAULT_VALUE_SET_CAPACITY = 2; + static final double VALUE_SET_LOAD_FACTOR = 1.0; + + transient int valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; + private transient ValueEntry multimapHeaderEntry; + + private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) { + super(Platform.>newLinkedHashMapWithExpectedSize(keyCapacity)); + checkNonnegative(valueSetCapacity, "expectedValuesPerKey"); + + this.valueSetCapacity = valueSetCapacity; + this.multimapHeaderEntry = ValueEntry.newHeader(); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code LinkedHashSet} for a collection of values for one key. + * + * @return a new {@code LinkedHashSet} containing a collection of values for one key + */ + @Override + Set createCollection() { + return Platform.newLinkedHashSetWithExpectedSize(valueSetCapacity); + } + + /** + * {@inheritDoc} + * + *

Creates a decorated insertion-ordered set that also keeps track of the order in which + * key-value pairs are added to the multimap. + * + * @param key key to associate with values in the collection + * @return a new decorated set containing a collection of values for one key + */ + @Override + Collection createCollection(K key) { + return new ValueSet(key, valueSetCapacity); + } + + /** + * {@inheritDoc} + * + *

If {@code values} is not empty and the multimap already contains a mapping for {@code key}, + * the {@code keySet()} ordering is unchanged. However, the provided values always come last in + * the {@link #entries()} and {@link #values()} iteration orderings. + */ + @Override + public Set replaceValues(K key, Iterable values) { + return super.replaceValues(key, values); + } + + /** + * Returns a set of all key-value pairs. Changes to the returned set will update the underlying + * multimap, and vice versa. The entries set does not support the {@code add} or {@code addAll} + * operations. + * + *

The iterator generated by the returned set traverses the entries in the order they were + * added to the multimap. + * + *

Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the + * time the entry is returned by a method call to the collection or its iterator. + */ + @Override + public Set> entries() { + return super.entries(); + } + + /** + * Returns a view collection of all distinct keys contained in this multimap. Note that the + * key set contains a key if and only if this multimap maps that key to at least one value. + * + *

The iterator generated by the returned set traverses the keys in the order they were first + * added to the multimap. + * + *

Changes to the returned set will update the underlying multimap, and vice versa. However, + * adding to the returned set is not possible. + */ + @Override + public Set keySet() { + return super.keySet(); + } + + /** + * Returns a collection of all values in the multimap. Changes to the returned collection will + * update the underlying multimap, and vice versa. + * + *

The iterator generated by the returned collection traverses the values in the order they + * were added to the multimap. + */ + @Override + public Collection values() { + return super.values(); + } + + final class ValueSet extends ImprovedAbstractSet implements ValueSetLink { + /* + * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory + * consumption. + */ + + private final K key; + ValueEntry[] hashTable; + private int size = 0; + private int modCount = 0; + + // We use the set object itself as the end of the linked list, avoiding an unnecessary + // entry object per key. + private ValueSetLink firstEntry; + private ValueSetLink lastEntry; + + ValueSet(K key, int expectedValues) { + this.key = key; + this.firstEntry = this; + this.lastEntry = this; + // Round expected values up to a power of 2 to get the table size. + int tableSize = Hashing.closedTableSize(expectedValues, VALUE_SET_LOAD_FACTOR); + + @SuppressWarnings({"rawtypes", "unchecked"}) + ValueEntry[] hashTable = new ValueEntry[tableSize]; + this.hashTable = hashTable; + } + + private int mask() { + return hashTable.length - 1; + } + + @Override + public ValueSetLink getPredecessorInValueSet() { + return lastEntry; + } + + @Override + public ValueSetLink getSuccessorInValueSet() { + return firstEntry; + } + + @Override + public void setPredecessorInValueSet(ValueSetLink entry) { + lastEntry = entry; + } + + @Override + public void setSuccessorInValueSet(ValueSetLink entry) { + firstEntry = entry; + } + + @Override + public Iterator iterator() { + return new Iterator() { + ValueSetLink nextEntry = firstEntry; + ValueEntry toRemove; + int expectedModCount = modCount; + + private void checkForComodification() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + checkForComodification(); + return nextEntry != ValueSet.this; + } + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + ValueEntry entry = (ValueEntry) nextEntry; + V result = entry.getValue(); + toRemove = entry; + nextEntry = entry.getSuccessorInValueSet(); + return result; + } + + @Override + public void remove() { + checkForComodification(); + checkState(toRemove != null, "no calls to next() since the last call to remove()"); + ValueSet.this.remove(toRemove.getValue()); + expectedModCount = modCount; + toRemove = null; + } + }; + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + for (ValueSetLink entry = firstEntry; + entry != ValueSet.this; + entry = entry.getSuccessorInValueSet()) { + action.accept(((ValueEntry) entry).getValue()); + } + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + int smearedHash = Hashing.smearedHash(o); + for (ValueEntry entry = hashTable[smearedHash & mask()]; + entry != null; + entry = entry.nextInValueBucket) { + if (entry.matchesValue(o, smearedHash)) { + return true; + } + } + return false; + } + + @Override + public boolean add(V value) { + int smearedHash = Hashing.smearedHash(value); + int bucket = smearedHash & mask(); + ValueEntry rowHead = hashTable[bucket]; + for (ValueEntry entry = rowHead; entry != null; entry = entry.nextInValueBucket) { + if (entry.matchesValue(value, smearedHash)) { + return false; + } + } + + ValueEntry newEntry = new ValueEntry<>(key, value, smearedHash, rowHead); + succeedsInValueSet(lastEntry, newEntry); + succeedsInValueSet(newEntry, this); + succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry); + succeedsInMultimap(newEntry, multimapHeaderEntry); + hashTable[bucket] = newEntry; + size++; + modCount++; + rehashIfNecessary(); + return true; + } + + private void rehashIfNecessary() { + if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) { + @SuppressWarnings("unchecked") + ValueEntry[] hashTable = new ValueEntry[this.hashTable.length * 2]; + this.hashTable = hashTable; + int mask = hashTable.length - 1; + for (ValueSetLink entry = firstEntry; + entry != this; + entry = entry.getSuccessorInValueSet()) { + ValueEntry valueEntry = (ValueEntry) entry; + int bucket = valueEntry.smearedValueHash & mask; + valueEntry.nextInValueBucket = hashTable[bucket]; + hashTable[bucket] = valueEntry; + } + } + } + + @Override + public boolean remove(Object o) { + int smearedHash = Hashing.smearedHash(o); + int bucket = smearedHash & mask(); + ValueEntry prev = null; + for (ValueEntry entry = hashTable[bucket]; + entry != null; + prev = entry, entry = entry.nextInValueBucket) { + if (entry.matchesValue(o, smearedHash)) { + if (prev == null) { + // first entry in the bucket + hashTable[bucket] = entry.nextInValueBucket; + } else { + prev.nextInValueBucket = entry.nextInValueBucket; + } + deleteFromValueSet(entry); + deleteFromMultimap(entry); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public void clear() { + Arrays.fill(hashTable, null); + size = 0; + for (ValueSetLink entry = firstEntry; + entry != this; + entry = entry.getSuccessorInValueSet()) { + ValueEntry valueEntry = (ValueEntry) entry; + deleteFromMultimap(valueEntry); + } + succeedsInValueSet(this, this); + modCount++; + } + } + + @Override + Iterator> entryIterator() { + return new Iterator>() { + ValueEntry nextEntry = multimapHeaderEntry.getSuccessorInMultimap(); + ValueEntry toRemove; + + @Override + public boolean hasNext() { + return nextEntry != multimapHeaderEntry; + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + ValueEntry result = nextEntry; + toRemove = result; + nextEntry = nextEntry.getSuccessorInMultimap(); + return result; + } + + @Override + public void remove() { + checkState(toRemove != null, "no calls to next() since the last call to remove()"); + LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue()); + toRemove = null; + } + }; + } + + @Override + Spliterator> entrySpliterator() { + return Spliterators.spliterator(entries(), Spliterator.DISTINCT | Spliterator.ORDERED); + } + + @Override + Iterator valueIterator() { + return Maps.valueIterator(entryIterator()); + } + + @Override + Spliterator valueSpliterator() { + return CollectSpliterators.map(entrySpliterator(), Entry::getValue); + } + + @Override + public void clear() { + super.clear(); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultiset.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultiset.java new file mode 100644 index 0000000..fe82a65 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedHashMultiset.java @@ -0,0 +1,53 @@ +package org.xbib.datastructures.multi; + +import org.xbib.datastructures.api.Multiset; + +/** + * A {@code Multiset} implementation with predictable iteration order. Its iterator orders elements + * according to when the first occurrence of the element was added. When the multiset contains + * multiple instances of an element, those instances are consecutive in the iteration order. If all + * occurrences of an element are removed, after which that element is added to the multiset, the + * element will appear at the end of the iteration. + * + */ +public final class LinkedHashMultiset + extends AbstractMapBasedMultiset { + + /** Creates a new, empty {@code LinkedHashMultiset} using the default initial capacity. */ + public static LinkedHashMultiset create() { + return new LinkedHashMultiset(); + } + + /** + * Creates a new, empty {@code LinkedHashMultiset} with the specified expected number of distinct + * elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static LinkedHashMultiset create(int distinctElements) { + return new LinkedHashMultiset(distinctElements); + } + + /** + * Creates a new {@code LinkedHashMultiset} containing the specified elements. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + */ + public static LinkedHashMultiset create( + Iterable elements) { + LinkedHashMultiset multiset = create(Multisets.inferDistinctElements(elements)); + Iterables.addAll(multiset, elements); + return multiset; + } + + private LinkedHashMultiset() { + super(new LinkedHashMap()); + } + + private LinkedHashMultiset(int distinctElements) { + super(Maps.newLinkedHashMapWithExpectedSize(distinctElements)); + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedListMultimap.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedListMultimap.java new file mode 100644 index 0000000..7f10b32 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/LinkedListMultimap.java @@ -0,0 +1,866 @@ +package org.xbib.datastructures.multi; + +import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSequentialList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.Consumer; + +/** + * An implementation of {@code ListMultimap} that supports deterministic iteration order for both + * keys and values. The iteration order is preserved across non-distinct key values. For example, + * for the following multimap definition: + * + *

{@code
+ * Multimap multimap = LinkedListMultimap.create();
+ * multimap.put(key1, foo);
+ * multimap.put(key2, bar);
+ * multimap.put(key1, baz);
+ * }
+ * + * ... the iteration order for {@link #keys()} is {@code [key1, key2, key1]}, and similarly for + * {@link #entries()}. Unlike {@link LinkedHashMultimap}, the iteration order is kept consistent + * between keys, entries and values. For example, calling: + * + *
{@code
+ * multimap.remove(key1, foo);
+ * }
+ * + *

changes the entries iteration order to {@code [key2=bar, key1=baz]} and the key iteration + * order to {@code [key2, key1]}. The {@link #entries()} iterator returns mutable map entries, and + * {@link #replaceValues} attempts to preserve iteration order as much as possible. + * + *

The collections returned by {@link #keySet()} and {@link #asMap} iterate through the keys in + * the order they were first added to the multimap. Similarly, {@link #get}, {@link #removeAll}, and + * {@link #replaceValues} return collections that iterate through the values in the order they were + * added. The collections generated by {@link #entries()}, {@link #keys()}, and {@link #values} + * iterate across the key-value mappings in the order they were added to the multimap. + * + *

The {@link #values()} and {@link #entries()} methods both return a {@code List}, instead of + * the {@code Collection} specified by the {@link ListMultimap} interface. + * + *

The methods {@link #get}, {@link #keySet()}, {@link #keys()}, {@link #values}, {@link + * #entries()}, and {@link #asMap} return collections that are views of the multimap. If the + * multimap is modified while an iteration over any of those collections is in progress, except + * through the iterator's methods, the results of the iteration are undefined. + * + *

Keys and values may be null. All optional multimap methods are supported, and all returned + * views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the multimap. Concurrent + * read operations will work correctly. To allow concurrent update operations, wrap your multimap + * with a call to {@link Multimaps#synchronizedListMultimap}. + * + *

See the Guava User Guide article on {@code Multimap}. + * + * @author Mike Bostock + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +@ElementTypesAreNonnullByDefault +public class LinkedListMultimap + extends AbstractMultimap implements ListMultimap, Serializable { + /* + * Order is maintained using a linked list containing all key-value pairs. In + * addition, a series of disjoint linked lists of "siblings", each containing + * the values for a specific key, is used to implement {@link + * ValueForKeyIterator} in constant time. + */ + + private static final class Node + extends AbstractMapEntry { + @ParametricNullness final K key; + @ParametricNullness V value; + @CheckForNull Node next; // the next node (with any key) + @CheckForNull Node previous; // the previous node (with any key) + @CheckForNull Node nextSibling; // the next node with the same key + @CheckForNull Node previousSibling; // the previous node with the same key + + Node(@ParametricNullness K key, @ParametricNullness V value) { + this.key = key; + this.value = value; + } + + @Override + @ParametricNullness + public K getKey() { + return key; + } + + @Override + @ParametricNullness + public V getValue() { + return value; + } + + @Override + @ParametricNullness + public V setValue(@ParametricNullness V newValue) { + V result = value; + this.value = newValue; + return result; + } + } + + private static class KeyList { + Node head; + Node tail; + int count; + + KeyList(Node firstNode) { + this.head = firstNode; + this.tail = firstNode; + firstNode.previousSibling = null; + firstNode.nextSibling = null; + this.count = 1; + } + } + + @CheckForNull private transient Node head; // the head for all keys + @CheckForNull private transient Node tail; // the tail for all keys + private transient Map> keyToKeyList; + private transient int size; + + /* + * Tracks modifications to keyToKeyList so that addition or removal of keys invalidates + * preexisting iterators. This does *not* track simple additions and removals of values + * that are not the first to be added or last to be removed for their key. + */ + private transient int modCount; + + /** Creates a new, empty {@code LinkedListMultimap} with the default initial capacity. */ + public static + LinkedListMultimap create() { + return new LinkedListMultimap<>(); + } + + /** + * Constructs an empty {@code LinkedListMultimap} with enough capacity to hold the specified + * number of keys without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @throws IllegalArgumentException if {@code expectedKeys} is negative + */ + public static + LinkedListMultimap create(int expectedKeys) { + return new LinkedListMultimap<>(expectedKeys); + } + + /** + * Constructs a {@code LinkedListMultimap} with the same mappings as the specified {@code + * Multimap}. The new multimap has the same {@link Multimap#entries()} iteration order as the + * input multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static + LinkedListMultimap create(Multimap multimap) { + return new LinkedListMultimap<>(multimap); + } + + LinkedListMultimap() { + this(12); + } + + private LinkedListMultimap(int expectedKeys) { + keyToKeyList = Platform.newHashMapWithExpectedSize(expectedKeys); + } + + private LinkedListMultimap(Multimap multimap) { + this(multimap.keySet().size()); + putAll(multimap); + } + + /** + * Adds a new node for the specified key-value pair before the specified {@code nextSibling} + * element, or at the end of the list if {@code nextSibling} is null. Note: if {@code nextSibling} + * is specified, it MUST be for a node for the same {@code key}! + */ + @CanIgnoreReturnValue + private Node addNode( + @ParametricNullness K key, + @ParametricNullness V value, + @CheckForNull Node nextSibling) { + Node node = new Node<>(key, value); + if (head == null) { // empty list + head = tail = node; + keyToKeyList.put(key, new KeyList(node)); + modCount++; + } else if (nextSibling == null) { // non-empty list, add to tail + // requireNonNull is safe because the list is non-empty. + requireNonNull(tail).next = node; + node.previous = tail; + tail = node; + KeyList keyList = keyToKeyList.get(key); + if (keyList == null) { + keyToKeyList.put(key, keyList = new KeyList<>(node)); + modCount++; + } else { + keyList.count++; + Node keyTail = keyList.tail; + keyTail.nextSibling = node; + node.previousSibling = keyTail; + keyList.tail = node; + } + } else { // non-empty list, insert before nextSibling + /* + * requireNonNull is safe as long as callers pass a nextSibling that (a) has the same key and + * (b) is present in the multimap. (And they do, except maybe in case of concurrent + * modification, in which case all bets are off.) + */ + KeyList keyList = requireNonNull(keyToKeyList.get(key)); + keyList.count++; + node.previous = nextSibling.previous; + node.previousSibling = nextSibling.previousSibling; + node.next = nextSibling; + node.nextSibling = nextSibling; + if (nextSibling.previousSibling == null) { // nextSibling was key head + keyList.head = node; + } else { + nextSibling.previousSibling.nextSibling = node; + } + if (nextSibling.previous == null) { // nextSibling was head + head = node; + } else { + nextSibling.previous.next = node; + } + nextSibling.previous = node; + nextSibling.previousSibling = node; + } + size++; + return node; + } + + /** + * Removes the specified node from the linked list. This method is only intended to be used from + * the {@code Iterator} classes. See also {@link LinkedListMultimap#removeAllNodes(Object)}. + */ + private void removeNode(Node node) { + if (node.previous != null) { + node.previous.next = node.next; + } else { // node was head + head = node.next; + } + if (node.next != null) { + node.next.previous = node.previous; + } else { // node was tail + tail = node.previous; + } + if (node.previousSibling == null && node.nextSibling == null) { + /* + * requireNonNull is safe as long as we call removeNode only for nodes that are still in the + * Multimap. This should be the case (except in case of concurrent modification, when all bets + * are off). + */ + KeyList keyList = requireNonNull(keyToKeyList.remove(node.key)); + keyList.count = 0; + modCount++; + } else { + // requireNonNull is safe (under the conditions listed in the comment in the branch above). + KeyList keyList = requireNonNull(keyToKeyList.get(node.key)); + keyList.count--; + + if (node.previousSibling == null) { + // requireNonNull is safe because we checked that not *both* siblings were null. + keyList.head = requireNonNull(node.nextSibling); + } else { + node.previousSibling.nextSibling = node.nextSibling; + } + + if (node.nextSibling == null) { + // requireNonNull is safe because we checked that not *both* siblings were null. + keyList.tail = requireNonNull(node.previousSibling); + } else { + node.nextSibling.previousSibling = node.previousSibling; + } + } + size--; + } + + /** Removes all nodes for the specified key. */ + private void removeAllNodes(@ParametricNullness K key) { + Iterators.clear(new ValueForKeyIterator(key)); + } + + /** An {@code Iterator} over all nodes. */ + private class NodeIterator implements ListIterator> { + int nextIndex; + @CheckForNull Node next; + @CheckForNull Node current; + @CheckForNull Node previous; + int expectedModCount = modCount; + + NodeIterator(int index) { + int size = size(); + checkPositionIndex(index, size); + if (index >= (size / 2)) { + previous = tail; + nextIndex = size; + while (index++ < size) { + previous(); + } + } else { + next = head; + while (index-- > 0) { + next(); + } + } + current = null; + } + + private void checkForConcurrentModification() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + checkForConcurrentModification(); + return next != null; + } + + @CanIgnoreReturnValue + @Override + public Node next() { + checkForConcurrentModification(); + if (next == null) { + throw new NoSuchElementException(); + } + previous = current = next; + next = next.next; + nextIndex++; + return current; + } + + @Override + public void remove() { + checkForConcurrentModification(); + checkState(current != null, "no calls to next() since the last call to remove()"); + if (current != next) { // after call to next() + previous = current.previous; + nextIndex--; + } else { // after call to previous() + next = current.next; + } + removeNode(current); + current = null; + expectedModCount = modCount; + } + + @Override + public boolean hasPrevious() { + checkForConcurrentModification(); + return previous != null; + } + + @CanIgnoreReturnValue + @Override + public Node previous() { + checkForConcurrentModification(); + if (previous == null) { + throw new NoSuchElementException(); + } + next = current = previous; + previous = previous.previous; + nextIndex--; + return current; + } + + @Override + public int nextIndex() { + return nextIndex; + } + + @Override + public int previousIndex() { + return nextIndex - 1; + } + + @Override + public void set(Entry e) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(Entry e) { + throw new UnsupportedOperationException(); + } + + void setValue(@ParametricNullness V value) { + checkState(current != null); + current.value = value; + } + } + + /** An {@code Iterator} over distinct keys in key head order. */ + private class DistinctKeyIterator implements Iterator { + final Set seenKeys = Sets.newHashSetWithExpectedSize(keySet().size()); + @CheckForNull Node next = head; + @CheckForNull Node current; + int expectedModCount = modCount; + + private void checkForConcurrentModification() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + checkForConcurrentModification(); + return next != null; + } + + @Override + @ParametricNullness + public K next() { + checkForConcurrentModification(); + if (next == null) { + throw new NoSuchElementException(); + } + current = next; + seenKeys.add(current.key); + do { // skip ahead to next unseen key + next = next.next; + } while ((next != null) && !seenKeys.add(next.key)); + return current.key; + } + + @Override + public void remove() { + checkForConcurrentModification(); + checkState(current != null, "no calls to next() since the last call to remove()"); + removeAllNodes(current.key); + current = null; + expectedModCount = modCount; + } + } + + /** A {@code ListIterator} over values for a specified key. */ + private class ValueForKeyIterator implements ListIterator { + @ParametricNullness final K key; + int nextIndex; + @CheckForNull Node next; + @CheckForNull Node current; + @CheckForNull Node previous; + + /** Constructs a new iterator over all values for the specified key. */ + ValueForKeyIterator(@ParametricNullness K key) { + this.key = key; + KeyList keyList = keyToKeyList.get(key); + next = (keyList == null) ? null : keyList.head; + } + + /** + * Constructs a new iterator over all values for the specified key starting at the specified + * index. This constructor is optimized so that it starts at either the head or the tail, + * depending on which is closer to the specified index. This allows adds to the tail to be done + * in constant time. + * + * @throws IndexOutOfBoundsException if index is invalid + */ + public ValueForKeyIterator(@ParametricNullness K key, int index) { + KeyList keyList = keyToKeyList.get(key); + int size = (keyList == null) ? 0 : keyList.count; + checkPositionIndex(index, size); + if (index >= (size / 2)) { + previous = (keyList == null) ? null : keyList.tail; + nextIndex = size; + while (index++ < size) { + previous(); + } + } else { + next = (keyList == null) ? null : keyList.head; + while (index-- > 0) { + next(); + } + } + this.key = key; + current = null; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @CanIgnoreReturnValue + @Override + @ParametricNullness + public V next() { + if (next == null) { + throw new NoSuchElementException(); + } + previous = current = next; + next = next.nextSibling; + nextIndex++; + return current.value; + } + + @Override + public boolean hasPrevious() { + return previous != null; + } + + @CanIgnoreReturnValue + @Override + @ParametricNullness + public V previous() { + if (previous == null) { + throw new NoSuchElementException(); + } + next = current = previous; + previous = previous.previousSibling; + nextIndex--; + return current.value; + } + + @Override + public int nextIndex() { + return nextIndex; + } + + @Override + public int previousIndex() { + return nextIndex - 1; + } + + @Override + public void remove() { + checkState(current != null, "no calls to next() since the last call to remove()"); + if (current != next) { // after call to next() + previous = current.previousSibling; + nextIndex--; + } else { // after call to previous() + next = current.nextSibling; + } + removeNode(current); + current = null; + } + + @Override + public void set(@ParametricNullness V value) { + checkState(current != null); + current.value = value; + } + + @Override + public void add(@ParametricNullness V value) { + previous = addNode(key, value, next); + nextIndex++; + current = null; + } + } + + // Query Operations + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return head == null; + } + + @Override + public boolean containsKey(@CheckForNull Object key) { + return keyToKeyList.containsKey(key); + } + + @Override + public boolean containsValue(@CheckForNull Object value) { + return values().contains(value); + } + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} always + */ + @CanIgnoreReturnValue + @Override + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { + addNode(key, value, null); + return true; + } + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

If any entries for the specified {@code key} already exist in the multimap, their values are + * changed in-place without affecting the iteration order. + * + *

The returned list is immutable and implements {@link java.util.RandomAccess}. + */ + @CanIgnoreReturnValue + @Override + public List replaceValues(@ParametricNullness K key, Iterable values) { + List oldValues = getCopy(key); + ListIterator keyValues = new ValueForKeyIterator(key); + Iterator newValues = values.iterator(); + + // Replace existing values, if any. + while (keyValues.hasNext() && newValues.hasNext()) { + keyValues.next(); + keyValues.set(newValues.next()); + } + + // Remove remaining old values, if any. + while (keyValues.hasNext()) { + keyValues.next(); + keyValues.remove(); + } + + // Add remaining new values, if any. + while (newValues.hasNext()) { + keyValues.add(newValues.next()); + } + + return oldValues; + } + + private List getCopy(@ParametricNullness K key) { + return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key))); + } + + /** + * {@inheritDoc} + * + *

The returned list is immutable and implements {@link java.util.RandomAccess}. + */ + @CanIgnoreReturnValue + @Override + public List removeAll(@Nullable Object key) { + /* + * Safe because all we do is remove values for the key, not add them. (If we wanted to make sure + * to call getCopy and removeAllNodes only with a true K, then we could check containsKey first. + * But that check wouldn't eliminate the warnings.) + */ + @SuppressWarnings({"unchecked", "nullness"}) + K castKey = (K) key; + List oldValues = getCopy(castKey); + removeAllNodes(castKey); + return oldValues; + } + + @Override + public void clear() { + head = null; + tail = null; + keyToKeyList.clear(); + size = 0; + modCount++; + } + + // Views + + /** + * {@inheritDoc} + * + *

If the multimap is modified while an iteration over the list is in progress (except through + * the iterator's own {@code add}, {@code set} or {@code remove} operations) the results of the + * iteration are undefined. + * + *

The returned list is not serializable and does not have random access. + */ + @Override + public List get(@ParametricNullness final K key) { + return new AbstractSequentialList() { + @Override + public int size() { + KeyList keyList = keyToKeyList.get(key); + return (keyList == null) ? 0 : keyList.count; + } + + @Override + public ListIterator listIterator(int index) { + return new ValueForKeyIterator(key, index); + } + }; + } + + @Override + Set createKeySet() { + @WeakOuter + class KeySetImpl extends Sets.ImprovedAbstractSet { + @Override + public int size() { + return keyToKeyList.size(); + } + + @Override + public Iterator iterator() { + return new DistinctKeyIterator(); + } + + @Override + public boolean contains(@CheckForNull Object key) { // for performance + return containsKey(key); + } + + @Override + public boolean remove(@CheckForNull Object o) { // for performance + return !LinkedListMultimap.this.removeAll(o).isEmpty(); + } + } + return new KeySetImpl(); + } + + @Override + Multiset createKeys() { + return new Multimaps.Keys(this); + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values in the order they + * were added to the multimap. Because the values may have duplicates and follow the insertion + * ordering, this method returns a {@link List}, instead of the {@link Collection} specified in + * the {@link ListMultimap} interface. + */ + @Override + public List values() { + return (List) super.values(); + } + + @Override + List createValues() { + @WeakOuter + class ValuesImpl extends AbstractSequentialList { + @Override + public int size() { + return size; + } + + @Override + public ListIterator listIterator(int index) { + final NodeIterator nodeItr = new NodeIterator(index); + return new TransformedListIterator, V>(nodeItr) { + @Override + @ParametricNullness + V transform(Entry entry) { + return entry.getValue(); + } + + @Override + public void set(@ParametricNullness V value) { + nodeItr.setValue(value); + } + }; + } + } + return new ValuesImpl(); + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the entries in the order they + * were added to the multimap. Because the entries may have duplicates and follow the insertion + * ordering, this method returns a {@link List}, instead of the {@link Collection} specified in + * the {@link ListMultimap} interface. + * + *

An entry's {@link Entry#getKey} method always returns the same key, regardless of what + * happens subsequently. As long as the corresponding key-value mapping is not removed from the + * multimap, {@link Entry#getValue} returns the value from the multimap, which may change over + * time, and {@link Entry#setValue} modifies that value. Removing the mapping from the multimap + * does not alter the value returned by {@code getValue()}, though a subsequent {@code setValue()} + * call won't update the multimap but will lead to a revised value being returned by {@code + * getValue()}. + */ + @Override + public List> entries() { + return (List>) super.entries(); + } + + @Override + List> createEntries() { + @WeakOuter + class EntriesImpl extends AbstractSequentialList> { + @Override + public int size() { + return size; + } + + @Override + public ListIterator> listIterator(int index) { + return new NodeIterator(index); + } + + @Override + public void forEach(Consumer> action) { + checkNotNull(action); + for (Node node = head; node != null; node = node.next) { + action.accept(node); + } + } + } + return new EntriesImpl(); + } + + @Override + Iterator> entryIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Map> createAsMap() { + return new Multimaps.AsMap<>(this); + } + + /** + * @serialData the number of distinct keys, and then for each distinct key: the first key, the + * number of values for that key, and the key's values, followed by successive keys and values + * from the entries() ordering + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(size()); + for (Entry entry : entries()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyToKeyList = Maps.newLinkedHashMap(); + int size = stream.readInt(); + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeObject + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + V value = (V) stream.readObject(); + put(key, value); + } + } + + @GwtIncompatible // java serialization not supported + private static final long serialVersionUID = 0; +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MapsKeySet.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MapsKeySet.java new file mode 100644 index 0000000..c54bdc9 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MapsKeySet.java @@ -0,0 +1,69 @@ +package org.xbib.datastructures.multi; + +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +public class MapsKeySet extends ImprovedAbstractSet { + final Map map; + + MapsKeySet(Map map) { + this.map = Objects.requireNonNull(map); + } + + Map map() { + return map; + } + + @Override + public Iterator iterator() { + return keyIterator(map().entrySet().iterator()); + } + + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); + // avoids entry allocation for those maps that allocate entries on iteration + map.forEach((k, v) -> action.accept(k)); + } + + @Override + public int size() { + return map().size(); + } + + @Override + public boolean isEmpty() { + return map().isEmpty(); + } + + @Override + public boolean contains(Object o) { + return map().containsKey(o); + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + map().remove(o); + return true; + } + return false; + } + + @Override + public void clear() { + map().clear(); + } + + private static Iterator keyIterator( + Iterator> entryIterator) { + return new TransformedIterator, K>(entryIterator) { + @Override + K transform(Map.Entry entry) { + return entry.getKey(); + } + }; + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapBuilder.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapBuilder.java new file mode 100644 index 0000000..a51e676 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapBuilder.java @@ -0,0 +1,461 @@ +package org.xbib.datastructures.multi; + +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.Objects; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Supplier; +import org.xbib.datastructures.api.ListMultimap; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.SetMultimap; +import org.xbib.datastructures.api.SortedSetMultimap; +import org.xbib.datastructures.immutable.order.Ordering; + +/** + * 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. + */ +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 hash table to map keys to value collections. */ + public static MultimapBuilderWithKeys hashKeys() { + return hashKeys(DEFAULT_EXPECTED_KEYS); + } + + /** + * Uses a hash table to map keys to value collections, initialized to expect the specified number + * of keys. + * + * @throws IllegalArgumentException if {@code expectedKeys < 0} + */ + public static MultimapBuilderWithKeys hashKeys(int expectedKeys) { + checkNonnegative(expectedKeys, "expectedKeys"); + return new MultimapBuilderWithKeys() { + @Override + Map> createMap() { + return new HashMap(expectedKeys); + } + }; + } + + /** + * Uses a hash table 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 an hash table 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(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 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 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( + Comparator comparator) { + Objects.requireNonNull(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(Class keyClass) { + Objects.requireNonNull(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 = Objects.requireNonNull(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 = Objects.requireNonNull(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. + * @since 16.0 + */ + 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(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 hash-based {@code Set} to store value collections. */ + public SetMultimapBuilder hashSetValues() { + return hashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); + } + + /** + * Uses a hash-based {@code Set} to store value collections, initialized to expect the specified + * number of values per key. + * + * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} + */ + public SetMultimapBuilder hashSetValues(int expectedValuesPerKey) { + checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + return new SetMultimapBuilder() { + @Override + public SetMultimap build() { + return Multimaps.newSetMultimap( + MultimapBuilderWithKeys.this.createMap(), + new HashSetSupplier(expectedValuesPerKey)); + } + }; + } + + /** Uses an insertion-ordered hash-based {@code Set} to store value collections. */ + public SetMultimapBuilder linkedHashSetValues() { + return linkedHashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); + } + + /** + * Uses an insertion-ordered hash-based {@code Set} to store value collections, initialized to + * expect the specified number of values per key. + * + * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} + */ + public SetMultimapBuilder linkedHashSetValues(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( + Comparator comparator) { + Objects.requireNonNull(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(Class valueClass) { + Objects.requireNonNull(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< + K0 extends Object, V0 extends Object> + 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. + * + * @since 16.0 + */ + public abstract static class SetMultimapBuilder< + K0 extends Object, V0 extends Object> + 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. + * + * @since 16.0 + */ + public abstract static class SortedSetMultimapBuilder< + K0 extends Object, V0 extends Object> + extends SetMultimapBuilder { + SortedSetMultimapBuilder() {} + + @Override + public abstract SortedSetMultimap build(); + + @Override + public SortedSetMultimap build( + Multimap multimap) { + return (SortedSetMultimap) super.build(multimap); + } + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/Multimaps.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/Multimaps.java new file mode 100644 index 0000000..c186b36 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/Multimaps.java @@ -0,0 +1,2214 @@ +package org.xbib.datastructures.multi; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Stream; +import org.xbib.datastructures.api.ListMultimap; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.Multiset; +import org.xbib.datastructures.api.SetMultimap; +import org.xbib.datastructures.api.SortedSetMultimap; + +/** + * Provides static methods acting on or generating a {@code Multimap}. + * + */ +public final class Multimaps { + private Multimaps() {} + + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. The keys and values of the entries are the result of applying the provided + * mapping functions to the input elements, accumulated in the encounter order of the stream. + * + *

Example: + * + *

{@code
+   * static final ListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             toMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1),
+   *                  MultimapBuilder.treeKeys().arrayListValues()::build));
+   *
+   * // is equivalent to
+   *
+   * static final ListMultimap FIRST_LETTER_MULTIMAP;
+   *
+   * static {
+   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.treeKeys().arrayListValues().build();
+   *     FIRST_LETTER_MULTIMAP.put('b', "anana");
+   *     FIRST_LETTER_MULTIMAP.put('a', "pple");
+   *     FIRST_LETTER_MULTIMAP.put('a', "sparagus");
+   *     FIRST_LETTER_MULTIMAP.put('c', "arrot");
+   *     FIRST_LETTER_MULTIMAP.put('c', "herry");
+   * }
+   * }
+ * + *

To collect to an {@link ImmutableMultimap}, use either {@link + * ImmutableSetMultimap#toImmutableSetMultimap} or {@link + * ImmutableListMultimap#toImmutableListMultimap}. + * + * @since 21.0 + */ + public static < + T extends Object, + K extends Object, + V extends Object, + M extends Multimap> + Collector toMultimap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier multimapSupplier) { + return CollectCollectors.toMultimap(keyFunction, valueFunction, multimapSupplier); + } + + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. Each input element is mapped to a key and a stream of values, each of which + * are put into the resulting {@code Multimap}, in the encounter order of the stream and the + * encounter order of the streams of values. + * + *

Example: + * + *

{@code
+   * static final ListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c),
+   *                  MultimapBuilder.linkedHashKeys().arrayListValues()::build));
+   *
+   * // is equivalent to
+   *
+   * static final ListMultimap FIRST_LETTER_MULTIMAP;
+   *
+   * static {
+   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.linkedHashKeys().arrayListValues().build();
+   *     FIRST_LETTER_MULTIMAP.putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'));
+   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('p', 'p', 'l', 'e'));
+   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'));
+   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'));
+   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'));
+   * }
+   * }
+ */ + public static < + T extends Object, + K extends Object, + V extends Object, + M extends Multimap> + Collector flatteningToMultimap( + java.util.function.Function keyFunction, + java.util.function.Function> valueFunction, + java.util.function.Supplier multimapSupplier) { + return CollectCollectors.flatteningToMultimap(keyFunction, valueFunction, multimapSupplier); + } + + /** + * Creates a new {@code Multimap} backed by {@code map}, whose internal value collections are + * generated by {@code factory}. + * + *

Warning: do not use this method when the collections returned by {@code factory} + * implement either {@link List} or {@code Set}! Use the more specific method {@link + * #newListMultimap}, {@link #newSetMultimap} or {@link #newSortedSetMultimap} instead, to avoid + * very surprising behavior from {@link Multimap#equals}. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the collections generated by + * {@code factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedMultimap}. + * + *

Call this method only when the simpler methods {@link ArrayListMultimap#create()}, {@link + * HashMultimap#create()}, {@link LinkedHashMultimap#create()}, {@link + * LinkedListMultimap#create()}, {@link TreeMultimap#create()}, and {@link + * TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the collections + * returned by {@code factory}. Those objects should not be manually updated and they should not + * use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty collections that will each hold all values for a given + * key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static Multimap newMultimap( + Map> map, final Supplier> factory) { + return new CustomMultimap<>(map, factory); + } + + private static class CustomMultimap + extends AbstractMapBasedMultimap { + transient Supplier> factory; + + CustomMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected Collection createCollection() { + return factory.get(); + } + + @Override + Collection unmodifiableCollectionSubclass( + Collection collection) { + if (collection instanceof NavigableSet) { + return Sets.unmodifiableNavigableSet((NavigableSet) collection); + } else if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } else { + return Collections.unmodifiableCollection(collection); + } + } + + @Override + Collection wrapCollection(@ParametricNullness K key, Collection collection) { + if (collection instanceof List) { + return wrapList(key, (List) collection, null); + } else if (collection instanceof NavigableSet) { + return new WrappedNavigableSet(key, (NavigableSet) collection, null); + } else if (collection instanceof SortedSet) { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } else if (collection instanceof Set) { + return new WrappedSet(key, (Set) collection); + } else { + return new WrappedCollection(key, collection, null); + } + } + + // can't use Serialization writeMultimap and populateMultimap methods since + // there's no way to generate the empty backing map. + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // java serialization not supported + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code ListMultimap} that uses the provided map and factory. It can generate a + * multimap based on arbitrary {@link Map} and {@link List} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. The multimap's {@code get}, {@code + * removeAll}, and {@code replaceValues} methods return {@code RandomAccess} lists if the factory + * does. However, the multimap's {@code get} method returns instances of a different class than + * does {@code factory.get()}. + * + *

The multimap is serializable if {@code map}, {@code factory}, the lists generated by {@code + * factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedListMultimap}. + * + *

Call this method only when the simpler methods {@link ArrayListMultimap#create()} and {@link + * LinkedListMultimap#create()} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the lists returned by + * {@code factory}. Those objects should not be manually updated, they should be empty when + * provided, and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty lists that will each hold all values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static + ListMultimap newListMultimap( + Map> map, final Supplier> factory) { + return new CustomListMultimap<>(map, factory); + } + + private static class CustomListMultimap + extends AbstractListMultimap { + transient Supplier> factory; + + CustomListMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected List createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // java serialization not supported + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SetMultimap} that uses the provided map and factory. It can generate a + * multimap based on arbitrary {@link Map} and {@link Set} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the sets generated by {@code + * factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedSetMultimap}. + * + *

Call this method only when the simpler methods {@link HashMultimap#create()}, {@link + * LinkedHashMultimap#create()}, {@link TreeMultimap#create()}, and {@link + * TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the sets returned by + * {@code factory}. Those objects should not be manually updated and they should not use soft, + * weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty sets that will each hold all values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static + SetMultimap newSetMultimap( + Map> map, final Supplier> factory) { + return new CustomSetMultimap<>(map, factory); + } + + private static class CustomSetMultimap + extends AbstractSetMultimap { + transient Supplier> factory; + + CustomSetMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected Set createCollection() { + return factory.get(); + } + + @Override + Collection unmodifiableCollectionSubclass( + Collection collection) { + if (collection instanceof NavigableSet) { + return Sets.unmodifiableNavigableSet((NavigableSet) collection); + } else if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else { + return Collections.unmodifiableSet((Set) collection); + } + } + + @Override + Collection wrapCollection(@ParametricNullness K key, Collection collection) { + if (collection instanceof NavigableSet) { + return new WrappedNavigableSet(key, (NavigableSet) collection, null); + } else if (collection instanceof SortedSet) { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } else { + return new WrappedSet(key, (Set) collection); + } + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SortedSetMultimap} that uses the provided map and factory. It can generate + * a multimap based on arbitrary {@link Map} and {@link SortedSet} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the sets generated by {@code + * factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedSortedSetMultimap}. + * + *

Call this method only when the simpler methods {@link TreeMultimap#create()} and {@link + * TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the sets returned by + * {@code factory}. Those objects should not be manually updated and they should not use soft, + * weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty sorted sets that will each hold all values for a given + * key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static + SortedSetMultimap newSortedSetMultimap( + Map> map, final Supplier> factory) { + return new CustomSortedSetMultimap<>(map, factory); + } + + private static class CustomSortedSetMultimap< + K extends Object, V extends Object> + extends AbstractSortedSetMultimap { + transient Supplier> factory; + @CheckForNull transient Comparator valueComparator; + + CustomSortedSetMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + valueComparator = factory.get().comparator(); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected SortedSet createCollection() { + return factory.get(); + } + + @Override + @CheckForNull + public Comparator valueComparator() { + return valueComparator; + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + valueComparator = factory.get().comparator(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 0; + } + + /** + * Copies each key-value mapping in {@code source} into {@code dest}, with its key and value + * reversed. + * + *

If {@code source} is an {@link ImmutableMultimap}, consider using {@link + * ImmutableMultimap#inverse} instead. + * + * @param source any multimap + * @param dest the multimap to copy into; usually empty + * @return {@code dest} + */ + @CanIgnoreReturnValue + public static > + M invertFrom(Multimap source, M dest) { + checkNotNull(dest); + for (Entry entry : source.entries()) { + dest.put(entry.getValue(), entry.getKey()); + } + return dest; + } + + /** + * Returns a synchronized (thread-safe) multimap backed by the specified multimap. In order to + * guarantee serial access, it is critical that all access to the backing multimap is + * accomplished through the returned multimap. + * + *

It is imperative that the user manually synchronize on the returned multimap when accessing + * any of its collection views: + * + *

{@code
+   * Multimap multimap = Multimaps.synchronizedMultimap(
+   *     HashMultimap.create());
+   * ...
+   * Collection values = multimap.get(key);  // Needn't be in synchronized block
+   * ...
+   * synchronized (multimap) {  // Synchronizing on multimap, not values!
+   *   Iterator i = values.iterator(); // Must be in synchronized block
+   *   while (i.hasNext()) {
+   *     foo(i.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and {@link + * Multimap#replaceValues} methods return collections that aren't synchronized. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static + Multimap synchronizedMultimap(Multimap multimap) { + return Synchronized.multimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified multimap. Query operations on the returned + * multimap "read through" to the specified multimap, and attempts to modify the returned + * multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static + Multimap unmodifiableMultimap(Multimap delegate) { + if (delegate instanceof UnmodifiableMultimap || delegate instanceof ImmutableMultimap) { + return delegate; + } + return new UnmodifiableMultimap<>(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static Multimap unmodifiableMultimap(ImmutableMultimap delegate) { + return checkNotNull(delegate); + } + + private static class UnmodifiableMultimap + extends ForwardingMultimap implements Serializable { + final Multimap delegate; + @LazyInit @CheckForNull transient Collection> entries; + @LazyInit @CheckForNull transient Multiset keys; + @LazyInit @CheckForNull transient Set keySet; + @LazyInit @CheckForNull transient Collection values; + @LazyInit @CheckForNull transient Map> map; + + UnmodifiableMultimap(final Multimap delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + protected Multimap delegate() { + return delegate; + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Map> asMap() { + Map> result = map; + if (result == null) { + result = + map = + Collections.unmodifiableMap( + Maps.transformValues( + delegate.asMap(), + new Function, Collection>() { + @Override + public Collection apply(Collection collection) { + return unmodifiableValueCollection(collection); + } + })); + } + return result; + } + + @Override + public Collection> entries() { + Collection> result = entries; + if (result == null) { + entries = result = unmodifiableEntries(delegate.entries()); + } + return result; + } + + @Override + public void forEach(BiConsumer consumer) { + delegate.forEach(checkNotNull(consumer)); + } + + @Override + public Collection get(@ParametricNullness K key) { + return unmodifiableValueCollection(delegate.get(key)); + } + + @Override + public Multiset keys() { + Multiset result = keys; + if (result == null) { + keys = result = Multisets.unmodifiableMultiset(delegate.keys()); + } + return result; + } + + @Override + public Set keySet() { + Set result = keySet; + if (result == null) { + keySet = result = Collections.unmodifiableSet(delegate.keySet()); + } + return result; + } + + @Override + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(@CheckForNull Object key, @CheckForNull Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection removeAll(@CheckForNull Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection replaceValues(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + Collection result = values; + if (result == null) { + values = result = Collections.unmodifiableCollection(delegate.values()); + } + return result; + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableListMultimap< + K extends Object, V extends Object> + extends UnmodifiableMultimap implements ListMultimap { + UnmodifiableListMultimap(ListMultimap delegate) { + super(delegate); + } + + @Override + public ListMultimap delegate() { + return (ListMultimap) super.delegate(); + } + + @Override + public List get(@ParametricNullness K key) { + return Collections.unmodifiableList(delegate().get(key)); + } + + @Override + public List removeAll(@CheckForNull Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public List replaceValues(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSetMultimap< + K extends Object, V extends Object> + extends UnmodifiableMultimap implements SetMultimap { + UnmodifiableSetMultimap(SetMultimap delegate) { + super(delegate); + } + + @Override + public SetMultimap delegate() { + return (SetMultimap) super.delegate(); + } + + @Override + public Set get(@ParametricNullness K key) { + /* + * Note that this doesn't return a SortedSet when delegate is a + * SortedSetMultiset, unlike (SortedSet) super.get(). + */ + return Collections.unmodifiableSet(delegate().get(key)); + } + + @Override + public Set> entries() { + return Maps.unmodifiableEntrySet(delegate().entries()); + } + + @Override + public Set removeAll(@CheckForNull Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Set replaceValues(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSortedSetMultimap< + K extends Object, V extends Object> + extends UnmodifiableSetMultimap implements SortedSetMultimap { + UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { + super(delegate); + } + + @Override + public SortedSetMultimap delegate() { + return (SortedSetMultimap) super.delegate(); + } + + @Override + public SortedSet get(@ParametricNullness K key) { + return Collections.unmodifiableSortedSet(delegate().get(key)); + } + + @Override + public SortedSet removeAll(@CheckForNull Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public SortedSet replaceValues(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + @CheckForNull + public Comparator valueComparator() { + return delegate().valueComparator(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) {@code SetMultimap} backed by the specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static + SetMultimap synchronizedSetMultimap(SetMultimap multimap) { + return Synchronized.setMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SetMultimap}. Query operations on the + * returned multimap "read through" to the specified multimap, and attempts to modify the returned + * multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static + SetMultimap unmodifiableSetMultimap(SetMultimap delegate) { + if (delegate instanceof UnmodifiableSetMultimap || delegate instanceof ImmutableSetMultimap) { + return delegate; + } + return new UnmodifiableSetMultimap<>(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static SetMultimap unmodifiableSetMultimap( + ImmutableSetMultimap delegate) { + return checkNotNull(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code SortedSetMultimap} backed by the specified + * multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static + SortedSetMultimap synchronizedSortedSetMultimap(SortedSetMultimap multimap) { + return Synchronized.sortedSetMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SortedSetMultimap}. Query operations on + * the returned multimap "read through" to the specified multimap, and attempts to modify the + * returned multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static + SortedSetMultimap unmodifiableSortedSetMultimap(SortedSetMultimap delegate) { + if (delegate instanceof UnmodifiableSortedSetMultimap) { + return delegate; + } + return new UnmodifiableSortedSetMultimap<>(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code ListMultimap} backed by the specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static + ListMultimap synchronizedListMultimap(ListMultimap multimap) { + return Synchronized.listMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code ListMultimap}. Query operations on the + * returned multimap "read through" to the specified multimap, and attempts to modify the returned + * multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static + ListMultimap unmodifiableListMultimap(ListMultimap delegate) { + if (delegate instanceof UnmodifiableListMultimap || delegate instanceof ImmutableListMultimap) { + return delegate; + } + return new UnmodifiableListMultimap<>(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static ListMultimap unmodifiableListMultimap( + ImmutableListMultimap delegate) { + return checkNotNull(delegate); + } + + /** + * Returns an unmodifiable view of the specified collection, preserving the interface for + * instances of {@code SortedSet}, {@code Set}, {@code List} and {@code Collection}, in that order + * of preference. + * + * @param collection the collection for which to return an unmodifiable view + * @return an unmodifiable view of the collection + */ + private static Collection unmodifiableValueCollection( + Collection collection) { + if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } + return Collections.unmodifiableCollection(collection); + } + + /** + * Returns an unmodifiable view of the specified collection of entries. The {@link Entry#setValue} + * operation throws an {@link UnsupportedOperationException}. If the specified collection is a + * {@code Set}, the returned collection is also a {@code Set}. + * + * @param entries the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + private static + Collection> unmodifiableEntries(Collection> entries) { + if (entries instanceof Set) { + return Maps.unmodifiableEntrySet((Set>) entries); + } + return new Maps.UnmodifiableEntries<>(Collections.unmodifiableCollection(entries)); + } + + /** + * Returns {@link ListMultimap#asMap multimap.asMap()}, with its type corrected from {@code Map>} to {@code Map>}. + * + * @since 15.0 + */ + + @SuppressWarnings("unchecked") + // safe by specification of ListMultimap.asMap() + public static Map> asMap( + ListMultimap multimap) { + return (Map>) (Map) multimap.asMap(); + } + + /** + * Returns {@link SetMultimap#asMap multimap.asMap()}, with its type corrected from {@code Map>} to {@code Map>}. + * + * @since 15.0 + */ + + @SuppressWarnings("unchecked") + // safe by specification of SetMultimap.asMap() + public static Map> asMap( + SetMultimap multimap) { + return (Map>) (Map) multimap.asMap(); + } + + /** + * Returns {@link SortedSetMultimap#asMap multimap.asMap()}, with its type corrected from {@code + * Map>} to {@code Map>}. + * + * @since 15.0 + */ + + @SuppressWarnings("unchecked") + // safe by specification of SortedSetMultimap.asMap() + public static Map> asMap( + SortedSetMultimap multimap) { + return (Map>) (Map) multimap.asMap(); + } + + /** + * Returns {@link Multimap#asMap multimap.asMap()}. This is provided for parity with the other + * more strongly-typed {@code asMap()} implementations. + * + * @since 15.0 + */ + + public static + Map> asMap(Multimap multimap) { + return multimap.asMap(); + } + + /** + * Returns a multimap view of the specified map. The multimap is backed by the map, so changes to + * the map are reflected in the multimap, and vice versa. If the map is modified while an + * iteration over one of the multimap's collection views is in progress (except through the + * iterator's own {@code remove} operation, or through the {@code setValue} operation on a map + * entry returned by the iterator), the results of the iteration are undefined. + * + *

The multimap supports mapping removal, which removes the corresponding mapping from the map. + * It does not support any operations which might add mappings, such as {@code put}, {@code + * putAll} or {@code replaceValues}. + * + *

The returned multimap will be serializable if the specified map is serializable. + * + * @param map the backing map for the returned multimap view + */ + public static SetMultimap forMap( + Map map) { + return new MapMultimap<>(map); + } + + /** @see Multimaps#forMap */ + private static class MapMultimap + extends AbstractMultimap implements SetMultimap, Serializable { + final Map map; + + MapMultimap(Map map) { + this.map = checkNotNull(map); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean containsKey(@CheckForNull Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(@CheckForNull Object value) { + return map.containsValue(value); + } + + @Override + public boolean containsEntry(@CheckForNull Object key, @CheckForNull Object value) { + return map.entrySet().contains(Maps.immutableEntry(key, value)); + } + + @Override + public Set get(@ParametricNullness final K key) { + return new Sets.ImprovedAbstractSet() { + @Override + public Iterator iterator() { + return new Iterator() { + int i; + + @Override + public boolean hasNext() { + return (i == 0) && map.containsKey(key); + } + + @Override + @ParametricNullness + public V next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + i++; + /* + * The cast is safe because of the containsKey check in hasNext(). (That means it's + * unsafe under concurrent modification, but all bets are off then, anyway.) + */ + return uncheckedCastNullableTToT(map.get(key)); + } + + @Override + public void remove() { + checkRemove(i == 1); + i = -1; + map.remove(key); + } + }; + } + + @Override + public int size() { + return map.containsKey(key) ? 1 : 0; + } + }; + } + + @Override + public boolean put(@ParametricNullness K key, @ParametricNullness V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @Override + public Set replaceValues(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(@CheckForNull Object key, @CheckForNull Object value) { + return map.entrySet().remove(Maps.immutableEntry(key, value)); + } + + @Override + public Set removeAll(@CheckForNull Object key) { + Set values = new HashSet(2); + if (!map.containsKey(key)) { + return values; + } + values.add(map.remove(key)); + return values; + } + + @Override + public void clear() { + map.clear(); + } + + @Override + Set createKeySet() { + return map.keySet(); + } + + @Override + Collection createValues() { + return map.values(); + } + + @Override + public Set> entries() { + return map.entrySet(); + } + + @Override + Collection> createEntries() { + throw new AssertionError("unreachable"); + } + + @Override + Multiset createKeys() { + return new Keys(this); + } + + @Override + Iterator> entryIterator() { + return map.entrySet().iterator(); + } + + @Override + Map> createAsMap() { + return new AsMap<>(this); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + private static final long serialVersionUID = 7845222491160860175L; + } + + /** + * Returns a view of a multimap where each value is transformed by a function. All other + * properties of the multimap, such as iteration order, are left intact. For example, the code: + * + *

{@code
+   * Multimap multimap =
+   *     ImmutableSetMultimap.of("a", 2, "b", -3, "b", -3, "a", 4, "c", 6);
+   * Function square = new Function() {
+   *     public String apply(Integer in) {
+   *       return Integer.toString(in * in);
+   *     }
+   * };
+   * Multimap transformed =
+   *     Multimaps.transformValues(multimap, square);
+   *   System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=[4, 16], b=[9, 9], c=[36]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys, and even null values + * provided that the function is capable of accepting null input. The transformed multimap might + * contain null values, if the function sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. The {@code equals} and {@code hashCode} methods of the returned multimap are meaningless, + * since there is not a definition of {@code equals} or {@code hashCode} for general collections, + * and {@code get()} will return a general {@code Collection} as opposed to a {@code List} or a + * {@code Set}. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the function will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@code Multimap.toString()}. For this to + * perform well, {@code function} should be fast. To avoid lazy evaluation when the returned + * multimap doesn't need to be a view, copy the returned multimap into a new multimap of your + * choosing. + * + * @since 7.0 + */ + public static < + K extends Object, V1 extends Object, V2 extends Object> + Multimap transformValues( + Multimap fromMultimap, final Function function) { + checkNotNull(function); + EntryTransformer transformer = Maps.asEntryTransformer(function); + return transformEntries(fromMultimap, transformer); + } + + /** + * Returns a view of a {@code ListMultimap} where each value is transformed by a function. All + * other properties of the multimap, such as iteration order, are left intact. For example, the + * code: + * + *

{@code
+   * ListMultimap multimap
+   *      = ImmutableListMultimap.of("a", 4, "a", 16, "b", 9);
+   * Function sqrt =
+   *     new Function() {
+   *       public Double apply(Integer in) {
+   *         return Math.sqrt((int) in);
+   *       }
+   *     };
+   * ListMultimap transformed = Multimaps.transformValues(map,
+   *     sqrt);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=[2.0, 4.0], b=[3.0]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys, and even null values + * provided that the function is capable of accepting null input. The transformed multimap might + * contain null values, if the function sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the function will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@code Multimap.toString()}. For this to + * perform well, {@code function} should be fast. To avoid lazy evaluation when the returned + * multimap doesn't need to be a view, copy the returned multimap into a new multimap of your + * choosing. + * + * @since 7.0 + */ + public static < + K extends Object, V1 extends Object, V2 extends Object> + ListMultimap transformValues( + ListMultimap fromMultimap, final Function function) { + checkNotNull(function); + EntryTransformer transformer = Maps.asEntryTransformer(function); + return transformEntries(fromMultimap, transformer); + } + + /** + * Returns a view of a multimap whose values are derived from the original multimap's entries. In + * contrast to {@link #transformValues}, this method's entry-transformation logic may depend on + * the key as well as the value. + * + *

All other properties of the transformed multimap, such as iteration order, are left intact. + * For example, the code: + * + *

{@code
+   * SetMultimap multimap =
+   *     ImmutableSetMultimap.of("a", 1, "a", 4, "b", -6);
+   * EntryTransformer transformer =
+   *     new EntryTransformer() {
+   *       public String transformEntry(String key, Integer value) {
+   *          return (value >= 0) ? key : "no" + key;
+   *       }
+   *     };
+   * Multimap transformed =
+   *     Multimaps.transformEntries(multimap, transformer);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=[a, a], b=[nob]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys and null values provided + * that the transformer is capable of accepting null inputs. The transformed multimap might + * contain null values if the transformer sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. The {@code equals} and {@code hashCode} methods of the returned multimap are meaningless, + * since there is not a definition of {@code equals} or {@code hashCode} for general collections, + * and {@code get()} will return a general {@code Collection} as opposed to a {@code List} or a + * {@code Set}. + * + *

The transformer is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the transformer will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned multimap + * doesn't need to be a view, copy the returned multimap into a new multimap of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of {@code + * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the + * transformed multimap. + * + * @since 7.0 + */ + public static < + K extends Object, V1 extends Object, V2 extends Object> + Multimap transformEntries( + Multimap fromMap, EntryTransformer transformer) { + return new TransformedEntriesMultimap<>(fromMap, transformer); + } + + /** + * Returns a view of a {@code ListMultimap} whose values are derived from the original multimap's + * entries. In contrast to {@link #transformValues(ListMultimap, Function)}, this method's + * entry-transformation logic may depend on the key as well as the value. + * + *

All other properties of the transformed multimap, such as iteration order, are left intact. + * For example, the code: + * + *

{@code
+   * Multimap multimap =
+   *     ImmutableMultimap.of("a", 1, "a", 4, "b", 6);
+   * EntryTransformer transformer =
+   *     new EntryTransformer() {
+   *       public String transformEntry(String key, Integer value) {
+   *         return key + value;
+   *       }
+   *     };
+   * Multimap transformed =
+   *     Multimaps.transformEntries(multimap, transformer);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {"a"=["a1", "a4"], "b"=["b6"]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys and null values provided + * that the transformer is capable of accepting null inputs. The transformed multimap might + * contain null values if the transformer sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. + * + *

The transformer is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the transformer will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned multimap + * doesn't need to be a view, copy the returned multimap into a new multimap of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of {@code + * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the + * transformed multimap. + * + * @since 7.0 + */ + public static < + K extends Object, V1 extends Object, V2 extends Object> + ListMultimap transformEntries( + ListMultimap fromMap, EntryTransformer transformer) { + return new TransformedEntriesListMultimap<>(fromMap, transformer); + } + + private static class TransformedEntriesMultimap< + K extends Object, V1 extends Object, V2 extends Object> + extends AbstractMultimap { + final Multimap fromMultimap; + final EntryTransformer transformer; + + TransformedEntriesMultimap( + Multimap fromMultimap, + final EntryTransformer transformer) { + this.fromMultimap = checkNotNull(fromMultimap); + this.transformer = checkNotNull(transformer); + } + + Collection transform(@ParametricNullness K key, Collection values) { + Function function = Maps.asValueToValueFunction(transformer, key); + if (values instanceof List) { + return Lists.transform((List) values, function); + } else { + return Collections2.transform(values, function); + } + } + + @Override + Map> createAsMap() { + return Maps.transformEntries( + fromMultimap.asMap(), + new EntryTransformer, Collection>() { + @Override + public Collection transformEntry(@ParametricNullness K key, Collection value) { + return transform(key, value); + } + }); + } + + @Override + public void clear() { + fromMultimap.clear(); + } + + @Override + public boolean containsKey(@CheckForNull Object key) { + return fromMultimap.containsKey(key); + } + + @Override + Collection> createEntries() { + return new Multimaps.Entries(); + } + + @Override + Iterator> entryIterator() { + return Iterators.transform( + fromMultimap.entries().iterator(), Maps.asEntryToEntryFunction(transformer)); + } + + @Override + public Collection get(@ParametricNullness final K key) { + return transform(key, fromMultimap.get(key)); + } + + @Override + public boolean isEmpty() { + return fromMultimap.isEmpty(); + } + + @Override + Set createKeySet() { + return fromMultimap.keySet(); + } + + @Override + Multiset createKeys() { + return fromMultimap.keys(); + } + + @Override + public boolean put(@ParametricNullness K key, @ParametricNullness V2 value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(@CheckForNull Object key, @CheckForNull Object value) { + return get((K) key).remove(value); + } + + @SuppressWarnings("unchecked") + @Override + public Collection removeAll(@CheckForNull Object key) { + return transform((K) key, fromMultimap.removeAll(key)); + } + + @Override + public Collection replaceValues(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + return fromMultimap.size(); + } + + @Override + Collection createValues() { + return Collections2.transform( + fromMultimap.entries(), Maps.asEntryToValueFunction(transformer)); + } + } + + private static final class TransformedEntriesListMultimap< + K extends Object, V1 extends Object, V2 extends Object> + extends TransformedEntriesMultimap implements ListMultimap { + + TransformedEntriesListMultimap( + ListMultimap fromMultimap, EntryTransformer transformer) { + super(fromMultimap, transformer); + } + + @Override + List transform(@ParametricNullness K key, Collection values) { + return Lists.transform((List) values, Maps.asValueToValueFunction(transformer, key)); + } + + @Override + public List get(@ParametricNullness K key) { + return transform(key, fromMultimap.get(key)); + } + + @SuppressWarnings("unchecked") + @Override + public List removeAll(@CheckForNull Object key) { + return transform((K) key, fromMultimap.removeAll(key)); + } + + @Override + public List replaceValues(@ParametricNullness K key, Iterable values) { + throw new UnsupportedOperationException(); + } + } + + /** + * Creates an index {@code ImmutableListMultimap} that contains the results of applying a + * specified function to each item in an {@code Iterable} of values. Each value will be stored as + * a value in the resulting multimap, yielding a multimap with the same size as the input + * iterable. The key used to store that value in the multimap will be the result of calling the + * function on that value. The resulting multimap is created as an immutable snapshot. In the + * returned multimap, keys appear in the order they are first encountered, and the values + * corresponding to each key appear in the same order as they are encountered. + * + *

For example, + * + *

{@code
+   * List badGuys =
+   *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
+   * Function stringLengthFunction = ...;
+   * Multimap index =
+   *     Multimaps.index(badGuys, stringLengthFunction);
+   * System.out.println(index);
+   * }
+ * + *

prints + * + *

{@code
+   * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
+   * }
+ * + *

The returned multimap is serializable if its keys and values are all serializable. + * + * @param values the values to use when constructing the {@code ImmutableListMultimap} + * @param keyFunction the function used to produce the key for each value + * @return {@code ImmutableListMultimap} mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code + * keyFunction} produces {@code null} for any key + */ + public static ImmutableListMultimap index( + Iterable values, Function keyFunction) { + return index(values.iterator(), keyFunction); + } + + /** + * Creates an index {@code ImmutableListMultimap} that contains the results of applying a + * specified function to each item in an {@code Iterator} of values. Each value will be stored as + * a value in the resulting multimap, yielding a multimap with the same size as the input + * iterator. The key used to store that value in the multimap will be the result of calling the + * function on that value. The resulting multimap is created as an immutable snapshot. In the + * returned multimap, keys appear in the order they are first encountered, and the values + * corresponding to each key appear in the same order as they are encountered. + * + *

For example, + * + *

{@code
+   * List badGuys =
+   *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
+   * Function stringLengthFunction = ...;
+   * Multimap index =
+   *     Multimaps.index(badGuys.iterator(), stringLengthFunction);
+   * System.out.println(index);
+   * }
+ * + *

prints + * + *

{@code
+   * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
+   * }
+ * + *

The returned multimap is serializable if its keys and values are all serializable. + * + * @param values the values to use when constructing the {@code ImmutableListMultimap} + * @param keyFunction the function used to produce the key for each value + * @return {@code ImmutableListMultimap} mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code + * keyFunction} produces {@code null} for any key + * @since 10.0 + */ + public static ImmutableListMultimap index( + Iterator values, Function keyFunction) { + checkNotNull(keyFunction); + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + while (values.hasNext()) { + V value = values.next(); + checkNotNull(value, values); + builder.put(keyFunction.apply(value), value); + } + return builder.build(); + } + + static class Keys + extends AbstractMultiset { + @Weak final Multimap multimap; + + Keys(Multimap multimap) { + this.multimap = multimap; + } + + @Override + Iterator> entryIterator() { + return new TransformedIterator>, Multiset.Entry>( + multimap.asMap().entrySet().iterator()) { + @Override + Multiset.Entry transform(final Map.Entry> backingEntry) { + return new Multisets.AbstractEntry() { + @Override + @ParametricNullness + public K getElement() { + return backingEntry.getKey(); + } + + @Override + public int getCount() { + return backingEntry.getValue().size(); + } + }; + } + }; + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.map(multimap.entries().spliterator(), Map.Entry::getKey); + } + + @Override + public void forEach(Consumer consumer) { + checkNotNull(consumer); + multimap.entries().forEach(entry -> consumer.accept(entry.getKey())); + } + + @Override + int distinctElements() { + return multimap.asMap().size(); + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + public boolean contains(@CheckForNull Object element) { + return multimap.containsKey(element); + } + + @Override + public Iterator iterator() { + return Maps.keyIterator(multimap.entries().iterator()); + } + + @Override + public int count(@CheckForNull Object element) { + Collection values = Maps.safeGet(multimap.asMap(), element); + return (values == null) ? 0 : values.size(); + } + + @Override + public int remove(@CheckForNull Object element, int occurrences) { + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(element); + } + + Collection values = Maps.safeGet(multimap.asMap(), element); + + if (values == null) { + return 0; + } + + int oldCount = values.size(); + if (occurrences >= oldCount) { + values.clear(); + } else { + Iterator iterator = values.iterator(); + for (int i = 0; i < occurrences; i++) { + iterator.next(); + iterator.remove(); + } + } + return oldCount; + } + + @Override + public void clear() { + multimap.clear(); + } + + @Override + public Set elementSet() { + return multimap.keySet(); + } + + @Override + Iterator elementIterator() { + throw new AssertionError("should never be called"); + } + } + + /** A skeleton implementation of {@link Multimap#entries()}. */ + abstract static class Entries + extends AbstractCollection> { + abstract Multimap multimap(); + + @Override + public int size() { + return multimap().size(); + } + + @Override + public boolean contains(@CheckForNull Object o) { + if (o instanceof Map.Entry) { + Entry entry = (Entry) o; + return multimap().containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public boolean remove(@CheckForNull Object o) { + if (o instanceof Map.Entry) { + Entry entry = (Entry) o; + return multimap().remove(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public void clear() { + multimap().clear(); + } + } + + /** A skeleton implementation of {@link Multimap#asMap()}. */ + static final class AsMap + extends Maps.ViewCachingAbstractMap> { + @Weak private final Multimap multimap; + + AsMap(Multimap multimap) { + this.multimap = checkNotNull(multimap); + } + + @Override + public int size() { + return multimap.keySet().size(); + } + + @Override + protected Set>> createEntrySet() { + return new EntrySet(); + } + + void removeValuesForKey(@CheckForNull Object key) { + multimap.keySet().remove(key); + } + + @WeakOuter + class EntrySet extends Maps.EntrySet> { + @Override + Map> map() { + return AsMap.this; + } + + @Override + public Iterator>> iterator() { + return Maps.asMapEntryIterator( + multimap.keySet(), + new Function>() { + @Override + public Collection apply(@ParametricNullness K key) { + return multimap.get(key); + } + }); + } + + @Override + public boolean remove(@CheckForNull Object o) { + if (!contains(o)) { + return false; + } + // requireNonNull is safe because of the contains check. + Entry entry = requireNonNull((Entry) o); + removeValuesForKey(entry.getKey()); + return true; + } + } + + @SuppressWarnings("unchecked") + @Override + @CheckForNull + public Collection get(@CheckForNull Object key) { + return containsKey(key) ? multimap.get((K) key) : null; + } + + @Override + @CheckForNull + public Collection remove(@CheckForNull Object key) { + return containsKey(key) ? multimap.removeAll(key) : null; + } + + @Override + public Set keySet() { + return multimap.keySet(); + } + + @Override + public boolean isEmpty() { + return multimap.isEmpty(); + } + + @Override + public boolean containsKey(@CheckForNull Object key) { + return multimap.containsKey(key); + } + + @Override + public void clear() { + multimap.clear(); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 11.0 + */ + public static Multimap filterKeys( + Multimap unfiltered, final Predicate keyPredicate) { + if (unfiltered instanceof SetMultimap) { + return filterKeys((SetMultimap) unfiltered, keyPredicate); + } else if (unfiltered instanceof ListMultimap) { + return filterKeys((ListMultimap) unfiltered, keyPredicate); + } else if (unfiltered instanceof FilteredKeyMultimap) { + FilteredKeyMultimap prev = (FilteredKeyMultimap) unfiltered; + return new FilteredKeyMultimap<>( + prev.unfiltered, Predicates.and(prev.keyPredicate, keyPredicate)); + } else if (unfiltered instanceof FilteredMultimap) { + FilteredMultimap prev = (FilteredMultimap) unfiltered; + return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); + } else { + return new FilteredKeyMultimap<>(unfiltered, keyPredicate); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + public static + SetMultimap filterKeys( + SetMultimap unfiltered, final Predicate keyPredicate) { + if (unfiltered instanceof FilteredKeySetMultimap) { + FilteredKeySetMultimap prev = (FilteredKeySetMultimap) unfiltered; + return new FilteredKeySetMultimap<>( + prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); + } else if (unfiltered instanceof FilteredSetMultimap) { + FilteredSetMultimap prev = (FilteredSetMultimap) unfiltered; + return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); + } else { + return new FilteredKeySetMultimap<>(unfiltered, keyPredicate); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + public static + ListMultimap filterKeys( + ListMultimap unfiltered, final Predicate keyPredicate) { + if (unfiltered instanceof FilteredKeyListMultimap) { + FilteredKeyListMultimap prev = (FilteredKeyListMultimap) unfiltered; + return new FilteredKeyListMultimap<>( + prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); + } else { + return new FilteredKeyListMultimap<>(unfiltered, keyPredicate); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose values satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a value that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose value satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code valuePredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 11.0 + */ + public static + Multimap filterValues( + Multimap unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose values satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a value that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose value satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code valuePredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + public static + SetMultimap filterValues( + SetMultimap unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} that satisfy a predicate. The + * returned multimap is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key/value pair that + * doesn't satisfy the predicate, multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. + * + * @since 11.0 + */ + public static + Multimap filterEntries( + Multimap unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + if (unfiltered instanceof SetMultimap) { + return filterEntries((SetMultimap) unfiltered, entryPredicate); + } + return (unfiltered instanceof FilteredMultimap) + ? filterFiltered((FilteredMultimap) unfiltered, entryPredicate) + : new FilteredEntryMultimap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} that satisfy a predicate. The + * returned multimap is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key/value pair that + * doesn't satisfy the predicate, multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. + * + * @since 14.0 + */ + public static + SetMultimap filterEntries( + SetMultimap unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof FilteredSetMultimap) + ? filterFiltered((FilteredSetMultimap) unfiltered, entryPredicate) + : new FilteredEntrySetMultimap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Support removal operations when filtering a filtered multimap. Since a filtered multimap has + * iterators that don't support remove, passing one to the FilteredEntryMultimap constructor would + * lead to a multimap whose removal operations would fail. This method combines the predicates to + * avoid that problem. + */ + private static + Multimap filterFiltered( + FilteredMultimap multimap, Predicate> entryPredicate) { + Predicate> predicate = + Predicates.>and(multimap.entryPredicate(), entryPredicate); + return new FilteredEntryMultimap<>(multimap.unfiltered(), predicate); + } + + /** + * Support removal operations when filtering a filtered multimap. Since a filtered multimap has + * iterators that don't support remove, passing one to the FilteredEntryMultimap constructor would + * lead to a multimap whose removal operations would fail. This method combines the predicates to + * avoid that problem. + */ + private static + SetMultimap filterFiltered( + FilteredSetMultimap multimap, Predicate> entryPredicate) { + Predicate> predicate = + Predicates.>and(multimap.entryPredicate(), entryPredicate); + return new FilteredEntrySetMultimap<>(multimap.unfiltered(), predicate); + } + + static boolean equalsImpl(Multimap multimap, @CheckForNull Object object) { + if (object == multimap) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return multimap.asMap().equals(that.asMap()); + } + return false; + } + + // TODO(jlevy): Create methods that filter a SortedSetMultimap. +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapsEntries.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapsEntries.java new file mode 100644 index 0000000..b5baddc --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultimapsEntries.java @@ -0,0 +1,39 @@ +package org.xbib.datastructures.multi; + +import java.util.AbstractCollection; +import java.util.Map; +import org.xbib.datastructures.api.Multimap; + +/** A skeleton implementation of {@link Multimap#entries()}. */ +abstract class MultimapsEntries + extends AbstractCollection> { + abstract Multimap multimap(); + + @Override + public int size() { + return multimap().size(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + return multimap().containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + return multimap().remove(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public void clear() { + multimap().clear(); + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsAbstractEntry.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsAbstractEntry.java new file mode 100644 index 0000000..93cc676 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsAbstractEntry.java @@ -0,0 +1,47 @@ +package org.xbib.datastructures.multi; + +import java.util.Objects; +import org.xbib.datastructures.api.Multiset; + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} methods of {@link + * Multiset.Entry}. + */ +public abstract class MultisetsAbstractEntry implements Multiset.Entry { + /** + * Indicates whether an object equals this entry, following the behavior specified in {@link + * Multiset.Entry#equals}. + */ + @Override + public boolean equals(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry that = (Multiset.Entry) object; + return this.getCount() == that.getCount() + && Objects.equals(this.getElement(), that.getElement()); + } + return false; + } + + /** + * Return this entry's hash code, following the behavior specified in {@link + * Multiset.Entry#hashCode}. + */ + @Override + public int hashCode() { + E e = getElement(); + return ((e == null) ? 0 : e.hashCode()) ^ getCount(); + } + + /** + * Returns a string representation of this multiset entry. The string representation consists of + * the associated element if the associated count is one, and otherwise the associated element + * followed by the characters " x " (space, x and space) followed by the count. Elements and + * counts are converted to strings as by {@code String.valueOf}. + */ + @Override + public String toString() { + String text = String.valueOf(getElement()); + int n = getCount(); + return (n == 1) ? text : (text + " x " + n); + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsImmutableEntry.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsImmutableEntry.java new file mode 100644 index 0000000..3022f50 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/MultisetsImmutableEntry.java @@ -0,0 +1,33 @@ +package org.xbib.datastructures.multi; + +import java.io.Serializable; + +public class MultisetsImmutableEntry extends MultisetsAbstractEntry + implements Serializable { + private final E element; + private final int count; + + MultisetsImmutableEntry(E element, int count) { + this.element = element; + this.count = count; + if (count < 0) { + throw new IllegalArgumentException("count"); + } + } + + @Override + public final E getElement() { + return element; + } + + @Override + public final int getCount() { + return count; + } + + public MultisetsImmutableEntry nextInBucket() { + return null; + } + + private static final long serialVersionUID = 0; +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/Serialization.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/Serialization.java new file mode 100644 index 0000000..75cb662 --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/Serialization.java @@ -0,0 +1,204 @@ +package org.xbib.datastructures.multi; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; +import org.xbib.datastructures.api.Multimap; +import org.xbib.datastructures.api.Multiset; + +/** + * Provides static methods for serializing collection classes. + * + *

This class assists the implementation of collection classes. Do not use this class to + * serialize collections that are defined elsewhere. + */ +final class Serialization { + private Serialization() {} + + /** + * Reads a count corresponding to a serialized map, multiset, or multimap. It returns the size of + * a map serialized by {@link #writeMap(Map, ObjectOutputStream)}, the number of distinct elements + * in a multiset serialized by {@link #writeMultiset(Multiset, ObjectOutputStream)}, or the number + * of distinct keys in a multimap serialized by {@link #writeMultimap(Multimap, + * ObjectOutputStream)}. + */ + static int readCount(ObjectInputStream stream) throws IOException { + return stream.readInt(); + } + + /** + * Stores the contents of a map in an output stream, as part of serialization. It does not support + * concurrent maps whose content may change while the method is running. + * + *

The serialized output consists of the number of entries, first key, first value, second key, + * second value, and so on. + */ + static void writeMap( + Map map, ObjectOutputStream stream) throws IOException { + stream.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + /** + * Populates a map by reading an input stream, as part of deserialization. See {@link #writeMap} + * for the data format. + */ + static void populateMap( + Map map, ObjectInputStream stream) throws IOException, ClassNotFoundException { + int size = stream.readInt(); + populateMap(map, stream, size); + } + + /** + * Populates a map by reading an input stream, as part of deserialization. See {@link #writeMap} + * for the data format. The size is determined by a prior call to {@link #readCount}. + */ + static void populateMap( + Map map, ObjectInputStream stream, int size) + throws IOException, ClassNotFoundException { + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMap + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeMap + V value = (V) stream.readObject(); + map.put(key, value); + } + } + + /** + * Stores the contents of a multiset in an output stream, as part of serialization. It does not + * support concurrent multisets whose content may change while the method is running. + * + *

The serialized output consists of the number of distinct elements, the first element, its + * count, the second element, its count, and so on. + */ + static void writeMultiset( + Multiset multiset, ObjectOutputStream stream) throws IOException { + int entryCount = multiset.entrySet().size(); + stream.writeInt(entryCount); + for (Multiset.Entry entry : multiset.entrySet()) { + stream.writeObject(entry.getElement()); + stream.writeInt(entry.getCount()); + } + } + + /** + * Populates a multiset by reading an input stream, as part of deserialization. See {@link + * #writeMultiset} for the data format. + */ + static void populateMultiset( + Multiset multiset, ObjectInputStream stream) throws IOException, ClassNotFoundException { + int distinctElements = stream.readInt(); + populateMultiset(multiset, stream, distinctElements); + } + + /** + * Populates a multiset by reading an input stream, as part of deserialization. See {@link + * #writeMultiset} for the data format. The number of distinct elements is determined by a prior + * call to {@link #readCount}. + */ + static void populateMultiset( + Multiset multiset, ObjectInputStream stream, int distinctElements) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctElements; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultiset + E element = (E) stream.readObject(); + int count = stream.readInt(); + multiset.add(element, count); + } + } + + /** + * Stores the contents of a multimap in an output stream, as part of serialization. It does not + * support concurrent multimaps whose content may change while the method is running. The {@link + * Multimap#asMap} view determines the ordering in which data is written to the stream. + * + *

The serialized output consists of the number of distinct keys, and then for each distinct + * key: the key, the number of values for that key, and the key's values. + */ + static void writeMultimap( + Multimap multimap, ObjectOutputStream stream) throws IOException { + stream.writeInt(multimap.asMap().size()); + for (Map.Entry> entry : multimap.asMap().entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeInt(entry.getValue().size()); + for (V value : entry.getValue()) { + stream.writeObject(value); + } + } + } + + /** + * Populates a multimap by reading an input stream, as part of deserialization. See {@link + * #writeMultimap} for the data format. + */ + static void populateMultimap( + Multimap multimap, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int distinctKeys = stream.readInt(); + populateMultimap(multimap, stream, distinctKeys); + } + + /** + * Populates a multimap by reading an input stream, as part of deserialization. See {@link + * #writeMultimap} for the data format. The number of distinct keys is determined by a prior call + * to {@link #readCount}. + */ + static void populateMultimap( + Multimap multimap, ObjectInputStream stream, int distinctKeys) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctKeys; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + K key = (K) stream.readObject(); + Collection values = multimap.get(key); + int valueCount = stream.readInt(); + for (int j = 0; j < valueCount; j++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + V value = (V) stream.readObject(); + values.add(value); + } + } + } + + // Secret sauce for setting final fields; don't make it public. + static FieldSetter getFieldSetter(Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + return new FieldSetter<>(field); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); // programmer error + } + } + + // Secret sauce for setting final fields; don't make it public. + static final class FieldSetter { + private final Field field; + + private FieldSetter(Field field) { + this.field = field; + field.setAccessible(true); + } + + void set(T instance, Object value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + + void set(T instance, int value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + } +} diff --git a/datastructures-multi/src/main/java/org/xbib/datastructures/multi/TransformedIterator.java b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/TransformedIterator.java new file mode 100644 index 0000000..e916a7f --- /dev/null +++ b/datastructures-multi/src/main/java/org/xbib/datastructures/multi/TransformedIterator.java @@ -0,0 +1,33 @@ +package org.xbib.datastructures.multi; + +import java.util.Iterator; +import java.util.Objects; + +/** + * An iterator that transforms a backing iterator; for internal use. + */ +abstract class TransformedIterator + implements Iterator { + final Iterator backingIterator; + + TransformedIterator(Iterator backingIterator) { + this.backingIterator = Objects.requireNonNull(backingIterator); + } + + abstract T transform(F from); + + @Override + public final boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override + public final T next() { + return transform(backingIterator.next()); + } + + @Override + public final void remove() { + backingIterator.remove(); + } +} diff --git a/settings.gradle b/settings.gradle index eb31ba3..2b0edcb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -46,14 +46,15 @@ dependencyResolutionManagement { } } +include 'config' include 'datastructures-api' -include 'datastructures-interpolation' -include 'datastructures-io' include 'datastructures-charset' include 'datastructures-common' -include 'datastructures-xml' include 'datastructures-csv' -include 'datastructures-xslx' +include 'datastructures-graphql' +include 'datastructures-immutable' +include 'datastructures-interpolation' +include 'datastructures-io' include 'datastructures-json-dsl' include 'datastructures-json-flat' include 'datastructures-json-iterator' @@ -62,15 +63,17 @@ include 'datastructures-json-mini' include 'datastructures-json-minimal' include 'datastructures-json-noggit' include 'datastructures-json-simple' -include 'datastructures-queue-tape' -include 'datastructures-tiny' include 'datastructures-json-tiny' -include 'datastructures-yaml-tiny' -include 'datastructures-validation' -include 'datastructures-trie' +include 'datastructures-multi' +include 'datastructures-queue-tape' include 'datastructures-raml' +include 'datastructures-tiny' +include 'datastructures-trie' +include 'datastructures-validation' +include 'datastructures-xml' +include 'datastructures-xslx' +include 'datastructures-yaml-tiny' include 'settings-api' include 'settings-datastructures' include 'settings-datastructures-json' include 'settings-datastructures-yaml' -include 'config'