package org.codefx.libfx.collection.transform;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.ToIntFunction;
/**
* Builds {@link EqualityTransformingSet}s and {@link EqualityTransformingMap}s.
* <p>
* (For simplification the comments only talk about sets but unless otherwise noted the same applies to maps.)
* <p>
* The implementations of {@code equals} and {@code hashCode} are provided as functions to the builder. They must of
* course fulfill the general contract between those two methods (see {@link Object#hashCode() here}). The functions can
* be provided on two ways:
* <ol>
* <li>Via the {@code with[Equals|Hash]}-methods. In this case, the functions will never be called with null instances,
* which are handled by this map as follows:
* <ul>
* <li>{@code hashCode(null) == 0}
* <li>{@code equals(null, null) == true};
* <li>{@code equals(not-null, null) == false}
* <li>{@code equals(null, not-null) == false}
* </ul>
* <li>If those defaults are not sufficient, the functions can handle null themselves. Those variants can be provided
* via the {@code with[Equals|Hash]HandlingNull}-methods.
* </ol>
* <p>
* The same builder instance can be reused to create multiple instances. The builder is not thread-safe and should not
* be used concurrently.
*
* @param <E>
* the type of elements maintained by the created set or keys by the created map.
*/
public class EqualityTransformingCollectionBuilder<E> {
private final Class<? super E> outerKeyTypeToken;
private BiPredicate<? super E, ? super E> equals;
private ToIntFunction<? super E> hash;
// #begin CONSTRUCTION
private EqualityTransformingCollectionBuilder(Class<? super E> outerKeyTypeToken) {
this.outerKeyTypeToken = outerKeyTypeToken;
// note that the methods from 'Objects' already implement the contract for null-safety
// imposed by the transforming set and map
this.equals = Objects::equals;
this.hash = Objects::hashCode;
}
/**
* Returns a new builder for the specified element type.
* <p>
* If a type token for the elements can not be provided, call {@link #forTypeUnknown()} instead.
*
* @param <E>
* the type of elements contained in the created set
* @param keyTypeToken
* a type token for the elements contained in the created set
* @return a new builder
*/
public static <E> EqualityTransformingCollectionBuilder<E> forType(Class<? super E> keyTypeToken) {
Objects.requireNonNull(keyTypeToken, "The argument 'keyTypeToken' must not be null.");
return new EqualityTransformingCollectionBuilder<>(keyTypeToken);
}
/**
* Returns a new builder for an unknown key type.
* <p>
* This is equivalent to calling {@link #forType(Class) forKeyType(Object.class)}. To obtain a builder for
* {@code <E>} you will have to call {@code EqualityTransformingCollectionBuilder.<E> forTypeUnknown()}.
*
* @param <E>
* the type of elements contained in the set created by the builder
* @return a new builder
*/
public static <E> EqualityTransformingCollectionBuilder<E> forTypeUnknown() {
return new EqualityTransformingCollectionBuilder<>(Object.class);
}
// #end CONSTRUCTION
// #begin SET PROPERTIES
/**
* @param equals
* a function determining equality of elements; might be called with null elements
* @return this builder
*/
public EqualityTransformingCollectionBuilder<E> withEqualsHandlingNull(BiPredicate<? super E, ? super E> equals) {
Objects.requireNonNull(equals, "The argument 'equals' must not be null.");
this.equals = equals;
return this;
}
/**
* @param equals
* a function determining equality of elements; will not be called with null elements
* @return this builder
*/
public EqualityTransformingCollectionBuilder<E> withEquals(BiPredicate<? super E, ? super E> equals) {
Objects.requireNonNull(equals, "The argument 'equals' must not be null.");
return withEqualsHandlingNull(makeNullSafe(equals));
}
private static <E> BiPredicate<? super E, ? super E> makeNullSafe(BiPredicate<? super E, ? super E> equals) {
return (outerKey1, outerKey2) -> {
if (outerKey1 == null && outerKey2 == null)
return true;
if (outerKey1 == null || outerKey2 == null)
return false;
return equals.test(outerKey1, outerKey2);
};
}
/**
* @param hash
* a function computing the hash code of an element; might be called with null elements
* @return this builder
*/
public EqualityTransformingCollectionBuilder<E> withHashHandlingNull(ToIntFunction<? super E> hash) {
Objects.requireNonNull(hash, "The argument 'hash' must not be null.");
this.hash = hash;
return this;
}
/**
* @param hash
* a function computing the hash code of an element; will not be called with null elements
* @return this builder
*/
public EqualityTransformingCollectionBuilder<E> withHash(ToIntFunction<? super E> hash) {
Objects.requireNonNull(hash, "The argument 'hash' must not be null.");
return withHashHandlingNull(makeNullSafe(hash));
}
private static <E> ToIntFunction<? super E> makeNullSafe(ToIntFunction<? super E> hash) {
return outerKey -> outerKey == null ? EqHash.NULL_KEY_HASH_CODE : hash.applyAsInt(outerKey);
}
// #end SET PROPERTIES
// #begin BUILD
/**
* Creates a new {@link EqualityTransformingSet} by decorating a {@link HashSet}.
*
* @return a new instance of {@link EqualityTransformingSet}
*/
public EqualityTransformingSet<E> buildSet() {
return new EqualityTransformingSet<>(new HashSet<>(), outerKeyTypeToken, equals, hash);
}
/**
* Creates a new {@link EqualityTransformingSet} by decorating the specified set.
*
* @param emptySet
* an empty set which is not otherwise referenced
* @return a new instance of {@link EqualityTransformingSet}
*/
public EqualityTransformingSet<E> buildSet(Set<Object> emptySet) {
return new EqualityTransformingSet<>(emptySet, outerKeyTypeToken, equals, hash);
}
/**
* Creates a new {@link EqualityTransformingMap} by decorating a {@link HashMap}.
*
* @param <V>
* the type of values mapped by the new map
* @return a new instance of {@link EqualityTransformingMap}
*/
public <V> EqualityTransformingMap<E, V> buildMap() {
return new EqualityTransformingMap<>(new HashMap<>(), outerKeyTypeToken, equals, hash);
}
/**
* Creates a new {@link EqualityTransformingMap} by decorating the specified map.
*
* @param <V>
* the type of values mapped by the new map
* @param emptyMap
* an empty map which is not otherwise referenced
* @return a new instance of {@link EqualityTransformingMap}
*/
public <V> EqualityTransformingMap<E, V> buildMap(Map<Object, Object> emptyMap) {
return new EqualityTransformingMap<>(emptyMap, outerKeyTypeToken, equals, hash);
}
// #end BUILD
}