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

io.github.theangrydev.fluentbdd.assertjgenerator.JavaEmitter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 Liam Williams .
 *
 * This file is part of fluent-bdd.
 *
 * 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.
 */
package io.github.theangrydev.fluentbdd.assertjgenerator;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseException;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.TypeParameter;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.squareup.javapoet.*;
import io.github.theangrydev.fluentbdd.core.WithFluentBdd;
import org.assertj.core.api.WithAssertions;

import java.io.InputStream;
import java.util.List;

import static io.github.theangrydev.fluentbdd.assertjgenerator.SuppressWarningsAnnotation.suppressWarnings;
import static io.github.theangrydev.fluentbdd.assertjgenerator.TypeNameDetermination.typeNameDetermination;
import static java.util.stream.Collectors.toList;
import static javax.lang.model.element.Modifier.*;

// TODO: https://github.com/theangrydev/fluent-bdd/issues/14 remove PMD suppression
@SuppressWarnings("PMD.TooManyMethods")
public class JavaEmitter {
    private static final String ASSERTJ_API_PACKAGE = WithAssertions.class.getPackage().getName();
    private static final String ASSERTJ_ASSERTIONS_JAVA_FILE = WithAssertions.class.getSimpleName() + ".java";
    private static final String ASSERTJ_ASSERTIONS_JAVA_FILE_RESOURCE_PATH = ASSERTJ_API_PACKAGE.replace('.', '/') + "/" + ASSERTJ_ASSERTIONS_JAVA_FILE;
    private static final String ASSERTJ_ASSERTIONS_JAVA_FILE_INCLUDING_PACKAGE = ASSERTJ_API_PACKAGE + "." + ASSERTJ_ASSERTIONS_JAVA_FILE;

    private static final String OUTPUT_CLASS_NAME = "WithFluentAssertJ";
    private static final String ASSERT_THAT_METHOD_PREFIX = "assertThat";
    private static final String THEN_METHOD_PREFIX = "then";
    private static final String AND_THEN_METHOD_PREFIX = "and";
    private static final String FLUENT_BDD = "fluent-bdd";

    private static final String MODIFICATION_DESCRIPTION =
            "This file was generated by the assertj-extensions-generator module of " + FLUENT_BDD + " using the " + ASSERTJ_ASSERTIONS_JAVA_FILE_INCLUDING_PACKAGE + " source code.\n" +
            "The original documentation from " + ASSERTJ_ASSERTIONS_JAVA_FILE + " has been preserved.\n" +
            "The modifications involve renaming '" + ASSERT_THAT_METHOD_PREFIX + "' methods to '" + THEN_METHOD_PREFIX + "' and '" + AND_THEN_METHOD_PREFIX + "' to better match the language used in " + FLUENT_BDD + ".\n";

    private static final String DELEGATE_WITH_ASSERTIONS_CLASS_NAME = "DelegateWithAssertions";
    private static final String NEW_DELEGATE_WITH_ASSERTIONS = "new " + DELEGATE_WITH_ASSERTIONS_CLASS_NAME + "()";
    private static final String DELEGATE_FIELD_NAME = "DELEGATE";
    private static final String SUPPRESS_WARNINGS_UNCHECKED_STRING = "@" + SuppressWarnings.class.getSimpleName() + "(\"unchecked\")";
    private static final String TEST_RESULT_TYPE_NAME = "TestResult";

    private static final SuppressWarnings SUPPRESS_WARNINGS_UNCHECKED = suppressWarnings("unchecked");
    private static final SuppressWarnings SUPPRESS_WARNINGS_PMD = suppressWarnings("PMD");

    private final ThenMethodCodeEmitter thenMethodCodeEmitter;
    private final JavadocEmitter javadocEmitter;
    private final CompilationUnit compilationUnit;

    private JavaEmitter(ThenMethodCodeEmitter thenMethodCodeEmitter, JavadocEmitter javadocEmitter, CompilationUnit compilationUnit) {
        this.thenMethodCodeEmitter = thenMethodCodeEmitter;
        this.javadocEmitter = javadocEmitter;
        this.compilationUnit = compilationUnit;
    }

    public static JavaEmitter javaEmitter(){
        InputStream assertionsSource = JavaEmitter.class.getClassLoader().getResourceAsStream(ASSERTJ_ASSERTIONS_JAVA_FILE_RESOURCE_PATH);
        CompilationUnit compilationUnit = parse(assertionsSource);
        JavadocEmitter javadocEmitter = new JavadocEmitter(ASSERT_THAT_METHOD_PREFIX);
        ThenMethodCodeEmitter thenMethodCodeEmitter = new ThenMethodCodeEmitter(DELEGATE_FIELD_NAME);
        return new JavaEmitter(thenMethodCodeEmitter, javadocEmitter, compilationUnit);
    }

    public JavaFile delegateWithAssertions(String outputPackage) {
        return JavaFile.builder(outputPackage, delegateWithAssertionsTypeSpec())
                .indent("\t")
                .build();
    }

    public JavaFile withFluentAssertJ(String outputPackage) throws ClassNotFoundException {
        return JavaFile.builder(outputPackage, withFluentAssertJTypeSpec(outputPackage, compilationUnit))
                .indent("\t")
                .skipJavaLangImports(true)
                .addFileComment(javadocEmitter.javadoc(compilationUnit.getComment().getContent(), THEN_METHOD_PREFIX))
                .build();
    }

    private static CompilationUnit parse(InputStream assertionsSource) {
        try {
            return JavaParser.parse(assertionsSource);
        } catch (ParseException parseException) {
            throw new IllegalStateException(parseException);
        }
    }
    private TypeSpec delegateWithAssertionsTypeSpec() {
        return TypeSpec.classBuilder(DELEGATE_WITH_ASSERTIONS_CLASS_NAME)
                .addModifiers(PUBLIC)
                .addSuperinterface(WithAssertions.class)
                .build();
    }

    private TypeSpec withFluentAssertJTypeSpec(String outputPackage, CompilationUnit compilationUnit) throws ClassNotFoundException {
        FieldSpec delegateField = FieldSpec.builder(
                ClassName.get(outputPackage, DELEGATE_WITH_ASSERTIONS_CLASS_NAME), DELEGATE_FIELD_NAME, PUBLIC, STATIC, FINAL)
                .initializer(NEW_DELEGATE_WITH_ASSERTIONS)
                .build();

        TypeDeclaration typeDeclaration = compilationUnit.getTypes().get(0);

        TypeVariableName testResult = TypeVariableName.get(TEST_RESULT_TYPE_NAME);
        TypeSpec.Builder builder = TypeSpec.interfaceBuilder(OUTPUT_CLASS_NAME)
                .addTypeVariable(testResult)
                .addAnnotation(AnnotationSpec.get(SUPPRESS_WARNINGS_PMD))
                .addModifiers(PUBLIC)
                .addSuperinterface(ParameterizedTypeName.get(ClassName.get(WithFluentBdd.class), testResult))
                .addField(delegateField)
                .addJavadoc(MODIFICATION_DESCRIPTION)
                .addJavadoc(javadocEmitter.javadoc(typeDeclaration.getJavaDoc().getContent(), THEN_METHOD_PREFIX));

        for (MethodDeclaration methodDeclaration : methodDeclarations(typeDeclaration)) {
            if (methodDeclaration.getName().startsWith(ASSERT_THAT_METHOD_PREFIX)) {
                builder.addMethod(methodSpec(methodDeclaration, THEN_METHOD_PREFIX));
                builder.addMethod(methodSpec(methodDeclaration, AND_THEN_METHOD_PREFIX));
            } else {
                builder.addMethod(methodSpec(methodDeclaration, ""));
            }
        }
        return builder.build();
    }

    private MethodSpec methodSpec(MethodDeclaration methodDeclaration, String thenMethodPrefix) throws ClassNotFoundException {
        PackageNameByClassName packageNames = PackageNameByClassName.packageNameByClassName(compilationUnit, ASSERTJ_API_PACKAGE);

        List rawTypeVariableNames = methodDeclaration.getTypeParameters().stream()
                .map(typeParameter -> TypeVariableName.get(typeParameter.getName()))
                .collect(toList());
        TypeNameDetermination typeNameDetermination = typeNameDetermination(rawTypeVariableNames, packageNames);

        List boundTypeVariableNames = methodDeclaration.getTypeParameters().stream()
                .map(typeParameter -> typeVariableName(typeParameter, typeNameDetermination))
                .collect(toList());

        TypeName returnTypeName = typeNameDetermination.determineTypeName(methodDeclaration.getType());
        String methodName = methodDeclaration.getName().replace(ASSERT_THAT_METHOD_PREFIX, thenMethodPrefix);
        MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
                .addModifiers(PUBLIC, DEFAULT)
                .addCode(thenMethodCodeEmitter.code(methodDeclaration, thenMethodPrefix))
                .addJavadoc(javadocEmitter.javadoc(thenMethodPrefix, methodDeclaration.getJavaDoc()))
                .returns(returnTypeName);

        boundTypeVariableNames.forEach(builder::addTypeVariable);

        if (methodDeclaration.getParameters().stream().anyMatch(this::isSuppressWarningsUnchecked)) {
            builder.addAnnotation(AnnotationSpec.get(SUPPRESS_WARNINGS_UNCHECKED));
        }

        for (Parameter parameter : methodDeclaration.getParameters()) {
            builder.addParameter(typeNameDetermination.determineTypeName(parameter.getType()), parameter.getName());
        }
        return builder.build();
    }

    private boolean isSuppressWarningsUnchecked(Parameter parameter) {
        return parameter.getAnnotations().stream().anyMatch(annotation -> annotation.toString().equals(SUPPRESS_WARNINGS_UNCHECKED_STRING));
    }

    private TypeVariableName typeVariableName(TypeParameter typeParameter, TypeNameDetermination typeNameDetermination) {
        List typeBounds = typeParameter.getTypeBound();
        TypeName[] typeNames = typeBounds.stream()
                .map(typeNameDetermination::determineTypeName)
                .toArray(TypeName[]::new);

        return TypeVariableName.get(typeParameter.getName(), typeNames);
    }

    private List methodDeclarations(TypeDeclaration typeDeclaration) {
        return typeDeclaration.getMembers().stream()
                .filter(MethodDeclaration.class::isInstance)
                .map(MethodDeclaration.class::cast)
                .collect(toList());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy