
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