start work of immutable and multi subprojects

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

View file

@ -0,0 +1,75 @@
package org.xbib.datastructures.api;
import java.util.Map;
import java.util.Set;
/**
* A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as
* that of its keys. This constraint enables bimaps to support an "inverse view", which is another
* bimap containing the same entries as this bimap but with reversed keys and values.
*/
public interface BiMap<K extends Object, V extends Object> extends Map<K, V> {
/**
* {@inheritDoc}
*
* @throws IllegalArgumentException if the given value is already bound to a different key in this
* bimap. The bimap will remain unmodified in this event. To avoid this exception, call {@link
* #forcePut} instead.
*/
@Override
V put(K key, V value);
/**
* An alternate form of {@code put} that silently removes any existing entry with the value {@code
* value} before proceeding with the {@link #put} operation. If the bimap previously contained the
* provided key-value mapping, this method has no effect.
*
* <p>Note that a successful call to this method could cause the size of the bimap to increase by
* one, stay the same, or even decrease by one.
*
* <p><b>Warning:</b> If an existing entry with this value is removed, the key for that entry is
* discarded and not returned.
*
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
* @return the value that was previously associated with the key, or {@code null} if there was no
* previous entry. (If the bimap contains null values, then {@code forcePut}, like {@code
* put}, returns {@code null} both if the key is absent and if it is present with a null
* value.)
*/
V forcePut(K key, V value);
/**
* {@inheritDoc}
*
* <p><b>Warning:</b> the results of calling this method may vary depending on the iteration order
* of {@code map}.
*
* @throws IllegalArgumentException if an attempt to {@code put} any entry fails. Note that some
* map entries may have been added to the bimap before the exception was thrown.
*/
@Override
void putAll(Map<? extends K, ? extends V> map);
/**
* {@inheritDoc}
*
* <p>Because a bimap has unique values, this method returns a {@link Set}, instead of the {@link
* java.util.Collection} specified in the {@link Map} interface.
*/
@Override
Set<V> values();
/**
* Returns the inverse view of this bimap, which maps each of this bimap's values to its
* associated key. The two bimaps are backed by the same data; any changes to one will appear in
* the other.
*
* <p><b>Note:</b>There is no guaranteed correspondence between the iteration order of a bimap and
* that of its inverse.
*
* @return the inverse view of this bimap
*/
BiMap<V, K> inverse();
}

View file

@ -0,0 +1,67 @@
package org.xbib.datastructures.api;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* A {@code Multimap} that can hold duplicate key-value pairs and that maintains the insertion
* ordering of values for a given key. See the {@link Multimap} documentation for information common
* to all multimaps.
*
* <p>The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link
* List} of values. Though the method signature doesn't say so explicitly, the map returned by
* {@link #asMap} has {@code List} values.
*/
public interface ListMultimap<K extends Object, V extends Object>
extends Multimap<K, V> {
/**
* {@inheritDoc}
*
* <p>Because the values for a given key may have duplicates and follow the insertion ordering,
* this method returns a {@link List}, instead of the {@link Collection} specified in
* the {@link Multimap} interface.
*/
@Override
List<V> get(K key);
/**
* {@inheritDoc}
*
* <p>Because the values for a given key may have duplicates and follow the insertion ordering,
* this method returns a {@link List}, instead of the {@link Collection} specified in
* the {@link Multimap} interface.
*/
@Override
List<V> removeAll(Object key);
/**
* {@inheritDoc}
*
* <p>Because the values for a given key may have duplicates and follow the insertion ordering,
* this method returns a {@link List}, instead of the {@link Collection} specified in
* the {@link Multimap} interface.
*/
@Override
List<V> replaceValues(K key, Iterable<? extends V> values);
/**
* {@inheritDoc}
*
* <p><b>Note:</b> The returned map's values are guaranteed to be of type {@link List}.
*/
@Override
Map<K, Collection<V>> asMap();
/**
* Compares the specified object to this multimap for equality.
*
* <p>Two {@code ListMultimap} instances are equal if, for each key, they contain the same values
* in the same order. If the value orderings disagree, the multimaps will not be considered equal.
*
* <p>An empty {@code ListMultimap} is equal to any other empty {@code Multimap}, including an
* empty {@code SetMultimap}.
*/
@Override
boolean equals(Object obj);
}

View file

@ -0,0 +1,339 @@
package org.xbib.datastructures.api;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
/**
* A collection that maps keys to values, similar to {@link Map}, but in which each key may be
* associated with <i>multiple</i> values. You can visualize the contents of a multimap either as a
* map from keys to <i>nonempty</i> collections of values:
*
* <ul>
* <li>a 1, 2
* <li>b 3
* </ul>
* <p>
* ... or as a single "flattened" collection of key-value pairs:
*
* <ul>
* <li>a 1
* <li>a 2
* <li>b 3
* </ul>
*
* <p><b>Important:</b> although the first interpretation resembles how most multimaps are
* <i>implemented</i>, the design of the {@code Multimap} API is based on the <i>second</i> form.
* So, using the multimap shown above as an example, the {@link #size} is {@code 3}, not {@code 2},
* and the {@link #values} collection is {@code [1, 2, 3]}, not {@code [[1, 2], [3]]}. For those
* times when the first style is more useful, use the multimap's {@link #asMap} view (or create a
* {@code Map<K, Collection<V>>} in the first place).
*
* <h3>Example</h3>
*
* <p>The following code:
*
* <pre>{@code
* ListMultimap<String, String> multimap = ArrayListMultimap.create();
* for (President pres : US_PRESIDENTS_IN_ORDER) {
* multimap.put(pres.firstName(), pres.lastName());
* }
* for (String firstName : multimap.keySet()) {
* List<String> lastNames = multimap.get(firstName);
* out.println(firstName + ": " + lastNames);
* }
* }</pre>
* <p>
* ... produces output such as:
*
* <pre>{@code
* Zachary: [Taylor]
* John: [Adams, Adams, Tyler, Kennedy] // Remember, Quincy!
* George: [Washington, Bush, Bush]
* Grover: [Cleveland, Cleveland] // Two, non-consecutive terms, rep'ing NJ!
* ...
* }</pre>
*
* <h3>Views</h3>
*
* <p>Much of the power of the multimap API comes from the <i>view collections</i> it provides.
* These always reflect the latest state of the multimap itself. When they support modification, the
* changes are <i>write-through</i> (they automatically update the backing multimap). These view
* collections are:
*
* <ul>
* <li>{@link #asMap}, mentioned above
* <li>{@link #keys}, {@link #keySet}, {@link #values}, {@link #entries}, which are similar to the
* corresponding view collections of {@link Map}
* <li>and, notably, even the collection returned by {@link #get get(key)} is an active view of
* the values corresponding to {@code key}
* </ul>
*
* <p>The collections returned by the {@link #replaceValues replaceValues} and {@link #removeAll
* removeAll} methods, which contain values that have just been removed from the multimap, are
* naturally <i>not</i> views.
*
* <h3>Subinterfaces</h3>
*
* <p>Instead of using the {@code Multimap} interface directly, prefer the subinterfaces {@link
* ListMultimap} and {@link SetMultimap}. These take their names from the fact that the collections
* they return from {@code get} behave like (and, of course, implement) {@link List} and {@link
* Set}, respectively.
*
* <p>For example, the "presidents" code snippet above used a {@code ListMultimap}; if it had used a
* {@code SetMultimap} instead, two presidents would have vanished, and last names might or might
* not appear in chronological order.
*
* <p><b>Warning:</b> instances of type {@code Multimap} may not implement {@link Object#equals} in
* the way you expect. Multimaps containing the same key-value pairs, even in the same order, may or
* may not be equal and may or may not have the same {@code hashCode}. The recommended subinterfaces
* provide much stronger guarantees.
*
* <h3>Comparison to a map of collections</h3>
*
* <p>Multimaps are commonly used in places where a {@code Map<K, Collection<V>>} would otherwise
* have appeared. The differences include:
*
* <ul>
* <li>There is no need to populate an empty collection before adding an entry with {@link #put
* put}.
* <li>{@code get} never returns {@code null}, only an empty collection.
* <li>A key is contained in the multimap if and only if it maps to at least one value. Any
* operation that causes a key to have zero associated values has the effect of
* <i>removing</i> that key from the multimap.
* <li>The total entry count is available as {@link #size}.
* <li>Many complex operations become easier; for example, {@code
* Collections.min(multimap.values())} finds the smallest value across all keys.
* </ul>
*
* <h3>Other Notes</h3>
*
* <p>As with {@code Map}, the behavior of a {@code Multimap} is not specified if key objects
* already present in the multimap change in a manner that affects {@code equals} comparisons. Use
* caution if mutable objects are used as keys in a {@code Multimap}.
*
* <p>All methods that modify the multimap are optional. The view collections returned by the
* multimap may or may not be modifiable. Any modification method that is not supported will throw
* {@link UnsupportedOperationException}.
*/
public interface Multimap<K, V> {
// Query Operations
/**
* Returns the number of key-value pairs in this multimap.
*
* <p><b>Note:</b> this method does not return the number of <i>distinct keys</i> in the multimap,
* which is given by {@code keySet().size()} or {@code asMap().size()}. See the opening section of
* the {@link Multimap} class documentation for clarification.
*/
int size();
/**
* Returns {@code true} if this multimap contains no key-value pairs. Equivalent to {@code size()
* == 0}, but can in some cases be more efficient.
*/
boolean isEmpty();
/**
* Returns {@code true} if this multimap contains at least one key-value pair with the key {@code
* key}.
*/
boolean containsKey(Object key);
/**
* Returns {@code true} if this multimap contains at least one key-value pair with the value
* {@code value}.
*/
boolean containsValue(Object value);
/**
* Returns {@code true} if this multimap contains at least one key-value pair with the key {@code
* key} and the value {@code value}.
*/
boolean containsEntry(Object key, Object value);
// Modification Operations
/**
* Stores a key-value pair in this multimap.
*
* <p>Some multimap implementations allow duplicate key-value pairs, in which case {@code put}
* always adds a new key-value pair and increases the multimap size by 1. Other implementations
* prohibit duplicates, and storing a key-value pair that's already in the multimap has no effect.
*
* @return {@code true} if the method increased the size of the multimap, or {@code false} if the
* multimap already contained the key-value pair and doesn't allow duplicates
*/
boolean put(K key, V value);
/**
* Removes a single key-value pair with the key {@code key} and the value {@code value} from this
* multimap, if such exists. If multiple key-value pairs in the multimap fit this description,
* which one is removed is unspecified.
*
* @return {@code true} if the multimap changed
*/
boolean remove(Object key, Object value);
// Bulk Operations
/**
* Stores a key-value pair in this multimap for each of {@code values}, all using the same key,
* {@code key}. Equivalent to (but expected to be more efficient than):
*
* <pre>{@code
* for (V value : values) {
* put(key, value);
* }
* }</pre>
*
* <p>In particular, this is a no-op if {@code values} is empty.
*
* @return {@code true} if the multimap changed
*/
boolean putAll(K key, Iterable<? extends V> values);
/**
* Stores all key-value pairs of {@code multimap} in this multimap, in the order returned by
* {@code multimap.entries()}.
*
* @return {@code true} if the multimap changed
*/
boolean putAll(Multimap<? extends K, ? extends V> multimap);
/**
* Stores a collection of values with the same key, replacing any existing values for that key.
*
* <p>If {@code values} is empty, this is equivalent to {@link #removeAll(Object) removeAll(key)}.
*
* @return the collection of replaced values, or an empty collection if no values were previously
* associated with the key. The collection <i>may</i> be modifiable, but updating it will have
* no effect on the multimap.
*/
Collection<V> replaceValues(K key, Iterable<? extends V> values);
/**
* Removes all values associated with the key {@code key}.
*
* <p>Once this method returns, {@code key} will not be mapped to any values, so it will not
* appear in {@link #keySet()}, {@link #asMap()}, or any other views.
*
* @return the values that were removed (possibly empty). The returned collection <i>may</i> be
* modifiable, but updating it will have no effect on the multimap.
*/
Collection<V> removeAll(Object key);
/**
* Removes all key-value pairs from the multimap, leaving it {@linkplain #isEmpty empty}.
*/
void clear();
// Views
/**
* Returns a view collection of the values associated with {@code key} in this multimap, if any.
* Note that when {@code containsKey(key)} is false, this returns an empty collection, not {@code
* null}.
*
* <p>Changes to the returned collection will update the underlying multimap, and vice versa.
*/
Collection<V> get(K key);
/**
* Returns a view collection of all <i>distinct</i> keys contained in this multimap. Note that the
* key set contains a key if and only if this multimap maps that key to at least one value.
*
* <p>Changes to the returned set will update the underlying multimap, and vice versa. However,
* <i>adding</i> to the returned set is not possible.
*/
Set<K> keySet();
/**
* Returns a view collection containing the key from each key-value pair in this multimap,
* <i>without</i> collapsing duplicates. This collection has the same size as this multimap, and
* {@code keys().count(k) == get(k).size()} for all {@code k}.
*
* <p>Changes to the returned multiset will update the underlying multimap, and vice versa.
* However, <i>adding</i> to the returned collection is not possible.
*/
Multiset<K> keys();
/**
* Returns a view collection containing the <i>value</i> from each key-value pair contained in
* this multimap, without collapsing duplicates (so {@code values().size() == size()}).
*
* <p>Changes to the returned collection will update the underlying multimap, and vice versa.
* However, <i>adding</i> to the returned collection is not possible.
*/
Collection<V> values();
/**
* Returns a view collection of all key-value pairs contained in this multimap, as {@link Entry}
* instances.
*
* <p>Changes to the returned collection or the entries it contains will update the underlying
* multimap, and vice versa. However, <i>adding</i> to the returned collection is not possible.
*/
Collection<Entry<K, V>> entries();
/**
* Performs the given action for all key-value pairs contained in this multimap. If an ordering is
* specified by the {@code Multimap} implementation, actions will be performed in the order of
* iteration of {@link #entries()}. Exceptions thrown by the action are relayed to the caller.
*
* <p>To loop over all keys and their associated value collections, write {@code
* Multimaps.asMap(multimap).forEach((key, valueCollection) -> action())}.
*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
entries().forEach(entry -> action.accept(entry.getKey(), entry.getValue()));
}
/**
* Returns a view of this multimap as a {@code Map} from each distinct key to the nonempty
* collection of that key's associated values. Note that {@code this.asMap().get(k)} is equivalent
* to {@code this.get(k)} only when {@code k} is a key contained in the multimap; otherwise it
* returns {@code null} as opposed to an empty collection.
*
* <p>Changes to the returned map or the collections that serve as its values will update the
* underlying multimap, and vice versa. The map does not support {@code put} or {@code putAll},
* nor do its entries support {@link Entry#setValue setValue}.
*/
Map<K, Collection<V>> asMap();
// Comparison and hashing
/**
* Compares the specified object with this multimap for equality. Two multimaps are equal when
* their map views, as returned by {@link #asMap}, are also equal.
*
* <p>In general, two multimaps with identical key-value mappings may or may not be equal,
* depending on the implementation. For example, two {@link SetMultimap} instances with the same
* key-value mappings are equal, but equality of two {@link ListMultimap} instances depends on the
* ordering of the values for each key.
*
* <p>A non-empty {@link SetMultimap} cannot be equal to a non-empty {@link ListMultimap}, since
* their {@link #asMap} views contain unequal collections as values. However, any two empty
* multimaps are equal, because they both have empty {@link #asMap} views.
*/
@Override
boolean equals(Object obj);
/**
* Returns the hash code for this multimap.
*
* <p>The hash code of a multimap is defined as the hash code of the map view, as returned by
* {@link Multimap#asMap}.
*
* <p>In general, two multimaps with identical key-value mappings may or may not have the same
* hash codes, depending on the implementation. For example, two {@link SetMultimap} instances
* with the same key-value mappings will have the same {@code hashCode}, but the {@code hashCode}
* of {@link ListMultimap} instances depends on the ordering of the values for each key.
*/
@Override
int hashCode();
}

View file

@ -0,0 +1,585 @@
package org.xbib.datastructures.api;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ObjIntConsumer;
/**
* A collection that supports order-independent equality, like {@link Set}, but may have duplicate
* elements. A multiset is also sometimes called a <i>bag</i>.
*
* <p>Elements of a multiset that are equal to one another are referred to as <i>occurrences</i> of
* the same single element. The total number of occurrences of an element in a multiset is called
* the <i>count</i> of that element (the terms "frequency" and "multiplicity" are equivalent, but
* not used in this API). Since the count of an element is represented as an {@code int}, a multiset
* may never contain more than {@link Integer#MAX_VALUE} occurrences of any one element.
*
* <p>{@code Multiset} refines the specifications of several methods from {@code Collection}. It
* also defines an additional query operation, {@link #count}, which returns the count of an
* element. There are five new bulk-modification operations, for example {@link #add(Object, int)},
* to add or remove multiple occurrences of an element at once, or to set the count of an element to
* a specific value. These modification operations are optional, but implementations which support
* the standard collection operations {@link #add(Object)} or {@link #remove(Object)} are encouraged
* to implement the related methods as well. Finally, two collection views are provided: {@link
* #elementSet} contains the distinct elements of the multiset "with duplicates collapsed", and
* {@link #entrySet} is similar but contains {@link Entry Multiset.Entry} instances, each providing
* both a distinct element and the count of that element.
*
* <p>In addition to these required methods, implementations of {@code Multiset} are expected to
* provide two {@code static} creation methods: {@code create()}, returning an empty multiset, and
* {@code create(Iterable<? extends E>)}, returning a multiset containing the given initial
* elements. This is simply a refinement of {@code Collection}'s constructor recommendations,
* reflecting the new developments of Java 5.
*
* <p>As with other collection types, the modification operations are optional, and should throw
* {@link UnsupportedOperationException} when they are not implemented. Most implementations should
* support either all add operations or none of them, all removal operations or none of them, and if
* and only if all of these are supported, the {@code setCount} methods as well.
*
* <p>A multiset uses {@link Object#equals} to determine whether two instances should be considered
* "the same," <i>unless specified otherwise</i> by the implementation.
*
* <p><b>Warning:</b> as with normal {@link Set}s, it is almost always a bad idea to modify an
* element (in a way that affects its {@link Object#equals} behavior) while it is contained in a
* multiset. Undefined behavior and bugs will result.
*/
public interface Multiset<E extends Object> extends Collection<E> {
// Query Operations
/**
* Returns the total number of all occurrences of all elements in this multiset.
*
* <p><b>Note:</b> this method does not return the number of <i>distinct elements</i> in the
* multiset, which is given by {@code entrySet().size()}.
*/
@Override
int size();
/**
* Returns the number of occurrences of an element in this multiset (the <i>count</i> of the
* element). Note that for an {@link Object#equals}-based multiset, this gives the same result as
* {@link Collections#frequency} (which would presumably perform more poorly).
*
* @param element the element to count occurrences of
* @return the number of occurrences of the element in this multiset; possibly zero but never
* negative
*/
int count(Object element);
// Bulk Operations
/**
* Adds a number of occurrences of an element to this multiset. Note that if {@code occurrences ==
* 1}, this method has the identical effect to {@link #add(Object)}. This method is functionally
* equivalent (except in the case of overflow) to the call {@code
* addAll(Collections.nCopies(element, occurrences))}, which would presumably perform much more
* poorly.
*
* @param element the element to add occurrences of; may be null only if explicitly allowed by the
* implementation
* @param occurrences the number of occurrences of the element to add. May be zero, in which case
* no change will be made.
* @return the count of the element before the operation; possibly zero
* @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation would
* result in more than {@link Integer#MAX_VALUE} occurrences of the element
* @throws NullPointerException if {@code element} is null and this implementation does not permit
* null elements. Note that if {@code occurrences} is zero, the implementation may opt to
* return normally.
*/
int add(E element, int occurrences);
/**
* Adds a single occurrence of the specified element to this multiset.
*
* <p>This method refines {@link Collection#add}, which only <i>ensures</i> the presence of the
* element, to further specify that a successful call must always increment the count of the
* element, and the overall size of the collection, by one.
*
* <p>To both add the element and obtain the previous count of that element, use {@link
* #add(Object, int) add}{@code (element, 1)} instead.
*
* @param element the element to add one occurrence of; may be null only if explicitly allowed by
* the implementation
* @return {@code true} always, since this call is required to modify the multiset, unlike other
* {@link Collection} types
* @throws NullPointerException if {@code element} is null and this implementation does not permit
* null elements
* @throws IllegalArgumentException if {@link Integer#MAX_VALUE} occurrences of {@code element}
* are already contained in this multiset
*/
@Override
boolean add(E element);
/**
* Removes a number of occurrences of the specified element from this multiset. If the multiset
* contains fewer than this number of occurrences to begin with, all occurrences will be removed.
* Note that if {@code occurrences == 1}, this is functionally equivalent to the call {@code
* remove(element)}.
*
* @param element the element to conditionally remove occurrences of
* @param occurrences the number of occurrences of the element to remove. May be zero, in which
* case no change will be made.
* @return the count of the element before the operation; possibly zero
* @throws IllegalArgumentException if {@code occurrences} is negative
*/
int remove(Object element, int occurrences);
/**
* Removes a <i>single</i> occurrence of the specified element from this multiset, if present.
*
* <p>This method refines {@link Collection#remove} to further specify that it <b>may not</b>
* throw an exception in response to {@code element} being null or of the wrong type.
*
* <p>To both remove the element and obtain the previous count of that element, use {@link
* #remove(Object, int) remove}{@code (element, 1)} instead.
*
* @param element the element to remove one occurrence of
* @return {@code true} if an occurrence was found and removed
*/
@Override
boolean remove(Object element);
/**
* Adds or removes the necessary occurrences of an element such that the element attains the
* desired count.
*
* @param element the element to add or remove occurrences of; may be null only if explicitly
* allowed by the implementation
* @param count the desired count of the element in this multiset
* @return the count of the element before the operation; possibly zero
* @throws IllegalArgumentException if {@code count} is negative
* @throws NullPointerException if {@code element} is null and this implementation does not permit
* null elements. Note that if {@code count} is zero, the implementor may optionally return
* zero instead.
*/
int setCount(E element, int count);
/**
* Conditionally sets the count of an element to a new value, as described in {@link
* #setCount(Object, int)}, provided that the element has the expected current count. If the
* current count is not {@code oldCount}, no change is made.
*
* @param element the element to conditionally set the count of; may be null only if explicitly
* allowed by the implementation
* @param oldCount the expected present count of the element in this multiset
* @param newCount the desired count of the element in this multiset
* @return {@code true} if the condition for modification was met. This implies that the multiset
* was indeed modified, unless {@code oldCount == newCount}.
* @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is negative
* @throws NullPointerException if {@code element} is null and the implementation does not permit
* null elements. Note that if {@code oldCount} and {@code newCount} are both zero, the
* implementor may optionally return {@code true} instead.
*/
boolean setCount(E element, int oldCount, int newCount);
// Views
/**
* Returns the set of distinct elements contained in this multiset. The element set is backed by
* the same data as the multiset, so any change to either is immediately reflected in the other.
* The order of the elements in the element set is unspecified.
*
* <p>If the element set supports any removal operations, these necessarily cause <b>all</b>
* occurrences of the removed element(s) to be removed from the multiset. Implementations are not
* expected to support the add operations, although this is possible.
*
* <p>A common use for the element set is to find the number of distinct elements in the multiset:
* {@code elementSet().size()}.
*
* @return a view of the set of distinct elements in this multiset
*/
Set<E> elementSet();
/**
* Returns a view of the contents of this multiset, grouped into {@code Multiset.Entry} instances,
* each providing an element of the multiset and the count of that element. This set contains
* exactly one entry for each distinct element in the multiset (thus it always has the same size
* as the {@link #elementSet}). The order of the elements in the element set is unspecified.
*
* <p>The entry set is backed by the same data as the multiset, so any change to either is
* immediately reflected in the other. However, multiset changes may or may not be reflected in
* any {@code Entry} instances already retrieved from the entry set (this is
* implementation-dependent). Furthermore, implementations are not required to support
* modifications to the entry set at all, and the {@code Entry} instances themselves don't even
* have methods for modification. See the specific implementation class for more details on how
* its entry set handles modifications.
*
* @return a set of entries representing the data of this multiset
*/
Set<Entry<E>> entrySet();
/**
* An unmodifiable element-count pair for a multiset. The {@link Multiset#entrySet} method returns
* a view of the multiset whose elements are of this class. A multiset implementation may return
* Entry instances that are either live "read-through" views to the Multiset, or immutable
* snapshots. Note that this type is unrelated to the similarly-named type {@code Map.Entry}.
*
* @since 2.0
*/
interface Entry<E extends Object> {
/**
* Returns the multiset element corresponding to this entry. Multiple calls to this method
* always return the same instance.
*
* @return the element corresponding to this entry
*/
E getElement();
/**
* Returns the count of the associated element in the underlying multiset. This count may either
* be an unchanging snapshot of the count at the time the entry was retrieved, or a live view of
* the current count of the element in the multiset, depending on the implementation. Note that
* in the former case, this method can never return zero, while in the latter, it will return
* zero if all occurrences of the element were since removed from the multiset.
*
* @return the count of the element; never negative
*/
int getCount();
/**
* {@inheritDoc}
*
* <p>Returns {@code true} if the given object is also a multiset entry and the two entries
* represent the same element and count. That is, two entries {@code a} and {@code b} are equal
* if:
*
* <pre>{@code
* Objects.equal(a.getElement(), b.getElement())
* && a.getCount() == b.getCount()
* }</pre>
*/
@Override
boolean equals(Object o);
/**
* {@inheritDoc}
*
* <p>The hash code of a multiset entry for element {@code element} and count {@code count} is
* defined as:
*
* <pre>{@code
* ((element == null) ? 0 : element.hashCode()) ^ count
* }</pre>
*/
@Override
int hashCode();
/**
* Returns the canonical string representation of this entry, defined as follows. If the count
* for this entry is one, this is simply the string representation of the corresponding element.
* Otherwise, it is the string representation of the element, followed by the three characters
* {@code " x "} (space, letter x, space), followed by the count.
*/
@Override
String toString();
}
/**
* Runs the specified action for each distinct element in this multiset, and the number of
* occurrences of that element. For some {@code Multiset} implementations, this may be more
* efficient than iterating over the {@link #entrySet()} either explicitly or with {@code
* entrySet().forEach(action)}.
*/
default void forEachEntry(ObjIntConsumer<? super E> action) {
Objects.requireNonNull(action);
entrySet().forEach(entry -> action.accept(entry.getElement(), entry.getCount()));
}
// Comparison and hashing
/**
* Compares the specified object with this multiset for equality. Returns {@code true} if the
* given object is also a multiset and contains equal elements with equal counts, regardless of
* order.
*/
@Override
// TODO(kevinb): caveats about equivalence-relation?
boolean equals(Object object);
/**
* Returns the hash code for this multiset. This is defined as the sum of
*
* <pre>{@code
* ((element == null) ? 0 : element.hashCode()) ^ count(element)
* }</pre>
*
* <p>over all distinct elements in the multiset. It follows that a multiset and its entry set
* always have the same hash code.
*/
@Override
int hashCode();
/**
* {@inheritDoc}
*
* <p>It is recommended, though not mandatory, that this method return the result of invoking
* {@link #toString} on the {@link #entrySet}, yielding a result such as {@code [a x 3, c, d x 2,
* e]}.
*/
@Override
String toString();
// Refined Collection Methods
/**
* {@inheritDoc}
*
* <p>Elements that occur multiple times in the multiset will appear multiple times in this
* iterator, though not necessarily sequentially.
*/
@Override
Iterator<E> iterator();
/**
* Determines whether this multiset contains the specified element.
*
* <p>This method refines {@link Collection#contains} to further specify that it <b>may not</b>
* throw an exception in response to {@code element} being null or of the wrong type.
*
* @param element the element to check for
* @return {@code true} if this multiset contains at least one occurrence of the element
*/
@Override
boolean contains(Object element);
/**
* Returns {@code true} if this multiset contains at least one occurrence of each element in the
* specified collection.
*
* <p>This method refines {@link Collection#containsAll} to further specify that it <b>may not</b>
* throw an exception in response to any of {@code elements} being null or of the wrong type.
*
* <p><b>Note:</b> this method does not take into account the occurrence count of an element in
* the two collections; it may still return {@code true} even if {@code elements} contains several
* occurrences of an element and this multiset contains only one. This is no different than any
* other collection type like {@link List}, but it may be unexpected to the user of a multiset.
*
* @param elements the collection of elements to be checked for containment in this multiset
* @return {@code true} if this multiset contains at least one occurrence of each element
* contained in {@code elements}
* @throws NullPointerException if {@code elements} is null
*/
@Override
boolean containsAll(Collection<?> elements);
/**
* {@inheritDoc}
*
* <p><b>Note:</b> This method ignores how often any element might appear in {@code c}, and only
* cares whether or not an element appears at all. If you wish to remove one occurrence in this
* multiset for every occurrence in {@code c}.
*
* <p>This method refines {@link Collection#removeAll} to further specify that it <b>may not</b>
* throw an exception in response to any of {@code elements} being null or of the wrong type.
*/
@Override
boolean removeAll(Collection<?> c);
/**
* {@inheritDoc}
*
* <p><b>Note:</b> This method ignores how often any element might appear in {@code c}, and only
* cares whether or not an element appears at all. If you wish to remove one occurrence in this
* multiset for every occurrence in {@code c}.
*
* <p>This method refines {@link Collection#retainAll} to further specify that it <b>may not</b>
* throw an exception in response to any of {@code elements} being null or of the wrong type.
*/
@Override
boolean retainAll(Collection<?> c);
/**
* {@inheritDoc}
*
* <p>Elements that occur multiple times in the multiset will be passed to the {@code Consumer}
* correspondingly many times, though not necessarily sequentially.
*/
@Override
default void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
entrySet()
.forEach(
entry -> {
E elem = entry.getElement();
int count = entry.getCount();
for (int i = 0; i < count; i++) {
action.accept(elem);
}
});
}
@Override
default Spliterator<E> spliterator() {
return spliteratorImpl(this);
}
private static <E extends Object> Spliterator<E> spliteratorImpl(Multiset<E> multiset) {
Spliterator<Entry<E>> entrySpliterator = multiset.entrySet().spliterator();
return flatMap(
entrySpliterator,
entry -> Collections.nCopies(entry.getCount(), entry.getElement()).spliterator(),
Spliterator.SIZED
| (entrySpliterator.characteristics()
& (Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE)),
multiset.size());
}
/**
* Returns a {@code Spliterator} that iterates over the elements of the spliterators generated by
* applying {@code function} to the elements of {@code fromSpliterator}.
*/
private static <InElementT extends Object, OutElementT extends Object> Spliterator<OutElementT> flatMap(
Spliterator<InElementT> fromSpliterator,
Function<? super InElementT, Spliterator<OutElementT>> function,
int topCharacteristics,
long topSize) {
if (!((topCharacteristics & Spliterator.SUBSIZED) == 0)) {
throw new IllegalArgumentException("flatMap does not support SUBSIZED characteristic");
}
if (!((topCharacteristics & Spliterator.SORTED) == 0)) {
throw new IllegalArgumentException("flatMap does not support SORTED characteristic");
}
Objects.requireNonNull(fromSpliterator);
Objects.requireNonNull(function);
return new FlatMapSpliteratorOfObject<>(null, fromSpliterator, function, topCharacteristics, topSize);
}
public static class FlatMapSpliteratorOfObject<InElementT extends Object, OutElementT extends Object>
extends FlatMapSpliterator<InElementT, OutElementT, Spliterator<OutElementT>> {
FlatMapSpliteratorOfObject(Spliterator<OutElementT> prefix,
Spliterator<InElementT> from,
Function<? super InElementT, Spliterator<OutElementT>> function,
int characteristics,
long estimatedSize) {
super(prefix, from, function, FlatMapSpliteratorOfObject::new, characteristics, estimatedSize);
}
}
static abstract class FlatMapSpliterator<
InElementT extends Object,
OutElementT extends Object,
OutSpliteratorT extends Spliterator<OutElementT>>
implements Spliterator<OutElementT> {
/**
* Factory for constructing {@link FlatMapSpliterator} instances.
*/
@FunctionalInterface
interface Factory<InElementT extends Object, OutSpliteratorT extends Spliterator<?>> {
OutSpliteratorT newFlatMapSpliterator(OutSpliteratorT prefix,
Spliterator<InElementT> fromSplit,
Function<? super InElementT, OutSpliteratorT> function,
int splitCharacteristics,
long estSplitSize);
}
OutSpliteratorT prefix;
final Spliterator<InElementT> from;
final Function<? super InElementT, OutSpliteratorT> function;
final Factory<InElementT, OutSpliteratorT> factory;
int characteristics;
long estimatedSize;
FlatMapSpliterator(OutSpliteratorT prefix,
Spliterator<InElementT> from,
Function<? super InElementT, OutSpliteratorT> function,
Factory<InElementT, OutSpliteratorT> factory,
int characteristics,
long estimatedSize) {
this.prefix = prefix;
this.from = from;
this.function = function;
this.factory = factory;
this.characteristics = characteristics;
this.estimatedSize = estimatedSize;
}
/*
* The tryAdvance and forEachRemaining in FlatMapSpliteratorOfPrimitive are overloads of these
* methods, not overrides. They are annotated @Override because they implement methods from
* Spliterator.OfPrimitive (and override default implementations from Spliterator.OfPrimitive or
* a subtype like Spliterator.OfInt).
*/
@Override
public final boolean tryAdvance(Consumer<? super OutElementT> action) {
while (true) {
if (prefix != null && prefix.tryAdvance(action)) {
if (estimatedSize != Long.MAX_VALUE) {
estimatedSize--;
}
return true;
} else {
prefix = null;
}
if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) {
return false;
}
}
}
@Override
public final void forEachRemaining(Consumer<? super OutElementT> action) {
if (prefix != null) {
prefix.forEachRemaining(action);
prefix = null;
}
from.forEachRemaining(
fromElement -> {
Spliterator<OutElementT> elements = function.apply(fromElement);
if (elements != null) {
elements.forEachRemaining(action);
}
});
estimatedSize = 0;
}
@Override
public final OutSpliteratorT trySplit() {
Spliterator<InElementT> fromSplit = from.trySplit();
if (fromSplit != null) {
int splitCharacteristics = characteristics & ~Spliterator.SIZED;
long estSplitSize = estimateSize();
if (estSplitSize < Long.MAX_VALUE) {
estSplitSize /= 2;
this.estimatedSize -= estSplitSize;
this.characteristics = splitCharacteristics;
}
OutSpliteratorT result =
factory.newFlatMapSpliterator(
this.prefix, fromSplit, function, splitCharacteristics, estSplitSize);
this.prefix = null;
return result;
} else if (prefix != null) {
OutSpliteratorT result = prefix;
this.prefix = null;
return result;
} else {
return null;
}
}
@Override
public final long estimateSize() {
if (prefix != null) {
estimatedSize = Math.max(estimatedSize, prefix.estimateSize());
}
return Math.max(estimatedSize, 0);
}
@Override
public final int characteristics() {
return characteristics;
}
}
}

View file

@ -0,0 +1,92 @@
package org.xbib.datastructures.api;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* A {@code Multimap} that cannot hold duplicate key-value pairs. Adding a key-value pair that's
* already in the multimap has no effect. See the {@link Multimap} documentation for information
* common to all multimaps.
*
* <p>The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link
* Set} of values, while {@link #entries} returns a {@code Set} of map entries. Though the method
* signature doesn't say so explicitly, the map returned by {@link #asMap} has {@code Set} values.
*
* <p>If the values corresponding to a single key should be ordered according to a {@link
* java.util.Comparator} (or the natural order), see the {@link SortedSetMultimap} subinterface.
*
* <p>Since the value collections are sets, the behavior of a {@code SetMultimap} is not specified
* if key <em>or value</em> objects already present in the multimap change in a manner that affects
* {@code equals} comparisons. Use caution if mutable objects are used as keys or values in a {@code
* SetMultimap}.
*
* <p><b>Warning:</b> Do not modify either a key <i>or a value</i> of a {@code SetMultimap} in a way
* that affects its {@link Object#equals} behavior. Undefined behavior and bugs will result.