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

org.organicdesign.testUtils.ComparatorContract Maven / Gradle / Ivy

package org.organicdesign.testUtils;

import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.*;
import static org.organicdesign.testUtils.ComparatorContract.CompToZero.*;
/**
 Created by Glen K. Peterson on 3/28/17.
 */
public class ComparatorContract {
    public enum CompToZero {
        LTZ {
            @Override public String english() { return "less than"; }
            @Override public boolean vsZero(int i) { return i < 0; }
        },
        GTZ {
            @Override public String english() { return "greater than"; }
            @Override public boolean vsZero(int i) { return i > 0; }
        },
        EQZ {
            @Override public String english() { return "equal to"; }
            @Override public boolean vsZero(int i) { return i == 0; }
        };
        public abstract String english();
        public abstract boolean vsZero(int i);
    }

    private static class Named {
        final @NotNull T a;
        final @NotNull String name;
        Named(
                @NotNull T theA,
                @NotNull String nm
        ) {
            a = theA;
            name = nm;
        }
    }

    private static 
    void pairComp(
            @NotNull Named first,
            @NotNull CompToZero comp,
            @NotNull Named second,
            @NotNull Comparator comparator
    ) {
        assertTrue("The " + first.name + " item must be " + comp.english() +
                   " the " + second.name,
                   comp.vsZero(comparator.compare(first.a, second.a)));
    }

    /**
     Tests the various properties the Comparable contract is supposed to uphold.  Also tests that
     the behavior of compareTo() is compatible with equals() and hashCode() which is strongly
     suggested, but not actually required, but keeps things much simpler (your class will behave the same
     way in a sorted and unsorted set or map).

     It's safest to also make your comparator serializable.  Instead of a static class, use an enum singleton.
     If your comparator must be instantiated with parameters, ensure that it is serializable, for instance
     with {@link Serialization#serializeDeserialize(Object)} before passing it to this method.

     Expects three unique objects.
     The first must be less than the second, which in turn is less than the third.

     See note in class documentation.
     */
    // Many of the comments in this method are paraphrases or direct quotes from the Javadocs for
    // the Comparable interface.  That is where this contract is specified.
    // https://docs.oracle.com/javase/8/docs/api/
    public static 
    void testComparator(
            @NotNull T least1,
            @NotNull T middle1,
            @NotNull T greatest1, Comparator comparator
    ) {

        // TODO: Do we want to ensure that comparators are serializable?

        AtomicBoolean anySame = new AtomicBoolean();
        EqualsContract.permutations(Arrays.asList(least1, middle1, greatest1),
                                    (T a, T b) -> {
                                        if (a == b) {
                                            anySame.set(true);
                                        }
                                        return null;
                                    });
        if (anySame.get()) {
            throw new IllegalArgumentException("You must provide three pair of different objects in order");
        }

        int i = 0;
        for (T item : Arrays.asList(least1, middle1, greatest1)) {
            i++;
            // null is not an instance of any class, and e.compareTo(null) should throw a
            // NullPointerException
            try {
                //noinspection ResultOfMethodCallIgnored
                comparator.compare(item, null);
                fail("comparator.compare(item, null) should throw an exception " +
                     "even though e.equals(null) returns false, but item " + i + " did not.");
            } catch (RuntimeException ignore) {
                // Previously we had allowed NullPointerException and IllegalArgumentException.
                // Kotlin throws IllegalStateException, so we now expect any RuntimeException
                // to be thrown.
            }

            try {
                //noinspection ResultOfMethodCallIgnored
                comparator.compare(null, item);
                fail("comparator.compare(null, item) should throw an exception " +
                     "even though e.equals(null) returns false, but item " + i + " did not.");
            } catch (RuntimeException ignore) {
                // Previously we had allowed NullPointerException and IllegalArgumentException.
                // Kotlin throws IllegalStateException, so we now expect any RuntimeException
                // to be thrown.
            }
        }

        Named least = new Named<>(least1, "Least");
        Named middle = new Named<>(middle1, "Middle");
        Named greatest = new Named<>(greatest1, "Greatest");

        for (Named pair : Arrays.asList(least, middle, greatest)) {
            // Consistent with equals: (e1.compareTo(e2) == 0) if and only if e1.equals(e2)
            pairComp(pair, EQZ, pair, comparator);
        }

        pairComp(least, LTZ, middle, comparator);
        pairComp(least, LTZ, greatest, comparator);
        pairComp(middle, LTZ, greatest, comparator);

        pairComp(greatest, GTZ, middle, comparator);
        pairComp(greatest, GTZ, least, comparator);
        pairComp(middle, GTZ, least, comparator);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy