All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jnape.palatable.lambda.adt.hmap.HMap Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
package com.jnape.palatable.lambda.adt.hmap;

import com.jnape.palatable.lambda.adt.hlist.Tuple2;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;

/**
 * An immutable heterogeneous mapping from a parametrized type-safe key to any value, supporting a minimal mapping
 * interface.
 *
 * @see TypeSafeKey
 * @see com.jnape.palatable.lambda.adt.hlist.HList
 */
public class HMap implements Iterable> {

    private static final HMap EMPTY = new HMap(emptyMap());

    private final Map table;

    private HMap(Map table) {
        this.table = table;
    }

    /**
     * Retrieve the value at this key.
     *
     * @param key the key
     * @param  the value type
     * @return the value at this key wrapped in an {@link Optional}, or {@link Optional#empty}.
     */
    @SuppressWarnings("unchecked")
    public  Optional get(TypeSafeKey key) {
        return Optional.ofNullable((T) table.get(key));
    }

    /**
     * Retrieve the value at this key, throwing a {@link NoSuchElementException} if this key is unmapped.
     *
     * @param key the key
     * @param  the value type
     * @return the value at this key
     * @throws NoSuchElementException if the key is unmapped
     */
    public  V demand(TypeSafeKey key) throws NoSuchElementException {
        return get(key).orElseThrow(() -> new NoSuchElementException("Demanded value for key " + key + ", but couldn't find one."));
    }

    /**
     * Store a value for the given key.
     *
     * @param key   the key
     * @param value the value
     * @param    the value type
     * @return the updated HMap
     */
    public  HMap put(TypeSafeKey key, V value) {
        return alter(t -> t.put(key, value));
    }

    /**
     * Store all the key/value mappings in hMap in this HMap.
     *
     * @param hMap the other HMap
     * @return the updated HMap
     */
    public HMap putAll(HMap hMap) {
        return alter(t -> t.putAll(hMap.table));
    }

    /**
     * Determine if a key is mapped.
     *
     * @param key the key
     * @return true if the key is mapped; false otherwise
     */
    public boolean containsKey(TypeSafeKey key) {
        return table.containsKey(key);
    }

    /**
     * Remove a mapping from this HMap.
     *
     * @param key the key
     * @return the updated HMap
     */
    public HMap remove(TypeSafeKey key) {
        return alter(t -> t.remove(key));
    }

    /**
     * Remove all the key/value mappings in hMap from this HMap.
     *
     * @param hMap the other HMap
     * @return the updated HMap
     */
    public HMap removeAll(HMap hMap) {
        return alter(t -> t.keySet().removeAll(hMap.table.keySet()));
    }

    /**
     * Retrieve all the mapped keys.
     *
     * @return an Iterable of all the mapped keys
     */
    public Iterable keys() {
        return map(Tuple2::_1, this);
    }

    /**
     * Retrieve all the mapped values.
     *
     * @return an Iterable of all the mapped values
     */
    public Iterable values() {
        return map(Tuple2::_2, this);
    }

    /**
     * Return a standard {@link Map} view of the current snapshot of this {@link HMap}. Note that updates to either the
     * {@link Map} view or to the original {@link HMap} do not propagate to the other.
     *
     * @return the map view
     */
    public Map toMap() {
        return new HashMap<>(table);
    }

    @Override
    public Iterator> iterator() {
        return map(Tuple2::fromEntry, table.entrySet()).iterator();
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof HMap) {
            HMap that = (HMap) other;
            return Objects.equals(this.table, that.table);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return 31 * Objects.hashCode(table);
    }

    @Override
    public String toString() {
        return "HMap{" +
                "table=" + table +
                '}';
    }

    private HMap alter(Consumer> alterFn) {
        HashMap copy = new HashMap<>(table);
        alterFn.accept(copy);
        return new HMap(copy);
    }

    /**
     * Static factory method for creating an empty HMap.
     *
     * @return an empty HMap
     */
    public static HMap emptyHMap() {
        return EMPTY;
    }

    /**
     * Static factory method for creating a singleton HMap.
     *
     * @param key   the only mapped key
     * @param value the only mapped value
     * @param    the only mapped value type
     * @return a singleton HMap
     */
    public static  HMap singletonHMap(TypeSafeKey key, V value) {
        return new HMap(singletonMap(key, value));
    }

    /**
     * Static factory method for creating an HMap from two given associations.
     *
     * @param key1   the first mapped key
     * @param value1 the value mapped at key1
     * @param key2   the second mapped key
     * @param value2 the value mapped at key2
     * @param    value1's type
     * @param    value2's type
     * @return an HMap with the given associations
     */
    public static  HMap hMap(TypeSafeKey key1, V1 value1,
                                     TypeSafeKey key2, V2 value2) {
        return singletonHMap(key1, value1).put(key2, value2);
    }

    /**
     * Static factory method for creating an HMap from three given associations.
     *
     * @param key1   the first mapped key
     * @param value1 the value mapped at key1
     * @param key2   the second mapped key
     * @param value2 the value mapped at key2
     * @param key3   the third mapped key
     * @param value3 the value mapped at key3
     * @param    value1's type
     * @param    value2's type
     * @param    value3's type
     * @return an HMap with the given associations
     */
    public static  HMap hMap(TypeSafeKey key1, V1 value1,
                                         TypeSafeKey key2, V2 value2,
                                         TypeSafeKey key3, V3 value3) {
        return hMap(key1, value1, key2, value2).put(key3, value3);
    }
}