package org.codefx.libfx.collection.transform; import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.ToIntFunction; /** * An equality transforming set allows to define the implementations of {@link Object#equals(Object) equals} and * {@link Object#hashCode() hashCode} which are used by the set. * <p> * It does so by storing the entries in an inner set and providing a transforming view on them. See the * {@link org.codefx.libfx.collection.transform package} documentation for general comments on that. * <p> * This implementation mitigates the type safety problems by optionally using a token of the outer 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 set will be a {@link HashSet} but 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 set preserve object identity of outer values. This means if values are added to this * set, an iteration over it will return the same instances. * <p> * {@code EqualityTransformingSet}s are created with a {@link EqualityTransformingCollectionBuilder}. * * @param <E> * the type of elements in this set */ public class EqualityTransformingSet<E> extends AbstractTransformingSet<EqHash<E>, E> { // #begin FIELDS private final Set<EqHash<E>> innerSet; private final Class<? super E> outerTypeToken; /** * Compares two outer elements for equality. */ private final BiPredicate<? super E, ? super E> equals; /** * Computes a hashCode for an outer element. */ private final ToIntFunction<? super E> hash; // #end FIELDS // #begin CONSTRUCTION /** * Creates a new transforming set. * * @param innerSet * the decorated set; must be empty * @param outerTypeToken * the token used to verify outer elements * @param equals * the function computing equality of elements * @param hash * the function computing the hash code of elements */ EqualityTransformingSet( Set<?> innerSet, Class<? super E> outerTypeToken, BiPredicate<? super E, ? super E> equals, ToIntFunction<? super E> hash) { this.innerSet = castInnerSet(innerSet); this.outerTypeToken = outerTypeToken; this.equals = equals; this.hash = hash; } private static <E> Set<EqHash<E>> castInnerSet(Set<?> untypedInnerSet) { @SuppressWarnings("unchecked") // This class' contract states that the 'innerSet' 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 element type can hence be cast to any other type. Set<EqHash<E>> innerMap = (Set<EqHash<E>>) untypedInnerSet; return innerMap; } // #end CONSTRUCTION @Override protected Set<EqHash<E>> getInnerSet() { return innerSet; } @Override protected boolean isInnerElement(Object object) { // this excludes null objects from being inner element which is correct because even null will be wrapped in EqHash return object instanceof EqHash; } @Override protected E transformToOuter(EqHash<E> innerElement) throws ClassCastException { return innerElement.getElement(); } @Override protected boolean isOuterElement(Object object) { return object == null || outerTypeToken.isInstance(object); } @Override protected EqHash<E> transformToInner(E outerElement) throws ClassCastException { return EqHash.create(outerElement, equals, hash); } }