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

org.ethelred.util.collect.BiMap Maven / Gradle / Ivy

The newest version!
/* (C) 2024 */
package org.ethelred.util.collect;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * A bidirectional mapping. Does not implement Map because I don't find that useful now. May not contain nulls or
 * duplicate keys/values. Immutable.
 * @param  Type of one of the keys/values
 * @param  Type of the other keys/values
 */
public class BiMap {
    private final Map first;
    private final Map second;

    /**
     * Construct a BiMap from the entries in map.
     * @param map entries to use
     * @throws NullPointerException when any key or value in map is null
     * @throws IllegalArgumentException when there are duplicate keys or values
     */
    public BiMap(Map map) {
        this(
                map.entrySet().stream()
                        .map(mapEntry -> entry(mapEntry.getKey(), mapEntry.getValue()))
                        .collect(Collectors.toList()),
                map.size());
    }

    private BiMap(Iterable> entries, int size) {
        first = new HashMap<>(size);
        second = new HashMap<>(size);
        entries.forEach(e -> {
            var a = e.a;
            var b = e.b;
            if (first.containsKey(Objects.requireNonNull(a)) || second.containsKey(Objects.requireNonNull(b))) {
                throw new IllegalArgumentException(String.format("Duplicate value in (%s, %s)", a, b));
            }
            first.put(a, b);
            second.put(b, a);
        });
    }

    public int size() {
        // The constructor invariants ensure that first and second have the same size
        return first.size();
    }

    /**
     * Get the B value corresponding to the A key.
     * @param key A key
     * @return Optional containing value, empty if not found
     */
    public Optional getByA(A key) {
        return Optional.ofNullable(first.get(key));
    }

    /**
     * Get the A value corresponding to the B key.
     * @param key B key
     * @return Optional containing value, empty if not found
     */
    public Optional getByB(B key) {
        return Optional.ofNullable(second.get(key));
    }

    /**
     * Construct an entry for building a BiMap.
     * @param a one of the keys/values
     * @param b the other key/value
     * @return an entry
     * @param  the type of the first key/value
     * @param  the type of the second key/value
     */
    public static  Entry entry(A a, B b) {
        return new Entry<>(Objects.requireNonNull(a), Objects.requireNonNull(b));
    }

    /**
     * Construct a BiMap from given entries.
     * @param entries entries to use
     * @throws IllegalArgumentException when there are duplicate keys or values
     */
    public static  BiMap ofEntries(Iterable> entries) {
        return new BiMap<>(entries, 0);
    }

    /**
     * Construct a BiMap from given entries.
     * @param entries entries to use
     * @throws IllegalArgumentException when there are duplicate keys or values
     */
    public static  BiMap ofEntries(Collection> entries) {
        return new BiMap<>(entries, entries.size());
    }

    /**
     * Construct a BiMap from given entries.
     * @param entries entries to use
     * @throws IllegalArgumentException when there are duplicate keys or values
     */
    @SafeVarargs
    public static  BiMap ofEntries(Entry... entries) {
        return new BiMap<>(List.of(entries), entries.length);
    }

    /**
     *
     * @return An unmodifiable view of the mapping from A keys
     */
    public Map mapByA() {
        return Collections.unmodifiableMap(first);
    }

    /**
     *
     * @return An unmodifiable view of the mapping from B keys
     */
    public Map mapByB() {
        return Collections.unmodifiableMap(second);
    }

    /**
     *
     * @return An unmodifiable view of the set of A keys
     */
    public Set keysA() {
        return Collections.unmodifiableSet(first.keySet());
    }

    /**
     *
     * @return An unmodifiable view of the set of B keys
     */
    public Set keysB() {
        return Collections.unmodifiableSet(second.keySet());
    }

    /**
     *
     * @return Collect a stream of Entry into a BiMap
     * @param  Type of one of the keys/values
     * @param  Type of the other keys/values
     */
    public static  Collector, ?, BiMap> toBiMap() {
        return Collectors.collectingAndThen(Collectors.toList(), BiMap::ofEntries);
    }

    public static class Entry {
        private final A a;
        private final B b;

        private Entry(A a, B b) {
            this.a = a;
            this.b = b;
        }
    }
}