start work of immutable and multi subprojects

This commit is contained in:
Jörg Prante 2024-03-06 22:43:14 +01:00
parent 711ff4a6a9
commit a79c8a8e9e
91 changed files with 21957 additions and 10 deletions

View file

@ -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<K extends Object, V extends Object> extends Map<K, V> {
/**
* {@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.
*
* <p>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.
*
* <p><b>Warning:</b> 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}
*
* <p><b>Warning:</b> 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<? extends K, ? extends V> map);
/**
* {@inheritDoc}
*
* <p>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<V> 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.
*
* <p><b>Note:</b>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<V, K> inverse();
}

View file

@ -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.
*
* <p>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<K extends Object, V extends Object>
extends Multimap<K, V> {
/**
* {@inheritDoc}
*
* <p>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<V> get(K key);
/**
* {@inheritDoc}
*
* <p>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<V> removeAll(Object key);
/**
* {@inheritDoc}
*
* <p>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<V> replaceValues(K key, Iterable<? extends V> values);
/**
* {@inheritDoc}
*
* <p><b>Note:</b> The returned map's values are guaranteed to be of type {@link List}.
*/
@Override
Map<K, Collection<V>> asMap();
/**
* Compares the specified object to this multimap for equality.
*
* <p>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.
*
* <p>An empty {@code ListMultimap} is equal to any other empty {@code Multimap}, including an
* empty {@code SetMultimap}.
*/
@Override
boolean equals(Object obj);
}

View file

@ -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 <i>multiple</i> values. You can visualize the contents of a multimap either as a
* map from keys to <i>nonempty</i> collections of values:
*
* <ul>
* <li>a 1, 2
* <li>b 3
* </ul>
* <p>
* ... or as a single "flattened" collection of key-value pairs:
*
* <ul>
* <li>a 1
* <li>a 2
* <li>b 3
* </ul>
*
* <p><b>Important:</b> although the first interpretation resembles how most multimaps are
* <i>implemented</i>, the design of the {@code Multimap} API is based on the <i>second</i> 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<K, Collection<V>>} in the first place).
*
* <h3>Example</h3>
*
* <p>The following code:
*
* <pre>{@code
* ListMultimap<String, String> multimap = ArrayListMultimap.create();
* for (President pres : US_PRESIDENTS_IN_ORDER) {
* multimap.put(pres.firstName(), pres.lastName());
* }
* for (String firstName : multimap.keySet()) {
* List<String> lastNames = multimap.get(firstName);
* out.println(firstName + ": " + lastNames);
* }
* }</pre>
* <p>
* ... produces output such as:
*
* <pre>{@code
* Zachary: [Taylor]
* John: [Adams, Adams, Tyler, Kennedy] // Remember, Quincy!
* George: [Washington, Bush, Bush]
* Grover: [Cleveland, Cleveland] // Two, non-consecutive terms, rep'ing NJ!
* ...
* }</pre>
*
* <h3>Views</h3>
*
* <p>Much of the power of the multimap API comes from the <i>view collections</i> it provides.
* These always reflect the latest state of the multimap itself. When they support modification, the
* changes are <i>write-through</i> (they automatically update the backing multimap). These view
* collections are:
*
* <ul>
* <li>{@link #asMap}, mentioned above
* <li>{@link #keys}, {@link #keySet}, {@link #values}, {@link #entries}, which are similar to the
* corresponding view collections of {@link Map}
* <li>and, notably, even the collection returned by {@link #get get(key)} is an active view of
* the values corresponding to {@code key}
* </ul>
*
* <p>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 <i>not</i> views.
*
* <h3>Subinterfaces</h3>
*
* <p>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.
*
* <p>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.
*
* <p><b>Warning:</b> 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.
*
* <h3>Comparison to a map of collections</h3>
*
* <p>Multimaps are commonly used in places where a {@code Map<K, Collection<V>>} would otherwise
* have appeared. The differences include:
*
* <ul>
* <li>There is no need to populate an empty collection before adding an entry with {@link #put
* put}.
* <li>{@code get} never returns {@code null}, only an empty collection.
* <li>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
* <i>removing</i> that key from the multimap.
* <li>The total entry count is available as {@link #size}.
* <li>Many complex operations become easier; for example, {@code
* Collections.min(multimap.values())} finds the smallest value across all keys.
* </ul>
*
* <h3>Other Notes</h3>
*
* <p>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}.
*
* <p>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<K, V> {
// Query Operations
/**
* Returns the number of key-value pairs in this multimap.
*
* <p><b>Note:</b> this method does not return the number of <i>distinct keys</i> 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.
*
* <p>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):
*
* <pre>{@code
* for (V value : values) {
* put(key, value);
* }
* }</pre>
*
* <p>In particular, this is a no-op if {@code values} is empty.
*
* @return {@code true} if the multimap changed
*/
boolean putAll(K key, Iterable<? extends V> 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<? extends K, ? extends V> multimap);
/**
* Stores a collection of values with the same key, replacing any existing values for that key.
*
* <p>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 <i>may</i> be modifiable, but updating it will have
* no effect on the multimap.
*/
Collection<V> replaceValues(K key, Iterable<? extends V> values);
/**
* Removes all values associated with the key {@code key}.
*
* <p>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 <i>may</i> be
* modifiable, but updating it will have no effect on the multimap.
*/
Collection<V> 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}.
*
* <p>Changes to the returned collection will update the underlying multimap, and vice versa.
*/
Collection<V> get(K key);
/**
* Returns a view collection of all <i>distinct</i> 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.
*
* <p>Changes to the returned set will update the underlying multimap, and vice versa. However,
* <i>adding</i> to the returned set is not possible.
*/
Set<K> keySet();
/**
* Returns a view collection containing the key from each key-value pair in this multimap,
* <i>without</i> collapsing duplicates. This collection has the same size as this multimap, and
* {@code keys().count(k) == get(k).size()} for all {@code k}.
*
* <p>Changes to the returned multiset will update the underlying multimap, and vice versa.
* However, <i>adding</i> to the returned collection is not possible.
*/
Multiset<K> keys();
/**
* Returns a view collection containing the <i>value</i> from each key-value pair contained in
* this multimap, without collapsing duplicates (so {@code values().size() == size()}).
*
* <p>Changes to the returned collection will update the underlying multimap, and vice versa.
* However, <i>adding</i> to the returned collection is not possible.
*/
Collection<V> values();
/**
* Returns a view collection of all key-value pairs contained in this multimap, as {@link Entry}
* instances.
*
* <p>Changes to the returned collection or the entries it contains will update the underlying
* multimap, and vice versa. However, <i>adding</i> to the returned collection is not possible.
*/
Collection<Entry<K, V>> 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.
*
* <p>To loop over all keys and their associated value collections, write {@code
* Multimaps.asMap(multimap).forEach((key, valueCollection) -> action())}.
*/
default void forEach(BiConsumer<? super K, ? super V> 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.
*
* <p>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<K, Collection<V>> 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.
*
* <p>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.
*
* <p>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.
*
* <p>The hash code of a multimap is defined as the hash code of the map view, as returned by
* {@link Multimap#asMap}.
*
* <p>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();
}

View file

@ -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 <i>bag</i>.
*
* <p>Elements of a multiset that are equal to one another are referred to as <i>occurrences</i> of
* the same single element. The total number of occurrences of an element in a multiset is called
* the <i>count</i> 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.
*
* <p>{@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.
*
* <p>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<? extends E>)}, 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.
*
* <p>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.
*
* <p>A multiset uses {@link Object#equals} to determine whether two instances should be considered
* "the same," <i>unless specified otherwise</i> by the implementation.
*
* <p><b>Warning:</b> 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<E extends Object> extends Collection<E> {
// Query Operations
/**
* Returns the total number of all occurrences of all elements in this multiset.
*
* <p><b>Note:</b> this method does not return the number of <i>distinct elements</i> 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 <i>count</i> 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.
*
* <p>This method refines {@link Collection#add}, which only <i>ensures</i> 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.
*
* <p>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 <i>single</i> occurrence of the specified element from this multiset, if present.
*
* <p>This method refines {@link Collection#remove} to further specify that it <b>may not</b>
* throw an exception in response to {@code element} being null or of the wrong type.
*
* <p>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.
*
* <p>If the element set supports any removal operations, these necessarily cause <b>all</b>
* 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.
*
* <p>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<E> 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.
*
* <p>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<Entry<E>> 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<E extends Object> {
/**
* 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}
*
* <p>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:
*
* <pre>{@code
* Objects.equal(a.getElement(), b.getElement())
* && a.getCount() == b.getCount()
* }</pre>
*/
@Override
boolean equals(Object o);
/**
* {@inheritDoc}
*
* <p>The hash code of a multiset entry for element {@code element} and count {@code count} is
* defined as:
*
* <pre>{@code
* ((element == null) ? 0 : element.hashCode()) ^ count
* }</pre>
*/
@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<? super E> 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
*
* <pre>{@code
* ((element == null) ? 0 : element.hashCode()) ^ count(element)
* }</pre>
*
* <p>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}
*
* <p>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}
*
* <p>Elements that occur multiple times in the multiset will appear multiple times in this
* iterator, though not necessarily sequentially.
*/
@Override
Iterator<E> iterator();
/**
* Determines whether this multiset contains the specified element.
*
* <p>This method refines {@link Collection#contains} to further specify that it <b>may not</b>
* 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.
*
* <p>This method refines {@link Collection#containsAll} to further specify that it <b>may not</b>
* throw an exception in response to any of {@code elements} being null or of the wrong type.
*
* <p><b>Note:</b> 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}
*
* <p><b>Note:</b> 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}.
*
* <p>This method refines {@link Collection#removeAll} to further specify that it <b>may not</b>
* throw an exception in response to any of {@code elements} being null or of the wrong type.
*/
@Override
boolean removeAll(Collection<?> c);
/**
* {@inheritDoc}
*
* <p><b>Note:</b> 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}.
*
* <p>This method refines {@link Collection#retainAll} to further specify that it <b>may not</b>
* throw an exception in response to any of {@code elements} being null or of the wrong type.
*/
@Override
boolean retainAll(Collection<?> c);
/**
* {@inheritDoc}
*
* <p>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<? super E> 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<E> spliterator() {
return spliteratorImpl(this);
}
private static <E extends Object> Spliterator<E> spliteratorImpl(Multiset<E> multiset) {
Spliterator<Entry<E>> 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 <InElementT extends Object, OutElementT extends Object> Spliterator<OutElementT> flatMap(
Spliterator<InElementT> fromSpliterator,
Function<? super InElementT, Spliterator<OutElementT>> 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<InElementT extends Object, OutElementT extends Object>
extends FlatMapSpliterator<InElementT, OutElementT, Spliterator<OutElementT>> {
FlatMapSpliteratorOfObject(Spliterator<OutElementT> prefix,
Spliterator<InElementT> from,
Function<? super InElementT, Spliterator<OutElementT>> 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<OutElementT>>
implements Spliterator<OutElementT> {
/**
* Factory for constructing {@link FlatMapSpliterator} instances.
*/
@FunctionalInterface
interface Factory<InElementT extends Object, OutSpliteratorT extends Spliterator<?>> {
OutSpliteratorT newFlatMapSpliterator(OutSpliteratorT prefix,
Spliterator<InElementT> fromSplit,
Function<? super InElementT, OutSpliteratorT> function,
int splitCharacteristics,
long estSplitSize);
}
OutSpliteratorT prefix;
final Spliterator<InElementT> from;
final Function<? super InElementT, OutSpliteratorT> function;
final Factory<InElementT, OutSpliteratorT> factory;
int characteristics;
long estimatedSize;
FlatMapSpliterator(OutSpliteratorT prefix,
Spliterator<InElementT> from,
Function<? super InElementT, OutSpliteratorT> function,
Factory<InElementT, OutSpliteratorT> 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<? super OutElementT> 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<? super OutElementT> action) {
if (prefix != null) {
prefix.forEachRemaining(action);
prefix = null;
}
from.forEachRemaining(
fromElement -> {
Spliterator<OutElementT> elements = function.apply(fromElement);
if (elements != null) {
elements.forEachRemaining(action);
}
});
estimatedSize = 0;
}
@Override
public final OutSpliteratorT trySplit() {
Spliterator<InElementT> 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;
}
}
}

View file

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>Since the value collections are sets, the behavior of a {@code SetMultimap} is not specified
* if key <em>or value</em> 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}.
*
* <p><b>Warning:</b> Do not modify either a key <i>or a value</i> of a {@code SetMultimap} in a way
* that affects its {@link Object#equals} behavior. Undefined behavior and bugs will result.
*
*/
public interface SetMultimap<K extends Object, V extends Object>
extends Multimap<K, V> {
/**
* {@inheritDoc}
*
* <p>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<V> get(K key);
/**
* {@inheritDoc}
*
* <p>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<V> removeAll(Object key);
/**
* {@inheritDoc}
*
* <p>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.
*
* <p>Any duplicates in {@code values} will be stored in the multimap once.
*/
@Override
Set<V> replaceValues(K key, Iterable<? extends V> values);
/**
* {@inheritDoc}
*
* <p>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<Entry<K, V>> entries();
/**
* {@inheritDoc}
*
* <p><b>Note:</b> The returned map's values are guaranteed to be of type {@link Set}.
*/
@Override
Map<K, Collection<V>> asMap();
/**
* Compares the specified object to this multimap for equality.
*
* <p>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.
*
* <p>An empty {@code SetMultimap} is equal to any other empty {@code Multimap}, including an
* empty {@code ListMultimap}.
*/
@Override
boolean equals(Object obj);
}

View file

@ -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<T extends Object> extends Iterable<T> {
/**
* 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<? super T> 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<T> iterator();
}

View file

@ -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.
*
* <p>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.
*
* <p><b>Warning:</b> As in all {@link SetMultimap}s, do not modify either a key <i>or a value</i>
* 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<K extends Object, V extends Object>
extends SetMultimap<K, V> {
// 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.
*
* <p>Changes to the returned collection will update the underlying multimap, and vice versa.
*
* <p>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<V> get(K key);
/**
* Removes all values associated with a given key.
*
* <p>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<V> removeAll(Object key);
/**
* Stores a collection of values with the same key, replacing any existing values for that key.
*
* <p>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.
*
* <p>Any duplicates in {@code values} will be stored in the multimap once.
*/
@Override
SortedSet<V> replaceValues(K key, Iterable<? extends V> 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}.
*
* <p>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.
*
* <p><b>Note:</b> 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<K, SortedSet<V>>}, call {@link
* Multimaps#asMap(SortedSetMultimap)} instead. <b>However</b>, the returned map <i>itself</i> is
* not necessarily a {@link SortedMap}: A {@code SortedSetMultimap} must expose the <i>values</i>
* for a given key in sorted order, but it need not expose the <i>keys</i> in sorted order.
* Individual {@code SortedSetMultimap} implementations, like those built with {@link
* MultimapBuilder#treeKeys()}, may make additional guarantees.
*/
@Override
Map<K, Collection<V>> asMap();
/**
* Returns the comparator that orders the multimap values, with {@code null} indicating that
* natural ordering is used.
*/
Comparator<? super V> valueComparator();
}

View file

@ -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

View file

@ -0,0 +1,3 @@
dependencies {
api project(':datastructures-api')
}

View file

@ -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;
}

View file

@ -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<E extends Object>
extends UnmodifiableListIterator<E> {
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);
}
}
}

View file

@ -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<K extends Object, V extends Object>
implements Entry<K, V> {
@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();
}
}

View file

@ -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<E> extends ImmutableList<E> {
abstract ImmutableCollection<E> 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();
}
}

View file

@ -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<K, V> extends ImmutableMap<K, V>
implements BiMap<K, V> {
/**
* 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 <T extends Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> 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 <T extends Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction,
BinaryOperator<V> 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.
*
* <p>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 <T extends Object, K, V> Collector<T, ?, ImmutableBiMap<K, V>> toImmutableBiMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction) {
Objects.requireNonNull(keyFunction);
Objects.requireNonNull(valueFunction);
return Collector.of(
ImmutableBiMap.Builder<K, V>::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.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
// Casting to any type is safe because the set will never hold any elements.
@SuppressWarnings("unchecked")
public static <K, V> ImmutableBiMap<K, V> of() {
return (ImmutableBiMap<K, V>) RegularImmutableBiMap.EMPTY;
}
/**
* Returns an immutable bimap containing a single entry.
*/
public static <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> 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 <K, V> ImmutableBiMap<K, V> ofEntries(Entry<? extends K, ? extends V>... entries) {
@SuppressWarnings("unchecked") // we will only ever read these
Entry<K, V>[] entries2 = (Entry<K, V>[]) 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 <K, V> Builder<K, V> builder() {
return new Builder<>();
}
/**
* Returns a new builder, expecting the specified number of entries to be added.
*
* <p>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.
*
* <p>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 <K, V> Builder<K, V> 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:
*
* <pre>{@code
* static final ImmutableBiMap<String, Integer> WORD_TO_INT =
* new ImmutableBiMap.Builder<String, Integer>()
* .put("one", 1)
* .put("two", 2)
* .put("three", 3)
* .buildOrThrow();
* }</pre>
*
* <p>For <i>small</i> immutable bimaps, the {@code ImmutableBiMap.of()} methods are even more
* convenient.
*
* <p>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.
*
* <p>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<K, V> extends ImmutableMap.Builder<K, V> {
/**
* 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<K, V> 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<K, V> put(Entry<? extends K, ? extends V> 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<K, V> putAll(Map<? extends K, ? extends V> 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<K, V> putAll(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
super.putAll(entries);
return this;
}
/**
* Configures this {@code Builder} to order entries by value according to the specified
* comparator.
*
* <p>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<K, V> orderEntriesByValue(Comparator<? super V> valueComparator) {
super.orderEntriesByValue(valueComparator);
return this;
}
@Override
Builder<K, V> combine(ImmutableMap.Builder<K, V> 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.
*
* <p>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<K, V> 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<K, V> buildOrThrow() {
switch (size) {
case 0:
return of();
case 1:
// requireNonNull is safe because the first `size` elements have been filled in.
Entry<K, V> 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<K, V> 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 <i>consistent with equals</i>), the results of this method are undefined.
*
* <p>The returned {@code BiMap} iterates over entries in the same order as the {@code entrySet}
* of the original map.
*
* <p>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 <K, V> ImmutableBiMap<K, V> copyOf(Map<? extends K, ? extends V> map) {
if (map instanceof ImmutableBiMap) {
@SuppressWarnings("unchecked") // safe since map is not writable
ImmutableBiMap<K, V> bimap = (ImmutableBiMap<K, V>) 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 <K, V> ImmutableBiMap<K, V> copyOf(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
@SuppressWarnings("unchecked") // we'll only be using getKey and getValue, which are covariant
Entry<K, V>[] entryArray = (Entry<K, V>[]) toArray(entries, EMPTY_ENTRY_ARRAY);
switch (entryArray.length) {
case 0:
return of();
case 1:
Entry<K, V> 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}
*
* <p>The inverse of an {@code ImmutableBiMap} is another {@code ImmutableBiMap}.
*/
@Override
public abstract ImmutableBiMap<V, K> inverse();
/**
* Returns an immutable set of the values in this map, in the same order they appear in {@link
* #entrySet()}.
*/
@Override
public ImmutableSet<V> values() {
return inverse().keySet();
}
@Override
final ImmutableSet<V> 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();
}
}

View file

@ -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.
*
* <p><b>Warning:</b> avoid <i>direct</i> 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.
*
* <h3>About <i>all</i> {@code Immutable-} collections</h3>
*
* <p>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.
*
* <h4>Guarantees</h4>
*
* <p>Each makes the following guarantees:
*
* <ul>
* <li><b>Shallow immutability.</b> 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.
* <li><b>Null-hostility.</b> This collection will never contain a null element.
* <li><b>Deterministic iteration.</b> 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.
* <li><b>Thread safety.</b> It is safe to access this collection concurrently from multiple
* threads.
* <li><b>Integrity.</b> This type cannot be subclassed outside this package (which would allow
* these guarantees to be violated).
* </ul>
*
* <h4>"Interfaces", not implementations</h4>
*
* <p>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
* <i>type</i> offering meaningful behavioral guarantees. This is substantially different from the
* case of (say) {@link HashSet}, which is an <i>implementation</i>, with semantics that were
* largely defined by its supertype.
*
* <p>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.
*
* <p>On the other hand, a <i>parameter</i> 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.
*
* <p>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 <a
* href="https://docs.oracle.com/javase/9/docs/api/java/util/Set.html#immutable">{@code Set.of}</a>,
* we recommend using <i>these</i> classes instead for this reason (as well as for consistency).
*
* <h4>Creation</h4>
*
* <p>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:
*
* <ul>
* <li>Static methods named {@code of}, accepting an explicit list of elements or entries.
* <li>Static methods named {@code copyOf} (or {@code copyOfSorted}), accepting an existing
* collection whose contents should be copied.
* <li>A static nested {@code Builder} class which can be used to populate a new immutable
* instance.
* </ul>
*
* <h4>Warnings</h4>
*
* <ul>
* <li><b>Warning:</b> 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
* <i>deeply</i> immutable.
* </ul>
*
* <h4>Performance notes</h4>
*
* <ul>
* <li>Implementations can be generally assumed to prioritize memory efficiency, then speed of
* access, and lastly speed of creation.
* <li>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.
* <li><b>Warning:</b> 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.
* <li>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.
* <li>Implementations generally do not cache hash codes. If your element or key type has a slow
* {@code hashCode} implementation, it should cache it itself.
* </ul>
*
* <h4>Example usage</h4>
*
* <pre>{@code
* class Foo {
* private static final ImmutableSet<String> RESERVED_CODES =
* ImmutableSet.of("AZ", "CQ", "ZX");
*
* private final ImmutableSet<String> codes;
*
* public Foo(Iterable<String> codes) {
* this.codes = ImmutableSet.copyOf(codes);
* checkArgument(Collections.disjoint(this.codes, RESERVED_CODES));
* }
* }
* }</pre>
*/
public abstract class ImmutableCollection<E> extends AbstractCollection<E> {
/*
* 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<E> iterator();
@Override
public Spliterator<E> 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 extends Object> 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<? extends E> 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<? super E> 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.
*
* <p><b>Performance note:</b> 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<E> asList() {
switch (size()) {
case 0:
return ImmutableList.of();
case 1:
return ImmutableList.of(iterator().next());
default:
return new RegularImmutableAsList<E>(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<E> {
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.
*
* <p>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<E> add(E element);
/**
* Adds each element of {@code elements} to the {@code ImmutableCollection} being built.
*
* <p>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<E> add(E... elements) {
for (E element : elements) {
add(element);
}
return this;
}
/**
* Adds each element of {@code elements} to the {@code ImmutableCollection} being built.
*
* <p>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<E> addAll(Iterable<? extends E> elements) {
for (E element : elements) {
add(element);
}
return this;
}
/**
* Adds each element of {@code elements} to the {@code ImmutableCollection} being built.
*
* <p>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<E> addAll(Iterator<? extends E> 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.
*
* <p>Note that each builder class covariantly returns the appropriate type of {@code
* ImmutableCollection} from this method.
*/
public abstract ImmutableCollection<E> 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 extends Object> T[] copy(Object[] source, int from, int to, T[] arrayOfType) {
return Arrays.copyOfRange(source, from, to, (Class<? extends T[]>) arrayOfType.getClass());
}
public static <T> T[] newArray(Class<T> 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 extends Object> 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 <T extends Object> Spliterator<T> indexed(int size,
int extraCharacteristics,
IntFunction<T> function) {
return indexed(size, extraCharacteristics, function, null);
}
protected static <T extends Object> Spliterator<T> indexed(int size,
int extraCharacteristics,
IntFunction<T> function, Comparator<? super T> comparator) {
if (comparator != null) {
if ((extraCharacteristics & Spliterator.SORTED) != 0) {
throw new IllegalArgumentException();
}
}
class WithCharacteristics implements Spliterator<T> {
private final Spliterator.OfInt delegate;
WithCharacteristics(Spliterator.OfInt delegate) {
this.delegate = delegate;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
return delegate.tryAdvance((IntConsumer) i -> action.accept(function.apply(i)));
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
delegate.forEachRemaining((IntConsumer) i -> action.accept(function.apply(i)));
}
@Override
public Spliterator<T> 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<? super T> 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.
*
* <p><b>Note:</b> It is often preferable to represent your data using a collection type, for
* example using {@link Arrays#asList(Object[])}, making this method unnecessary.
*
* <p>The {@code Iterable} equivalent of this method is either {@link Arrays#asList(Object[])},
* {@link ImmutableList#copyOf(Object[])}}, or {@link ImmutableList#of}.
*/
@SafeVarargs
protected static <T extends Object> UnmodifiableIterator<T> 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.
*
* <p>The {@code Iterable} equivalent of this method is {@code
* Arrays.asList(array).subList(offset, offset + length).listIterator(index)}.
*/
protected static <T extends Object> UnmodifiableListIterator<T> 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 <T extends Object> UnmodifiableListIterator<T> emptyListIterator() {
return (UnmodifiableListIterator<T>) 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<T extends Object>
extends AbstractIndexedListIterator<T> {
static final UnmodifiableListIterator<Object> 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> T[] toArray(Iterable<? extends T> iterable, Class<T> type) {
return toArray(iterable, newArray(type, 0));
}
public static <T extends Object> T[] toArray(Iterable<? extends T> iterable, T[] array) {
Collection<? extends T> collection = castOrCopyToCollection(iterable);
return collection.toArray(array);
}
}

View file

@ -0,0 +1,68 @@
package org.xbib.datastructures.immutable;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
public class ImmutableEntry<K extends Object, V extends Object> extends AbstractMapEntry<K, V> {
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 <K extends Object, V extends Object> Map.Entry<K, V> unmodifiableEntry(final Map.Entry<? extends K, ? extends V> entry) {
Objects.requireNonNull(entry);
return new AbstractMapEntry<K, V>() {
@Override
public K getKey() {
return entry.getKey();
}
@Override
public V getValue() {
return entry.getValue();
}
};
}
static <K extends Object, V extends Object> UnmodifiableIterator<Map.Entry<K, V>> unmodifiableEntryIterator(final Iterator<Map.Entry<K, V>> entryIterator) {
return new UnmodifiableIterator<Map.Entry<K, V>>() {
@Override
public boolean hasNext() {
return entryIterator.hasNext();
}
@Override
public Map.Entry<K, V> next() {
return unmodifiableEntry(entryIterator.next());
}
};
}
}

View file

@ -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<K extends Enum<K>, V> extends IteratorBasedImmutableMap<K, V> {
static <K extends Enum<K>, V> ImmutableMap<K, V> asImmutable(EnumMap<K, V> map) {
return switch (map.size()) {
case 0 -> ImmutableMap.of();
case 1 -> {
Entry<K, V> entry = getOnlyElement(map.entrySet().iterator());
yield ImmutableMap.of(entry.getKey(), entry.getValue());
}
default -> new ImmutableEnumMap<>(map);
};
}
private final transient EnumMap<K, V> delegate;
private ImmutableEnumMap(EnumMap<K, V> delegate) {
this.delegate = delegate;
if (delegate.isEmpty()) {
throw new IllegalArgumentException();
}
}
@Override
UnmodifiableIterator<K> keyIterator() {
return unmodifiableIterator(delegate.keySet().iterator());
}
@Override
Spliterator<K> 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<Entry<K, V>> entryIterator() {
return ImmutableEntry.unmodifiableEntryIterator(delegate.entrySet().iterator());
}
@Override
Spliterator<Entry<K, V>> entrySpliterator() {
return Spliterators.map(delegate.entrySet().spliterator(), ImmutableEntry::unmodifiableEntry);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
delegate.forEach(action);
}
@Override
boolean isPartialView() {
return false;
}
static <T extends Object> T getOnlyElement(Iterator<T> 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());
}
}

View file

@ -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<E extends Enum<E>> extends ImmutableSet<E> {
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<E> delegate;
private ImmutableEnumSet(EnumSet<E> delegate) {
this.delegate = delegate;
}
@Override
boolean isPartialView() {
return false;
}
@Override
public UnmodifiableIterator<E> iterator() {
return unmodifiableIterator(delegate.iterator());
}
@Override
public Spliterator<E> spliterator() {
return delegate.spliterator();
}
@Override
public void forEach(Consumer<? super E> 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();
}
}

View file

@ -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<E> extends ImmutableCollection<E> implements List<E>, RandomAccess {
private static final Collector<Object, ?, ImmutableList<Object>> 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 <E> Collector<E, ?, ImmutableList<E>> 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.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
// Casting to any type is safe because the list will never hold any elements.
@SuppressWarnings("unchecked")
public static <E> ImmutableList<E> of() {
return (ImmutableList<E>) 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 <E> ImmutableList<E> of(E element) {
return new SingletonImmutableList<E>(element);
}
/**
* Returns an immutable list containing the given elements, in order.
*
* @throws NullPointerException if any element is null
*/
public static <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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 <E> ImmutableList<E> 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.
*
* <p>The array {@code others} must not be longer than {@code Integer.MAX_VALUE - 12}.
*
* @throws NullPointerException if any element is null
*/
public static <E> ImmutableList<E> 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 <E> ImmutableList<E> copyOf(Iterable<? extends E> elements) {
Objects.requireNonNull(elements);
return (elements instanceof Collection)
? copyOf((Collection<? extends E>) elements)
: copyOf(elements.iterator());
}
/**
* Returns an immutable list containing the given elements, in order.
*
* <p>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.
*
* <p>Note that if {@code list} is a {@code List<String>}, then {@code ImmutableList.copyOf(list)}
* returns an {@code ImmutableList<String>} containing each of the strings in {@code list}, while
* ImmutableList.of(list)} returns an {@code ImmutableList<List<String>>} containing one element
* (the given list itself).
*
* <p>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 <E> ImmutableList<E> copyOf(Collection<? extends E> elements) {
if (elements instanceof ImmutableCollection) {
@SuppressWarnings("unchecked") // all supported methods are covariant
ImmutableList<E> list = ((ImmutableCollection<E>) 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 <E> ImmutableList<E> copyOf(Iterator<? extends E> 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<E>().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 <E> ImmutableList<E> 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.
*
* <p>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.
*
* <p><b>Java 8 users:</b> 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 <E extends Comparable<? super E>> ImmutableList<E> sortedCopyOf(Iterable<? extends E> 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.
*
* <p>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.
*
* <p><b>Java 8 users:</b> 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 <E> ImmutableList<E> sortedCopyOf(
Comparator<? super E> comparator, Iterable<? extends E> 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 <E> ImmutableList<E> construct(Object... elements) {
return asImmutableList(checkElementsNotNull(elements));
}
/**
* Views the array as an immutable list. Does not check for nulls; does not copy.
*
* <p>The array must be internally created.
*/
static <E> ImmutableList<E> 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 <E> ImmutableList<E> 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<E>(elementsWithoutTrailingNulls);
}
}
ImmutableList() {
}
// This declaration is needed to make List.iterator() and
// ImmutableCollection.iterator() consistent.
@Override
public UnmodifiableIterator<E> iterator() {
return listIterator();
}
@Override
public UnmodifiableListIterator<E> listIterator() {
return listIterator(0);
}
@Override
public UnmodifiableListIterator<E> listIterator(int index) {
return new AbstractIndexedListIterator<E>(size(), index) {
@Override
protected E get(int index) {
return ImmutableList.this.get(index);
}
};
}
@Override
public void forEach(Consumer<? super E> 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<E>
/**
* 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<E> 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<E> subListUnchecked(int fromIndex, int toIndex) {
return new SubList(fromIndex, toIndex - fromIndex);
}
class SubList extends ImmutableList<E> {
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<E> 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<? extends E> 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<E> operator) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the list unmodified.
*
* @throws UnsupportedOperationException always
*/
@Override
public final void sort(Comparator<? super E> c) {
throw new UnsupportedOperationException();
}
@Override
public Spliterator<E> 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<E> reverse() {
return (size() <= 1) ? this : new ReverseImmutableList<E>(this);
}
private static class ReverseImmutableList<E> extends ImmutableList<E> {
private final transient ImmutableList<E> forwardList;
ReverseImmutableList(ImmutableList<E> backingList) {
this.forwardList = backingList;
}
private int reverseIndex(int index) {
return (size() - 1) - index;
}
private int reversePosition(int index) {
return size() - index;
}
@Override
public ImmutableList<E> 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<E> 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 <E> Builder<E> builder() {
return new Builder<E>();
}
/**
* Returns a new builder, expecting the specified number of elements to be added.
*
* <p>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.
*
* <p>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 <E> Builder<E> builderWithExpectedSize(int expectedSize) {
if (expectedSize < 0) {
throw new IllegalArgumentException("expectedSize");
}
return new Builder<E>(expectedSize);
}
/**
* A builder for creating immutable list instances, especially {@code public static final} lists
* ("constant lists"). Example:
*
* <pre>{@code
* public static final ImmutableList<Color> GOOGLE_COLORS
* = new ImmutableList.Builder<Color>()
* .addAll(WEBSAFE_COLORS)
* .add(new Color(0, 191, 255))
* .build();
* }</pre>
*
* <p>Elements appear in the resulting list in the same order they were added to the builder.
*
* <p>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<E> extends ImmutableCollection.Builder<E> {
// 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<E> 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<E> 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<E> addAll(Iterable<? extends E> 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<E> addAll(Iterator<? extends E> elements) {
super.addAll(elements);
return this;
}
Builder<E> combine(Builder<E> 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<E> 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 <E extends Object> Collection<E> castOrCopyToCollection(Iterable<E> iterable) {
return (iterable instanceof Collection)
? (Collection<E>) iterable
: newArrayList(iterable.iterator());
}
public static <E extends Object> ArrayList<E> newArrayList(Iterator<? extends E> elements) {
ArrayList<E> list = new ArrayList<>();
addAll(list, elements);
return list;
}
static <T extends Object> boolean addAll(Collection<T> addTo, Iterator<? extends T> 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;
}
}

View file

@ -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<K, V> implements Map<K, V> {
/**
* 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.
*
* <p>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 <T extends Object, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction) {
Objects.requireNonNull(keyFunction);
Objects.requireNonNull(valueFunction);
return Collector.of(
ImmutableMap.Builder<K, V>::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.
*
* <p>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 <T extends Object, K, V>
Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction,
BinaryOperator<V> 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.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
@SuppressWarnings("unchecked")
public static <K, V> ImmutableMap<K, V> of() {
return (ImmutableMap<K, V>) 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> 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 <K, V> ImmutableMap<K, V> ofEntries(Entry<? extends K, ? extends V>... entries) {
Entry<K, V>[] entries2 = (Entry<K, V>[]) entries;
return RegularImmutableMap.fromEntries(entries2);
}
/**
* Verifies that {@code key} and {@code value} are non-null, and returns a new immutable entry
* with those values.
*
* <p>A call to {@link Entry#setValue} on the returned entry will always throw {@link
* UnsupportedOperationException}.
*/
static <K, V> Entry<K, V> 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 <K, V> Builder<K, V> builder() {
return new Builder<>();
}
/**
* Returns a new builder, expecting the specified number of entries to be added.
*
* <p>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.
*
* <p>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 <K, V> Builder<K, V> 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:
*
* <pre>{@code
* static final ImmutableMap<String, Integer> WORD_TO_INT =
* new ImmutableMap.Builder<String, Integer>()
* .put("one", 1)
* .put("two", 2)
* .put("three", 3)
* .buildOrThrow();
* }</pre>
*
* <p>For <i>small</i> immutable maps, the {@code ImmutableMap.of()} methods are even more
* convenient.
*
* <p>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.
*
* <p>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<K, V> {
Comparator<? super V> valueComparator;
Entry<K, V>[] 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<K, V> put(K key, V value) {
ensureCapacity(size + 1);
Entry<K, V> 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<K, V> put(Entry<? extends K, ? extends V> 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<K, V> putAll(Map<? extends K, ? extends V> 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<K, V> putAll(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
if (entries instanceof Collection) {
ensureCapacity(size + ((Collection<?>) entries).size());
}
for (Entry<? extends K, ? extends V> entry : entries) {
put(entry);
}
return this;
}
/**
* Configures this {@code Builder} to order entries by value according to the specified
* comparator.
*
* <p>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<K, V> orderEntriesByValue(Comparator<? super V> valueComparator) {
if (this.valueComparator != null) {
throw new IllegalArgumentException("valueComparator was already set");
}
this.valueComparator = Objects.requireNonNull(valueComparator, "valueComparator");
return this;
}
Builder<K, V> combine(Builder<K, V> 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<K, V> 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<K, V> 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<K, V>[] 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<K, V>[] 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.
*
* <p>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<K, V> 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<K, V> 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.
*
* <p>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<K, V> buildKeepingLast() {
return build(false);
}
private static <K, V> Entry<K, V>[] lastEntryForEachKey(Entry<K, V>[] entries, int size) {
Set<K> 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<K, V>[] 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 <i>consistent with equals</i>), the results of this method are undefined.
*
* <p>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 <K, V> ImmutableMap<K, V> copyOf(Map<? extends K, ? extends V> map) {
if ((map instanceof ImmutableMap) && !(map instanceof SortedMap)) {
@SuppressWarnings("unchecked") // safe since map is not writable
ImmutableMap<K, V> kvMap = (ImmutableMap<K, V>) map;
if (!kvMap.isPartialView()) {
return kvMap;
}
} else if (map instanceof EnumMap) {
@SuppressWarnings("unchecked") // safe since map is not writable
ImmutableMap<K, V> kvMap = (ImmutableMap<K, V>) 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 <K, V> ImmutableMap<K, V> copyOf(
Iterable<? extends Entry<? extends K, ? extends V>> entries) {
@SuppressWarnings("unchecked") // we'll only be using getKey and getValue, which are covariant
Entry<K, V>[] entryArray = (Entry<K, V>[]) 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<K, V> 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 <K extends Enum<K>, V> ImmutableMap<K, V> copyOfEnumMap(
EnumMap<K, ? extends V> original) {
EnumMap<K, V> copy = new EnumMap<>(original);
for (Entry<K, V> 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<? super K, ? extends V> 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<? super K, ? super V, ? extends V> 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<? super K, ? super V, ? extends V> 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<? super V, ? super V, ? extends V> 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<? extends K, ? extends V> map) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the map unmodified.
*
* @throws UnsupportedOperationException always
*/
@Override
public final void replaceAll(BiFunction<? super K, ? super V, ? extends V> 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<Entry<K, V>> 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<Entry<K, V>> entrySet() {
ImmutableSet<Entry<K, V>> result = entrySet;
return (result == null) ? entrySet = createEntrySet() : result;
}
abstract ImmutableSet<Entry<K, V>> createEntrySet();
private transient ImmutableSet<K> keySet;
/**
* Returns an immutable set of the keys in this map, in the same order that they appear in {@link
* #entrySet}.
*/
@Override
public ImmutableSet<K> keySet() {
ImmutableSet<K> result = keySet;
return (result == null) ? keySet = createKeySet() : result;
}
/*
* This could have a good default implementation of return new ImmutableKeySet<K, V>(this),
* but ProGuard can't figure out how to eliminate that default when RegularImmutableMap
* overrides it.
*/
abstract ImmutableSet<K> createKeySet();
UnmodifiableIterator<K> keyIterator() {
final UnmodifiableIterator<Entry<K, V>> entryIterator = entrySet().iterator();
return new UnmodifiableIterator<K>() {
@Override
public boolean hasNext() {
return entryIterator.hasNext();
}
@Override
public K next() {
return entryIterator.next().getKey();
}
};
}
Spliterator<K> keySpliterator() {
return Spliterators.map(entrySet().spliterator(), Entry::getKey);
}
private transient ImmutableCollection<V> values;
/**
* Returns an immutable collection of the values in this map, in the same order that they appear
* in {@link #entrySet}.
*/
@Override
public ImmutableCollection<V> values() {
ImmutableCollection<V> result = values;
return (result == null) ? values = createValues() : result;
}
/*
* This could have a good default implementation of {@code return new
* ImmutableMapValues<K, V>(this)}, but ProGuard can't figure out how to eliminate that default
* when RegularImmutableMap overrides it.
*/
abstract ImmutableCollection<V> 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 <V extends Object> Function<Entry<?, V>, 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 <E> ImmutableMap<E, Integer> indexMap(Collection<E> list) {
ImmutableMap.Builder<E, Integer> builder = new ImmutableMap.Builder<>(list.size());
int i = 0;
for (E e : list) {
builder.put(e, i++);
}
return builder.buildOrThrow();
}
}

View file

@ -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.
*
* <p>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<K, V> extends ImmutableEntry<K, V> {
ImmutableMapEntry(K key, V value) {
super(key, value);
checkEntryNotNull(key, value);
}
ImmutableMapEntry(ImmutableMapEntry<K, V> contents) {
super(contents.getKey(), contents.getValue());
// null check would be redundant
}
ImmutableMapEntry<K, V> getNextInKeyBucket() {
return null;
}
ImmutableMapEntry<K, V> 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.
*
* <p>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 <K, V> ImmutableMapEntry<K, V>[] createEntryArray(int size) {
return new ImmutableMapEntry[size];
}
}

View file

@ -0,0 +1,43 @@
package org.xbib.datastructures.immutable;
import java.util.Map.Entry;
/**
* {@code entrySet()} implementation for {@link ImmutableMap}.
*/
abstract class ImmutableMapEntrySet<K, V> extends ImmutableSet.CachingAsList<Entry<K, V>> {
ImmutableMapEntrySet() {
}
abstract ImmutableMap<K, V> 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();
}
}

View file

@ -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<K, V> extends IndexedImmutableSet<K> {
private final ImmutableMap<K, V> map;
ImmutableMapKeySet(ImmutableMap<K, V> map) {
this.map = map;
}
@Override
public int size() {
return map.size();
}
@Override
public UnmodifiableIterator<K> iterator() {
return map.keyIterator();
}
@Override
public Spliterator<K> 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<? super K> action) {
Objects.requireNonNull(action);
map.forEach((k, v) -> action.accept(k));
}
@Override
boolean isPartialView() {
return true;
}
}

View file

@ -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<K, V> extends ImmutableCollection<V> {
private final ImmutableMap<K, V> map;
ImmutableMapValues(ImmutableMap<K, V> map) {
this.map = map;
}
@Override
public int size() {
return map.size();
}
@Override
public UnmodifiableIterator<V> iterator() {
return new UnmodifiableIterator<V>() {
final UnmodifiableIterator<Entry<K, V>> entryItr = map.entrySet().iterator();
@Override
public boolean hasNext() {
return entryItr.hasNext();
}
@Override
public V next() {
return entryItr.next().getValue();
}
};
}
@Override
public Spliterator<V> 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<V> asList() {
final ImmutableList<Entry<K, V>> entryList = map.entrySet().asList();
return new ImmutableAsList<V>() {
@Override
public V get(int index) {
return entryList.get(index).getValue();
}
@Override
ImmutableCollection<V> delegateCollection() {
return ImmutableMapValues.this;
}
};
}
@Override
public void forEach(Consumer<? super V> 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;
}
}

View file

@ -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<E> extends RegularImmutableAsList<E>
implements SortedIterable<E> {
ImmutableSortedAsList(ImmutableSortedSet<E> backingSet, ImmutableList<E> backingList) {
super(backingSet, backingList);
}
@Override
ImmutableSortedSet<E> delegateCollection() {
return (ImmutableSortedSet<E>) super.delegateCollection();
}
@Override
public Comparator<? super E> 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<E> subListUnchecked(int fromIndex, int toIndex) {
ImmutableList<E> parentSubList = super.subListUnchecked(fromIndex, toIndex);
return new RegularImmutableSortedSet<E>(parentSubList, comparator()).asList();
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.indexed(
size(),
ImmutableList.SPLITERATOR_CHARACTERISTICS | Spliterator.SORTED | Spliterator.DISTINCT,
delegateList()::get,
comparator());
}
}

View file

@ -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}.
*
* <p><b>Warning:</b> as with any sorted collection, you are strongly advised not to use a {@link
* Comparator} or {@link Comparable} type whose comparison behavior is <i>inconsistent with
* equals</i>. That is, {@code a.compareTo(b)} or {@code comparator.compare(a, b)} should equal zero
* <i>if and only if</i> {@code a.equals(b)}. If this advice is not followed, the resulting
* collection will not correctly obey its specification.
*/
public abstract class ImmutableSortedSet<E> extends ImmutableSet.CachingAsList<E>
implements NavigableSet<E>, SortedIterable<E> {
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.
*
* <p>If the elements contain duplicates (according to the comparator), only the first duplicate
* in encounter order will appear in the result.
*/
public static <E> Collector<E, ?, ImmutableSortedSet<E>> toImmutableSortedSet(
Comparator<? super E> comparator) {
Objects.requireNonNull(comparator);
return Collector.of(
() -> new ImmutableSortedSet.Builder<E>(comparator),
ImmutableSortedSet.Builder::add,
ImmutableSortedSet.Builder::combine,
ImmutableSortedSet.Builder::build);
}
static <E> RegularImmutableSortedSet<E> emptySet(Comparator<? super E> comparator) {
if (Ordering.natural().equals(comparator)) {
return (RegularImmutableSortedSet<E>) RegularImmutableSortedSet.NATURAL_EMPTY_SET;
} else {
return new RegularImmutableSortedSet<E>(ImmutableList.of(), comparator);
}
}
/**
* Returns the empty immutable sorted set.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
public static <E> ImmutableSortedSet<E> of() {
return (ImmutableSortedSet<E>) RegularImmutableSortedSet.NATURAL_EMPTY_SET;
}
/**
* Returns an immutable sorted set containing a single element.
*/
public static <E extends Comparable<? super E>> ImmutableSortedSet<E> of(E element) {
return new RegularImmutableSortedSet<E>(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 <E extends Comparable<? super E>> ImmutableSortedSet<E> 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 <E extends Comparable<? super E>> ImmutableSortedSet<E> 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 <E extends Comparable<? super E>> ImmutableSortedSet<E> 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 <E extends Comparable<? super E>> ImmutableSortedSet<E> 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 <E extends Comparable<? super E>> ImmutableSortedSet<E> 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 <E extends Comparable<? super E>> ImmutableSortedSet<E> 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.
*
* <p>Note that if {@code s} is a {@code Set<String>}, then {@code ImmutableSortedSet.copyOf(s)}
* returns an {@code ImmutableSortedSet<String>} containing each of the strings in {@code s},
* while {@code ImmutableSortedSet.of(s)} returns an {@code ImmutableSortedSet<Set<String>>}
* containing one element (the given set itself).
*
* <p>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.
*
* <p>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 <E> ImmutableSortedSet<E> copyOf(Iterable<? extends E> elements) {
// Hack around E not being a subtype of Comparable.
// Unsafe, see ImmutableSortedSetFauxverideShim.
@SuppressWarnings("unchecked")
Ordering<E> naturalOrder = (Ordering<E>) 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.
*
* <p>Note that if {@code s} is a {@code Set<String>}, then {@code ImmutableSortedSet.copyOf(s)}
* returns an {@code ImmutableSortedSet<String>} containing each of the strings in {@code s},
* while {@code ImmutableSortedSet.of(s)} returns an {@code ImmutableSortedSet<Set<String>>}
* containing one element (the given set itself).
*
* <p><b>Note:</b> Despite what the method name suggests, if {@code elements} is an {@code
* ImmutableSortedSet}, it may be returned instead of a copy.
*
* <p>This method is not type-safe, as it may be called on elements that are not mutually
* comparable.
*
* <p>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 <E> ImmutableSortedSet<E> copyOf(Collection<? extends E> elements) {
// Hack around E not being a subtype of Comparable.
// Unsafe, see ImmutableSortedSetFauxverideShim.
@SuppressWarnings("unchecked")
Ordering<E> naturalOrder = (Ordering<E>) 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.
*
* <p>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 <E> ImmutableSortedSet<E> copyOf(Iterator<? extends E> elements) {
// Hack around E not being a subtype of Comparable.
// Unsafe, see ImmutableSortedSetFauxverideShim.
@SuppressWarnings("unchecked")
Ordering<E> naturalOrder = (Ordering<E>) 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 <E> ImmutableSortedSet<E> copyOf(
Comparator<? super E> comparator, Iterator<? extends E> elements) {
return new Builder<E>(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.
*
* <p>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 <E> ImmutableSortedSet<E> copyOf(
Comparator<? super E> comparator, Iterable<? extends E> elements) {
Objects.requireNonNull(comparator);
boolean hasSameComparator = hasSameComparator(comparator, elements);
if (hasSameComparator && (elements instanceof ImmutableSortedSet)) {
@SuppressWarnings("unchecked")
ImmutableSortedSet<E> original = (ImmutableSortedSet<E>) 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.
*
* <p>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.
*
* <p>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 <E> ImmutableSortedSet<E> copyOf(
Comparator<? super E> comparator, Collection<? extends E> elements) {
return copyOf(comparator, (Iterable<? extends E>) 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.
*
* <p>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.
*
* <p>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 <E> ImmutableSortedSet<E> copyOfSorted(SortedSet<E> sortedSet) {
Comparator<? super E> comparator = comparator(sortedSet);
ImmutableList<E> list = ImmutableList.copyOf(sortedSet);
if (list.isEmpty()) {
return emptySet(comparator);
} else {
return new RegularImmutableSortedSet<E>(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}.
*
* <p>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 <E> ImmutableSortedSet<E> construct(
Comparator<? super E> 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<E>(
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<Integer>} with a {@code Comparator<Number>}, use the {@link Builder} constructor
* instead.
*
* @throws NullPointerException if {@code comparator} is null
*/
public static <E> Builder<E> orderedBy(Comparator<E> comparator) {
return new Builder<E>(comparator);
}
/**
* Returns a builder that creates immutable sorted sets whose elements are ordered by the reverse
* of their natural ordering.
*/
public static <E extends Comparable<?>> Builder<E> reverseOrder() {
return new Builder<E>(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 <E extends Comparable<?>> Builder<E> naturalOrder() {
return new Builder<E>(Ordering.natural());
}
/**
* A builder for creating immutable sorted set instances, especially {@code public static final}
* sets ("constant sets"), with a given comparator. Example:
*
* <pre>{@code
* public static final ImmutableSortedSet<Number> LUCKY_NUMBERS =
* new ImmutableSortedSet.Builder<Number>(ODDS_FIRST_COMPARATOR)
* .addAll(SINGLE_DIGIT_PRIMES)
* .add(42)
* .build();
* }</pre>
*
* <p>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<E> extends ImmutableSet.Builder<E> {
private final Comparator<? super E> 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<? super E> 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<E> 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<E> 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<E> addAll(Iterable<? extends E> 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<E> addAll(Iterator<? extends E> elements) {
super.addAll(elements);
return this;
}
@Override
Builder<E> combine(ImmutableSet.Builder<E> builder) {
copyIfNecessary();
Builder<E> other = (Builder<E>) 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<E> build() {
sortAndDedup();
if (n == 0) {
return emptySet(comparator);
} else {
forceCopy = true;
return new RegularImmutableSortedSet<E>(
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<Object> unsafeComparator = (Comparator<Object>) comparator;
return unsafeComparator.compare(a, b);
}
final transient Comparator<? super E> comparator;
ImmutableSortedSet(Comparator<? super E> 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<? super E> comparator() {
return comparator;
}
@Override // needed to unify the iterator() methods in Collection and SortedIterable
public abstract UnmodifiableIterator<E> iterator();
/**
* {@inheritDoc}
*
* <p>This method returns a {@code ImmutableSortedSet}.
*
* <p>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<E> headSet(E toElement) {
return headSet(toElement, false);
}
@Override
public ImmutableSortedSet<E> headSet(E toElement, boolean inclusive) {
return headSetImpl(Objects.requireNonNull(toElement), inclusive);
}
/**
* {@inheritDoc}
*
* <p>This method returns a {@code ImmutableSortedSet}.
*
* <p>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<E> subSet(E fromElement, E toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public ImmutableSortedSet<E> 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}
*
* <p>This method returns a {@code ImmutableSortedSet}.
*
* <p>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<E> tailSet(E fromElement) {
return tailSet(fromElement, true);
}
@Override
public ImmutableSortedSet<E> 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<E> headSetImpl(E toElement, boolean inclusive);
abstract ImmutableSortedSet<E> subSetImpl(
E fromElement, boolean fromInclusive, E toElement, boolean toInclusive);
abstract ImmutableSortedSet<E> 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<E> descendingSet;
@Override
public ImmutableSortedSet<E> descendingSet() {
// racy single-check idiom
ImmutableSortedSet<E> result = descendingSet;
if (result == null) {
result = descendingSet = createDescendingSet();
result.descendingSet = this;
}
return result;
}
// Most classes should implement this as new DescendingImmutableSortedSet<E>(this),
// but we push down that implementation because ProGuard can't eliminate it even when it's always
// overridden.
abstract ImmutableSortedSet<E> createDescendingSet();
@Override
public Spliterator<E> spliterator() {
return new Spliterators.AbstractSpliterator<E>(
size(), SPLITERATOR_CHARACTERISTICS | Spliterator.SIZED) {
final UnmodifiableIterator<E> iterator = iterator();
@Override
public boolean tryAdvance(Consumer<? super E> action) {
if (iterator.hasNext()) {
action.accept(iterator.next());
return true;
} else {
return false;
}
}
@Override
public Comparator<? super E> getComparator() {
return comparator;
}
};
}
@Override
public abstract UnmodifiableIterator<E> 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 extends Object> T getNext(Iterator<? extends T> iterator, T defaultValue) {
return iterator.hasNext() ? iterator.next() : defaultValue;
}
private static <E extends Object> Comparator<? super E> comparator(SortedSet<E> sortedSet) {
Comparator<? super E> result = sortedSet.comparator();
if (result == null) {
result = (Comparator<? super E>) 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);
}
}

View file

@ -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<E> extends ImmutableSet.CachingAsList<E> {
abstract E get(int index);
@Override
public UnmodifiableIterator<E> iterator() {
return asList().iterator();
}
@Override
public Spliterator<E> spliterator() {
return indexed(size(), SPLITERATOR_CHARACTERISTICS, this::get);
}
@Override
public void forEach(Consumer<? super E> 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<E> createAsList() {
return new ImmutableAsList<E>() {
@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<E> delegateCollection() {
return IndexedImmutableSet.this;
}
};
}
}

View file

@ -0,0 +1,41 @@
package org.xbib.datastructures.immutable;
import java.util.Spliterator;
import java.util.Spliterators;
abstract class IteratorBasedImmutableMap<K, V> extends ImmutableMap<K, V> {
abstract UnmodifiableIterator<Entry<K, V>> entryIterator();
Spliterator<Entry<K, V>> entrySpliterator() {
return Spliterators.spliterator(
entryIterator(),
size(),
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.ORDERED);
}
@Override
ImmutableSet<K> createKeySet() {
return new ImmutableMapKeySet<>(this);
}
@Override
ImmutableSet<Entry<K, V>> createEntrySet() {
class EntrySetImpl extends ImmutableMapEntrySet<K, V> {
@Override
ImmutableMap<K, V> map() {
return IteratorBasedImmutableMap.this;
}
@Override
public UnmodifiableIterator<Entry<K, V>> iterator() {
return entryIterator();
}
}
return new EntrySetImpl();
}
@Override
ImmutableCollection<V> createValues() {
return new ImmutableMapValues<>(this);
}
}

View file

@ -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<K, V> extends ImmutableBiMap<K, V> {
static <K, V> ImmutableBiMap<K, V> create(int n, Entry<K, V>[] entryArray) {
Map<K, V> forwardDelegate = new HashMap<>(n);
Map<V, K> backwardDelegate = new HashMap<>(n);
for (int i = 0; i < n; i++) {
// requireNonNull is safe because the first `n` elements have been filled in.
Entry<K, V> 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<Entry<K, V>> entryList = ImmutableList.asImmutableList(entryArray, n);
return new JdkBackedImmutableBiMap<>(entryList, forwardDelegate, backwardDelegate);
}
private final transient ImmutableList<Entry<K, V>> entries;
private final Map<K, V> forwardDelegate;
private final Map<V, K> backwardDelegate;
private JdkBackedImmutableBiMap(
ImmutableList<Entry<K, V>> entries, Map<K, V> forwardDelegate, Map<V, K> backwardDelegate) {
this.entries = entries;
this.forwardDelegate = forwardDelegate;
this.backwardDelegate = backwardDelegate;
}
@Override
public int size() {
return entries.size();
}
private transient JdkBackedImmutableBiMap<V, K> inverse;
@Override
public ImmutableBiMap<V, K> inverse() {
JdkBackedImmutableBiMap<V, K> result = inverse;
if (result == null) {
inverse =
result =
new JdkBackedImmutableBiMap<>(
new InverseEntries(), backwardDelegate, forwardDelegate);
result.inverse = this;
}
return result;
}
private final class InverseEntries extends ImmutableList<Entry<V, K>> {
@Override
public Entry<V, K> get(int index) {
Entry<K, V> 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<Entry<K, V>> createEntrySet() {
return new RegularEntrySet<>(this, entries);
}
@Override
ImmutableSet<K> createKeySet() {
return new ImmutableMapKeySet<>(this);
}
@Override
boolean isPartialView() {
return false;
}
}

View file

@ -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<K, V> extends ImmutableMap<K, V> {
/**
* 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 <K, V> ImmutableMap<K, V> create(
int n, Entry<K, V>[] entryArray, boolean throwIfDuplicateKeys) {
Map<K, V> 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<K, V> 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<K, V>[] newEntryArray = new Entry[n - dupCount];
for (int inI = 0, outI = 0; inI < n; inI++) {
Entry<K, V> 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<K, V> delegateMap;
private final transient ImmutableList<Entry<K, V>> entries;
JdkBackedImmutableMap(Map<K, V> delegateMap, ImmutableList<Entry<K, V>> 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<Entry<K, V>> createEntrySet() {
return new RegularEntrySet<>(this, entries);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
entries.forEach(e -> action.accept(e.getKey(), e.getValue()));
}
@Override
ImmutableSet<K> createKeySet() {
return new ImmutableMapKeySet<>(this);
}
@Override
ImmutableCollection<V> createValues() {
return new ImmutableMapValues<>(this);
}
@Override
boolean isPartialView() {
return false;
}
}

View file

@ -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<E> extends IndexedImmutableSet<E> {
private final Set<?> delegate;
private final ImmutableList<E> delegateList;
JdkBackedImmutableSet(Set<?> delegate, ImmutableList<E> 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();
}
}

View file

@ -0,0 +1,19 @@
package org.xbib.datastructures.immutable;
class NonTerminalImmutableBiMapEntry<K, V>
extends NonTerminalImmutableMapEntry<K, V> {
private final transient ImmutableMapEntry<K, V> nextInValueBucket;
NonTerminalImmutableBiMapEntry(K key,
V value,
ImmutableMapEntry<K, V> nextInKeyBucket,
ImmutableMapEntry<K, V> nextInValueBucket) {
super(key, value, nextInKeyBucket);
this.nextInValueBucket = nextInValueBucket;
}
@Override
ImmutableMapEntry<K, V> getNextInValueBucket() {
return nextInValueBucket;
}
}

View file

@ -0,0 +1,26 @@
package org.xbib.datastructures.immutable;
class NonTerminalImmutableMapEntry<K, V> extends ImmutableMapEntry<K, V> {
/*
* 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<K, V> nextInKeyBucket;
NonTerminalImmutableMapEntry(K key, V value, ImmutableMapEntry<K, V> nextInKeyBucket) {
super(key, value);
this.nextInKeyBucket = nextInKeyBucket;
}
@Override
final ImmutableMapEntry<K, V> getNextInKeyBucket() {
return nextInKeyBucket;
}
@Override
final boolean isReusable() {
return false;
}
}

View file

@ -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<K, V> extends ImmutableMapEntrySet<K, V> {
private final transient ImmutableMap<K, V> map;
private final transient ImmutableList<Map.Entry<K, V>> entries;
RegularEntrySet(ImmutableMap<K, V> map, Map.Entry<K, V>[] entries) {
this(map, ImmutableList.asImmutableList(entries));
}
RegularEntrySet(ImmutableMap<K, V> map, ImmutableList<Map.Entry<K, V>> entries) {
this.map = map;
this.entries = entries;
}
@Override
ImmutableMap<K, V> map() {
return map;
}
@Override
int copyIntoArray(Object[] dst, int offset) {
return entries.copyIntoArray(dst, offset);
}
@Override
public UnmodifiableIterator<Map.Entry<K, V>> iterator() {
return entries.iterator();
}
@Override
public Spliterator<Map.Entry<K, V>> spliterator() {
return entries.spliterator();
}
@Override
public void forEach(Consumer<? super Map.Entry<K, V>> action) {
entries.forEach(action);
}
@Override
ImmutableList<Map.Entry<K, V>> createAsList() {
return new RegularImmutableAsList<>(this, entries);
}
}

View file

@ -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<E> extends ImmutableAsList<E> {
private final ImmutableCollection<E> delegate;
private final ImmutableList<? extends E> delegateList;
RegularImmutableAsList(ImmutableCollection<E> delegate, ImmutableList<? extends E> delegateList) {
this.delegate = delegate;
this.delegateList = delegateList;
}
RegularImmutableAsList(ImmutableCollection<E> delegate, Object[] array) {
this(delegate, ImmutableList.asImmutableList(array));
}
@Override
ImmutableCollection<E> delegateCollection() {
return delegate;
}
ImmutableList<? extends E> delegateList() {
return delegateList;
}
@SuppressWarnings("unchecked") // safe covariant cast!
@Override
public UnmodifiableListIterator<E> listIterator(int index) {
return (UnmodifiableListIterator<E>) delegateList.listIterator(index);
}
@Override
public void forEach(Consumer<? super E> 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);
}
}

View file

@ -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<K, V> extends ImmutableBiMap<K, V> {
@SuppressWarnings("unchecked")
static final RegularImmutableBiMap<Object, Object> EMPTY =
new RegularImmutableBiMap<>(
null, null, (Entry<Object, Object>[]) EMPTY_ENTRY_ARRAY, 0, 0);
static final double MAX_LOAD_FACTOR = 1.2;
private final transient ImmutableMapEntry<K, V>[] keyTable;
private final transient ImmutableMapEntry<K, V>[] valueTable;
final transient Entry<K, V>[] entries;
private final transient int mask;
private final transient int hashCode;
@SuppressWarnings("unchecked")
static <K, V> ImmutableBiMap<K, V> fromEntries(Entry<K, V>... entries) {
return fromEntryArray(entries.length, entries);
}
static <K, V> ImmutableBiMap<K, V> fromEntryArray(int n, Entry<K, V>[] entryArray) {
checkPositionIndex(n, entryArray.length, "n");
int tableSize = closedTableSize(n, MAX_LOAD_FACTOR);
int mask = tableSize - 1;
ImmutableMapEntry<K, V>[] keyTable = createEntryArray(tableSize);
ImmutableMapEntry<K, V>[] 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<K, V>[] 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<K, V> 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<K, V> nextInKeyBucket = keyTable[keyBucket];
ImmutableMapEntry<K, V> nextInValueBucket = valueTable[valueBucket];
try {
checkNoConflictInKeyBucket(key, value, nextInKeyBucket, /* throwIfDuplicateKeys= */ true);
checkNoConflictInValueBucket(value, entry, nextInValueBucket);
} catch (RegularImmutableMap.BucketOverflowException e) {
return JdkBackedImmutableBiMap.create(n, entryArray);
}
ImmutableMapEntry<K, V> 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<K, V>[] keyTable, ImmutableMapEntry<K, V>[] valueTable,
Entry<K, V>[] 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<Entry<K, V>> createEntrySet() {
return isEmpty()
? ImmutableSet.of()
: new RegularEntrySet<K, V>(this, entries);
}
@Override
ImmutableSet<K> createKeySet() {
return new ImmutableMapKeySet<>(this);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Entry<K, V> 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<V, K> inverse;
@Override
public ImmutableBiMap<V, K> inverse() {
if (isEmpty()) {
return of();
}
ImmutableBiMap<V, K> result = inverse;
return (result == null) ? inverse = new Inverse() : result;
}
private final class Inverse extends ImmutableBiMap<V, K> {
@Override
public int size() {
return inverse().size();
}
@Override
public ImmutableBiMap<K, V> inverse() {
return RegularImmutableBiMap.this;
}
@Override
public void forEach(BiConsumer<? super V, ? super K> 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<K, V> entry = valueTable[bucket];
entry != null;
entry = entry.getNextInValueBucket()) {
if (value.equals(entry.getValue())) {
return entry.getKey();
}
}
return null;
}
@Override
ImmutableSet<V> createKeySet() {
return new ImmutableMapKeySet<>(this);
}
@Override
ImmutableSet<Entry<V, K>> createEntrySet() {
return new InverseEntrySet();
}
final class InverseEntrySet extends ImmutableMapEntrySet<V, K> {
@Override
ImmutableMap<V, K> map() {
return Inverse.this;
}
@Override
boolean isHashCodeFast() {
return true;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public UnmodifiableIterator<Entry<V, K>> iterator() {
return asList().iterator();
}
@Override
public void forEach(Consumer<? super Entry<V, K>> action) {
asList().forEach(action);
}
@Override
ImmutableList<Entry<V, K>> createAsList() {
return new ImmutableAsList<Entry<V, K>>() {
@Override
public Entry<V, K> get(int index) {
Entry<K, V> entry = entries[index];
return new ImmutableMapEntry<>(entry.getValue(), entry.getKey());
}
@Override
ImmutableCollection<Entry<V, K>> 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;
}

View file

@ -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<E> extends ImmutableList<E> {
static final ImmutableList<Object> 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<E> listIterator(int index) {
// for performance
// The fake cast to E is safe because the creation methods only allow E's
return (UnmodifiableListIterator<E>) forArray(array, 0, array.length, index);
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(array, SPLITERATOR_CHARACTERISTICS);
}
}

View file

@ -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<K, V> extends ImmutableMap<K, V> {
@SuppressWarnings("unchecked")
static final ImmutableMap<Object, Object> EMPTY =
new RegularImmutableMap<>((Entry<Object, Object>[]) 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<K, V>[] entries;
// array of linked lists of entries
private final transient ImmutableMapEntry<K, V>[] table;
// 'and' with an int to get a table index
private final transient int mask;
static <K, V> ImmutableMap<K, V> fromEntries(Entry<K, V>... 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 <K, V> ImmutableMap<K, V> fromEntryArray(
int n, Entry<K, V>[] 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<K, V> empty = (ImmutableMap<K, V>) 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 <K, V> ImmutableMap<K, V> fromEntryArrayCheckingBucketOverflow(
int n, Entry<K, V>[] 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<K, V>[] entries =
(n == entryArray.length) ? entryArray : createEntryArray(n);
int tableSize = closedTableSize(n, MAX_LOAD_FACTOR);
ImmutableMapEntry<K, V>[] 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<Entry<K, V>, 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<K, V> entry = requireNonNull(entryArray[entryIndex]);
K key = entry.getKey();
V value = entry.getValue();
checkEntryNotNull(key, value);
int tableIndex = smear(key.hashCode()) & mask;
ImmutableMapEntry<K, V> keyBucketHead = table[tableIndex];
ImmutableMapEntry<K, V> 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<K, V>(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 <K, V> Entry<K, V>[] removeDuplicates(
Entry<K, V>[] entries, int n, int newN, IdentityHashMap<Entry<K, V>, Boolean> duplicates) {
Entry<K, V>[] newEntries = createEntryArray(newN);
for (int in = 0, out = 0; in < n; in++) {
Entry<K, V> 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 <K, V> ImmutableMapEntry<K, V> makeImmutable(Entry<K, V> entry, K key, V value) {
boolean reusable =
entry instanceof ImmutableMapEntry && ((ImmutableMapEntry<K, V>) entry).isReusable();
return reusable ? (ImmutableMapEntry<K, V>) entry : new ImmutableMapEntry<K, V>(key, value);
}
/**
* Makes an entry usable internally by a new ImmutableMap.
*/
static <K, V> ImmutableMapEntry<K, V> makeImmutable(Entry<K, V> entry) {
return makeImmutable(entry, entry.getKey(), entry.getValue());
}
private RegularImmutableMap(Entry<K, V>[] entries, ImmutableMapEntry<K, V>[] 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 <K, V> ImmutableMapEntry<K, V> checkNoConflictInKeyBucket(
Object key,
Object newValue,
ImmutableMapEntry<K, V> 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> V get(Object key, ImmutableMapEntry<?, V>[] keyTable,
int mask) {
if (key == null || keyTable == null) {
return null;
}
int index = smear(key.hashCode()) & mask;
for (ImmutableMapEntry<?, V> 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<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Entry<K, V> entry : entries) {
action.accept(entry.getKey(), entry.getValue());
}
}
@Override
public int size() {
return entries.length;
}
@Override
boolean isPartialView() {
return false;
}
@Override
ImmutableSet<Entry<K, V>> createEntrySet() {
return new RegularEntrySet<>(this, entries);
}
@Override
ImmutableSet<K> createKeySet() {
return new RegularImmutableMapKeySet<>(this);
}
@Override
ImmutableCollection<V> 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;
}

View file

@ -0,0 +1,29 @@
package org.xbib.datastructures.immutable;
final class RegularImmutableMapKeySet<K> extends IndexedImmutableSet<K> {
private final RegularImmutableMap<K, ?> map;
RegularImmutableMapKeySet(RegularImmutableMap<K, ?> 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();
}
}

View file

@ -0,0 +1,24 @@
package org.xbib.datastructures.immutable;
final class RegularImmutableMapValues<K, V> extends ImmutableList<V> {
private final RegularImmutableMap<K, V> map;
RegularImmutableMapValues(RegularImmutableMap<K, V> 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;
}
}

View file

@ -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<E> extends ImmutableSet.CachingAsList<E> {
private static final Object[] EMPTY_ARRAY = new Object[0];
static final RegularImmutableSet<Object> 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<E> iterator() {
return (UnmodifiableIterator<E>) forArray(elements);
}
@Override
public Spliterator<E> 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<E> createAsList() {
return (table.length == 0)
? ImmutableList.of()
: new RegularImmutableAsList<E>(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;
}

View file

@ -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<E> extends ImmutableSortedSet<E> {
static final RegularImmutableSortedSet<Comparable<?>> NATURAL_EMPTY_SET =
new RegularImmutableSortedSet<>(ImmutableList.of(), Ordering.natural());
private final transient ImmutableList<E> elements;
RegularImmutableSortedSet(ImmutableList<E> elements, Comparator<? super E> 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<E> iterator() {
return elements.iterator();
}
@Override
public UnmodifiableIterator<E> descendingIterator() {
return elements.reverse().iterator();
}
@Override
public Spliterator<E> spliterator() {
return asList().spliterator();
}
@Override
public void forEach(Consumer<? super E> 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<E> 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<E> 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<E> 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<E> subSetImpl(
E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
return tailSetImpl(fromElement, fromInclusive).headSetImpl(toElement, toInclusive);
}
@Override
ImmutableSortedSet<E> 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<Object> unsafeComparator() {
return (Comparator<Object>) comparator;
}
RegularImmutableSortedSet<E> getSubSet(int newFromIndex, int newToIndex) {
if (newFromIndex == 0 && newToIndex == size()) {
return this;
} else if (newFromIndex < newToIndex) {
return new RegularImmutableSortedSet<E>(
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<E> createAsList() {
return (size() <= 1) ? elements : new ImmutableSortedAsList<E>(this, elements);
}
@Override
ImmutableSortedSet<E> createDescendingSet() {
Comparator<? super E> reversedOrder = Collections.reverseOrder(comparator);
return isEmpty()
? emptySet(reversedOrder)
: new RegularImmutableSortedSet<E>(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 <E extends Object> Comparator<? super E> comparator(SortedSet<E> sortedSet) {
Comparator<? super E> result = sortedSet.comparator();
if (result == null) {
result = (Comparator<? super E>) Ordering.natural();
}
return result;
}
}

View file

@ -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<K, V> extends ImmutableBiMap<K, V> {
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<V, K> 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<? super K, ? super V> 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<Entry<K, V>> createEntrySet() {
return ImmutableSet.of(new ImmutableMapEntry<>(singleKey, singleValue));
}
@Override
ImmutableSet<K> createKeySet() {
return ImmutableSet.of(singleKey);
}
private final transient ImmutableBiMap<V, K> inverse;
private transient ImmutableBiMap<V, K> lazyInverse;
@Override
public ImmutableBiMap<V, K> inverse() {
if (inverse != null) {
return inverse;
} else {
// racy single-check idiom
ImmutableBiMap<V, K> result = lazyInverse;
if (result == null) {
return lazyInverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this);
} else {
return result;
}
}
}
}

View file

@ -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<E> extends ImmutableList<E> {
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<E> iterator() {
return singletonIterator(element);
}
@Override
public Spliterator<E> spliterator() {
return Collections.singleton(element).spliterator();
}
@Override
public int size() {
return 1;
}
@Override
public ImmutableList<E> 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}.
*
* <p>The {@link Iterable} equivalent of this method is {@link Collections#singleton}.
*/
public static <T extends Object> UnmodifiableIterator<T> singletonIterator(T value) {
return new UnmodifiableIterator<T>() {
boolean done;
@Override
public boolean hasNext() {
return !done;
}
@Override
public T next() {
if (done) {
throw new NoSuchElementException();
}
done = true;
return value;
}
};
}
}

View file

@ -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<E> extends ImmutableSet<E> {
// 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<E> iterator() {
return singletonIterator(element);
}
@Override
public ImmutableList<E> 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() + ']';
}
}

View file

@ -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 <InElementT extends Object, OutElementT extends Object> Spliterator<OutElementT> map(
Spliterator<InElementT> fromSpliterator,
Function<? super InElementT, ? extends OutElementT> function) {
Objects.requireNonNull(fromSpliterator);
Objects.requireNonNull(function);
return new Spliterator<OutElementT>() {
@Override
public boolean tryAdvance(Consumer<? super OutElementT> action) {
return fromSpliterator.tryAdvance(
fromElement -> action.accept(function.apply(fromElement)));
}
@Override
public void forEachRemaining(Consumer<? super OutElementT> action) {
fromSpliterator.forEachRemaining(fromElement -> action.accept(function.apply(fromElement)));
}
@Override
public Spliterator<OutElementT> trySplit() {
Spliterator<InElementT> 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 <InElementT extends Object, OutElementT extends Object> Spliterator<OutElementT> flatMap(
Spliterator<InElementT> fromSpliterator,
Function<? super InElementT, Spliterator<OutElementT>> 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<InElementT extends Object, OutElementT extends Object>
extends FlatMapSpliterator<InElementT, OutElementT, Spliterator<OutElementT>> {
FlatMapSpliteratorOfObject(Spliterator<OutElementT> prefix,
Spliterator<InElementT> from,
Function<? super InElementT, Spliterator<OutElementT>> 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<OutElementT>>
implements Spliterator<OutElementT> {
/**
* Factory for constructing {@link FlatMapSpliterator} instances.
*/
@FunctionalInterface
interface Factory<InElementT extends Object, OutSpliteratorT extends Spliterator<?>> {
OutSpliteratorT newFlatMapSpliterator(OutSpliteratorT prefix,
Spliterator<InElementT> fromSplit,
Function<? super InElementT, OutSpliteratorT> function,
int splitCharacteristics,
long estSplitSize);
}
OutSpliteratorT prefix;
final Spliterator<InElementT> from;
final Function<? super InElementT, OutSpliteratorT> function;
final Factory<InElementT, OutSpliteratorT> factory;
int characteristics;
long estimatedSize;
FlatMapSpliterator(OutSpliteratorT prefix,
Spliterator<InElementT> from,
Function<? super InElementT, OutSpliteratorT> function,
Factory<InElementT, OutSpliteratorT> 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<? super OutElementT> 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<? super OutElementT> action) {
if (prefix != null) {
prefix.forEachRemaining(action);
prefix = null;
}
from.forEachRemaining(
fromElement -> {
Spliterator<OutElementT> elements = function.apply(fromElement);
if (elements != null) {
elements.forEachRemaining(action);
}
});
estimatedSize = 0;
}
@Override
public final OutSpliteratorT trySplit() {
Spliterator<InElementT> 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 <T extends Object> Spliterator<T> indexed(
int size, int extraCharacteristics, IntFunction<T> function) {
return indexed(size, extraCharacteristics, function, null);
}
static <T extends Object> Spliterator<T> indexed(
int size,
int extraCharacteristics,
IntFunction<T> function,
Comparator<? super T> comparator) {
if (comparator != null) {
if ((extraCharacteristics & Spliterator.SORTED) == 0) {
throw new IllegalArgumentException();
}
}
class WithCharacteristics implements Spliterator<T> {
private final Spliterator.OfInt delegate;
WithCharacteristics(Spliterator.OfInt delegate) {
this.delegate = delegate;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
return delegate.tryAdvance((IntConsumer) i -> action.accept(function.apply(i)));
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
delegate.forEachRemaining((IntConsumer) i -> action.accept(function.apply(i)));
}
@Override
public Spliterator<T> 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<? super T> getComparator() {
if (hasCharacteristics(Spliterator.SORTED)) {
return comparator;
} else {
throw new IllegalStateException();
}
}
}
return new WithCharacteristics(IntStream.range(0, size).spliterator());
}
}

View file

@ -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}.
*
* <p>{@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<E extends Object> implements Iterator<E> {
/**
* 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 <T extends Object> UnmodifiableIterator<T> unmodifiableIterator(Iterator<? extends T> iterator) {
Objects.requireNonNull(iterator);
if (iterator instanceof UnmodifiableIterator) {
@SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe
UnmodifiableIterator<T> result = (UnmodifiableIterator<T>) iterator;
return result;
}
return new UnmodifiableIterator<T>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public T next() {
return iterator.next();
}
};
}
}

View file

@ -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<E extends Object>
extends UnmodifiableIterator<E> implements ListIterator<E> {
/**
* 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();
}
}

View file

@ -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<Object> {
static final AllEqualOrdering INSTANCE = new AllEqualOrdering();
@Override
public int compare(Object left, Object right) {
return 0;
}
@Override
public <E> List<E> sortedCopy(Iterable<E> iterable) {
return ImmutableList.newArrayList(iterable.iterator());
}
@Override
@SuppressWarnings("nullness") // unsafe: see supertype
public <E> ImmutableList<E> immutableSortedCopy(Iterable<E> iterable) {
return ImmutableList.copyOf(iterable);
}
@SuppressWarnings("unchecked")
@Override
public <S> Ordering<S> reverse() {
return (Ordering<S>) this;
}
@Override
public String toString() {
return "Ordering.allEqual()";
}
}

View file

@ -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<F, T> extends Ordering<F> {
final Function<F, ? extends T> function;
final Ordering<T> ordering;
ByFunctionOrdering(Function<F, ? extends T> function, Ordering<T> 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 + ")";
}
}

View file

@ -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<T> extends Ordering<T> {
final Comparator<T> comparator;
ComparatorOrdering(Comparator<T> 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();
}
}

View file

@ -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<T> extends Ordering<T> {
final Comparator<? super T>[] comparators;
@SuppressWarnings("unchecked")
CompoundOrdering(Comparator<? super T> primary, Comparator<? super T> secondary) {
this.comparators = (Comparator<? super T>[]) new Comparator[]{primary, secondary};
}
@SuppressWarnings("unchecked")
CompoundOrdering(Iterable<? extends Comparator<? super T>> comparators) {
this.comparators = toArray(comparators, new Comparator[0]);
}
@Override
public int compare(T left, T right) {
for (Comparator<? super T> 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) + ")";
}
}

View file

@ -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<T> extends Ordering<T> {
final ImmutableMap<T, Integer> rankMap;
ExplicitOrdering(List<T> valuesInOrder) {
this(indexMap(valuesInOrder));
}
ExplicitOrdering(ImmutableMap<T, Integer> 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() + ")";
}
}

View file

@ -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<T> extends Ordering<Iterable<T>> {
final Comparator<? super T> elementOrder;
LexicographicalOrdering(Comparator<? super T> elementOrder) {
this.elementOrder = elementOrder;
}
@Override
public int compare(Iterable<T> leftIterable, Iterable<T> rightIterable) {
Iterator<T> left = leftIterable.iterator();
Iterator<T> 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()";
}
}

View file

@ -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<Comparable<?>> {
static final NaturalOrdering INSTANCE = new NaturalOrdering();
private transient Ordering<Comparable<?>> nullsFirst;
private transient Ordering<Comparable<?>> nullsLast;
private NaturalOrdering() {
}
@Override
public int compare(Comparable<?> left, Comparable<?> right) {
Objects.requireNonNull(left);
Objects.requireNonNull(right);
return ((Comparable<Object>) left).compareTo(right);
}
@Override
public <S extends Comparable<?>> Ordering<S> nullsFirst() {
Ordering<Comparable<?>> result = nullsFirst;
if (result == null) {
result = nullsFirst = super.nullsFirst();
}
return (Ordering<S>) result;
}
@Override
public <S extends Comparable<?>> Ordering<S> nullsLast() {
Ordering<Comparable<?>> result = nullsLast;
if (result == null) {
result = nullsLast = super.nullsLast();
}
return (Ordering<S>) result;
}
@Override
public <S extends Comparable<?>> Ordering<S> reverse() {
return (Ordering<S>) ReverseNaturalOrdering.INSTANCE;
}
@Override
public String toString() {
return "Ordering.natural()";
}
}

View file

@ -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<T> extends Ordering<T> {
final Ordering<? super T> ordering;
NullsFirstOrdering(Ordering<? super T> 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 <S extends T> Ordering<S> 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 <S extends T> Ordering<S> nullsFirst() {
return (Ordering<S>) this;
}
@Override
public <S extends T> Ordering<S> 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()";
}
}

View file

@ -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<T extends Object> extends Ordering<T> {
final Ordering<? super T> ordering;
NullsLastOrdering(Ordering<? super T> 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 <S extends T> Ordering<S> reverse() {
// ordering.reverse() might be optimized, so let it do its thing
return ordering.reverse().nullsFirst();
}
@Override
public <S extends T> Ordering<S> nullsFirst() {
return ordering.nullsFirst();
}
@SuppressWarnings("unchecked") // still need the right way to explain this
@Override
public <S extends T> Ordering<S> nullsLast() {
return (Ordering<S>) 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()";
}
}

View file

@ -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.
*
* <h3>Three types of methods</h3>
* <p>
* Like other fluent types, there are three types of methods present: methods for <i>acquiring</i>,
* <i>chaining</i>, and <i>using</i>.
*
* <h4>Acquiring</h4>
*
* <p>The common ways to get an instance of {@code Ordering} are:
*
* <ul>
* <li>Subclass it and implement {@link #compare} instead of implementing {@link Comparator}
* directly
* <li>Pass a <i>pre-existing</i> {@link Comparator} instance to {@link #from(Comparator)}
* <li>Use the natural ordering, {@link Ordering#natural}
* </ul>
*
* <h4>Chaining</h4>
*
* <p>Then you can use the <i>chaining</i> methods to get an altered version of that {@code
* Ordering}, including:
*
* <ul>
* <li>{@link #reverse}
* <li>{@link #compound(Comparator)}
* <li>{@link #onResultOf(Function)}
* <li>{@link #nullsFirst} / {@link #nullsLast}
* </ul>
*
* <h4>Using</h4>
*
* <p>Finally, use the resulting {@code Ordering} anywhere a {@link Comparator} is required, or use
* any of its special operations, such as:
*
* <ul>
* <li>{@link #immutableSortedCopy}
* <li>{@link #isOrdered} / {@link #isStrictlyOrdered}
* <li>{@link #min} / {@link #max}
* </ul>
*
* <h3>Understanding complex orderings</h3>
*
* <p>Complex chained orderings like the following example can be challenging to understand.
*
* <pre>{@code
* Ordering<Foo> ordering =
* Ordering.natural()
* .nullsFirst()
* .onResultOf(getBarFunction)
* .nullsLast();
* }</pre>
* <p>
* 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 <i>before</i> handing off to that backing instance.
* As a result, it usually helps to read chained ordering expressions <i>backwards</i>. For example,
* when {@code compare} is called on the above ordering:
*
* <ol>
* <li>First, if only one {@code Foo} is null, that null value is treated as <i>greater</i>
* <li>Next, non-null {@code Foo} values are passed to {@code getBarFunction} (we will be
* comparing {@code Bar} values from now on)
* <li>Next, if only one {@code Bar} is null, that null value is treated as <i>lesser</i>
* <li>Finally, natural ordering is used (i.e. the result of {@code Bar.compareTo(Bar)} is
* returned)
* </ol>
*
* <p>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.
*
* <h3>For Java 8 users</h3>
*
* <p>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
* <i>deprecate</i> this class.
*
* <p>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<T> implements Comparator<T> {
// Natural order
/**
* Returns a ordering that uses the natural order of the values. The ordering throws
* a {@link NullPointerException} when passed a null parameter.
*
* <p>The type specification is {@code <C extends Comparable>}, instead of the technically correct
* {@code <C extends Comparable<? super C>>}, to support legacy types from before Java 5.
*
* <p><b>Java 8 users:</b> use {@link Comparator#naturalOrder} instead.
*/
@SuppressWarnings("unchecked")
public static <C extends Comparable> Ordering<C> natural() {
return (Ordering<C>) NaturalOrdering.INSTANCE;
}
// Static factories
/**
* Returns an ordering based on an <i>existing</i> comparator instance. Note that it is
* unnecessary to create a <i>new</i> anonymous inner class implementing {@code Comparator} just
* to pass it in here. Instead, simply subclass {@code Ordering} and implement its {@code compare}
* method directly.
*
* <p><b>Java 8 users:</b> 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 <T extends Object> Ordering<T> from(Comparator<T> comparator) {
return (comparator instanceof Ordering)
? (Ordering<T>) comparator
: new ComparatorOrdering<T>(comparator);
}
/**
* Simply returns its argument.
*/
public static <T extends Object> Ordering<T> from(Ordering<T> 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.
*
* <p>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 <T> Ordering<T> explicit(List<T> valuesInOrder) {
return new ExplicitOrdering<T>(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.
*
* <p>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 <T> Ordering<T> explicit(T leastValue, T... remainingValuesInOrder) {
return explicit(asList(leastValue, remainingValuesInOrder));
}
public static <E> List<E> asList(E first, E[] rest) {
return new OnePlusArrayList<>(first, rest);
}
private static class OnePlusArrayList<E extends Object> extends AbstractList<E> 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<Object> singletons
/**
* Returns an ordering which treats all values as equal, indicating "no ordering." Passing this
* ordering to any <i>stable</i> 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.
*
* <p>Example:
*
* <pre>{@code
* Ordering.allEqual().nullsLast().sortedCopy(
* asList(t, null, e, s, null, t, null))
* }</pre>
*
* <p>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).
*
* <p><b>Warning:</b> by definition, this comparator is not <i>consistent with equals</i> (as
* defined {@linkplain Comparator here}). Avoid its use in APIs, such as {@link
* TreeSet#TreeSet(Comparator)}, where such consistency is expected.
*
* <p><b>Java 8 users:</b> Use the lambda expression {@code (a, b) -> 0} instead (in certain cases
* you may need to cast that to {@code Comparator<YourType>}).
*/
public static Ordering<Object> 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.
*
* <p><b>Java 8 users:</b> Use {@code Comparator.comparing(Object::toString)} instead.
*/
public static Ordering<Object> 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)}.
*
* <p><b>Java 8 users:</b> Use {@code thisComparator.reversed()} instead.
*/
// type parameter <S> lets us avoid the extra <String> in statements like:
// Ordering<String> o = Ordering.<String>natural().reverse();
public <S extends T> Ordering<S> reverse() {
return new ReverseOrdering<S>(this);
}
/**
* Returns an ordering that treats {@code null} as less than all other values and uses {@code
* this} to compare non-null values.
*
* <p><b>Java 8 users:</b> Use {@code Comparator.nullsFirst(thisComparator)} instead.
*/
// type parameter <S> lets us avoid the extra <String> in statements like:
// Ordering<String> o = Ordering.<String>natural().nullsFirst();
public <S extends T> Ordering<S> nullsFirst() {
return new NullsFirstOrdering<S>(this);
}
/**
* Returns an ordering that treats {@code null} as greater than all other values and uses this
* ordering to compare non-null values.
*
* <p><b>Java 8 users:</b> Use {@code Comparator.nullsLast(thisComparator)} instead.
*/
// type parameter <S> lets us avoid the extra <String> in statements like:
// Ordering<String> o = Ordering.<String>natural().nullsLast();
public <S extends T> Ordering<S> nullsLast() {
return new NullsLastOrdering<S>(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:
*
* <pre>{@code
* Ordering.from(String.CASE_INSENSITIVE_ORDER)
* .onResultOf(Functions.toStringFunction())
* }</pre>
*
* <p><b>Java 8 users:</b> Use {@code Comparator.comparing(function, thisComparator)} instead (you
* can omit the comparator if it is the natural order).
*/
public <F extends Object> Ordering<F> onResultOf(Function<F, ? extends T> function) {
return new ByFunctionOrdering<>(function, this);
}
<T2 extends T> Ordering<Entry<T2, ?>> 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.
*
* <p>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.
*
* <p><b>Java 8 users:</b> 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 <U extends T> Ordering<U> compound(Comparator<? super U> secondaryComparator) {
return new CompoundOrdering<U>(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.
*
* <p>The returned ordering is equivalent to that produced using {@code
* Ordering.from(comp1).compound(comp2).compound(comp3) . . .}.
*
* <p><b>Warning:</b> Supplying an argument with undefined iteration order, such as a {@link
* HashSet}, will produce non-deterministic results.
*
* <p><b>Java 8 users:</b> 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 <T extends Object> Ordering<T> compound(
Iterable<? extends Comparator<? super T>> comparators) {
return new CompoundOrdering<T>(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]}.
*
* <p>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]}).
*
* <p><b>Java 8 users:</b> Use {@link Comparators#lexicographical(Comparator)} instead.
*/
// type parameter <S> lets us avoid the extra <String> in statements like:
// Ordering<Iterable<String>> o =
// Ordering.<String>natural().lexicographical();
public <S extends T> Ordering<Iterable<S>> lexicographical() {
/*
* Note that technically the returned ordering should be capable of
* handling not just {@code Iterable<S>} instances, but also any {@code
* Iterable<? extends S>}. 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<S>(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}.
*
* <p><b>Java 8 users:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> E min(Iterator<E> 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.
*
* <p><b>Java 8 users:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> E min(Iterable<E> 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.
*
* <p><b>Implementation note:</b> this method is invoked by the default implementations of the
* other {@code min} overloads, so overriding it will affect their behavior.
*
* <p><b>Note:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> 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.
*
* <p><b>Java 8 users:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> 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}.
*
* <p><b>Java 8 users:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> E max(Iterator<E> 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.
*
* <p><b>Java 8 users:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> E max(Iterable<E> 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.
*
* <p><b>Implementation note:</b> this method is invoked by the default implementations of the
* other {@code max} overloads, so overriding it will affect their behavior.
*
* <p><b>Note:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> 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.
*
* <p><b>Java 8 users:</b> 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 <i>mutually comparable</i> under this
* ordering.
*/
public <E extends T> 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.
*
* <p>The implementation does not necessarily use a <i>stable</i> sorting algorithm; when multiple
* elements are equivalent, it is undefined which will come first.
*
* <p><b>Java 8 users:</b> 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 <E extends T> List<E> leastOf(Iterable<E> iterable, int k) {
if (iterable instanceof Collection<E> 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.
*
* <p>The implementation does not necessarily use a <i>stable</i> sorting algorithm; when multiple
* elements are equivalent, it is undefined which will come first.
*
* <p><b>Java 8 users:</b> 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 <E extends T> List<E> leastOf(Iterator<E> 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<E> 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<E> 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.
*
* <p>The implementation does not necessarily use a <i>stable</i> sorting algorithm; when multiple
* elements are equivalent, it is undefined which will come first.
*
* <p><b>Java 8 users:</b> Use {@code Streams.stream(iterable).collect(Comparators.greatest(k,
* thisComparator))} instead.
*
* @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in
* <i>descending order</i>
* @throws IllegalArgumentException if {@code k} is negative
*/
public <E extends T> List<E> greatestOf(Iterable<E> 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.
*
* <p>The implementation does not necessarily use a <i>stable</i> sorting algorithm; when multiple
* elements are equivalent, it is undefined which will come first.
*
* <p><b>Java 8 users:</b> Use {@code Streams.stream(iterator).collect(Comparators.greatest(k,
* thisComparator))} instead.
*
* @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in
* <i>descending order</i>
* @throws IllegalArgumentException if {@code k} is negative
*/
public <E extends T> List<E> greatestOf(Iterator<E> iterator, int k) {
return reverse().leastOf(iterator, k);
}
/**
* Returns a <b>mutable</b> 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.
*
* <p>Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard elements that are
* duplicates according to the comparator. The sort performed is <i>stable</i>, meaning that such
* elements will appear in the returned list in the same order they appeared in {@code elements}.
*
* <p><b>Performance note:</b> 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 <E extends T> List<E> sortedCopy(Iterable<E> 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 <b>immutable</b> list containing {@code elements} sorted by this ordering. The input
* is not modified.
*
* <p>Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard elements that are
* duplicates according to the comparator. The sort performed is <i>stable</i>, meaning that such
* elements will appear in the returned list in the same order they appeared in {@code elements}.
*
* <p><b>Performance note:</b> 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 <E extends T> ImmutableList<E> immutableSortedCopy(Iterable<E> 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.
*
* <p><b>Java 8 users:</b> 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<? extends T> iterable) {
Iterator<? extends T> 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 <i>strictly</i>
* 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.
*
* <p><b>Java 8 users:</b> 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<? extends T> iterable) {
Iterator<? extends T> 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<? extends T> 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;
}

View file

@ -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<Comparable<?>> {
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<Object>) right).compareTo(left);
}
@Override
public <S extends Comparable<?>> Ordering<S> reverse() {
return Ordering.natural();
}
// Override the min/max methods to "hoist" delegation outside loops
@Override
public <E extends Comparable<?>> E min(E a, E b) {
return NaturalOrdering.INSTANCE.max(a, b);
}
@Override
public <E extends Comparable<?>> E min(E a, E b, E c, E... rest) {
return NaturalOrdering.INSTANCE.max(a, b, c, rest);
}
@Override
public <E extends Comparable<?>> E min(Iterator<E> iterator) {
return NaturalOrdering.INSTANCE.max(iterator);
}
@Override
public <E extends Comparable<?>> E min(Iterable<E> iterable) {
return NaturalOrdering.INSTANCE.max(iterable);
}
@Override
public <E extends Comparable<?>> E max(E a, E b) {
return NaturalOrdering.INSTANCE.min(a, b);
}
@Override
public <E extends Comparable<?>> E max(E a, E b, E c, E... rest) {
return NaturalOrdering.INSTANCE.min(a, b, c, rest);
}
@Override
public <E extends Comparable<?>> E max(Iterator<E> iterator) {
return NaturalOrdering.INSTANCE.min(iterator);
}
@Override
public <E extends Comparable<?>> E max(Iterable<E> iterable) {
return NaturalOrdering.INSTANCE.min(iterable);
}
@Override
public String toString() {
return "Ordering.natural().reverse()";
}
}

View file

@ -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<T extends Object> extends Ordering<T> {
final Ordering<? super T> forwardOrder;
ReverseOrdering(Ordering<? super T> forwardOrder) {
this.forwardOrder = Objects.requireNonNull(forwardOrder);
}
@Override
public int compare(T a, T b) {
return forwardOrder.compare(b, a);
}
@SuppressWarnings("unchecked")
@Override
public <S extends T> Ordering<S> reverse() {
return (Ordering<S>) forwardOrder;
}
// Override the min/max methods to "hoist" delegation outside loops
@Override
public <E extends T> E min(E a, E b) {
return forwardOrder.max(a, b);
}
@Override
public <E extends T> E min(E a, E b, E c, E... rest) {
return forwardOrder.max(a, b, c, rest);
}
@Override
public <E extends T> E min(Iterator<E> iterator) {
return forwardOrder.max(iterator);
}
@Override
public <E extends T> E min(Iterable<E> iterable) {
return forwardOrder.max(iterable);
}
@Override
public <E extends T> E max(E a, E b) {
return forwardOrder.min(a, b);
}
@Override
public <E extends T> E max(E a, E b, E c, E... rest) {
return forwardOrder.min(a, b, c, rest);
}
@Override
public <E extends T> E max(Iterator<E> iterator) {
return forwardOrder.min(iterator);
}
@Override
public <E extends T> E max(Iterable<E> 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()";
}
}

View file

@ -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.
*
* <p>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)}.
*
* <p>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.
*
* <p>The implementation does not necessarily use a <i>stable</i> sorting algorithm; when multiple
* equivalent elements are added to it, it is undefined which will come first in the output.
*/
final class TopKSelector<T> {
/**
* 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 <T extends Comparable<? super T>> TopKSelector<T> 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 <T extends Object> TopKSelector<T> least(
int k, Comparator<? super T> comparator) {
return new TopKSelector<T>(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 <T extends Comparable<? super T>> TopKSelector<T> 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 <T extends Object> TopKSelector<T> greatest(
int k, Comparator<? super T> comparator) {
return new TopKSelector<T>(Ordering.from(comparator).reverse(), k);
}
private final int k;
private final Comparator<? super T> 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<? super T> 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<T> combine(TopKSelector<T> 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}.
*
* <p>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<? extends T> 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.
*
* <p>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<? extends T> 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}.
*
* <p>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<T> 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 extends Object> 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;
}
}

View file

@ -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<Object> {
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()";
}
}

View file

@ -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<String> list = new ArrayList<>(Arrays.asList("one", "two", "three"));
List<String> unmodifiableList = ImmutableList.copyOf(list);
unmodifiableList.add("four");
});
}
@Test
public void givenUsingBuilderWhenUnmodifiableListIsCreatedThenNoLongerModifiable() {
assertThrows(UnsupportedOperationException.class, () -> {
List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three"));
ImmutableList<String> unmodifiableList = ImmutableList.<String>builder().addAll(list).build();
unmodifiableList.add("four");
});
}
}

View file

@ -0,0 +1,3 @@
dependencies {
api project(':datastructures-immutable')
}

View file

@ -0,0 +1,5 @@
module org.xbib.datastructures.multi {
exports org.xbib.datastructures.multi;
requires org.xbib.datastructures.api;
requires org.xbib.datastructures.immutable;
}

View file

@ -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<E>} backed by an instance of {@code Map<E, Count>}.
*
* <p>For serialization to work, the subclass must specify explicit {@code readObject} and {@code
* writeObject} methods.
*/
abstract class AbstractMapBasedMultiset<E extends Object> extends AbstractMultiset<E>
implements Serializable {
// TODO(lowasser): consider overhauling this back to Map<E, Integer>
private transient Map<E, Count> 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<E, Count> backingMap) {
checkArgument(backingMap.isEmpty());
this.backingMap = backingMap;
}
/** Used during deserialization only. The backing map must be empty. */
void setBackingMap(Map<E, Count> backingMap) {
this.backingMap = backingMap;
}
// Required Implementations
/**
* {@inheritDoc}
*
* <p>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<Entry<E>> entrySet() {
return super.entrySet();
}
@Override
Iterator<E> elementIterator() {
final Iterator<Map.Entry<E, Count>> backingEntries = backingMap.entrySet().iterator();
return new Iterator<E>() {
@CheckForNull Map.Entry<E, Count> toRemove;
@Override
public boolean hasNext() {
return backingEntries.hasNext();
}
@Override
@ParametricNullness
public E next() {
final Map.Entry<E, Count> 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<Entry<E>> entryIterator() {
final Iterator<Map.Entry<E, Count>> backingEntries = backingMap.entrySet().iterator();
return new Iterator<Entry<E>>() {
@CheckForNull Map.Entry<E, Count> toRemove;
@Override
public boolean hasNext() {
return backingEntries.hasNext();
}
@Override
public Entry<E> next() {
final Map.Entry<E, Count> mapEntry = backingEntries.next();
toRemove = mapEntry;
return new Multisets.AbstractEntry<E>() {
@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<? super E> 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<E> iterator() {
return new MapBasedMultisetIterator();
}
/*
* Not subclassing AbstractMultiset$MultisetIterator because next() needs to
* retrieve the Map.Entry<E, Count> entry, which can then be used for
* a more efficient remove() call.
*/
private class MapBasedMultisetIterator implements Iterator<E> {
final Iterator<Map.Entry<E, Count>> entryIterator;
@CheckForNull Map.Entry<E, Count> 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;
}

View file

@ -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<K extends Object, V extends Object>
implements Multimap<K, V> {
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean containsValue(Object value) {
for (Collection<V> collection : asMap().values()) {
if (collection.contains(value)) {
return true;
}
}
return false;
}
@Override
public boolean containsEntry(Object key, Object value) {
Collection<V> collection = asMap().get(key);
return collection != null && collection.contains(value);
}
@Override
public boolean remove(Object key, Object value) {
Collection<V> 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<? extends V> 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<? extends V> valueCollection) {
return !valueCollection.isEmpty() && get(key).addAll(valueCollection);
} else {
Iterator<? extends V> valueItr = values.iterator();
return valueItr.hasNext() && addAll(get(key), valueItr);
}
}
@Override
public boolean putAll(Multimap<? extends K, ? extends V> multimap) {
boolean changed = false;
for (Entry<? extends K, ? extends V> entry : multimap.entries()) {
changed |= put(entry.getKey(), entry.getValue());
}
return changed;
}
@Override
public Collection<V> replaceValues(K key, Iterable<? extends V> values) {
Objects.requireNonNull(values);
Collection<V> result = removeAll(key);
putAll(key, values);
return result;
}
private transient Collection<Entry<K, V>> entries;
@Override
public Collection<Entry<K, V>> entries() {
Collection<Entry<K, V>> result = entries;
return (result == null) ? entries = createEntries() : result;
}
abstract Collection<Entry<K, V>> createEntries();
class Entries extends MultimapsEntries<K, V> {
@Override
Multimap<K, V> multimap() {
return AbstractMultimap.this;
}
@Override
public Iterator<Entry<K, V>> iterator() {
return entryIterator();
}
@Override
public Spliterator<Entry<K, V>> spliterator() {
return entrySpliterator();
}
}
class EntrySet extends Entries implements Set<Entry<K, V>> {
@Override
public int hashCode() {
return hashCodeImpl(this);
}
@Override
public boolean equals(Object obj) {
return equalsImpl(this, obj);
}
}
abstract Iterator<Entry<K, V>> entryIterator();
Spliterator<Entry<K, V>> entrySpliterator() {
return Spliterators.spliterator(
entryIterator(), size(), (this instanceof SetMultimap) ? Spliterator.DISTINCT : 0);
}
private transient Set<K> keySet;
@Override
public Set<K> keySet() {
Set<K> result = keySet;
return (result == null) ? keySet = createKeySet() : result;
}
abstract Set<K> createKeySet();
private transient Multiset<K> keys;
@Override
public Multiset<K> keys() {
Multiset<K> result = keys;
return (result == null) ? keys = createKeys() : result;
}
abstract Multiset<K> createKeys();
private transient Collection<V> values;
@Override
public Collection<V> values() {
Collection<V> result = values;
return (result == null) ? values = createValues() : result;
}
abstract Collection<V> createValues();
class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
return valueIterator();
}
@Override
public Spliterator<V> 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<V> valueIterator() {
return valueIterator(entries().iterator());
}
Spliterator<V> valueSpliterator() {
return Spliterators.spliterator(valueIterator(), size(), 0);
}
private transient Map<K, Collection<V>> asMap;
@Override
public Map<K, Collection<V>> asMap() {
Map<K, Collection<V>> result = asMap;
return (result == null) ? asMap = createAsMap() : result;
}
abstract Map<K, Collection<V>> createAsMap();
// Comparison and hashing
@Override
public boolean equals(Object object) {
return equalsImpl(this, object);
}
/**
* Returns the hash code for this multimap.
*
* <p>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 <K extends Object, V extends Object> Iterator<V> valueIterator(Iterator<Entry<K, V>> entryIterator) {
return new TransformedIterator<Entry<K, V>, V>(entryIterator) {
@Override
V transform(Entry<K, V> entry) {
return entry.getValue();
}
};
}
private static <T extends Object> boolean addAll(
Collection<T> addTo, Iterator<? extends T> 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;
}
}

View file

@ -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.
*
* <p>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<E extends @Nullable Object> extends AbstractCollection<E>
implements Multiset<E> {
// 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}
*
* <p>This implementation is highly efficient when {@code elementsToAdd} is itself a {@link
* Multiset}.
*/
@CanIgnoreReturnValue
@Override
public final boolean addAll(Collection<? extends E> 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<E> elementSet;
@Override
public Set<E> elementSet() {
Set<E> 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<E> createElementSet() {
return new ElementSet();
}
@WeakOuter
class ElementSet extends Multisets.ElementSet<E> {
@Override
Multiset<E> multiset() {
return AbstractMultiset.this;
}
@Override
public Iterator<E> iterator() {
return elementIterator();
}
}
abstract Iterator<E> elementIterator();
@LazyInit @CheckForNull private transient Set<Entry<E>> entrySet;
@Override
public Set<Entry<E>> entrySet() {
Set<Entry<E>> result = entrySet;
if (result == null) {
entrySet = result = createEntrySet();
}
return result;
}
@WeakOuter
class EntrySet extends Multisets.EntrySet<E> {
@Override
Multiset<E> multiset() {
return AbstractMultiset.this;
}
@Override
public Iterator<Entry<E>> iterator() {
return entryIterator();
}
@Override
public int size() {
return distinctElements();
}
}
Set<Entry<E>> createEntrySet() {
return new EntrySet();
}
abstract Iterator<Entry<E>> entryIterator();
abstract int distinctElements();
// Object methods
/**
* {@inheritDoc}
*
* <p>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}
*
* <p>This implementation returns the hash code of {@link Multiset#entrySet()}.
*/
@Override
public final int hashCode() {
return entrySet().hashCode();
}
/**
* {@inheritDoc}
*
* <p>This implementation returns the result of invoking {@code toString} on {@link
* Multiset#entrySet()}.
*/
@Override
public final String toString() {
return entrySet().toString();
}
}

View file

@ -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<K extends Object, V extends Object>
extends AbstractMapBasedMultimap<K, V> implements SetMultimap<K, V> {
/**
* 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<K, Collection<V>> map) {
super(map);
}
@Override
abstract Set<V> createCollection();
@Override
Set<V> createUnmodifiableEmptyCollection() {
return Collections.emptySet();
}
@Override
<E extends Object> Collection<E> unmodifiableCollectionSubclass(
Collection<E> collection) {
return Collections.unmodifiableSet((Set<E>) collection);
}
@Override
Collection<V> wrapCollection(K key, Collection<V> collection) {
return new WrappedSet(key, (Set<V>) collection);
}
/**
* {@inheritDoc}
*
* <p>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<V> get(K key) {
return (Set<V>) super.get(key);
}
/**
* {@inheritDoc}
*
* <p>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<Entry<K, V>> entries() {
return (Set<Entry<K, V>>) super.entries();
}
/**
* {@inheritDoc}
*
* <p>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<V> removeAll(Object key) {
return (Set<V>) super.removeAll(key);
}
/**
* {@inheritDoc}
*
* <p>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.
*
* <p>Any duplicates in {@code values} will be stored in the multimap once.
*/
@Override
public Set<V> replaceValues(K key, Iterable<? extends V> values) {
return (Set<V>) super.replaceValues(key, values);
}
/**
* {@inheritDoc}
*
* <p>Though the method signature doesn't say so explicitly, the returned map has {@link Set}
* values.
*/
@Override
public Map<K, Collection<V>> 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.
*
* <p>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);
}
}

View file

@ -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<K, V> extends AbstractMultimap<K, V> {
}

View file

@ -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<K, V> extends ImmutableMultimap<K, V>
implements ListMultimap<K, V> {
/**
* 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.
*
* <p>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 <a
* href="ImmutableMultimap.html#iteration">grouped by key</a>.
*
* <p>Example:
*
* <pre>{@code
* static final Multimap<Character, String> 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<Character, String> FIRST_LETTER_MULTIMAP =
* new ImmutableListMultimap.Builder<Character, String>()
* .put('b', "anana")
* .putAll('a', "pple", "sparagus")
* .putAll('c', "arrot", "herry")
* .build();
* }</pre>
*
* @since 21.0
*/
public static <T extends Object, K, V>
Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> 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.
*
* <p>Example:
*
* <pre>{@code
* static final ImmutableListMultimap<Character, Character> 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<Character, Character> FIRST_LETTER_MULTIMAP =
* ImmutableListMultimap.<Character, Character>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();
* }
* }</pre>
*
* @since 21.0
*/
public static <T extends Object, K, V>
Collector<T, ?, ImmutableListMultimap<K, V>> flatteningToImmutableListMultimap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction);
}
/**
* Returns the empty multimap.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
// Casting is safe because the multimap will never hold any elements.
@SuppressWarnings("unchecked")
public static <K, V> ImmutableListMultimap<K, V> of() {
return (ImmutableListMultimap<K, V>) EmptyImmutableListMultimap.INSTANCE;
}
/** Returns an immutable multimap containing a single entry. */
public static <K, V> ImmutableListMultimap<K, V> of(K k1, V v1) {
Builder<K, V> builder = ImmutableListMultimap.builder();
builder.put(k1, v1);
return builder.build();
}
/** Returns an immutable multimap containing the given entries, in order. */
public static <K, V> ImmutableListMultimap<K, V> of(K k1, V v1, K k2, V v2) {
Builder<K, V> 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 <K, V> ImmutableListMultimap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
Builder<K, V> 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 <K, V> ImmutableListMultimap<K, V> of(
K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
Builder<K, V> 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 <K, V> ImmutableListMultimap<K, V> of(
K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
Builder<K, V> 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 <K, V> Builder<K, V> builder() {
return new Builder<>();
}
/**
* A builder for creating immutable {@code ListMultimap} instances, especially {@code public
* static final} multimaps ("constant multimaps"). Example:
*
* <pre>{@code
* static final Multimap<String, Integer> STRING_TO_INTEGER_MULTIMAP =
* new ImmutableListMultimap.Builder<String, Integer>()
* .put("one", 1)
* .putAll("several", 1, 2, 3)
* .putAll("many", 1, 2, 3, 4, 5)
* .build();
* }</pre>
*
* <p>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<K, V> extends ImmutableMultimap.Builder<K, V> {
/**
* Creates a new builder. The returned builder is equivalent to the builder generated by {@link
* ImmutableListMultimap#builder}.
*/
public Builder() {}
@Override
public Builder<K, V> put(K key, V value) {
super.put(key, value);
return this;
}
@Override
public Builder<K, V> put(Entry<? extends K, ? extends V> entry) {
super.put(entry);
return this;
}
@Override
public Builder<K, V> putAll(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
super.putAll(entries);
return this;
}
@Override
public Builder<K, V> putAll(K key, Iterable<? extends V> values) {
super.putAll(key, values);
return this;
}
@Override
public Builder<K, V> putAll(K key, V... values) {
super.putAll(key, values);
return this;
}
@Override
public Builder<K, V> putAll(Multimap<? extends K, ? extends V> multimap) {
super.putAll(multimap);
return this;
}
@Override
Builder<K, V> combine(ImmutableMultimap.Builder<K, V> other) {
super.combine(other);
return this;
}
@Override
public Builder<K, V> orderKeysBy(Comparator<? super K> keyComparator) {
super.orderKeysBy(keyComparator);
return this;
}
@Override
public Builder<K, V> orderValuesBy(Comparator<? super V> valueComparator) {
super.orderValuesBy(valueComparator);
return this;
}
/** Returns a newly-created immutable list multimap. */
@Override
public ImmutableListMultimap<K, V> build() {
return (ImmutableListMultimap<K, V>) 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.
*
* <p>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 <K, V> ImmutableListMultimap<K, V> copyOf(
Multimap<? extends K, ? extends V> 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<K, V> kvMultimap = (ImmutableListMultimap<K, V>) 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 <K, V> ImmutableListMultimap<K, V> copyOf(
Iterable<? extends Entry<? extends K, ? extends V>> entries) {
return new Builder<K, V>().putAll(entries).build();
}
/** Creates an ImmutableListMultimap from an asMap.entrySet. */
static <K, V> ImmutableListMultimap<K, V> fromMapEntries(
Collection<? extends Entry<? extends K, ? extends Collection<? extends V>>> mapEntries,
Comparator<? super V> valueComparator) {
if (mapEntries.isEmpty()) {
return of();
}
ImmutableMap.Builder<K, ImmutableList<V>> builder =
new ImmutableMap.Builder<>(mapEntries.size());
int size = 0;
for (Entry<? extends K, ? extends Collection<? extends V>> entry : mapEntries) {
K key = entry.getKey();
Collection<? extends V> values = entry.getValue();
ImmutableList<V> 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<K, ImmutableList<V>> 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<V> get(K key) {
// This cast is safe as its type is known in constructor.
ImmutableList<V> list = (ImmutableList<V>) map.get(key);
return (list == null) ? ImmutableList.<V>of() : list;
}
private transient ImmutableListMultimap<V, K> inverse;
/**
* {@inheritDoc}
*
* <p>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<V, K> inverse() {
ImmutableListMultimap<V, K> result = inverse;
return (result == null) ? (inverse = invert()) : result;
}
private ImmutableListMultimap<V, K> invert() {
Builder<V, K> builder = builder();
for (Entry<K, V> entry : entries()) {
builder.put(entry.getValue(), entry.getKey());
}
ImmutableListMultimap<V, K> 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<V> removeAll(Object key) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the multimap unmodified.
*
* @throws UnsupportedOperationException always
*/
@Override
public final ImmutableList<V> replaceValues(K key, Iterable<? extends V> 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<Object, ImmutableList<Object>> 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<Object> valuesBuilder = ImmutableList.builder();
for (int j = 0; j < valueCount; j++) {
valuesBuilder.add(stream.readObject());
}
builder.put(key, valuesBuilder.build());
tmpSize += valueCount;
}
ImmutableMap<Object, ImmutableList<Object>> 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;
}

View file

@ -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}.
*
* <p><b>Warning:</b> avoid <i>direct</i> 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.
*
* <p><b>Note:</b> every {@link ImmutableMultimap} offers an {@link #inverse} view, so there is no
* need for a distinct {@code ImmutableBiMultimap} type.
*
* <p><a id="iteration"></a>
*
* <p><b>Key-grouped iteration.</b> 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<K, V> extends BaseImmutableMultimap<K, V>
implements Serializable {
/**
* Returns an empty multimap.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
public static <K, V> ImmutableMultimap<K, V> of() {
return ImmutableListMultimap.of();
}
/**
* Returns an immutable multimap containing a single entry.
*/
public static <K, V> ImmutableMultimap<K, V> of(K k1, V v1) {
return ImmutableListMultimap.of(k1, v1);
}
/**
* Returns an immutable multimap containing the given entries, in order.
*/
public static <K, V> ImmutableMultimap<K, V> 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 <a href="#iteration">class documentation</a>.
*/
public static <K, V> ImmutableMultimap<K, V> 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 <a href="#iteration">class documentation</a>.
*/
public static <K, V> ImmutableMultimap<K, V> 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 <a href="#iteration">class documentation</a>.
*/
public static <K, V> ImmutableMultimap<K, V> 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 <K, V> Builder<K, V> builder() {
return new Builder<>();
}
/**
* A builder for creating immutable multimap instances, especially {@code public static final}
* multimaps ("constant multimaps"). Example:
*
* <pre>{@code
* static final Multimap<String, Integer> STRING_TO_INTEGER_MULTIMAP =
* new ImmutableMultimap.Builder<String, Integer>()
* .put("one", 1)
* .putAll("several", 1, 2, 3)
* .putAll("many", 1, 2, 3, 4, 5)
* .build();
* }</pre>
*
* <p>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<K, V> {
final Map<K, Collection<V>> builderMap;
Comparator<? super K> keyComparator;
Comparator<? super V> 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<V> newMutableValueCollection() {
return new ArrayList<>();
}
/**
* Adds a key-value mapping to the built multimap.
*/
public Builder<K, V> put(K key, V value) {
checkEntryNotNull(key, value);
Collection<V> 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<K, V> put(Entry<? extends K, ? extends V> entry) {
return put(entry.getKey(), entry.getValue());
}
/**
* Adds entries to the built multimap.
*/
public Builder<K, V> putAll(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
for (Entry<? extends K, ? extends V> 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<K, V> putAll(K key, Iterable<? extends V> values) {
if (key == null) {
throw new NullPointerException("null key in entry: null=" + toString(values.iterator()));
}
Collection<V> valueCollection = builderMap.get(key);
if (valueCollection != null) {
for (V value : values) {
checkEntryNotNull(key, value);
valueCollection.add(value);
}
return this;
}
Iterator<? extends V> 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<K, V> 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<K, V> putAll(Multimap<? extends K, ? extends V> multimap) {
for (Entry<? extends K, ? extends Collection<? extends V>> entry :
multimap.asMap().entrySet()) {
putAll(entry.getKey(), entry.getValue());
}
return this;
}
/**
* Specifies the ordering of the generated multimap's keys.
*/
public Builder<K, V> orderKeysBy(Comparator<? super K> keyComparator) {
this.keyComparator = Objects.requireNonNull(keyComparator);
return this;
}
/**
* Specifies the ordering of the generated multimap's values for each key.
*/
public Builder<K, V> orderValuesBy(Comparator<? super V> valueComparator) {
this.valueComparator = Objects.requireNonNull(valueComparator);
return this;
}
Builder<K, V> combine(Builder<K, V> other) {
for (Entry<K, Collection<V>> entry : other.builderMap.entrySet()) {
putAll(entry.getKey(), entry.getValue());
}
return this;
}
/**
* Returns a newly-created immutable multimap.
*/
public ImmutableMultimap<K, V> build() {
Collection<Entry<K, Collection<V>>> mapEntries = builderMap.entrySet();
if (keyComparator != null) {
mapEntries = Ordering.from(keyComparator).<K>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.
*
* <p>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 <K, V> ImmutableMultimap<K, V> copyOf(Multimap<? extends K, ? extends V> multimap) {
if (multimap instanceof ImmutableMultimap) {
@SuppressWarnings("unchecked") // safe since multimap is not writable
ImmutableMultimap<K, V> kvMultimap = (ImmutableMultimap<K, V>) 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 <K, V> ImmutableMultimap<K, V> copyOf(
Iterable<? extends Entry<? extends K, ? extends V>> entries) {
return ImmutableListMultimap.copyOf(entries);
}
final transient ImmutableMap<K, ? extends ImmutableCollection<V>> 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<ImmutableMultimap> MAP_FIELD_SETTER =
Serialization.getFieldSetter(ImmutableMultimap.class, "map");
static final Serialization.FieldSetter<ImmutableMultimap> SIZE_FIELD_SETTER =
Serialization.getFieldSetter(ImmutableMultimap.class, "size");
}
ImmutableMultimap(ImmutableMap<K, ? extends ImmutableCollection<V>> 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<V> 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<V> replaceValues(K key, Iterable<? extends V> 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<V> 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<V, K> 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<? extends V> values) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the multimap unmodified.
*
* @throws UnsupportedOperationException always
*/
@Override
public final boolean putAll(Multimap<? extends K, ? extends V> 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<K> keySet() {
return map.keySet();
}
@Override
Set<K> 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<K, Collection<V>> asMap() {
return (ImmutableMap) map;
}
@Override
Map<K, Collection<V>> createAsMap() {
throw new AssertionError("should never be called");
}
/**
* Returns an immutable collection of all key-value pairs in the multimap.
*/
@Override
public ImmutableCollection<Entry<K, V>> entries() {
return (ImmutableCollection<Entry<K, V>>) super.entries();
}
@Override
ImmutableCollection<Entry<K, V>> createEntries() {
return new EntryCollection<>(this);
}
private static class EntryCollection<K, V> extends ImmutableCollection<Entry<K, V>> {
final ImmutableMultimap<K, V> multimap;
EntryCollection(ImmutableMultimap<K, V> multimap) {
this.multimap = multimap;
}
@Override
public UnmodifiableIterator<Entry<K, V>> 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<Entry<K, V>> entryIterator() {
return new UnmodifiableIterator<Entry<K, V>>() {
final Iterator<? extends Entry<K, ? extends ImmutableCollection<V>>> asMapItr =
map.entrySet().iterator();
K currentKey = null;
Iterator<V> valueItr = emptyIterator();
@Override
public boolean hasNext() {
return valueItr.hasNext() || asMapItr.hasNext();
}
@Override
public Entry<K, V> next() {
if (!valueItr.hasNext()) {
Entry<K, ? extends ImmutableCollection<V>> 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<Entry<K, V>> entrySpliterator() {
return CollectSpliterators.flatMap(
asMap().entrySet().spliterator(),
keyToValueCollectionEntry -> {
K key = keyToValueCollectionEntry.getKey();
Collection<V> 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<? super K, ? super V> 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<K> keys() {
return (ImmutableMultiset<K>) super.keys();
}
@Override
ImmutableMultiset<K> createKeys() {
return new Keys();
}
@SuppressWarnings("serial") // Uses writeReplace, not default serialization
class Keys extends ImmutableMultiset<K> {
@Override
public boolean contains(Object object) {
return containsKey(object);
}
@Override
public int count(Object element) {
Collection<V> values = map.get(element);
return (values == null) ? 0 : values.size();
}
@Override
public ImmutableSet<K> elementSet() {
return keySet();
}
@Override
public int size() {
return ImmutableMultimap.this.size();
}
@Override
ImmutableMultiset.Entry<K> getEntry(int index) {
Entry<K, ? extends Collection<V>> 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<V> values() {
return (ImmutableCollection<V>) super.values();
}
@Override
ImmutableCollection<V> createValues() {
return new Values<>(this);
}
@Override
UnmodifiableIterator<V> valueIterator() {
return new UnmodifiableIterator<V>() {
final Iterator<? extends ImmutableCollection<V>> valueCollectionItr = map.values().iterator();
Iterator<V> 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<K, V> extends ImmutableCollection<V> {
private final transient ImmutableMultimap<K, V> multimap;
Values(ImmutableMultimap<K, V> multimap) {
this.multimap = multimap;
}
@Override
public boolean contains(Object object) {
return multimap.containsValue(object);
}
@Override
public UnmodifiableIterator<V> iterator() {
return multimap.valueIterator();
}
@Override
int copyIntoArray(Object[] dst, int offset) {
for (ImmutableCollection<V> 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 <T extends Object> UnmodifiableIterator<T> emptyIterator() {
return emptyListIterator();
}
/**
* Returns the empty iterator.
*
* <p>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 <T extends Object> UnmodifiableListIterator<T> emptyListIterator() {
return (UnmodifiableListIterator<T>) ImmutableCollection.ArrayItr.EMPTY;
}
}

View file

@ -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}.
*
* <p><b>Grouped iteration.</b> In all current implementations, duplicate elements always appear
* consecutively when iterating. Elements iterate in order by the <i>first</i> appearance of that
* element when the multiset was created.
*
*/
@SuppressWarnings("serial") // we're overriding default serialization
public abstract class ImmutableMultiset<E> extends ImmutableCollection<E>
implements Multiset<E> {
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code
* ImmutableMultiset}. Elements iterate in order by the <i>first</i> appearance of that element in
* encounter order.
*
* @since 21.0
*/
public static <E> Collector<E, ?, ImmutableMultiset<E>> 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.
*
* <p>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 <T extends Object, E>
Collector<T, ?, ImmutableMultiset<E>> toImmutableMultiset(
Function<? super T, ? extends E> elementFunction,
ToIntFunction<? super T> countFunction) {
return CollectCollectors.toImmutableMultiset(elementFunction, countFunction);
}
/**
* Returns the empty immutable multiset.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
@SuppressWarnings("unchecked") // all supported methods are covariant
public static <E> ImmutableMultiset<E> of() {
return (ImmutableMultiset<E>) 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 <E> ImmutableMultiset<E> 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 <E> ImmutableMultiset<E> 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 <E> ImmutableMultiset<E> 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 <E> ImmutableMultiset<E> 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 <E> ImmutableMultiset<E> 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 <E> ImmutableMultiset<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) {
return new Builder<E>().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 <E> ImmutableMultiset<E> 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 <E> ImmutableMultiset<E> copyOf(Iterable<? extends E> elements) {
if (elements instanceof ImmutableMultiset) {
@SuppressWarnings("unchecked") // all supported methods are covariant
ImmutableMultiset<E> result = (ImmutableMultiset<E>) elements;
if (!result.isPartialView()) {
return result;
}
}
Multiset<? extends E> 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 <E> ImmutableMultiset<E> copyOf(Iterator<? extends E> elements) {
Multiset<E> multiset = LinkedHashMultiset.create();
Iterators.addAll(multiset, elements);
return copyFromEntries(multiset.entrySet());
}
private static <E> ImmutableMultiset<E> copyFromElements(E... elements) {
Multiset<E> multiset = LinkedHashMultiset.create();
Collections.addAll(multiset, elements);
return copyFromEntries(multiset.entrySet());
}
static <E> ImmutableMultiset<E> copyFromEntries(
Collection<? extends Entry<? extends E>> entries) {
if (entries.isEmpty()) {
return of();
} else {
return RegularImmutableMultiset.create(entries);
}
}
ImmutableMultiset() {}
@Override
public UnmodifiableIterator<E> iterator() {
final Iterator<Entry<E>> entryIterator = entrySet().iterator();
return new UnmodifiableIterator<E>() {
int remaining;
E element;
@Override
public boolean hasNext() {
return (remaining > 0) || entryIterator.hasNext();
}
@Override
public E next() {
if (remaining <= 0) {
Entry<E> 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<E> asList;
@Override
public ImmutableList<E> asList() {
ImmutableList<E> 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<E> 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<E> elementSet();
private transient ImmutableSet<Entry<E>> entrySet;
@Override
public ImmutableSet<Entry<E>> entrySet() {
ImmutableSet<Entry<E>> es = entrySet;
return (es == null) ? (entrySet = createEntrySet()) : es;
}
private ImmutableSet<Entry<E>> createEntrySet() {
return isEmpty() ? ImmutableSet.<Entry<E>>of() : new EntrySet();
}
abstract Entry<E> getEntry(int index);
private final class EntrySet extends IndexedImmutableSet<Entry<E>> {
@Override
boolean isPartialView() {
return ImmutableMultiset.this.isPartialView();
}
@Override
Entry<E> 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<E>(ImmutableMultiset.this);
}
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Use EntrySetSerializedForm");
}
private static final long serialVersionUID = 0;
}
static class EntrySetSerializedForm<E> implements Serializable {
final ImmutableMultiset<E> multiset;
EntrySetSerializedForm(ImmutableMultiset<E> 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 <E> Builder<E> builder() {
return new Builder<E>();
}
/**
* A builder for creating immutable multiset instances, especially {@code public static final}
* multisets ("constant multisets"). Example:
*
* <pre>{@code
* public static final ImmutableMultiset<Bean> BEANS =
* new ImmutableMultiset.Builder<Bean>()
* .addCopies(Bean.COCOA, 4)
* .addCopies(Bean.GARDEN, 6)
* .addCopies(Bean.RED, 8)
* .addCopies(Bean.BLACK_EYED, 10)
* .build();
* }</pre>
*
* <p>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<E> extends ImmutableCollection.Builder<E> {
final Multiset<E> contents;
/**
* Creates a new builder. The returned builder is equivalent to the builder generated by {@link
* ImmutableMultiset#builder}.
*/
public Builder() {
this(LinkedHashMultiset.<E>create());
}
Builder(Multiset<E> 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<E> 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<E> 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<E> 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<E> 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<E> addAll(Iterable<? extends E> elements) {
if (elements instanceof Multiset) {
Multiset<? extends E> 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<E> addAll(Iterator<? extends E> elements) {
super.addAll(elements);
return this;
}
/**
* Returns a newly-created {@code ImmutableMultiset} based on the contents of the {@code
* Builder}.
*/
@Override
public ImmutableMultiset<E> build() {
return copyOf(contents);
}
ImmutableMultiset<E> buildJdkBacked() {
if (contents.isEmpty()) {
return of();
}
return JdkBackedImmutableMultiset.create(contents.entrySet());
}
}
static final class ElementSet<E> extends ImmutableSet.Indexed<E> {
private final List<Entry<E>> entries;
// TODO(cpovirk): @Weak?
private final Multiset<E> delegate;
ElementSet(List<Entry<E>> entries, Multiset<E> 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<? extends Object> multiset) {
int distinct = multiset.entrySet().size();
elements = new Object[distinct];
counts = new int[distinct];
int i = 0;
for (Entry<? extends Object> entry : multiset.entrySet()) {
elements[i] = entry.getElement();
counts[i] = entry.getCount();
i++;
}
}
Object readResolve() {
LinkedHashMultiset<Object> 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;
}
}

View file

@ -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}.
*
* <p><b>Warning:</b> As in all {@link SetMultimap}s, do not modify either a key <i>or a value</i>
* of a {@code ImmutableSetMultimap} in a way that affects its {@link Object#equals} behavior.
* Undefined behavior and bugs will result.
*
* <p>See the Guava User Guide article on <a href=
* "https://github.com/google/guava/wiki/ImmutableCollectionsExplained">immutable collections</a>.
*/
public class ImmutableSetMultimap<K, V> extends ImmutableMultimap<K, V>
implements SetMultimap<K, V> {
/**
* 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.
*
* <p>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 <a
* href="ImmutableMultimap.html#iteration">grouped by key</a>.
*
* <p>Example:
*
* <pre>{@code
* static final Multimap<Character, String> 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<Character, String> FIRST_LETTER_MULTIMAP =
* new ImmutableSetMultimap.Builder<Character, String>()
* .put('b', "anana")
* .putAll('a', "pple", "sparagus")
* .putAll('c', "arrot", "herry")
* .build();
* }</pre>
*/
public static <T extends Object, K, V>
Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends V> valueFunction) {
Objects.requireNonNull(keyFunction, "keyFunction");
Objects.requireNonNull(valueFunction, "valueFunction");
return Collector.of(
ImmutableSetMultimap::<K, V>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.
*
* <p>Example:
*
* <pre>{@code
* static final ImmutableSetMultimap<Character, Character> 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<Character, Character> FIRST_LETTER_MULTIMAP =
* ImmutableSetMultimap.<Character, Character>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<Character, Character> FIRST_LETTER_MULTIMAP =
* ImmutableSetMultimap.<Character, Character>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();
* }
* }</pre>
*
*/
public static <T extends Object, K, V>
Collector<T, ?, ImmutableSetMultimap<K, V>> flatteningToImmutableSetMultimap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends Stream<? extends V>> 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()::<K, V>build),
ImmutableSetMultimap::copyOf);
}
private static <
T extends Object,
K extends Object,
V extends Object,
M extends Multimap<K, V>>
Collector<T, ?, M> flatteningToMultimap(
Function<? super T, ? extends K> keyFunction,
Function<? super T, ? extends Stream<? extends V>> valueFunction,
Supplier<M> multimapSupplier) {
Objects.requireNonNull(keyFunction);
Objects.requireNonNull(valueFunction);
Objects.requireNonNull(multimapSupplier);
return Collector.of(
multimapSupplier,
(multimap, input) -> {
K key = keyFunction.apply(input);
Collection<V> valuesForKey = multimap.get(key);
valueFunction.apply(input).forEachOrdered(valuesForKey::add);
},
(multimap1, multimap2) -> {
multimap1.putAll(multimap2);
return multimap1;
});
}
/**
* Returns the empty multimap.
*
* <p><b>Performance note:</b> the instance returned is a singleton.
*/
// Casting is safe because the multimap will never hold any elements.
@SuppressWarnings("unchecked")
public static <K, V> ImmutableSetMultimap<K, V> of() {
return (ImmutableSetMultimap<K, V>) EmptyImmutableSetMultimap.INSTANCE;
}
/**
* Returns an immutable multimap containing a single entry.
*/
public static <K, V> ImmutableSetMultimap<K, V> of(K k1, V v1) {
Builder<K, V> 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 <K, V> ImmutableSetMultimap<K, V> of(K k1, V v1, K k2, V v2) {
Builder<K, V> 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 <K, V> ImmutableSetMultimap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
Builder<K, V> 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 <K, V> ImmutableSetMultimap<K, V> of(
K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
Builder<K, V> 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 <K, V> ImmutableSetMultimap<K, V> of(
K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
Builder<K, V> 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 <K, V> Builder<K, V> builder() {
return new Builder<>();
}
/**
* A builder for creating immutable {@code SetMultimap} instances, especially {@code public static
* final} multimaps ("constant multimaps"). Example:
*
* <pre>{@code
* static final Multimap<String, Integer> STRING_TO_INTEGER_MULTIMAP =
* new ImmutableSetMultimap.Builder<String, Integer>()
* .put("one", 1)
* .putAll("several", 1, 2, 3)
* .putAll("many", 1, 2, 3, 4, 5)
* .build();
* }</pre>
*
* <p>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<K, V> extends ImmutableMultimap.Builder<K, V> {
/**
* Creates a new builder. The returned builder is equivalent to the builder generated by {@link
* ImmutableSetMultimap#builder}.
*/
public Builder() {
super();
}
@Override
Collection<V> newMutableValueCollection() {
return Platform.preservesInsertionOrderOnAddsSet();
}
/**
* Adds a key-value mapping to the built multimap if it is not already present.
*/
@Override
public Builder<K, V> 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<K, V> put(Entry<? extends K, ? extends V> entry) {
super.put(entry);
return this;
}
@Override
public Builder<K, V> putAll(Iterable<? extends Entry<? extends K, ? extends V>> entries) {
super.putAll(entries);
return this;
}
@Override
public Builder<K, V> putAll(K key, Iterable<? extends V> values) {
super.putAll(key, values);
return this;
}
@Override
public Builder<K, V> putAll(K key, V... values) {
return putAll(key, Arrays.asList(values));
}
@Override
public Builder<K, V> putAll(Multimap<? extends K, ? extends V> multimap) {
for (Entry<? extends K, ? extends Collection<? extends V>> entry :
multimap.asMap().entrySet()) {
putAll(entry.getKey(), entry.getValue());
}
return this;
}
@Override
Builder<K, V> combine(ImmutableMultimap.Builder<K, V> other) {
super.combine(other);
return this;
}
@Override
public Builder<K, V> orderKeysBy(Comparator<? super K> keyComparator) {
super.orderKeysBy(keyComparator);
return this;
}
/**
* Specifies the ordering of the generated multimap's values for each key.
*
* <p>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<K, V> orderValuesBy(Comparator<? super V> valueComparator) {
super.orderValuesBy(valueComparator);
return this;
}
/**
* Returns a newly-created immutable set multimap.
*/
@Override
public ImmutableSetMultimap<K, V> build() {
Collection<Entry<K, Collection<V>>> mapEntries = builderMap.entrySet();
if (keyComparator != null) {
mapEntries = Ordering.from(keyComparator).<K>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.
*
* <p>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 <K, V> ImmutableSetMultimap<K, V> copyOf(
Multimap<? extends K, ? extends V> multimap) {
return copyOf(multimap, null);
}
private static <K, V> ImmutableSetMultimap<K, V> copyOf(
Multimap<? extends K, ? extends V> multimap,
Comparator<? super V> 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<K, V> kvMultimap = (ImmutableSetMultimap<K, V>) 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 <K, V> ImmutableSetMultimap<K, V> copyOf(
Iterable<? extends Entry<? extends K, ? extends V>> entries) {
return new Builder<K, V>().putAll(entries).build();
}
/**
* Creates an ImmutableSetMultimap from an asMap.entrySet.
*/
static <K, V> ImmutableSetMultimap<K, V> fromMapEntries(
Collection<? extends Entry<? extends K, ? extends Collection<? extends V>>> mapEntries,
Comparator<? super V> valueComparator) {
if (mapEntries.isEmpty()) {
return of();
}
ImmutableMap.Builder<K, ImmutableSet<V>> builder =
new ImmutableMap.Builder<>(mapEntries.size());
int size = 0;
for (Entry<? extends K, ? extends Collection<? extends V>> entry : mapEntries) {
K key = entry.getKey();
Collection<? extends V> values = entry.getValue();
ImmutableSet<V> 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<V> emptySet;
ImmutableSetMultimap(
ImmutableMap<K, ImmutableSet<V>> map,
int size,
Comparator<? super V> 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<V> get(K key) {
// This cast is safe as its type is known in constructor.
ImmutableSet<V> set = (ImmutableSet<V>) map.get(key);
return MoreObjects.firstNonNull(set, emptySet);
}
private transient ImmutableSetMultimap<V, K> inverse;
/**
* {@inheritDoc}
*
* <p>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<V, K> inverse() {
ImmutableSetMultimap<V, K> result = inverse;
return (result == null) ? (inverse = invert()) : result;
}
private ImmutableSetMultimap<V, K> invert() {
Builder<V, K> builder = builder();
for (Entry<K, V> entry : entries()) {
builder.put(entry.getValue(), entry.getKey());
}
ImmutableSetMultimap<V, K> 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<V> removeAll(Object key) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the multimap unmodified.
*
* @throws UnsupportedOperationException always
*/
@Override
public final ImmutableSet<V> replaceValues(K key, Iterable<? extends V> values) {
throw new UnsupportedOperationException();
}
private transient ImmutableSet<Entry<K, V>> 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<Entry<K, V>> entries() {
ImmutableSet<Entry<K, V>> result = entries;
return result == null ? (entries = new EntrySet<>(this)) : result;
}
private static final class EntrySet<K, V> extends ImmutableSet<Entry<K, V>> {
private final transient ImmutableSetMultimap<K, V> multimap;
EntrySet(ImmutableSetMultimap<K, V> 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<Entry<K, V>> iterator() {
return multimap.entryIterator();
}
@Override
boolean isPartialView() {
return false;
}
}
private static <V> ImmutableSet<V> valueSet(Comparator<? super V> valueComparator, Collection<? extends V> values) {
return (valueComparator == null)
? ImmutableSet.copyOf(values)
: ImmutableSortedSet.copyOf(valueComparator, values);
}
private static <V> ImmutableSet<V> emptySet(Comparator<? super V> valueComparator) {
return (valueComparator == null)
? ImmutableSet.of()
: ImmutableSortedSet.<V>emptySet(valueComparator);
}
private static <V> ImmutableSet.Builder<V> valuesBuilder(Comparator<? super V> valueComparator) {
return (valueComparator == null)
? new ImmutableSet.Builder<V>()
: new ImmutableSortedSet.Builder<V>(valueComparator);
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeObject(valueComparator());
Serialization.writeMultimap(this, stream);
}
Comparator<? super V> valueComparator() {
return emptySet instanceof ImmutableSortedSet
? ((ImmutableSortedSet<V>) emptySet).comparator()
: null;
}
private static final class SetFieldSettersHolder {
static final Serialization.FieldSetter<ImmutableSetMultimap> EMPTY_SET_FIELD_SETTER =
Serialization.getFieldSetter(ImmutableSetMultimap.class, "emptySet");
}
@SuppressWarnings("unchecked")
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
Comparator<Object> valueComparator = (Comparator<Object>) stream.readObject();
int keyCount = stream.readInt();
if (keyCount < 0) {
throw new InvalidObjectException("Invalid key count " + keyCount);
}
ImmutableMap.Builder<Object, ImmutableSet<Object>> 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<Object> valuesBuilder = valuesBuilder(valueComparator);
for (int j = 0; j < valueCount; j++) {
valuesBuilder.add(stream.readObject());
}
ImmutableSet<Object> 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<Object, ImmutableSet<Object>> 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;
}

View file

@ -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<E extends Object> extends AbstractSet<E> {
@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;
}
}

View file

@ -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<E> extends ImmutableMultiset<E> {
private final Map<E, Integer> delegateMap;
private final ImmutableList<Entry<E>> entries;
private final long size;
static <E> ImmutableMultiset<E> create(Collection<? extends Entry<? extends E>> entries) {
@SuppressWarnings("unchecked")
Entry<E>[] entriesArray = entries.toArray(new Entry[0]);
Map<E, Integer> delegateMap = new HashMap<>(entriesArray.length);
long size = 0;
for (int i = 0; i < entriesArray.length; i++) {
Entry<E> 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<E, Integer> delegateMap, ImmutableList<Entry<E>> 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<E> elementSet;
@Override
public ImmutableSet<E> elementSet() {
ImmutableSet<E> result = elementSet;
return (result == null) ? elementSet = new ElementSet<>(entries, this) : result;
}
@Override
Entry<E> 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;
}
}

View file

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>Keys and values may be null. All optional multimap methods are supported, and all returned
* views are modifiable.
*
* <p>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}.
*
* <p><b>Warning:</b> Do not modify either a key <i>or a value</i> of a {@code LinkedHashMultimap}
* in a way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will
* result.
*
*/
public final class LinkedHashMultimap<K extends Object, V extends Object>
extends AbstractSetMultimap<K, V> {
/** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */
public static <K extends Object, V extends Object>
LinkedHashMultimap<K, V> 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 <K extends Object, V extends Object>
LinkedHashMultimap<K, V> 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 <K extends Object, V extends Object>
LinkedHashMultimap<K, V> create(Multimap<? extends K, ? extends V> multimap) {
LinkedHashMultimap<K, V> result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY);
result.putAll(multimap);
return result;
}
private interface ValueSetLink<K extends Object, V extends Object> {
ValueSetLink<K, V> getPredecessorInValueSet();
ValueSetLink<K, V> getSuccessorInValueSet();
void setPredecessorInValueSet(ValueSetLink<K, V> entry);
void setSuccessorInValueSet(ValueSetLink<K, V> entry);
}
private static <K extends Object, V extends Object> void succeedsInValueSet(
ValueSetLink<K, V> pred, ValueSetLink<K, V> succ) {
pred.setSuccessorInValueSet(succ);
succ.setPredecessorInValueSet(pred);
}
private static <K extends Object, V extends Object> void succeedsInMultimap(
ValueEntry<K, V> pred, ValueEntry<K, V> succ) {
pred.setSuccessorInMultimap(succ);
succ.setPredecessorInMultimap(pred);
}
private static <K extends Object, V extends Object> void deleteFromValueSet(
ValueSetLink<K, V> entry) {
succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet());
}
private static <K extends Object, V extends Object> void deleteFromMultimap(
ValueEntry<K, V> 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<V>} associated with a key, the linked list of insertion-ordered
* entries in that {@code Set<V>}, and the linked list of entries in the LinkedHashMultimap as a
* whole.
*/
static final class ValueEntry<K extends Object, V extends Object>
extends ImmutableEntry<K, V> implements ValueSetLink<K, V> {
final int smearedValueHash;
ValueEntry<K, V> 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<K, V> predecessorInValueSet;
ValueSetLink<K, V> successorInValueSet;
ValueEntry<K, V> predecessorInMultimap;
ValueEntry<K, V> successorInMultimap;
ValueEntry(K key, V value,
int smearedValueHash,
ValueEntry<K, V> nextInValueBucket) {
super(key, value);
this.smearedValueHash = smearedValueHash;
this.nextInValueBucket = nextInValueBucket;
}
@SuppressWarnings("nullness") // see the comment on the class fields, especially about newHeader
static <K extends Object, V extends Object> ValueEntry<K, V> newHeader() {
return new ValueEntry<>(null, null, 0, null);
}
boolean matchesValue(Object v, int smearedVHash) {
return smearedValueHash == smearedVHash && Objects.equal(getValue(), v);
}
@Override
public ValueSetLink<K, V> getPredecessorInValueSet() {
return requireNonNull(predecessorInValueSet); // see the comment on the class fields
}
@Override
public ValueSetLink<K, V> getSuccessorInValueSet() {
return requireNonNull(successorInValueSet); // see the comment on the class fields
}
@Override
public void setPredecessorInValueSet(ValueSetLink<K, V> entry) {
predecessorInValueSet = entry;
}
@Override
public void setSuccessorInValueSet(ValueSetLink<K, V> entry) {
successorInValueSet = entry;
}
public ValueEntry<K, V> getPredecessorInMultimap() {
return requireNonNull(predecessorInMultimap); // see the comment on the class fields
}
public ValueEntry<K, V> getSuccessorInMultimap() {
return requireNonNull(successorInMultimap); // see the comment on the class fields
}
public void setSuccessorInMultimap(ValueEntry<K, V> multimapSuccessor) {
this.successorInMultimap = multimapSuccessor;
}
public void setPredecessorInMultimap(ValueEntry<K, V> 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<K, V> multimapHeaderEntry;
private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) {
super(Platform.<K, Collection<V>>newLinkedHashMapWithExpectedSize(keyCapacity));
checkNonnegative(valueSetCapacity, "expectedValuesPerKey");
this.valueSetCapacity = valueSetCapacity;
this.multimapHeaderEntry = ValueEntry.newHeader();
succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry);
}
/**
* {@inheritDoc}
*
* <p>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<V> createCollection() {
return Platform.newLinkedHashSetWithExpectedSize(valueSetCapacity);
}
/**
* {@inheritDoc}
*
* <p>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<V> createCollection(K key) {
return new ValueSet(key, valueSetCapacity);
}
/**
* {@inheritDoc}
*
* <p>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<V> replaceValues(K key, Iterable<? extends V> 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.
*
* <p>The iterator generated by the returned set traverses the entries in the order they were
* added to the multimap.
*
* <p>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<Entry<K, V>> entries() {
return super.entries();
}
/**
* Returns a view collection of all <i>distinct</i> 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.
*
* <p>The iterator generated by the returned set traverses the keys in the order they were first
* added to the multimap.
*
* <p>Changes to the returned set will update the underlying multimap, and vice versa. However,
* <i>adding</i> to the returned set is not possible.
*/
@Override
public Set<K> 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.
*
* <p>The iterator generated by the returned collection traverses the values in the order they
* were added to the multimap.
*/
@Override
public Collection<V> values() {
return super.values();
}
final class ValueSet extends ImprovedAbstractSet<V> implements ValueSetLink<K, V> {
/*
* We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory
* consumption.
*/
private final K key;
ValueEntry<K, V>[] 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<K, V> firstEntry;
private ValueSetLink<K, V> 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<K, V>[] hashTable = new ValueEntry[tableSize];
this.hashTable = hashTable;
}
private int mask() {
return hashTable.length - 1;
}
@Override
public ValueSetLink<K, V> getPredecessorInValueSet() {
return lastEntry;
}
@Override
public ValueSetLink<K, V> getSuccessorInValueSet() {
return firstEntry;
}
@Override
public void setPredecessorInValueSet(ValueSetLink<K, V> entry) {
lastEntry = entry;
}
@Override
public void setSuccessorInValueSet(ValueSetLink<K, V> entry) {
firstEntry = entry;
}
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
ValueSetLink<K, V> nextEntry = firstEntry;
ValueEntry<K, V> 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<K, V> entry = (ValueEntry<K, V>) 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<? super V> action) {
checkNotNull(action);
for (ValueSetLink<K, V> entry = firstEntry;
entry != ValueSet.this;
entry = entry.getSuccessorInValueSet()) {
action.accept(((ValueEntry<K, V>) entry).getValue());
}
}
@Override
public int size() {
return size;
}
@Override
public boolean contains(Object o) {
int smearedHash = Hashing.smearedHash(o);
for (ValueEntry<K, V> 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<K, V> rowHead = hashTable[bucket];
for (ValueEntry<K, V> entry = rowHead; entry != null; entry = entry.nextInValueBucket) {
if (entry.matchesValue(value, smearedHash)) {
return false;
}
}
ValueEntry<K, V> 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<K, V>[] hashTable = new ValueEntry[this.hashTable.length * 2];
this.hashTable = hashTable;
int mask = hashTable.length - 1;
for (ValueSetLink<K, V> entry = firstEntry;
entry != this;
entry = entry.getSuccessorInValueSet()) {
ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) 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<K, V> prev = null;
for (ValueEntry<K, V> 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<K, V> entry = firstEntry;
entry != this;
entry = entry.getSuccessorInValueSet()) {
ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry;
deleteFromMultimap(valueEntry);
}
succeedsInValueSet(this, this);
modCount++;
}
}
@Override
Iterator<Entry<K, V>> entryIterator() {
return new Iterator<Entry<K, V>>() {
ValueEntry<K, V> nextEntry = multimapHeaderEntry.getSuccessorInMultimap();
ValueEntry<K, V> toRemove;
@Override
public boolean hasNext() {
return nextEntry != multimapHeaderEntry;
}
@Override
public Entry<K, V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
ValueEntry<K, V> 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<Entry<K, V>> entrySpliterator() {
return Spliterators.spliterator(entries(), Spliterator.DISTINCT | Spliterator.ORDERED);
}
@Override
Iterator<V> valueIterator() {
return Maps.valueIterator(entryIterator());
}
@Override
Spliterator<V> valueSpliterator() {
return CollectSpliterators.map(entrySpliterator(), Entry::getValue);
}
@Override
public void clear() {
super.clear();
succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry);
}
}

View file

@ -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<E extends Object>
extends AbstractMapBasedMultiset<E> {
/** Creates a new, empty {@code LinkedHashMultiset} using the default initial capacity. */
public static <E extends Object> LinkedHashMultiset<E> create() {
return new LinkedHashMultiset<E>();
}
/**
* 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 <E extends Object> LinkedHashMultiset<E> create(int distinctElements) {
return new LinkedHashMultiset<E>(distinctElements);
}
/**
* Creates a new {@code LinkedHashMultiset} containing the specified elements.
*
* <p>This implementation is highly efficient when {@code elements} is itself a {@link Multiset}.
*
* @param elements the elements that the multiset should contain
*/
public static <E extends Object> LinkedHashMultiset<E> create(
Iterable<? extends E> elements) {
LinkedHashMultiset<E> multiset = create(Multisets.inferDistinctElements(elements));
Iterables.addAll(multiset, elements);
return multiset;
}
private LinkedHashMultiset() {
super(new LinkedHashMap<E, Count>());
}
private LinkedHashMultiset(int distinctElements) {
super(Maps.<E, Count>newLinkedHashMapWithExpectedSize(distinctElements));
}
}

View file

@ -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:
*
* <pre>{@code
* Multimap<K, V> multimap = LinkedListMultimap.create();
* multimap.put(key1, foo);
* multimap.put(key2, bar);
* multimap.put(key1, baz);
* }</pre>
*
* ... 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:
*
* <pre>{@code
* multimap.remove(key1, foo);
* }</pre>
*
* <p>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.
*
* <p>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.
*
* <p>The {@link #values()} and {@link #entries()} methods both return a {@code List}, instead of
* the {@code Collection} specified by the {@link ListMultimap} interface.
*
* <p>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.
*
* <p>Keys and values may be null. All optional multimap methods are supported, and all returned
* views are modifiable.
*
* <p>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}.
*
* <p>See the Guava User Guide article on <a href=
* "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}</a>.
*
* @author Mike Bostock
* @since 2.0
*/
@GwtCompatible(serializable = true, emulated = true)
@ElementTypesAreNonnullByDefault
public class LinkedListMultimap<K extends @Nullable Object, V extends @Nullable Object>
extends AbstractMultimap<K, V> implements ListMultimap<K, V>, 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<K extends @Nullable Object, V extends @Nullable Object>
extends AbstractMapEntry<K, V> {
@ParametricNullness final K key;
@ParametricNullness V value;
@CheckForNull Node<K, V> next; // the next node (with any key)
@CheckForNull Node<K, V> previous; // the previous node (with any key)
@CheckForNull Node<K, V> nextSibling; // the next node with the same key
@CheckForNull Node<K, V> 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<K extends @Nullable Object, V extends @Nullable Object> {
Node<K, V> head;
Node<K, V> tail;
int count;
KeyList(Node<K, V> firstNode) {
this.head = firstNode;
this.tail = firstNode;
firstNode.previousSibling = null;
firstNode.nextSibling = null;
this.count = 1;
}
}
@CheckForNull private transient Node<K, V> head; // the head for all keys
@CheckForNull private transient Node<K, V> tail; // the tail for all keys
private transient Map<K, KeyList<K, V>> 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 <K extends @Nullable Object, V extends @Nullable Object>
LinkedListMultimap<K, V> 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 <K extends @Nullable Object, V extends @Nullable Object>
LinkedListMultimap<K, V> 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 <K extends @Nullable Object, V extends @Nullable Object>
LinkedListMultimap<K, V> create(Multimap<? extends K, ? extends V> multimap) {
return new LinkedListMultimap<>(multimap);
}
LinkedListMultimap() {
this(12);
}
private LinkedListMultimap(int expectedKeys) {
keyToKeyList = Platform.newHashMapWithExpectedSize(expectedKeys);
}
private LinkedListMultimap(Multimap<? extends K, ? extends V> 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<K, V> addNode(
@ParametricNullness K key,
@ParametricNullness V value,
@CheckForNull Node<K, V> nextSibling) {
Node<K, V> node = new Node<>(key, value);
if (head == null) { // empty list
head = tail = node;
keyToKeyList.put(key, new KeyList<K, V>(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<K, V> keyList = keyToKeyList.get(key);
if (keyList == null) {
keyToKeyList.put(key, keyList = new KeyList<>(node));
modCount++;
} else {
keyList.count++;
Node<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<Entry<K, V>> {
int nextIndex;
@CheckForNull Node<K, V> next;
@CheckForNull Node<K, V> current;
@CheckForNull Node<K, V> 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<K, V> 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<K, V> 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<K, V> e) {
throw new UnsupportedOperationException();
}
@Override
public void add(Entry<K, V> 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<K> {
final Set<K> seenKeys = Sets.<K>newHashSetWithExpectedSize(keySet().size());
@CheckForNull Node<K, V> next = head;
@CheckForNull Node<K, V> 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<V> {
@ParametricNullness final K key;
int nextIndex;
@CheckForNull Node<K, V> next;
@CheckForNull Node<K, V> current;
@CheckForNull Node<K, V> previous;
/** Constructs a new iterator over all values for the specified key. */
ValueForKeyIterator(@ParametricNullness K key) {
this.key = key;
KeyList<K, V> 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<K, V> 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}
*
* <p>If any entries for the specified {@code key} already exist in the multimap, their values are
* changed in-place without affecting the iteration order.
*
* <p>The returned list is immutable and implements {@link java.util.RandomAccess}.
*/
@CanIgnoreReturnValue
@Override
public List<V> replaceValues(@ParametricNullness K key, Iterable<? extends V> values) {
List<V> oldValues = getCopy(key);
ListIterator<V> keyValues = new ValueForKeyIterator(key);
Iterator<? extends V> 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<V> getCopy(@ParametricNullness K key) {
return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key)));
}
/**
* {@inheritDoc}
*
* <p>The returned list is immutable and implements {@link java.util.RandomAccess}.
*/
@CanIgnoreReturnValue
@Override
public List<V> 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<V> oldValues = getCopy(castKey);
removeAllNodes(castKey);
return oldValues;
}
@Override
public void clear() {
head = null;
tail = null;
keyToKeyList.clear();
size = 0;
modCount++;
}
// Views
/**
* {@inheritDoc}
*
* <p>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.
*
* <p>The returned list is not serializable and does not have random access.
*/
@Override
public List<V> get(@ParametricNullness final K key) {
return new AbstractSequentialList<V>() {
@Override
public int size() {
KeyList<K, V> keyList = keyToKeyList.get(key);
return (keyList == null) ? 0 : keyList.count;
}
@Override
public ListIterator<V> listIterator(int index) {
return new ValueForKeyIterator(key, index);
}
};
}
@Override
Set<K> createKeySet() {
@WeakOuter
class KeySetImpl extends Sets.ImprovedAbstractSet<K> {
@Override
public int size() {
return keyToKeyList.size();
}
@Override
public Iterator<K> 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<K> createKeys() {
return new Multimaps.Keys<K, V>(this);
}
/**
* {@inheritDoc}
*
* <p>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<V> values() {
return (List<V>) super.values();
}
@Override
List<V> createValues() {
@WeakOuter
class ValuesImpl extends AbstractSequentialList<V> {
@Override
public int size() {
return size;
}
@Override
public ListIterator<V> listIterator(int index) {
final NodeIterator nodeItr = new NodeIterator(index);
return new TransformedListIterator<Entry<K, V>, V>(nodeItr) {
@Override
@ParametricNullness
V transform(Entry<K, V> entry) {
return entry.getValue();
}
@Override
public void set(@ParametricNullness V value) {
nodeItr.setValue(value);
}
};
}
}
return new ValuesImpl();
}
/**
* {@inheritDoc}
*
* <p>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.
*
* <p>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<Entry<K, V>> entries() {
return (List<Entry<K, V>>) super.entries();
}
@Override
List<Entry<K, V>> createEntries() {
@WeakOuter
class EntriesImpl extends AbstractSequentialList<Entry<K, V>> {
@Override
public int size() {
return size;
}
@Override
public ListIterator<Entry<K, V>> listIterator(int index) {
return new NodeIterator(index);
}
@Override
public void forEach(Consumer<? super Entry<K, V>> action) {
checkNotNull(action);
for (Node<K, V> node = head; node != null; node = node.next) {
action.accept(node);
}
}
}
return new EntriesImpl();
}
@Override
Iterator<Entry<K, V>> entryIterator() {
throw new AssertionError("should never be called");
}
@Override
Map<K, Collection<V>> 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<K, V> 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;
}

View file

@ -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<K extends Object, V extends Object> extends ImprovedAbstractSet<K> {
final Map<K, V> map;
MapsKeySet(Map<K, V> map) {
this.map = Objects.requireNonNull(map);
}
Map<K, V> map() {
return map;
}
@Override
public Iterator<K> iterator() {
return keyIterator(map().entrySet().iterator());
}
@Override
public void forEach(Consumer<? super K> 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 <K extends Object, V extends Object> Iterator<K> keyIterator(
Iterator<Map.Entry<K, V>> entryIterator) {
return new TransformedIterator<Map.Entry<K, V>, K>(entryIterator) {
@Override
K transform(Map.Entry<K, V> entry) {
return entry.getKey();
}
};
}
}

View file

@ -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.
*
* <p>This can be used to easily configure multimap data structure implementations not provided
* explicitly in {@code com.google.common.collect}, for example:
*
* <pre>{@code
* ListMultimap<String, Integer> treeListMultimap =
* MultimapBuilder.treeKeys().arrayListValues().build();
* SetMultimap<Integer, MyEnum> hashEnumMultimap =
* MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
* }</pre>
*
* <p>{@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.
*
* <p>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 <K0> An upper bound on the key type of the generated multimap.
* @param <V0> An upper bound on the value type of the generated multimap.
*/
public abstract class MultimapBuilder<K0 extends Object, V0 extends Object> {
/*
* 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<Object> 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<Object> hashKeys(int expectedKeys) {
checkNonnegative(expectedKeys, "expectedKeys");
return new MultimapBuilderWithKeys<Object>() {
@Override
<K extends Object, V extends Object> Map<K, Collection<V>> createMap() {
return new HashMap(expectedKeys);
}
};
}
/**
* Uses a hash table to map keys to value collections.
*
* <p>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<Object> linkedHashKeys() {
return linkedHashKeys(DEFAULT_EXPECTED_KEYS);
}
/**
* Uses an hash table to map keys to value collections, initialized to expect the specified number
* of keys.
*
* <p>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<Object> linkedHashKeys(int expectedKeys) {
checkNonnegative(expectedKeys, "expectedKeys");
return new MultimapBuilderWithKeys<Object>() {
@Override
<K extends Object, V extends Object> Map<K, Collection<V>> createMap() {
return new LinkedHashMap<>(expectedKeys);
}
};
}
/**
* Uses a naturally-ordered {@link TreeMap} to map keys to value collections.
*
* <p>The collections returned by {@link Multimap#keySet()}, {@link Multimap#keys()}, and {@link
* Multimap#asMap()} will iterate through the keys in sorted order.
*
* <p>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<Comparable> treeKeys() {
return treeKeys(Ordering.natural());
}
/**
* Uses a {@link TreeMap} sorted by the specified comparator to map keys to value collections.
*
* <p>The collections returned by {@link Multimap#keySet()}, {@link Multimap#keys()}, and {@link
* Multimap#asMap()} will iterate through the keys in sorted order.
*
* <p>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}.
*
* <p>Multimaps generated by the resulting builder will not be serializable if {@code comparator}
* is not serializable.
*/
public static <K0 extends Object> MultimapBuilderWithKeys<K0> treeKeys(
Comparator<K0> comparator) {
Objects.requireNonNull(comparator);
return new MultimapBuilderWithKeys<K0>() {
@Override
<K extends K0, V extends Object> Map<K, Collection<V>> createMap() {
return new TreeMap<>(comparator);
}
};
}
/**
* Uses an {@link EnumMap} to map keys to value collections.
*/
public static <K0 extends Enum<K0>> MultimapBuilderWithKeys<K0> enumKeys(Class<K0> keyClass) {
Objects.requireNonNull(keyClass);
return new MultimapBuilderWithKeys<K0>() {
@SuppressWarnings("unchecked")
@Override
<K extends K0, V extends Object> Map<K, Collection<V>> createMap() {
// K must actually be K0, since enums are effectively final
// (their subclasses are inaccessible)
return (Map<K, Collection<V>>) new EnumMap<K0, Collection<V>>(keyClass);
}
};
}
private static final class ArrayListSupplier<V extends Object>
implements Supplier<List<V>>, Serializable {
private final int expectedValuesPerKey;
ArrayListSupplier(int expectedValuesPerKey) {
this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey");
}
@Override
public List<V> get() {
return new ArrayList<>(expectedValuesPerKey);
}
}
private enum LinkedListSupplier implements Supplier<List<?>> {
INSTANCE;
public static <V extends Object> Supplier<List<V>> instance() {
// Each call generates a fresh LinkedList, which can serve as a List<V> for any V.
@SuppressWarnings({"rawtypes", "unchecked"})
Supplier<List<V>> result = (Supplier) INSTANCE;
return result;
}
@Override
public List<?> get() {
return new LinkedList<>();
}
}
private static final class HashSetSupplier<V extends Object>
implements Supplier<Set<V>>, Serializable {
private final int expectedValuesPerKey;
HashSetSupplier(int expectedValuesPerKey) {
this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey");
}
@Override
public Set<V> get() {
return new HashSet<>(expectedValuesPerKey);
}
}
private static final class LinkedHashSetSupplier<V extends Object>
implements Supplier<Set<V>>, Serializable {
private final int expectedValuesPerKey;
LinkedHashSetSupplier(int expectedValuesPerKey) {
this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey");
}
@Override
public Set<V> get() {
return new LinkedHashSet(expectedValuesPerKey);
}
}
private static final class TreeSetSupplier<V extends Object>
implements Supplier<SortedSet<V>>, Serializable {
private final Comparator<? super V> comparator;
TreeSetSupplier(Comparator<? super V> comparator) {
this.comparator = Objects.requireNonNull(comparator);
}
@Override
public SortedSet<V> get() {
return new TreeSet<>(comparator);
}
}
private static final class EnumSetSupplier<V extends Enum<V>>
implements Supplier<Set<V>>, Serializable {
private final Class<V> clazz;
EnumSetSupplier(Class<V> clazz) {
this.clazz = Objects.requireNonNull(clazz);
}
@Override
public Set<V> 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 <K0> The upper bound on the key type of the generated multimap.
* @since 16.0
*/
public abstract static class MultimapBuilderWithKeys<K0 extends Object> {
private static final int DEFAULT_EXPECTED_VALUES_PER_KEY = 2;
MultimapBuilderWithKeys() {}
abstract <K extends K0, V extends Object> Map<K, Collection<V>> createMap();
/** Uses an {@link ArrayList} to store value collections. */
public ListMultimapBuilder<K0, Object> 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<K0, Object> arrayListValues(int expectedValuesPerKey) {
checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey");
return new ListMultimapBuilder<K0, Object>() {
@Override
public <K extends K0, V extends Object> ListMultimap<K, V> build() {
return Multimaps.newListMultimap(
MultimapBuilderWithKeys.this.<K, V>createMap(),
new ArrayListSupplier<V>(expectedValuesPerKey));
}
};
}
/** Uses a {@link LinkedList} to store value collections. */
public ListMultimapBuilder<K0, Object> linkedListValues() {
return new ListMultimapBuilder<K0, Object>() {
@Override
public <K extends K0, V extends Object> ListMultimap<K, V> build() {
return Multimaps.newListMultimap(
MultimapBuilderWithKeys.this.<K, V>createMap(), LinkedListSupplier.<V>instance());
}
};
}
/** Uses a hash-based {@code Set} to store value collections. */
public SetMultimapBuilder<K0, Object> 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<K0, Object> hashSetValues(int expectedValuesPerKey) {
checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey");
return new SetMultimapBuilder<K0, Object>() {
@Override
public <K extends K0, V extends Object> SetMultimap<K, V> build() {
return Multimaps.newSetMultimap(
MultimapBuilderWithKeys.this.<K, V>createMap(),
new HashSetSupplier<V>(expectedValuesPerKey));
}
};
}
/** Uses an insertion-ordered hash-based {@code Set} to store value collections. */
public SetMultimapBuilder<K0, Object> 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<K0, Object> linkedHashSetValues(int expectedValuesPerKey) {
checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey");
return new SetMultimapBuilder<K0, Object>() {
@Override
public <K extends K0, V extends Object> SetMultimap<K, V> build() {
return Multimaps.newSetMultimap(
MultimapBuilderWithKeys.this.<K, V>createMap(),
new LinkedHashSetSupplier<V>(expectedValuesPerKey));
}
};
}
/** Uses a naturally-ordered {@link TreeSet} to store value collections. */
@SuppressWarnings("rawtypes")
public SortedSetMultimapBuilder<K0, Comparable> treeSetValues() {
return treeSetValues(Ordering.natural());
}
/**
* Uses a {@link TreeSet} ordered by the specified comparator to store value collections.
*
* <p>Multimaps generated by the resulting builder will not be serializable if {@code
* comparator} is not serializable.
*/
public <V0 extends Object> SortedSetMultimapBuilder<K0, V0> treeSetValues(
Comparator<V0> comparator) {
Objects.requireNonNull(comparator, "comparator");
return new SortedSetMultimapBuilder<K0, V0>() {
@Override
public <K extends K0, V extends V0> SortedSetMultimap<K, V> build() {
return Multimaps.newSortedSetMultimap(
MultimapBuilderWithKeys.this.<K, V>createMap(), new TreeSetSupplier<V>(comparator));
}
};
}
/** Uses an {@link EnumSet} to store value collections. */
public <V0 extends Enum<V0>> SetMultimapBuilder<K0, V0> enumSetValues(Class<V0> valueClass) {
Objects.requireNonNull(valueClass, "valueClass");
return new SetMultimapBuilder<K0, V0>() {
@Override
public <K extends K0, V extends V0> SetMultimap<K, V> build() {
// V must actually be V0, since enums are effectively final
// (their subclasses are inaccessible)
@SuppressWarnings({"unchecked", "rawtypes"})
Supplier<Set<V>> factory = (Supplier) new EnumSetSupplier<V0>(valueClass);
return Multimaps.newSetMultimap(MultimapBuilderWithKeys.this.<K, V>createMap(), factory);
}
};
}
}
/** Returns a new, empty {@code Multimap} with the specified implementation. */
public abstract <K extends K0, V extends V0> Multimap<K, V> build();
/**
* Returns a {@code Multimap} with the specified implementation, initialized with the entries of
* {@code multimap}.
*/
public <K extends K0, V extends V0> Multimap<K, V> build(
Multimap<? extends K, ? extends V> multimap) {
Multimap<K, V> 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<K0, V0> {
ListMultimapBuilder() {}
@Override
public abstract <K extends K0, V extends V0> ListMultimap<K, V> build();
@Override
public <K extends K0, V extends V0> ListMultimap<K, V> build(
Multimap<? extends K, ? extends V> multimap) {
return (ListMultimap<K, V>) 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<K0, V0> {
SetMultimapBuilder() {}
@Override
public abstract <K extends K0, V extends V0> SetMultimap<K, V> build();
@Override
public <K extends K0, V extends V0> SetMultimap<K, V> build(
Multimap<? extends K, ? extends V> multimap) {
return (SetMultimap<K, V>) 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<K0, V0> {
SortedSetMultimapBuilder() {}
@Override
public abstract <K extends K0, V extends V0> SortedSetMultimap<K, V> build();
@Override
public <K extends K0, V extends V0> SortedSetMultimap<K, V> build(
Multimap<? extends K, ? extends V> multimap) {
return (SortedSetMultimap<K, V>) super.build(multimap);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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<K extends Object, V extends Object>
extends AbstractCollection<Map.Entry<K, V>> {
abstract Multimap<K, V> 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();
}
}

View file

@ -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<E extends Object> implements Multiset.Entry<E> {
/**
* 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);
}
}

View file

@ -0,0 +1,33 @@
package org.xbib.datastructures.multi;
import java.io.Serializable;
public class MultisetsImmutableEntry<E extends Object> extends MultisetsAbstractEntry<E>
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<E> nextInBucket() {
return null;
}
private static final long serialVersionUID = 0;
}

View file

@ -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.
*
* <p>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.
*
* <p>The serialized output consists of the number of entries, first key, first value, second key,
* second value, and so on.
*/
static <K extends Object, V extends Object> void writeMap(
Map<K, V> map, ObjectOutputStream stream) throws IOException {
stream.writeInt(map.size());
for (Map.Entry<K, V> 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 <K extends Object, V extends Object> void populateMap(
Map<K, V> 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 <K extends Object, V extends Object> void populateMap(
Map<K, V> 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.
*
* <p>The serialized output consists of the number of distinct elements, the first element, its
* count, the second element, its count, and so on.
*/
static <E extends Object> void writeMultiset(
Multiset<E> multiset, ObjectOutputStream stream) throws IOException {
int entryCount = multiset.entrySet().size();
stream.writeInt(entryCount);
for (Multiset.Entry<E> 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 <E extends Object> void populateMultiset(
Multiset<E> 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 <E extends Object> void populateMultiset(
Multiset<E> 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.
*
* <p>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 <K extends Object, V extends Object> void writeMultimap(
Multimap<K, V> multimap, ObjectOutputStream stream) throws IOException {
stream.writeInt(multimap.asMap().size());
for (Map.Entry<K, Collection<V>> 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 <K extends Object, V extends Object> void populateMultimap(
Multimap<K, V> 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 <K extends Object, V extends Object> void populateMultimap(
Multimap<K, V> 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<V> 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 <T> FieldSetter<T> getFieldSetter(Class<T> 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<T> {
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);
}
}
}
}

View file

@ -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<F extends Object, T extends Object>
implements Iterator<T> {
final Iterator<? extends F> backingIterator;
TransformedIterator(Iterator<? extends F> 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();
}
}

View file

@ -46,14 +46,15 @@ dependencyResolutionManagement {
} }
} }
include 'config'
include 'datastructures-api' include 'datastructures-api'
include 'datastructures-interpolation'
include 'datastructures-io'
include 'datastructures-charset' include 'datastructures-charset'
include 'datastructures-common' include 'datastructures-common'
include 'datastructures-xml'
include 'datastructures-csv' 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-dsl'
include 'datastructures-json-flat' include 'datastructures-json-flat'
include 'datastructures-json-iterator' include 'datastructures-json-iterator'
@ -62,15 +63,17 @@ include 'datastructures-json-mini'
include 'datastructures-json-minimal' include 'datastructures-json-minimal'
include 'datastructures-json-noggit' include 'datastructures-json-noggit'
include 'datastructures-json-simple' include 'datastructures-json-simple'
include 'datastructures-queue-tape'
include 'datastructures-tiny'
include 'datastructures-json-tiny' include 'datastructures-json-tiny'
include 'datastructures-yaml-tiny' include 'datastructures-multi'
include 'datastructures-validation' include 'datastructures-queue-tape'
include 'datastructures-trie'
include 'datastructures-raml' 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-api'
include 'settings-datastructures' include 'settings-datastructures'
include 'settings-datastructures-json' include 'settings-datastructures-json'
include 'settings-datastructures-yaml' include 'settings-datastructures-yaml'
include 'config'