start work of immutable and multi subprojects
This commit is contained in:
parent
711ff4a6a9
commit
a79c8a8e9e
91 changed files with 21957 additions and 10 deletions
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
31
datastructures-immutable/NOTICE.txt
Normal file
31
datastructures-immutable/NOTICE.txt
Normal 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
|
3
datastructures-immutable/build.gradle
Normal file
3
datastructures-immutable/build.gradle
Normal file
|
@ -0,0 +1,3 @@
|
|||
dependencies {
|
||||
api project(':datastructures-api')
|
||||
}
|
5
datastructures-immutable/src/main/java/module-info.java
Normal file
5
datastructures-immutable/src/main/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|