fr.landel.utils.assertor.utils.AssertorIterable.java Source code

Java tutorial

Introduction

Here is the source code for fr.landel.utils.assertor.utils.AssertorIterable.java

Source

/*-
 * #%L
 * utils-assertor
 * %%
 * Copyright (C) 2016 - 2018 Gilles Landel
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package fr.landel.utils.assertor.utils;

import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.commons.collections4.IterableUtils;

import fr.landel.utils.assertor.StepAssertor;
import fr.landel.utils.assertor.commons.ConstantsAssertor;
import fr.landel.utils.assertor.commons.MessageAssertor;
import fr.landel.utils.assertor.commons.ParameterAssertor;
import fr.landel.utils.assertor.enums.EnumAnalysisMode;
import fr.landel.utils.assertor.enums.EnumType;
import fr.landel.utils.assertor.helper.HelperAssertor;

/**
 * Utility class to prepare the check of {@link Iterable}
 *
 * @since Aug 10, 2016
 * @author Gilles
 *
 */
public class AssertorIterable extends ConstantsAssertor {

    /**
     * Prepare the next step to validate that the {@link Iterable} size is equal
     * to {@code size}.
     * 
     * <p>
     * precondition: {@link Iterable} cannot be {@code null} and size cannot be
     * lower than zero
     * </p>
     * 
     * @param step
     *            the current step
     * @param size
     *            the size to validate
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> hasSize(final StepAssertor<I> step, final int size,
            final MessageAssertor message) {

        final BiPredicate<I, Boolean> checker = (iterable, not) -> IterableUtils.size(iterable) == size;

        return checkSize(step, size, checker, MSG.ITERABLE.SIZE, message);
    }

    /**
     * Prepare the next step to validate that the {@link Iterable} size is
     * greater than {@code size}.
     * 
     * <p>
     * precondition: {@link Iterable} cannot be {@code null} and size cannot be
     * lower than zero
     * </p>
     * 
     * @param step
     *            the current step
     * @param size
     *            the size to validate
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> hasSizeGT(final StepAssertor<I> step, final int size,
            final MessageAssertor message) {

        final BiPredicate<I, Boolean> checker = (iterable, not) -> IterableUtils.size(iterable) > size;

        return checkSize(step, size, checker, MSG.ITERABLE.SIZE_GT, message);
    }

    /**
     * Prepare the next step to validate that the {@link Iterable} size is
     * greater than or equal to {@code size}.
     * 
     * <p>
     * precondition: {@link Iterable} cannot be {@code null} and size cannot be
     * lower than zero
     * </p>
     * 
     * @param step
     *            the current step
     * @param size
     *            the size to validate
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> hasSizeGTE(final StepAssertor<I> step, final int size,
            final MessageAssertor message) {

        final BiPredicate<I, Boolean> checker = (iterable, not) -> IterableUtils.size(iterable) >= size;

        return checkSize(step, size, checker, MSG.ITERABLE.SIZE_GTE, message);
    }

    /**
     * Prepare the next step to validate that the {@link Iterable} size is lower
     * than {@code size}.
     * 
     * <p>
     * precondition: {@link Iterable} cannot be {@code null} and size cannot be
     * lower than zero
     * </p>
     * 
     * @param step
     *            the current step
     * @param size
     *            the size to validate
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> hasSizeLT(final StepAssertor<I> step, final int size,
            final MessageAssertor message) {

        final BiPredicate<I, Boolean> checker = (iterable, not) -> IterableUtils.size(iterable) < size;

        return checkSize(step, size, checker, MSG.ITERABLE.SIZE_LT, message);
    }

    /**
     * Prepare the next step to validate that the {@link Iterable} size is lower
     * than or equal to {@code size}.
     * 
     * <p>
     * precondition: {@link Iterable} cannot be {@code null} and size cannot be
     * lower than zero
     * </p>
     * 
     * @param step
     *            the current step
     * @param size
     *            the size to validate
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> hasSizeLTE(final StepAssertor<I> step, final int size,
            final MessageAssertor message) {

        final BiPredicate<I, Boolean> checker = (iterable, not) -> IterableUtils.size(iterable) <= size;

        return checkSize(step, size, checker, MSG.ITERABLE.SIZE_LTE, message);
    }

    private static <I extends Iterable<T>, T> StepAssertor<I> checkSize(final StepAssertor<I> step, final int size,
            final BiPredicate<I, Boolean> checker, final String messageKey, final MessageAssertor message) {

        final Predicate<I> preChecker = (iterable) -> size >= 0 && iterable != null;

        return new StepAssertor<>(step, preChecker, checker, false, message, messageKey, false,
                new ParameterAssertor<>(size, EnumType.NUMBER_INTEGER, false));
    }

    /**
     * Prepare the next step to validate if the {@link Iterable} is {@code null}
     * or empty
     * 
     * <p>
     * precondition: none
     * </p>
     * 
     * @param step
     *            the current step
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> isEmpty(final StepAssertor<I> step,
            final MessageAssertor message) {

        final BiPredicate<I, Boolean> checker = (iterable, not) -> IterableUtils.isEmpty(iterable);

        return new StepAssertor<>(step, checker, false, message, MSG.ITERABLE.EMPTY, false);
    }

    /**
     * Prepare the next step to validate if the {@link Iterable} is NOT
     * {@code null} and NOT empty
     * 
     * <p>
     * precondition: none
     * </p>
     * 
     * @param step
     *            the current step
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> isNotEmpty(final StepAssertor<I> step,
            final MessageAssertor message) {

        final BiPredicate<I, Boolean> checker = (iterable, not) -> !IterableUtils.isEmpty(iterable);

        return new StepAssertor<>(step, checker, false, message, MSG.ITERABLE.EMPTY, true);
    }

    /**
     * Prepare the next step to validate if all {@link Iterable} elements match
     * the predicate.
     * 
     * <p>
     * precondition: {@link Iterable} cannot be {@code null} or empty and
     * {@code predicate} cannot be {@code null}
     * </p>
     * 
     * @param step
     *            the current step
     * @param predicate
     *            the predicate used to check each element
     * @param message
     *            the message on predicate failed
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> allMatch(final StepAssertor<I> step,
            final Predicate<T> predicate, final MessageAssertor message) {

        return match(step, predicate, true, MSG.ITERABLE.MATCH_ALL, message);
    }

    /**
     * Prepare the next step to validate if any {@link Iterable} element matches
     * the predicate.
     * 
     * <p>
     * precondition: {@link Iterable} cannot be {@code null} or empty and
     * {@code predicate} cannot be {@code null}
     * </p>
     * 
     * @param step
     *            the current step
     * @param predicate
     *            the predicate used to check each element
     * @param message
     *            the message on predicate failed
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> anyMatch(final StepAssertor<I> step,
            final Predicate<T> predicate, final MessageAssertor message) {

        return match(step, predicate, false, MSG.ITERABLE.MATCH_ANY, message);
    }

    private static <I extends Iterable<T>, T> StepAssertor<I> match(final StepAssertor<I> step,
            final Predicate<T> predicate, final boolean all, final String messageKey,
            final MessageAssertor message) {

        final Predicate<I> preChecker = (iterable) -> !IterableUtils.isEmpty(iterable) && predicate != null;

        final BiPredicate<I, Boolean> checker = (iterable, not) -> AssertorIterable.has(iterable, predicate, all,
                step.getAnalysisMode());

        return new StepAssertor<>(step, preChecker, checker, false, message, messageKey, false,
                new ParameterAssertor<>(predicate, EnumType.UNKNOWN));
    }

    /**
     * Prepare the next step to validate if the {@link Iterable} contains the
     * specified value
     * 
     * <p>
     * precondition: the {@link Iterable} cannot be {@code null} or empty
     * </p>
     * 
     * @param step
     *            the current step
     * @param value
     *            the value to find
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> contains(final StepAssertor<I> step, final T value,
            final MessageAssertor message) {

        final Predicate<I> preChecker = (iterable) -> !IterableUtils.isEmpty(iterable);

        final BiPredicate<I, Boolean> checker = (iterable, not) -> AssertorIterable.has(iterable, value,
                step.getAnalysisMode());

        return new StepAssertor<>(step, preChecker, checker, false, message, MSG.ITERABLE.CONTAINS_OBJECT, false,
                new ParameterAssertor<>(value));
    }

    private static <I extends Iterable<T>, T> StepAssertor<I> contains(final StepAssertor<I> step,
            final Iterable<T> iterable, final CharSequence key, final boolean all, final MessageAssertor message) {

        final Predicate<I> preChecker = (iterable1) -> !IterableUtils.isEmpty(iterable1)
                && !IterableUtils.isEmpty(iterable);

        final BiPredicate<I, Boolean> checker = (iterable1, not) -> AssertorIterable.has(iterable1, iterable, all,
                not, step.getAnalysisMode());

        return new StepAssertor<>(step, preChecker, checker, true, message, key, false,
                new ParameterAssertor<>(iterable, EnumType.ITERABLE));
    }

    /**
     * Prepare the next step to validate if the {@link Iterable} contains all
     * values
     * 
     * <p>
     * precondition: neither {@link Iterable} can be {@code null} or empty
     * </p>
     * 
     * @param step
     *            the current step
     * @param values
     *            the values to find
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> containsAll(final StepAssertor<I> step,
            final Iterable<T> values, final MessageAssertor message) {

        return contains(step, values, MSG.ITERABLE.CONTAINS_ALL, true, message);
    }

    /**
     * Prepare the next step to validate if the {@link Iterable} contains any
     * values
     * 
     * <p>
     * precondition: neither {@link Iterable} can be {@code null} or empty
     * </p>
     * 
     * @param step
     *            the current step
     * @param values
     *            the values to find
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> containsAny(final StepAssertor<I> step,
            final Iterable<T> values, final MessageAssertor message) {

        return contains(step, values, MSG.ITERABLE.CONTAINS_ANY, false, message);
    }

    /**
     * Prepare the next step to validate if the {@link Iterable} contains values
     * in a specified order
     * 
     * <p>
     * precondition: neither {@link Iterable} can be {@code null} or empty
     * </p>
     * 
     * @param step
     *            the current step
     * @param values
     *            the values to find
     * @param message
     *            the message if invalid
     * @param <I>
     *            the {@link Iterable} type
     * @param <T>
     *            the {@link Iterable} elements type
     * @return the next step
     */
    public static <I extends Iterable<T>, T> StepAssertor<I> containsInOrder(final StepAssertor<I> step,
            final Iterable<T> values, final MessageAssertor message) {

        final Predicate<I> preChecker = (iterable1) -> !IterableUtils.isEmpty(iterable1)
                && !IterableUtils.isEmpty(values);

        final BiPredicate<I, Boolean> checker = (iterable1, not) -> AssertorIterable.hasInOrder(iterable1, values,
                not, step.getAnalysisMode());

        return new StepAssertor<>(step, preChecker, checker, true, message, MSG.ITERABLE.CONTAINS_IN_ORDER, false,
                new ParameterAssertor<>(values, EnumType.ITERABLE));
    }

    private static <I extends Iterable<T>, T> boolean has(final I iterable, final T object,
            final EnumAnalysisMode analysisMode) {
        final Predicate<T> predicate;
        if (object != null) {
            predicate = object::equals;
        } else {
            predicate = Objects::isNull;
        }

        return has(iterable, predicate, false, analysisMode);
    }

    private static <I extends Iterable<T>, T> boolean has(final I iterable, final Predicate<T> predicate,
            final boolean all, final EnumAnalysisMode analysisMode) {
        if (EnumAnalysisMode.STANDARD.equals(analysisMode)) {
            if (all) {
                for (final T object : iterable) {
                    if (!predicate.test(object)) {
                        return false;
                    }
                }
                return true;
            } else {
                for (final T object : iterable) {
                    if (predicate.test(object)) {
                        return true;
                    }
                }
                return false;
            }
        } else {
            final Stream<T> stream = StreamSupport.stream(iterable.spliterator(),
                    EnumAnalysisMode.PARALLEL.equals(analysisMode));
            if (all) {
                return stream.allMatch(predicate);
            } else {
                return stream.anyMatch(predicate);
            }
        }
    }

    private static <I extends Iterable<T>, T> boolean has(final I iterable1, final Iterable<T> iterable2,
            final boolean all, final boolean not, final EnumAnalysisMode analysisMode) {

        final int size2 = IterableUtils.size(iterable2);

        if (all && !not && size2 > IterableUtils.size(iterable1)) {
            return false;
        }

        if (EnumAnalysisMode.STANDARD.equals(analysisMode)) {
            if (all && !not) {
                for (final T objectRef : iterable2) {
                    if (!AssertorIterable.has(iterable1, objectRef, analysisMode)) {
                        return false;
                    }
                }
                return true;
            } else if (!all) { // any and not any
                for (final T objectRef : iterable2) {
                    if (AssertorIterable.has(iterable1, objectRef, analysisMode)) {
                        return !not;
                    }
                }
                return not;
            } else { // not all
                long found = 0;
                for (final T objectRef : iterable2) {
                    if (AssertorIterable.has(iterable1, objectRef, analysisMode)) {
                        ++found;
                    }
                }
                return HelperAssertor.isValid(all, not, found, size2);
            }

        } else {
            return HelperAssertor.isValid(
                    StreamSupport.stream(iterable2.spliterator(), EnumAnalysisMode.PARALLEL.equals(analysisMode)),
                    o -> AssertorIterable.has(iterable1, o, analysisMode), all, not, () -> size2);
        }
    }

    private static <I extends Iterable<T>, T> boolean hasInOrder(final I iterable1, final Iterable<T> iterable2,
            final boolean not, final EnumAnalysisMode analysisMode) {

        long found = 0;
        final int size1 = IterableUtils.size(iterable1);
        final int size2 = IterableUtils.size(iterable2);

        if (size1 < size2) {
            return not;
        } else if (size1 == size2) {
            return not ^ iterable1.equals(iterable2);
        }

        if (EnumAnalysisMode.STANDARD.equals(analysisMode)) {
            final Iterator<T> iterator1 = iterable1.iterator();
            Iterator<T> iterator2 = iterable2.iterator();

            // not empty pre-check, so we call next directly
            T value2 = iterator2.next();
            while (iterator1.hasNext() && found < size2) {
                if (Objects.equals(iterator1.next(), value2)) {
                    ++found;
                    if (iterator2.hasNext()) {
                        value2 = iterator2.next();
                    }
                } else if (found > 0) {
                    found = 0;
                    iterator2 = iterable2.iterator();
                    value2 = iterator2.next();
                }
            }
        } else {
            final AtomicInteger count = new AtomicInteger(0);

            final List<T> list2 = IterableUtils.toList(iterable2);

            StreamSupport.stream(iterable1.spliterator(), EnumAnalysisMode.PARALLEL.equals(analysisMode))
                    .forEachOrdered(o -> {
                        int inc = count.get();
                        if (inc < size2) {
                            if (Objects.equals(o, list2.get(inc))) {
                                count.incrementAndGet();
                            } else if (inc > 0) {
                                count.set(0);
                            }
                        }
                    });

            found = count.get();
        }

        return not ^ (found == size2);
    }
}