ProvidesMatchersAnnotatedElementMirror.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.extensions.matchers.provideprocessor;

import static ch.powerunit.extensions.matchers.common.CommonUtils.addPrefix;
import static ch.powerunit.extensions.matchers.common.CommonUtils.generateGeneratedAnnotation;
import static ch.powerunit.extensions.matchers.common.CommonUtils.traceErrorAndDump;
import static ch.powerunit.extensions.matchers.common.FileObjectHelper.processFileWithIOExceptionAndResult;
import static ch.powerunit.extensions.matchers.provideprocessor.dsl.DSLMethod.of;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import ch.powerunit.extensions.matchers.common.ListJoining;
import ch.powerunit.extensions.matchers.common.RessourceLoaderHelper;
import ch.powerunit.extensions.matchers.provideprocessor.dsl.DSLMethod;
import ch.powerunit.extensions.matchers.provideprocessor.helper.ProvidesMatchersWithSameValueHelper;

public class ProvidesMatchersAnnotatedElementMirror extends ProvidesMatchersAnnotatedElementMatcherMirror {

	private final Collection<Supplier<Collection<DSLMethod>>> dslProvider;

	private static final String MATCHER_FORMAT = RessourceLoaderHelper
			.loadRessource(ProvidesMatchersAnnotatedElementMirror.class, "Matchers.txt");

	private static final String POSTPROCESSOR_FORMAT = addPrefix("  ",
			RessourceLoaderHelper.loadRessource(ProvidesMatchersAnnotatedElementMirror.class, "PostProcessor.txt"));

	private static final ListJoining<DSLMethod> JOIN_DSL_METHOD = ListJoining
			.joinWithMapperAndDelimiter(m -> addPrefix("  ", m.asStaticImplementation()), "\n");

	private static Supplier<Collection<DSLMethod>> asCollection(Supplier<DSLMethod> input) {
		return () -> singletonList(input.get());
	}

	public ProvidesMatchersAnnotatedElementMirror(TypeElement typeElement, RoundMirror roundMirror) {
		super(typeElement, roundMirror);
		List<Supplier<Collection<DSLMethod>>> tmp = new ArrayList<>(Arrays.asList(
				asCollection(this::generateDefaultDSLStarter), asCollection(this::generateDefaultForChainingDSLStarter),
				asCollection(this::generateMatcherClassMethod)));
		if (hasSuperClass()) {
			tmp.add(asCollection(this::generateParentDSLStarter));
			tmp.add(() -> ProvidesMatchersWithSameValueHelper.generateParentValueDSLStarter(this));
			if (((TypeElement) roundMirror.getTypeUtils().asElement(element.getSuperclass())).getTypeParameters()
					.isEmpty()) {
				tmp.add(asCollection(this::generateParentInSameRoundWithChaningDSLStarter));
			}
		} else {
			tmp.add(() -> ProvidesMatchersWithSameValueHelper.generateNoParentValueDSLStarter(this));
		}
		tmp.addAll(ofNullable(getDSLExtension()).orElseGet(Collections::emptyList).stream()
				.map(t -> t.getDSLMethodFor(() -> this)).flatMap(Collection::stream)
				.map(ProvidesMatchersAnnotatedElementMirror::asCollection).collect(toList()));
		tmp.addAll(roundMirror.getDSLMethodFor(() -> this).stream()
				.map(ProvidesMatchersAnnotatedElementMirror::asCollection).collect(toList()));
		this.dslProvider = unmodifiableList(tmp);
	}

	public Collection<DSLMethod> process() {
		Element te = element;
		return processFileWithIOExceptionAndResult(
				() -> getFiler().createSourceFile(getFullyQualifiedNameOfGeneratedClass(), te),
				jfo -> new Formatter(new PrintWriter(jfo.openWriter())), output -> {
					Collection<DSLMethod> tmp = generateDSLStarter();
					output.format(MATCHER_FORMAT, getPackageNameOfGeneratedClass(),
							getFullyQualifiedNameOfClassAnnotated(),
							generateGeneratedAnnotation(ProvidesMatchersAnnotationsProcessor.class, comments()),
							getSimpleNameOfGeneratedClass(), generateMatchers(), generatePublicInterface(),
							generatePrivateImplementation(), JOIN_DSL_METHOD.asString(tmp), POSTPROCESSOR_FORMAT,
							generateMetadata());
					output.flush();
					return tmp;
				}, e -> traceErrorAndDump(this, e, te));
	}

	public Collection<DSLMethod> generateDSLStarter() {
		return dslProvider.stream().map(Supplier::get).filter(Objects::nonNull).flatMap(Collection::stream)
				.filter(Objects::nonNull).collect(toList());
	}

	public String getDefaultStarterBody(boolean withParentBuilder) {
		String targetImpl = withParentBuilder ? getSimpleNameOfGeneratedImplementationMatcherWithGenericParent()
				: getSimpleNameOfGeneratedImplementationMatcherWithGenericNoParent();
		boolean withSuper = hasSuperClass();
		return "return new " + targetImpl + "(" + (withSuper ? "org.hamcrest.Matchers.anything()" : "")
				+ (withSuper && withParentBuilder ? "," : "") + (withParentBuilder ? "parentBuilder" : "") + ");";
	}

	public String generateDefaultJavaDocWithDSLStarter(Optional<String> param, String returnDescription,
			boolean withParent) {
		return generateDefaultJavaDoc(Optional.of(getJavadocForDSLStarter()), param, returnDescription, withParent);
	}

	public String generateDefaultJavaDocWithoutDSLStarter(Optional<String> param, String returnDescription,
			boolean withParent) {
		return generateDefaultJavaDoc(empty(), param, returnDescription, withParent);
	}

	public DSLMethod generateDefaultDSLStarter() {
		return of(fullGeneric + " " + getFullyQualifiedNameOfGeneratedClass() + "."
				+ getSimpleNameOfGeneratedInterfaceMatcherWithGenericNoParent() + " " + methodShortClassName + "With")
						.withImplementation(getDefaultStarterBody(false))
						.withJavadoc(generateDefaultJavaDocWithDSLStarter(empty(), "the DSL matcher", false));
	}

	public String getJavadocForDSLStarter() {
		return "The returned builder (which is also a Matcher), at this point accepts any object that is a "
				+ getDefaultLinkForAnnotatedClass() + ".";
	}

	public DSLMethod generateDefaultForChainingDSLStarter() {
		return of(getFullGenericParent() + " " + getFullyQualifiedNameOfGeneratedClass() + "."
				+ getSimpleNameOfGeneratedInterfaceMatcherWithGenericParent() + " " + getMethodNameDSLWithParent())
						.withOneArgument("_PARENT", "parentBuilder").withImplementation(getDefaultStarterBody(true))
						.withJavadoc(generateDefaultJavaDocWithDSLStarter(
								Optional.of("parentBuilder the parentBuilder."), "the DSL matcher", true));
	}

	public DSLMethod generateParentDSLStarter() {
		String mscn = methodShortClassName;
		String fqngc = getFullyQualifiedNameOfGeneratedClass();
		return of(fullGeneric + " " + fqngc + "." + getSimpleNameOfGeneratedInterfaceMatcherWithGenericNoParent() + " "
				+ mscn + "With").withOneArgument(
						"org.hamcrest.Matcher<? super " + fullyQualifiedNameOfSuperClassOfClassAnnotated.get() + ">",
						"matcherOnParent")
						.withImplementation(
								"return new " + getSimpleNameOfGeneratedImplementationMatcherWithGenericNoParent()
										+ "(matcherOnParent);")
						.withJavadoc(generateDefaultJavaDocWithoutDSLStarter(
								Optional.of("matcherOnParent the matcher on the parent data."), "the DSL matcher",
								false));
	}

	public DSLMethod generateParentInSameRoundWithChaningDSLStarter() {
		String implGenericNoParent = getSimpleNameOfGeneratedImplementationMatcherWithGenericNoParent();
		return getParentMirror().map(parentMirror -> {
			String pmfqngc = parentMirror.getFullyQualifiedNameOfGeneratedClass();
			String parentSimpleName = parentMirror.getSimpleNameOfGeneratedInterfaceMatcher();
			return of(fullGeneric + " " + pmfqngc + "." + parentSimpleName + genericForChaining + " "
					+ getMethodNameDSLWithParent())
							.withImplementation(
									implGenericNoParent + " m=new " + implGenericNoParent
											+ "(org.hamcrest.Matchers.anything());",
									pmfqngc + "." + parentSimpleName + " tmp = " + pmfqngc + "."
											+ parentMirror.getMethodNameDSLWithParent() + "(m);",
									"m._parent = new SuperClassMatcher(tmp);", "return tmp;")
							.withJavadoc(generateDefaultJavaDoc(empty(), empty(), "the DSL matcher", false));
		}).orElse(null);
	}

	public DSLMethod generateMatcherClassMethod() {
		return of(getFullGenericParent()+" Class<" + getFullyQualifiedNameOfGeneratedClass() + "."
				+ getSimpleNameOfGeneratedInterfaceMatcherWithGenericParent() + "> "
				+ getMethodShortClassName() + "MatcherClass").withoutArgument()
						.withImplementation("return (Class)" + getSimpleNameOfGeneratedInterfaceMatcher() + ".class;")
						.withJavadoc(
								"/**\n * Helper method to retrieve the Class of the matcher interface.\n * @return the class.\n */\n");
	}

}