package org.tenidwa.collections.utils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/**
* Map that uses function of K as keys instead of K.
* <p/>
* Its purpose is to allow mapping from keys that don't support
* {@link Object#equals(Object)}. When using the usual {@link Map} that has an
* interface as a key type, there is no sane way to treat instances of
* different types as equal, because then we'd have to:
* <ul>
* <li>re-implement {@link Object#equals(Object)} in every type (which would
* be a lot of duplicated code);</li>
* <li>or extend every implementation of the interface with an abstract
* class that implements {@link Object#equals(Object)} using methods of that
* interface (which would still be duplicating code, not per interface
* implementation but per interface).</li>
* </ul>
* <p/>
* "Content" in "ContentMap" means "something equatable that is obtained with
* public methods of interface {@code K}"
* <p/>
* For example, if {@code PersonWithoutEquals} and {@code PersonFromStream}
* don't implement equals but have method {@code Person#name()} that returns
* {@link String}, we could map {@code Person}s to something else like this:
* <pre>
* Map<Person, Integer> map =
* new ContentMap<Person, String, Integer>(Person::name);
* map.put(new PersonWithoutEquals("Jeff"), 1);
* map.get(new PersonFromStream(streamWithNameJeff)); // 1
* </pre>
* <p/>
* This map has deterministic ordering.
* <p/>
* This map doesn't support methods {@link Map#keySet()} and
* {@link Map#entrySet()} because it doesn't store the keys of type
* {@code K}, it just uses those to obtain the "hidden" keys of type {@code C}.
* <p/>
* This map is immutable, mutator methods will throw
* {@link UnsupportedOperationException}.
* @param <K> Key type
* @param <C> Some content obtainable from K
* @param <V> Value type
* @author Georgy Vlasov (suseika@tendiwa.org)
* @version $Id$
* @since 0.6.0
*/
public final class ContentMap<K, C, V> implements Map<K, V> {
/**
* Map from content of the base map to its values.
*/
private final transient ImmutableMap<C, V> map;
/**
* Function to extract content from keys.
*/
private final transient Function<K, C> function;
/**
* Original map.
*/
private final transient Map<K, V> original;
/**
* Ctor.
* @param base Base map.
* @param function Function to get content of a key.
*/
public ContentMap(
final ImmutableMap<K, V> base,
final Function<K, C> function
) {
this.function = function;
this.map = this.mapContentToValues(base);
this.original = base;
}
@Override
public int size() {
return this.map.size();
}
@Override
public boolean isEmpty() {
return this.map.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return this.map.containsKey(this.key(o));
}
@Override
public boolean containsValue(Object o) {
return this.map.containsValue(o);
}
@Override
public V get(Object o) {
return this.map.get(this.key(o));
}
@Deprecated
@Override
public V put(K k, V v) {
throw new UnsupportedOperationException(
"Put operation is not supported"
);
}
@Deprecated
@Override
public V remove(Object o) {
throw new UnsupportedOperationException(
"Remove operation is not supported"
);
}
@Deprecated
@Override
public void putAll(Map<? extends K, ? extends V> map) {
throw new UnsupportedOperationException(
"Put all operation is not supported"
);
}
@Deprecated
@Override
public void clear() {
throw new UnsupportedOperationException(
"Clear operation is not supported"
);
}
@Override
public Set<K> keySet() {
return ImmutableSet.copyOf(this.original.keySet());
}
@Override
public Collection<V> values() {
return this.map.values();
}
@Override
public Set<Entry<K, V>> entrySet() {
return ImmutableSet.copyOf(this.original.entrySet());
}
@SuppressWarnings("unchecked")
private C key(Object o) {
return this.function.apply((K)o);
}
/**
* Maps the content of the base map to its values.
* @param base Base map
* @return Map from content of the base map to its values.
*/
private ImmutableMap<C, V> mapContentToValues(
final ImmutableMap<K, V> base
) {
final ImmutableMap.Builder<C, V> builder = ImmutableMap.builder();
for (final Entry<K, V> entry : base.entrySet()) {
builder.put(
this.key(entry.getKey()),
entry.getValue()
);
}
return builder.build();
}
}