CollectorTester.java

/**
 * Powerunit - A JDK1.8 test framework
 * Copyright (C) 2014 Mathieu Boretti.
 *
 * This file is part of Powerunit
 *
 * Powerunit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Powerunit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Powerunit. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.powerunit.collector;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Stream;

import org.hamcrest.Matcher;

import ch.powerunit.TestInterface;
import ch.powerunit.TestSuite;
import ch.powerunit.collector.impl.CollectorTesterImpl;
import ch.powerunit.collector.lang.CollectorTesterDSL0;
import ch.powerunit.collector.lang.CollectorTesterDSL1;
import ch.powerunit.collector.lang.CollectorTesterDSL2;
import ch.powerunit.collector.lang.CollectorTesterDSL3;

/**
 * This is a tester for {@link Collector}.
 * <p>
 * The goal of this tester is to validate a {@link Collector}. The following
 * tests are done :
 * <ol>
 * <li><code>{@link Collector#accumulator() accumulator()}</code> must return a
 * not null result.</li>
 * <li><code>{@link Collector#combiner() combiner()}</code> must return a not
 * null result.</li>
 * <li><code>{@link Collector#finisher() finisher()}</code> must return a not
 * null result.</li>
 * <li><code>{@link Collector#supplier() supplier()}</code> must return a not
 * null result.</li>
 * <li><code>{@link Collector#characteristics() characteristics()}</code> must
 * return a not null result and be the same list that is specified in this
 * tester.</li>
 * <li>For each received sample either use it -
 * {@link ch.powerunit.collector.lang.CollectorTesterDSL0#withInput(Stream)
 * withInput} - or create a stream -
 * {@link ch.powerunit.collector.lang.CollectorTesterDSL0#withStreamFromList(List)
 * withStreamFromList} or
 * {@link ch.powerunit.collector.lang.CollectorTesterDSL0#withParallelStreamFromList(List)
 * withParallelStreamFromList} - and then execute the method
 * {@link java.util.stream.Stream#collect(Collector) collect} with the
 * {@link Collector} under test and validate the returned result.</li>
 * </ol>
 * 
 * @author borettim
 * @since 0.4.0
 * @param <T>
 *            the input type of the {@link java.util.stream.Collector Collector}
 *            .
 * @param <A>
 *            the accumulator type of the {@link java.util.stream.Collector
 *            Collector}.
 * @param <R>
 *            the return type of the {@link java.util.stream.Collector
 *            Collector}.
 */
@TestInterface(CollectorTesterImpl.class)
public final class CollectorTester<T, A, R> {
	private final Collector<T, A, R> collectorToTest;

	private final List<Stream<T>> inputs;

	private final List<Matcher<? super R>> results;

	private final Matcher<Iterable<? extends Characteristics>> expectedCharacteristics;

	private CollectorTester(Collector<T, A, R> collectorToTest,
			List<Stream<T>> inputs, List<Matcher<? super R>> results,
			Matcher<Iterable<? extends Characteristics>> expectedCharacteristics) {
		this.collectorToTest = collectorToTest;
		this.inputs = inputs;
		this.results = results;
		this.expectedCharacteristics = expectedCharacteristics;
	}

	/**
	 * Return a builder to create a tester of {@link java.util.stream.Collector
	 * Collector}.
	 * 
	 * @param collectorToTest
	 *            the {@link java.util.stream.Collector Collector} to be tested.
	 * @param <T>
	 *            the input type of the {@link java.util.stream.Collector
	 *            Collector} .
	 * 
	 * @param <R>
	 *            the return type of the {@link java.util.stream.Collector
	 *            Collector}.
	 * @return {@link CollectorTesterDSL0 the DSL to build the tester}
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <T, R> CollectorTesterDSL0<T, ?, R> of(
			Collector<T, ?, R> collectorToTest) {
		return new CollectorTesterDSL(collectorToTest);
	}

	/**
	 * Return a builder to create a tester of {@link java.util.stream.Collector
	 * Collector}.
	 * 
	 * @param inputClass
	 *            the class of the input of the
	 *            {@link java.util.stream.Collector Collector}.
	 * @param outputClass
	 *            the class of the output of the
	 *            {@link java.util.stream.Collector Collector}.
	 * @param collectorToTest
	 *            the {@link java.util.stream.Collector Collector} to be tested.
	 * @param <T>
	 *            the input type of the {@link java.util.stream.Collector
	 *            Collector}.
	 * 
	 * @param <R>
	 *            the return type of the {@link java.util.stream.Collector
	 *            Collector}.
	 * @return {@link CollectorTesterDSL0 the DSL to build the tester}
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <T, R> CollectorTesterDSL0<T, ?, R> of(Class<T> inputClass,
			Class<T> outputClass, Collector<T, ?, R> collectorToTest) {
		return new CollectorTesterDSL(collectorToTest);
	}

	private static class CollectorTesterDSL<T, A, R> implements
			CollectorTesterDSL0<T, A, R>, CollectorTesterDSL1<T, A, R>,
			CollectorTesterDSL2<T, A, R> {
		private final Collector<T, A, R> collectorToTest;

		private final List<Stream<T>> inputs = new ArrayList<>();

		private final List<Matcher<? super R>> results = new ArrayList<>();

		private Matcher<Iterable<? extends Characteristics>> expectedCharacteristics;

		public CollectorTesterDSL(Collector<T, A, R> collectorToTest) {
			this.collectorToTest = collectorToTest;
		}

		@Override
		public CollectorTesterDSL3<T, A, R> havingCharacteristics(
				Characteristics... expectedCharacteristics) {
			this.expectedCharacteristics = TestSuite.DSL
					.<Collector.Characteristics> containsInAnyOrder(expectedCharacteristics);
			return this;
		}

		@Override
		public CollectorTesterDSL2<T, A, R> withInput(Stream<T> input) {
			inputs.add(Objects.requireNonNull(input, "input can't be null"));
			return this;
		}

		@Override
		public CollectorTesterDSL2<T, A, R> withStreamFromList(List<T> input) {
			return withInput(Objects.requireNonNull(input,
					"input can't be null").stream());
		}

		@Override
		public CollectorTesterDSL2<T, A, R> withParallelStreamFromList(
				List<T> input) {
			return withInput(Objects.requireNonNull(input,
					"input can't be null").parallelStream());
		}

		@Override
		public CollectorTesterDSL2<T, A, R> withStreamFromArray(
				@SuppressWarnings("unchecked") T... input) {
			return withInput(Arrays.stream(input));
		}

		@SuppressWarnings("unchecked")
		@Override
		public CollectorTesterDSL2<T, A, R> withStreamFromArray(T first) {
			return withStreamFromArray((T[]) new Object[] { first });
		}

		@SuppressWarnings("unchecked")
		@Override
		public CollectorTesterDSL2<T, A, R> withStreamFromArray(T first,
				T second) {
			return withStreamFromArray((T[]) new Object[] { first, second });
		}

		@SuppressWarnings("unchecked")
		@Override
		public CollectorTesterDSL2<T, A, R> withStreamFromArray(T first,
				T second, T third) {
			return withStreamFromArray((T[]) new Object[] { first, second,
					third });
		}

		@SuppressWarnings("unchecked")
		@Override
		public CollectorTesterDSL2<T, A, R> withStreamFromArray(T first,
				T second, T third, T fourth) {
			return withStreamFromArray((T[]) new Object[] { first, second,
					third, fourth });
		}

		@SuppressWarnings("unchecked")
		@Override
		public CollectorTesterDSL2<T, A, R> withStreamFromArray(T first,
				T second, T third, T fourth, T fifth) {
			return withStreamFromArray((T[]) new Object[] { first, second,
					third, fourth, fifth });
		}

		@Override
		public CollectorTesterDSL1<T, A, R> expecting(
				Matcher<? super R> matching) {
			results.add(Objects.requireNonNull(matching,
					"matching can't be null"));
			return this;
		}

		@Override
		public CollectorTesterDSL1<T, A, R> expecting(R value) {
			return expecting(TestSuite.DSL.equalTo(value));
		}

		@Override
		public CollectorTesterDSL1<T, A, R> expectingNull() {
			return expecting(TestSuite.DSL.nullValue());
		}

		@Override
		public CollectorTester<T, A, R> build() {
			return new CollectorTester<T, A, R>(collectorToTest, inputs,
					results,
					expectedCharacteristics == null ? TestSuite.DSL
							.emptyIterable() : expectedCharacteristics);
		}

	}

	/**
	 * Used by the framework.
	 * 
	 * @return the collectorToTest
	 */
	public Collector<T, A, R> getCollectorToTest() {
		return collectorToTest;
	}

	/**
	 * Used by the framework.
	 * 
	 * @return the inputs
	 */
	public List<Stream<T>> getInputs() {
		return Collections.unmodifiableList(inputs);
	}

	/**
	 * Used by the framework.
	 * 
	 * @return the results
	 */
	public List<Matcher<? super R>> getResults() {
		return Collections.unmodifiableList(results);
	}

	/**
	 * Used by the framework.
	 * 
	 * @return the expectedCharacteristics
	 */
	public Matcher<Iterable<? extends Characteristics>> getExpectedCharacteristics() {
		return expectedCharacteristics;
	}

}