org.organicdesign.fp.collections.UnmodMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of UncleJim Show documentation
Show all versions of UncleJim Show documentation
Immutable Clojure collections and a Transformation abstraction for Java 8+, immutably, type-safely, and with good performance. Name will change to "Paguro" in November 2016.
The newest version!
// Copyright 2015-04-13 PlanBase Inc. & Glen Peterson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.organicdesign.fp.collections;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.organicdesign.fp.tuple.Tuple2;
/**
An unmodifiable map.
This cannot extend Collection because the remove() method would then be inherited
from both Collection and Map and Collection.remove() returns a boolean while Map.remove() returns
a V (the type of the value in the key/value pair). Maybe an UnmodSizedIterable is called for?
*/
public interface UnmodMap extends Map, UnmodIterable>, Sized {
// ========================================== Static ==========================================
/**
Implements equals and hashCode() methods compatible with java.util.Map (which ignores order)
to make defining unmod Maps easier. Inherits hashCode() and toString() from
AbstractUnmodIterable.
*/
abstract class AbstractUnmodMap extends AbstractUnmodIterable>
implements UnmodMap {
@Override public boolean equals(Object other) {
if (this == other) { return true; }
if (!(other instanceof Map)) { return false; }
Map, ?> that = (Map, ?>) other;
if (that.size() != size()) { return false; }
try {
for (Entry e : this) {
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(that.get(key) == null && that.containsKey(key))) {
return false;
}
} else {
if (!value.equals(that.get(key))) {
return false;
}
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
}
/**
* A map entry (key-value pair). The UnmodMap.entrySet method returns
* a collection-view of the map, whose elements are of this class. The
* only way to obtain a reference to a map entry is from the
* iterator of this collection-view.
*
* @see UnmodMap#entrySet()
*/
interface UnEntry extends Map.Entry {
class EntryToUnEntryIter implements UnmodIterator> {
//, Serializable {
// For serializable. Make sure to change whenever internal data format changes.
// private static final long serialVersionUID = 20160903082500L;
private final Iterator> innerIter;
EntryToUnEntryIter(Iterator> i) { innerIter = i; }
@Override public boolean hasNext() { return innerIter.hasNext(); }
@Override public UnEntry next() {
class Wrapper implements UnEntry, Serializable {
// For serializable. Make sure to change whenever internal data format changes.
private static final long serialVersionUID = 20160903082500L;
private final Entry entry;
private Wrapper(Entry e) { entry = e; }
@Override public K getKey() { return entry.getKey(); }
@Override public V getValue() { return entry.getValue(); }
@Override
public boolean equals(Object other) {
if (this == other) { return true; }
if ( !(other instanceof Entry) ) { return false; }
Entry that = (Entry) other;
return Objects.equals(entry.getKey(), that.getKey()) &&
Objects.equals(entry.getValue(), that.getValue());
}
@Override public int hashCode() {
return entry.hashCode();
}
@Override public String toString() {
return "entry(" + entry.getKey() + "," + entry.getValue() + ")";
}
};
return new Wrapper(innerIter.next());
}
}
class EntryToUnEntrySortedIter extends EntryToUnEntryIter
implements UnmodSortedIterator> {
//, Serializable {
// For serializable. Make sure to change whenever internal data format changes.
// private static final long serialVersionUID = 20160903082500L;
EntryToUnEntrySortedIter(Iterator> i) { super(i); }
}
class UnmodKeyIter implements UnmodIterator {
//, Serializable {
// For serializable. Make sure to change whenever internal data format changes.
// private static final long serialVersionUID = 20160903174100L;
private final Iterator extends Map.Entry> iter;
UnmodKeyIter(Iterator extends Map.Entry> i) { iter = i; }
@Override public boolean hasNext() { return iter.hasNext(); }
@Override public K next() { return iter.next().getKey(); }
}
class UnmodSortedKeyIter extends UnmodKeyIter implements UnmodSortedIterator {
// , Serializable {
// For serializable. Make sure to change whenever internal data format changes.
// private static final long serialVersionUID = 20160903174100L;
UnmodSortedKeyIter(Iterator extends Map.Entry> i) { super(i); }
}
class UnmodValIter implements UnmodIterator {
//, Serializable {
// For serializable. Make sure to change whenever internal data format changes.
// private static final long serialVersionUID = 20160903174100L;
private final Iterator extends Map.Entry> iter;
UnmodValIter(Iterator extends Map.Entry> i) { iter = i; }
@Override public boolean hasNext() { return iter.hasNext(); }
@Override public V next() { return iter.next().getValue(); }
}
class UnmodSortedValIter extends UnmodValIter implements UnmodSortedIterator {
// , Serializable {
// For serializable. Make sure to change whenever internal data format changes.
// private static final long serialVersionUID = 20160903174100L;
UnmodSortedValIter(Iterator extends Map.Entry> i) { super(i); }
}
/**
Use {@link org.organicdesign.fp.tuple.Tuple2#of(java.util.Map.Entry)} instead.
*/
@Deprecated
static UnEntry entryToUnEntry(Map.Entry entry) {
return Tuple2.of(entry);
}
static
UnmodIterator> entryIterToUnEntryUnIter(Iterator> innerIter) {
return new EntryToUnEntryIter<>(innerIter);
}
static
UnmodSortedIterator>
entryIterToUnEntrySortedUnIter(Iterator> innerIter) {
return new EntryToUnEntrySortedIter<>(innerIter);
}
// This should be done with a cast, not with code.
// static UnmodSortedIterator> unSortIterEntToUnSortIterUnEnt(
// UnmodSortedIterator> innerIter) {
// return new UnmodSortedIterator>() {
// @Override public boolean hasNext() { return innerIter.hasNext(); }
// @Override public UnEntry next() {
// return UnmodMap.UnEntry.entryToUnEntry(innerIter.next());
// }
// };
// }
//
/** Not allowed - this is supposed to be unmodifiable */
@SuppressWarnings("deprecation")
@Override @Deprecated default V setValue(V value) {
throw new UnsupportedOperationException("Modification attempted");
}
}
// ========================================= Instance =========================================
// Modification Operations
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default void clear() {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated
default V compute(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated
default V computeIfAbsent(K key, Function super K, ? extends V> mappingFunction) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated
default V computeIfPresent(K key,
BiFunction super K, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException("Modification attempted");
}
// boolean containsKey(Object key)
// boolean containsValue(Object value)
/**
Most maps are not designed for this - the default implementation has O(n) performance.
{@inheritDoc}
*/
// This is the place to define this slow operation so that it can be used in
// values().contains(), UnmodSortedMap.containsValue() and UnmodSortedMap.values().contains().
@SuppressWarnings("SuspiciousMethodCalls")
@Override default boolean containsValue(Object value) {
for (UnEntry item : this) {
if (Objects.equals(item.getValue(), value)) { return true; }
}
return false;
}
/**
Returns a view of the mappings contained in this map. The set will contain UnmodMap.UnEntry
items, but that return signature is illegal in Java, so you'll just have to remember. An
UnmodMap is iterable, so this method is probably not nearly as useful as it once was.
{@inheritDoc}
*/
@Override default UnmodSet> entrySet() {
class EntrySet extends UnmodSet.AbstractUnmodSet>
implements Serializable {
// For serializable. Make sure to change whenever internal data format changes.
private static final long serialVersionUID = 20160903104400L;
private final UnmodMap parent;
private EntrySet(UnmodMap p) { parent = p; }
@SuppressWarnings("unchecked")
@Override public boolean contains(Object o) {
if ( !(o instanceof Entry) ) { return false; }
Entry entry = (Entry) o;
if (!parent.containsKey(entry.getKey())) { return false; }
V value = parent.get(entry.getKey());
return Objects.equals(entry.getValue(), value);
}
@SuppressWarnings("unchecked")
@Override public UnmodIterator> iterator() {
// Converting from
// UnmodIterator> to
// UnmodIterator>
// Is a totally legal widening conversion (at runtime) because UnEntry extends
// (is an) Entry. But Java's type system doesn't know that because (I think)
// it's a higher kinded type. Thanks to type erasure, we can forget about all
// that and cast it to a base type then suppress the unchecked warning.
//
// Hmm... It's possible for this to return an Entry if the wrapped collection
// uses them... Not sure how much that matters.
return (UnmodIterator) parent.iterator();
}
@Override public int size() { return parent.size(); }
@Override public String toString() {
return UnmodIterable.toString("UnmodMap.entrySet", this);
}
}
return new EntrySet(this);
}
// boolean equals(Object o)
// @Override default boolean equals(Object other) {
// // Cheapest operation first...
// if (this == other) { return true; }
//
// if ( (other == null) ||
// !(other instanceof Map) ||
// (this.hashCode() != other.hashCode()) ) {
// return false;
// }
// // Details...
// final Map that = (Map) other;
// if (this.size() != that.size()) {
// return false;
// }
// return this.entrySet().containsAll(that.entrySet());
// }
// default void forEach(BiConsumer super K,? super V> action)
// V get(Object key)
// default V getOrDefault(Object key, V defaultValue)
// @Override default int hashCode() {
// if (size() == 0) { return 0; }
// return Arrays.hashCode(entrySet().toArray());
// };
/** {@inheritDoc} */
@Override default boolean isEmpty() { return size() == 0; }
/**
Returns a view of the keys contained in this map. An UnmodMap is iterable, so this method
is probably not nearly as useful as it once was.
{@inheritDoc}
*/
@Override default UnmodSet keySet() {
class KeySet extends UnmodSet.AbstractUnmodSet implements Serializable {
// For serializable. Make sure to change whenever internal data format changes.
private static final long serialVersionUID = 20160903104400L;
private final UnmodMap parent;
private KeySet(UnmodMap p) { parent = p; }
@SuppressWarnings("SuspiciousMethodCalls")
@Override public boolean contains(Object o) { return parent.containsKey(o); }
@Override public UnmodIterator iterator() {
return new UnEntry.UnmodKeyIter<>(parent.iterator());
}
@Override public int size() { return parent.size(); }
@Override public String toString() {
return UnmodIterable.toString("UnmodMap.keySet", this);
}
}
return new KeySet(this);
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated
default V merge(K key, V value,
BiFunction super V, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default V put(K key, V value) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default void putAll(Map extends K, ? extends V> m) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default V putIfAbsent(K key, V value) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default V remove(Object key) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default boolean remove(Object key, Object value) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default boolean replace(K key, V oldValue, V newValue) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated default V replace(K key, V value) {
throw new UnsupportedOperationException("Modification attempted");
}
/** Not allowed - this is supposed to be unmodifiable */
@Override @Deprecated
default void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
throw new UnsupportedOperationException("Modification attempted");
}
// int size()
/**
This method has been deprecated because it is impossible to implement equals() or hashCode()
on the resulting collection, and calling this method is probably at least a missed opportunity,
if not an outright error. Use an UnmodMap as an UnmodIterable<UnmodMap.UnEntry> instead.
If you don't care about eliminating duplicate values, and want a compatible return type call:
myMap.map((UnEntry<K,V> entry) -> entry.getValue())
.toImSet();
If you want to keep a count of duplicates, try something like this, but it has a different
signature:
ImMap<V,Integer> valueCounts = myMap.foldLeft(PersistentHashMap.empty(),
(ImMap<V,Integer> accum, UnEntry<K,V> origEntry) -> {
V inVal = origEntry.getValue();
return accum.assoc(inVal,
accum.getOrElse(inVal, 0) + 1);
});
You really shouldn't turn values() into a List, because a List has order and an unsorted Map
is unordered by key, and especially unordered by value. On a SortedMap, List is the proper
return type.
java.util.HashMap.values() returns an instance of java.util.HashMap.Values which does *not*
have equals() or hashCode() defined. This is because List.equals() and Set.equals() return
not-equal when compared to a Collection. There is no good way to implement a reflexive
equals with both of those because they are just too different. Ultimately, Collection just
isn't specific enough to instantiate, but we do it anyway here for backward compatibility.
We don't implement equals() or hashCode() either because the result could have duplicates.
If the Map isn't sorted, the result could have random ordering.
{@inheritDoc}
*/
@Deprecated
@Override default UnmodCollection values() {
class Impl implements UnmodCollection, Serializable {
// For serializable. Make sure to change whenever internal data format changes.
private static final long serialVersionUID = 20160903104400L;
private final UnmodMap parent;
private Impl(UnmodMap p) { parent = p; }
@SuppressWarnings("SuspiciousMethodCalls")
@Override public boolean contains(Object o) { return parent.containsValue(o); }
@Override public UnmodIterator iterator() {
return new UnEntry.UnmodValIter<>(parent.iterator());
}
@Override public int size() { return parent.size(); }
@Override public String toString() {
return UnmodIterable.toString("UnmodMap.values", this);
}
};
return new Impl(this);
}
}