Maps.java
package no.motif;
import java.util.Map;
import java.util.Map.Entry;
import no.motif.f.Fn;
import no.motif.f.Predicate;
import no.motif.maps.CombinedMapsView;
import no.motif.maps.MapViewOfFn;
import no.motif.maps.NotPossibleOnMapViewOfFn;
/**
* Operations on {@link Map maps} and {@link Entry map entries}.
*/
public final class Maps {
/**
* Gets the key of a {@link Entry map entry}.
*/
public static <K> Fn<Entry<K, ?>, K> key() { return new Fn<Map.Entry<K, ?>, K>() {
@Override public K $(Entry<K, ?> entry) { return entry.getKey(); }}; }
/**
* Gets the value of a {@link Entry map entry}.
*/
public static <V> Fn<Entry<?, V>, V> value() { return new Fn<Map.Entry<?, V>, V>() {
@Override public V $(Entry<?, V> entry) { return entry.getValue(); }}; }
/**
* Evaluate if objects are a key {@link Map#containsKey(Object) contained in}
* the given <code>Map</code>.
*/
public static <K> Predicate<K> keyIn(final Map<K, ?> map) { return new Predicate<K>() {
@Override public boolean $(K key) { return map.containsKey(key); }}; }
/**
* Evaluate if objects are a value {@link Map#containsValue(Object) contained in}
* the given <code>Map</code>.
*/
public static <V> Predicate<V> valueIn(final Map<?, ? extends V> map) { return new Predicate<V>() {
@Override public boolean $(V value) { return map.containsValue(value); }}; }
/**
* Use a given {@link Fn} as a {@link java.util.Map}.
* Any key which resolves to <code>null</code> using the given </code>Fn</code> are not
* considered to be {@link Map#containsKey(Object) contained} in the map, all other keys
* are contained in the map.
*
* <p>
* This imposes some very limiting
* constraints on the returned <code>Map</code>. Especially, any method which
* queries {@link Map#containsValue(Object) values without a key}, its {@link Map#size() size},
* whether it {@link Map#isEmpty() is empty}, and retrieving all
* {@link Map#keySet() keys}, {@link Map#values() values} and {@link Map#entrySet() entries}
* is not possible, and will throw {@link NotPossibleOnMapViewOfFn}.
*
* <p>
* The returned <code>Map</code> is mostly useable for retrieval of values based on
* keys which must be known beforehand. If the map is {@link Map#clear() cleared}, it
* "becomes" a {@link java.util.HashMap}, and thus will fully adhere to the <code>Map</code>
* contract as its entries are no longer being dependent of the <code>Fn</code>.
*
* <p>
* Fully supported <code>Map</code>-operations:
* <ul>
* <li> {@link Map#containsKey(Object)}</li>
* <li> {@link Map#get(Object)}</li>
* <li> {@link Map#put(Object, Object)}</li>
* <li> {@link Map#putAll(Map)}</li>
* <li> {@link Map#remove(Object)}</li>
* <li> {@link Map#clear()}</li>
* </ul>
*
* In conclusion, the behavior of the returned map seriously violates the contract of
* the <code>Map</code> interface, but it may still be appropriate for certain cases.
* Careful considerations should be taken before using a <code>Map</code> returned by this method.
*/
public static <K, V> Map<K, V> asMap(Fn<K, V> fn) {
return new MapViewOfFn<>(fn);
}
/**
* Uses a {@link java.util.Map} as the basis for a {@link Fn}.
* The value of the <code>Fn</code> is resolved simply my calling
* {@link Map#get(Object) .get(I)} on the map with the given argument for
* {@link Fn#$(Object) Fn.$(I)}. The semantics for <code>null</code> are the
* same as for {@link Map#get(Object)}; the <code>Fn</code> yields <code>null</code>
* both for keys not present in the map, and for mapped <code>null</code> values if
* the map permits it.
*/
public static <I, O> Fn<I, O> asFn(final Map<I, O> map) { return new Fn<I, O>() { @Override public O $(I key) {
return map.get(key);
}}; }
/**
* Creates a combined view of two maps, where the values of the first map corresponds
* to keys of the second map.
*
* @see #combine(Map, Fn, Map)
*/
public static <K, I, V> Map<K, V> combine(Map<K, I> first, Map<I, V> second) {
return combine(first, NOP.<I>fn(), second);
}
/**
* Creates a combined view of two maps, where the values of the first map will be mapped (no pun
* intended) to keys of the second map. This has the implication that any value in the first map not
* being mapped to a key in the second map will appear as not present in this view. The same applies
* to keys in the second map not being a value (when mapped) in the first map, they cannot be retrieved with
* this view.
* <p>
* Similarily, the semantics of the {@link Map#containsKey(Object) containsKey(key)} works transitively.
* It returns true <em>only</em> if the given key is contained in the first map, and yields a value
* which is contained as a key in the second map. The usual behavior considering possible
* <code>null</code>s as keys and values applies as specified in the {@link Map} interface, and is
* dependent of the two maps the view is based on.
* </p>
*
* @param first The first map. All or a subset of the keys of this map will be keys in the returned view.
* @param connector A connector {@link Fn} to map values of the first map to keys of the second map.
* @param second The second map. All or a subset of the values of this map will be values in the returned view.
* @return the {@link CombinedMapsView}.
*/
public static <K1, V1, K2, V2> Map<K1, V2> combine(
Map<K1, V1> first,
Fn<? super V1, ? extends K2> connector,
Map<K2, V2> second) {
return new CombinedMapsView<K1, V1, K2, V2>(first, connector, second);
}
private Maps() {}
}