package org.codefx.libfx.collection.transform; import java.util.HashMap; import java.util.Map; import java.util.function.BiPredicate; import java.util.function.ToIntFunction; /** * An equality transforming map allows to define the implementations of {@link Object#equals(Object) equals} and * {@link Object#hashCode() hashCode} which are used for the map's keys. * <p> * It does so by storing the entries in an inner map and providing a transforming view on them. See the * {@link org.codefx.libfx.collection.transform package} documentation for general comments on that. Note that instances * of {@code EqualityTransformingMap}s are created with a {@link EqualityTransformingCollectionBuilder builder}. * <p> * This implementation mitigates the type safety problems by optionally using a token of the (outer) key type to check * instances against them. This solves some of the critical situations but not all of them. In those other cases * {@link ClassCastException}s might still occur. * <p> * By default the inner map will be a new {@link HashMap} but the another map can be provided to the builder. Such * instances must be empty and not be referenced anywhere else. The implementations of {@code equals} and * {@code hashCode} are provided as functions to the builder - see there for details. * <p> * The transformations used by this map preserve object identity of outer keys and values. This means if keys and values * are added to this map, an iteration over it will return the same instances. * <p> * {@code EqualityTransformingMap}s are created with a {@link EqualityTransformingCollectionBuilder}. * * @param <K> * the type of keys maintained by this map * @param <V> * the type of mapped values */ public final class EqualityTransformingMap<K, V> extends AbstractTransformingMap<EqHash<K>, K, V, V> { // #begin FIELDS private final Map<EqHash<K>, V> innerMap; private final Class<? super K> outerKeyTypeToken; /** * Compares two outer keys for equality. */ private final BiPredicate<? super K, ? super K> equals; /** * Computes a hashCode for an outer key. */ private final ToIntFunction<? super K> hash; // #end FIELDS // #begin CONSTRUCTION /** * Creates a new transforming map. * * @param innerMap * the decorated map; must be empty * @param outerKeyTypeToken * the token used to verify outer keys * @param equals * the function computing equality of keys * @param hash * the function computing the hash code of keys */ EqualityTransformingMap( Map<?, ?> innerMap, Class<? super K> outerKeyTypeToken, BiPredicate<? super K, ? super K> equals, ToIntFunction<? super K> hash) { assert innerMap != null : "The argument 'innerMap' must not be null."; assert outerKeyTypeToken != null : "The argument 'outerKeyTypeToken' must not be null."; assert equals != null : "The argument 'equals' must not be null."; assert hash != null : "The argument 'hash' must not be null."; this.innerMap = castInnerMap(innerMap); this.outerKeyTypeToken = outerKeyTypeToken; this.equals = equals; this.hash = hash; } private static <K, V> Map<EqHash<K>, V> castInnerMap(Map<?, ?> untypedInnerMap) { @SuppressWarnings("unchecked") // This class' contract states that the 'innerMap' must be empty and that no other // references to it must exist. This implies that only this class can ever access or mutate it. // Thanks to erasure its generic key and value types can hence be cast to any other type. Map<EqHash<K>, V> innerMap = (Map<EqHash<K>, V>) untypedInnerMap; return innerMap; } // #end CONSTRUCTION // #begin IMPLEMENTATION OF 'AbstractTransformingMap' @Override protected Map<EqHash<K>, V> getInnerMap() { return innerMap; } @Override protected boolean isInnerKey(Object object) { // this excludes null objects from being inner keys which is correct because even null will be wrapped in EqHash return object instanceof EqHash; } @Override protected K transformToOuterKey(EqHash<K> innerKey) throws ClassCastException { return innerKey.getElement(); } @Override protected boolean isOuterKey(Object object) { return object == null || outerKeyTypeToken.isInstance(object); } @Override protected EqHash<K> transformToInnerKey(K outerKey) throws ClassCastException { return EqHash.create(outerKey, equals, hash); } @Override protected boolean isInnerValue(Object object) { return true; } @Override protected V transformToOuterValue(V innerValue) throws ClassCastException { return innerValue; } @Override protected boolean isOuterValue(Object object) { return true; } @Override protected V transformToInnerValue(V outerValue) throws ClassCastException { return outerValue; } // #end IMPLEMENTATION OF 'AbstractTransformingMap' }