Preconditions.java

package com.github.choonchernlim.betterPreconditions.core;

import com.github.choonchernlim.betterPreconditions.exception.ObjectEqualPreconditionException;
import com.github.choonchernlim.betterPreconditions.exception.ObjectNotEqualPreconditionException;
import com.github.choonchernlim.betterPreconditions.exception.ObjectNotNullPreconditionException;
import com.github.choonchernlim.betterPreconditions.exception.ObjectNotSameTypePreconditionException;
import com.github.choonchernlim.betterPreconditions.exception.ObjectNullPreconditionException;
import com.github.choonchernlim.betterPreconditions.exception.ObjectSameTypePreconditionException;
import com.github.choonchernlim.betterPreconditions.exception.StringBlankPreconditionException;
import static com.github.choonchernlim.betterPreconditions.preconditions.PreconditionFactory.expect;
import static com.google.common.base.Strings.nullToEmpty;
import com.google.common.collect.Lists;

import java.util.List;

/**
 * Abstract class for all preconditions.
 */
public abstract class Preconditions<C, V> {

    /**
     * Value to be checked.
     */
    protected final V value;

    /**
     * Label for the value.
     */
    protected final String label;

    /**
     * All assertions to be executed.
     */
    private final List<Assertion> assertions;

    /**
     * Mutable field to indicate whether the upcoming assertion is negated.
     */
    private boolean isNegated;

    /**
     * Creates a new instance.
     *
     * @param value Value
     * @param label Label
     */
    protected Preconditions(final V value, final String label) {
        // label is not empty... cannot use `expect(label).not().toBeBlank().check()` to prevent
        // infinite recursion calls.
        if (nullToEmpty(label).trim().isEmpty()) {
            throw new StringBlankPreconditionException(label, "Label");
        }

        this.value = value;
        this.label = label;
        this.assertions = Lists.newArrayList();
        this.isNegated = false;
    }

    /**
     * Enables negation.
     *
     * @return Current instance
     */
    @SuppressWarnings("unchecked")
    public final C not() {
        this.isNegated = true;
        return (C) this;
    }

    /**
     * Ensures the object is null.
     *
     * @return Current instance
     */
    @SuppressWarnings("unchecked")
    public final C toBeNull() {
        customMatcher(new Matcher<V>() {
            @Override
            public boolean match(final V value, final String label) {
                return value == null;
            }

            @Override
            public PreconditionException getException(final V value, final String label) {
                return new ObjectNotNullPreconditionException(value, label);
            }

            @Override
            public PreconditionException getNegatedException(final V value, final String label) {
                return new ObjectNullPreconditionException(value, label);
            }
        });

        return (C) this;
    }

    /**
     * Ensures given value is equal to expected value.
     *
     * @see Preconditions#toBeEqual(Object, String)
     */
    public C toBeEqual(final V expectedValue) {
        return toBeEqual(expectedValue, "Expected Value");
    }

    /**
     * Ensures given value is equal to expected value.
     *
     * @param expectedValue Second value
     * @param expectedLabel Second label
     * @return Current instance
     */
    @SuppressWarnings("unchecked")
    public C toBeEqual(final V expectedValue, final String expectedLabel) {
        expectValueLabelToExist(expectedValue, expectedLabel, "Expected Label");

        customMatcher(new Matcher<V>() {
            @Override
            public boolean match(final V givenValue, final String givenLabel) {
                return expect(givenValue, givenLabel).not().toBeNull().check().equals(expectedValue);
            }

            @Override
            public PreconditionException getException(final V givenValue, final String givenLabel) {
                return new ObjectNotEqualPreconditionException(givenValue, givenLabel, expectedValue, expectedLabel);
            }

            @Override
            public PreconditionException getNegatedException(final V givenValue, final String givenLabel) {
                return new ObjectEqualPreconditionException(givenValue, givenLabel, expectedValue, expectedLabel);
            }
        });

        return (C) this;
    }

    /**
     * Ensures given value is equal to expected value.
     *
     * @see Preconditions#toBeSameType(Object, String)
     */
    public C toBeSameType(final V expectedValue) {
        return toBeSameType(expectedValue, "Expected Value");
    }

    /**
     * Ensures given value has same class type as expected value.
     *
     * @param expectedValue Second value
     * @param expectedLabel Second label
     * @return Current instance
     */
    @SuppressWarnings("unchecked")
    public C toBeSameType(final V expectedValue, final String expectedLabel) {
        expectValueLabelToExist(expectedValue, expectedLabel, "Expected Label");

        customMatcher(new Matcher<V>() {
            @Override
            public boolean match(final V givenValue, final String givenLabel) {
                return expect(givenValue, givenLabel)
                        .not().toBeNull().check()
                        .getClass().equals(expectedValue.getClass());
            }

            @Override
            public PreconditionException getException(final V givenValue, final String givenLabel) {
                return new ObjectNotSameTypePreconditionException(givenValue, givenLabel, expectedValue, expectedLabel);
            }

            @Override
            public PreconditionException getNegatedException(final V givenValue, final String givenLabel) {
                return new ObjectSameTypePreconditionException(givenValue, givenLabel, expectedValue, expectedLabel);
            }
        });

        return (C) this;
    }

    /**
     * Adds new assertion.
     *
     * @param matcher Matcher
     * @return Current instance
     */
    @SuppressWarnings("unchecked")
    public final C customMatcher(final Matcher<V> matcher) {
        assertions.add(new Assertion<V>(matcher, isNegated, value, label));

        // after creating a new assertion, reset the negation
        this.isNegated = false;

        return (C) this;
    }

    /**
     * Returns value if all assertions pass.
     */
    public final V check() {
        for (Assertion assertion : assertions) {
            assertion.run();
        }

        return value;
    }

    /**
     * Ensures value not be null and label not be blank.
     *
     * @param value        Value
     * @param label        Label
     * @param defaultLabel Default label
     */
    protected final void expectValueLabelToExist(final Object value, final String label, final String defaultLabel) {
        expect(label, defaultLabel).not().toBeBlank().check();
        expect(value, label).not().toBeNull().check();
    }

    /**
     * Ensures value not null and same type.
     *
     * @param givenValue    Given label
     * @param givenLabel    Given label
     * @param expectedValue Expected value
     * @param expectedLabel Expected label
     */
    protected final void expectNotNullAndSameType(final V givenValue,
                                                  final String givenLabel,
                                                  final V expectedValue,
                                                  final String expectedLabel) {

        expect(expectedValue, expectedLabel)
                .not().toBeNull()
                .toBeSameType(givenValue, givenLabel)
                .check();
    }
}