CheckedComparator.java

/*-
 * #%L
 * io.earcam.unexceptional
 * %%
 * Copyright (C) 2016 - 2017 earcam
 * %%
 * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
 * 
 * You <b>must</b> choose to accept, in full - any individual or combination of 
 * the following licenses:
 * <ul>
 * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
 * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
 * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
 * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
 * </ul>
 * #L%
 */
package io.earcam.unexceptional;

import static io.earcam.unexceptional.Exceptional.apply;

import java.io.Serializable; //NOSONAR sonar false positive (suspect Sonar Squid is looking at bytecode here? as erasure of all but left-most bound where multiple bounds occur, see casts of return types in maxBy/minBy)
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;

/**
 * A checked equivalent of {@link java.util.Comparator}, also extends {@link CheckedToIntBiFunction}
 * 
 * @param <T> the type to compare
 * @param <E> the type of Throwable declared
 * 
 * @since 0.2.0
 * 
 * @see java.util.Comparator
 */
@FunctionalInterface
public interface CheckedComparator<T, E extends Throwable> extends CheckedToIntBiFunction<T, T, E> {

	public default int applyAsInt(T t, T u) throws E
	{
		return compare(t, u);
	}


	/**
	 * See {@link java.util.Comparator#compare(Object, Object)}
	 * 
	 * @param o1 first object to compare.
	 * @param o2 second object to compare.
	 * @return a negative integer, zero, or a positive integer as the
	 * first argument is less than, equal to, or greater than the
	 * second, respectively.
	 * @throws Throwable any throwable
	 */
	public abstract int compare(T o1, T o2) throws E;


	/**
	 * See {@link java.util.Comparator#reversed()}
	 * 
	 * @return an unchecked comparator of the reverse order to {@code this}
	 */
	public default Comparator<T> reversed()
	{
		return Collections.reverseOrder(Exceptional.uncheckComparator(this));
	}


	/**
	 * See {@link java.util.Comparator#thenComparing(Comparator)}
	 * 
	 * @param other the next comparator to be chained (IFF {@code this.compare(a, b)} returns zero)
	 * @return the chained {@link CheckedComparator}
	 * @throws NullPointerException if {@code other} is {@code null}
	 */
	@SuppressWarnings("squid:S1905") // SonarQube false positive
	public default CheckedComparator<T, E> thenComparing(/* @Nonnull */ CheckedComparator<? super T, ? extends E> other)
	{
		Objects.requireNonNull(other);
		return (CheckedComparator<T, E> & Serializable) (c1, c2) -> {
			int res = compare(c1, c2);// NOSONAR stfu false positive
			return (res == 0) ? other.compare(c1, c2) : res;
		};
	}


	/**
	 * See {@link java.util.Comparator#thenComparing(java.util.function.Function, Comparator)}
	 * 
	 * @param <U> the type of the sort key
	 * 
	 * @param keyExtractor function used to extract sort key
	 * @param keyComparator comparator used to compare extracted key
	 * @return the chained {@link CheckedComparator}
	 * 
	 * @see #thenComparing(CheckedComparator)
	 */
	public default <U> CheckedComparator<T, E> thenComparing(CheckedFunction<? super T, ? extends U, E> keyExtractor,
			CheckedComparator<? super U, E> keyComparator)
	{
		return thenComparing(comparing(keyExtractor, keyComparator));
	}


	/**
	 * See {@link java.util.Comparator#thenComparing(java.util.function.Function)}
	 * 
	 * @param <U> the type of the {@link Comparable} sort key
	 * 
	 * @param keyExtractor function used to extract sort key.
	 * @return the chained {@link CheckedComparator}
	 * 
	 * @see #thenComparing(CheckedComparator)
	 */
	public default <U extends Comparable<? super U>> CheckedComparator<T, E> thenComparing(CheckedFunction<? super T, ? extends U, E> keyExtractor)
	{
		return thenComparing(comparing(keyExtractor));
	}


	/**
	 * See {@link java.util.Comparator#thenComparingInt(java.util.function.ToIntFunction)}
	 * 
	 * @param keyExtractor function used to extract primitive {@code int} sort key.
	 * @return the chained {@link CheckedComparator}
	 * 
	 * @see #thenComparing(CheckedComparator)
	 */
	public default CheckedComparator<T, E> thenComparingInt(CheckedToIntFunction<? super T, ? extends E> keyExtractor)
	{
		return thenComparing(comparingInt(keyExtractor));
	}


	/**
	 * See {@link java.util.Comparator#thenComparingLong(java.util.function.ToLongFunction)}
	 * 
	 * @param keyExtractor function used to extract primitive {@code long} sort key.
	 * @return the chained {@link CheckedComparator}
	 * 
	 * @see #thenComparing(CheckedComparator)
	 */
	public default CheckedComparator<T, E> thenComparingLong(CheckedToLongFunction<? super T, ? extends E> keyExtractor)
	{
		return thenComparing(comparingLong(keyExtractor));
	}


	/**
	 * See {@link java.util.Comparator#thenComparingDouble(java.util.function.ToDoubleFunction)}
	 * 
	 * @param keyExtractor function used to extract primitive {@code double} sort key.
	 * @return the chained {@link CheckedComparator}
	 * @see #thenComparing(CheckedComparator)
	 */
	public default CheckedComparator<T, E> thenComparingDouble(CheckedToDoubleFunction<? super T, ? extends E> keyExtractor)
	{
		return thenComparing(comparingDouble(keyExtractor));
	}


	/**
	 * See {@link java.util.Comparator#comparing(java.util.function.Function, Comparator)}
	 * 
	 * @param <T> the type of element to be compared
	 * @param <U> the type of the sort key
	 * 
	 * @param keyExtractor function used to extract sort key.
	 * @param keyComparator comparator used to compare extracted key.
	 * @return a comparator that compares by an extracted key using the specified {@code CheckedComparator}.
	 * @throws NullPointerException if either argument is {@code null}.
	 */
	@SuppressWarnings("squid:S1905") // SonarQube false positive
	public static <T, U, E extends Throwable> CheckedComparator<T, E> comparing(
			/* @Nonnull */ CheckedFunction<? super T, ? extends U, ? extends E> keyExtractor,
			/* @Nonnull */ CheckedComparator<? super U, ? extends E> keyComparator)
	{
		Objects.requireNonNull(keyExtractor);
		Objects.requireNonNull(keyComparator);
		return (CheckedComparator<T, E> & Serializable) (c1, c2) -> keyComparator.compare(apply(keyExtractor, c1), apply(keyExtractor, c2));
	}


	/**
	 * See {@link java.util.Comparator#comparing(java.util.function.Function)}
	 * 
	 * @param <T> the type of element to be compared.
	 * @param <U> the type of the sort key.
	 * @param keyExtractor the function used to extract the sort key.
	 * @return a comparator that compares by an extracted key.
	 * @throws NullPointerException if the argument is {@code null}.
	 */
	@SuppressWarnings("squid:S1905") // SonarQube false positive
	public static <T, U extends Comparable<? super U>, E extends Throwable> CheckedComparator<T, E> comparing(
			/* @Nonnull */ CheckedFunction<? super T, ? extends U, ? extends E> keyExtractor)
	{
		Objects.requireNonNull(keyExtractor);
		return (CheckedComparator<T, E> & Serializable) (c1, c2) -> Exceptional.get(() -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)));
	}


	/**
	 * See {@link java.util.Comparator#comparingInt(java.util.function.ToIntFunction)}
	 * 
	 * @param <T> the type of element to be compared.
	 * 
	 * @param keyExtractor the function used to extract primitive {@code int} sort key.
	 * @return a checked comparator that compares by an extracted key.
	 * @throws NullPointerException if the argument is {@code null}.
	 * 
	 * @see #comparing(CheckedFunction)
	 */
	@SuppressWarnings("squid:S1905") // SonarQube false positive
	public static <T, E extends Throwable> CheckedComparator<T, E> comparingInt(/* @Nonnull */ CheckedToIntFunction<? super T, ? extends E> keyExtractor)
	{
		Objects.requireNonNull(keyExtractor);
		return (CheckedComparator<T, E> & Serializable) (c1, c2) -> Exceptional
				.get(() -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2)));
	}


	/**
	 * See {@link java.util.Comparator#comparingLong(java.util.function.ToLongFunction)}
	 * 
	 * @param <T> the type of element to be compared.
	 * 
	 * @param keyExtractor the function used to extract primitive {@code long} sort key.
	 * @return a checked comparator that compares by an extracted key.
	 * @throws NullPointerException if the argument is {@code null}.
	 * 
	 * @see #comparing(CheckedFunction)
	 */
	@SuppressWarnings("squid:S1905") // SonarQube false positive
	public static <T, E extends Throwable> CheckedComparator<T, E> comparingLong(/* @Nonnull */ CheckedToLongFunction<? super T, ? extends E> keyExtractor)
	{
		Objects.requireNonNull(keyExtractor);
		return (CheckedComparator<T, E> & Serializable) (c1, c2) -> Exceptional
				.get(() -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2)));
	}


	/**
	 * See {@link java.util.Comparator#comparingDouble(java.util.function.ToDoubleFunction)}
	 * 
	 * @param <T> the type of element to be compared.
	 * 
	 * @param keyExtractor the function used to extract primitive {@code double} sort key.
	 * @return a checked comparator that compares by an extracted key.
	 * @throws NullPointerException if the argument is {@code null}.
	 * 
	 * @see #comparing(CheckedFunction)
	 */
	@SuppressWarnings("squid:S1905") // SonarQube false positive
	public static <T, E extends Throwable> CheckedComparator<T, E> comparingDouble(/* @Nonnull */ CheckedToDoubleFunction<? super T, ? extends E> keyExtractor)
	{
		Objects.requireNonNull(keyExtractor);
		return (CheckedComparator<T, E> & Serializable) (c1, c2) -> Exceptional
				.get(() -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2)));
	}
}