From 3d0a7eb6477d1b83acf143472c4b2ae46e1a2392 Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Fri, 14 Jan 2022 14:19:38 +0100 Subject: [PATCH 1/4] #194: Make the Generated annotation configurable. You can now choose between javax.annotation.Generated and jakarta.annotation.Generated. Or if you really do not want any no annotation (not recommended). --- pom.xml | 6 + .../generator/BaseAssertionGenerator.java | 1607 +++++++++-------- .../generator/GeneratedAnnotationSource.java | 19 + ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ...stom_abstract_assertion_class_template.txt | 2 +- .../custom_assertion_class_template.txt | 2 +- ..._hierarchical_assertion_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- .../AssertionsEntryPointGeneratorTest.java | 2 +- .../generator/GenerationPathHandler.java | 2 +- .../my_assertion_entry_point_class.txt | 2 +- 16 files changed, 871 insertions(+), 787 deletions(-) create mode 100644 src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java diff --git a/pom.xml b/pom.xml index b39bee3..77dfb55 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,12 @@ 4.11 test + + javax.annotation + javax.annotation-api + 1.3.2 + test + commons-io diff --git a/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java b/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java index ca96069..d103343 100644 --- a/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java +++ b/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java @@ -20,6 +20,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.remove; import static org.apache.commons.lang3.StringUtils.replace; +import static org.assertj.assertions.generator.GeneratedAnnotationSource.JAKARTA; +import static org.assertj.assertions.generator.GeneratedAnnotationSource.JAVAX; import static org.assertj.assertions.generator.Template.Type.ABSTRACT_ASSERT_CLASS; import static org.assertj.assertions.generator.Template.Type.ASSERT_CLASS; import static org.assertj.assertions.generator.Template.Type.HIERARCHICAL_ASSERT_CLASS; @@ -53,778 +55,835 @@ public class BaseAssertionGenerator implements AssertionGenerator, AssertionsEntryPointGenerator { - static final String TEMPLATES_DIR = "templates" + File.separator; - private static final String IMPORT_LINE = "import %s;%s"; - private static final String PREDICATE = "${predicate}"; - private static final String PREDICATE_NEG = "${neg_predicate}"; - private static final String PREDICATE_FOR_JAVADOC = "${predicate_for_javadoc}"; - private static final String NEGATIVE_PREDICATE_FOR_JAVADOC = "${negative_predicate_for_javadoc}"; - private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = "${predicate_for_error_message_part1}"; - private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = "${predicate_for_error_message_part2}"; - private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = "${negative_predicate_for_error_message_part1}"; - private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = "${negative_predicate_for_error_message_part2}"; - private static final String PROPERTY_WITH_UPPERCASE_FIRST_CHAR = "${Property}"; - private static final String PROPERTY_GETTER_CALL = "${getter}"; - private static final String PROPERTY_WITH_LOWERCASE_FIRST_CHAR = "${property}"; - private static final String PROPERTY_WITH_SAFE = "${property_safe}"; - private static final String PACKAGE = "${package}"; - private static final String PROPERTY_TYPE = "${propertyType}"; - private static final String PROPERTY_SIMPLE_TYPE = "${propertySimpleType}"; - private static final String PROPERTY_ASSERT_TYPE = "${propertyAssertType}"; - private static final String CLASS_TO_ASSERT = "${class_to_assert}"; - private static final String CUSTOM_ASSERTION_CLASS = "${custom_assertion_class}"; - private static final String ABSTRACT_SUPER_ASSERTION_CLASS = "${super_assertion_class}"; - private static final String SELF_TYPE = "${self_type}"; - private static final String MYSELF = "${myself}"; - private static final String ELEMENT_TYPE = "${elementType}"; - private static final String ELEMENT_ASSERT_TYPE = "${elementAssertType}"; - private static final String ALL_ASSERTIONS_ENTRY_POINTS = "${all_assertions_entry_points}"; - private static final String IMPORTS = "${imports}"; - private static final String THROWS = "${throws}"; - private static final String THROWS_JAVADOC = "${throws_javadoc}"; - private static final String LINE_SEPARATOR = "\n"; - - private static final Comparator ORDER_BY_INCREASING_LENGTH = Comparator.comparingInt(String::length); - - private static final Set JAVA_KEYWORDS = newHashSet("abstract", - "assert", - "boolean", - "break", - "byte", - "case", - "catch", - "char", - // This one's not strictly required because you can't have - // a property called "class" - "class", - "const", - "continue", - "default", - "do", - "double", - "else", - "enum", - "extends", - "false", - "final", - "finally", - "float", - "for", - "goto", - "if", - "implements", - "import", - "instanceof", - "int", - "interface", - "long", - "native", - "new", - "null", - "package", - "protected", - "private", - "public", - "return", - "short", - "static", - "strictfp", - "super", - "switch", - "synchronized", - "this", - "throw", - "throws", - "transient", - "true", - "try", - "void", - "volatile", - "while"); - - /** - * This regexp shall match a java class's name inside an user template. - *

- * For this, we use the two character class {@code javaJavaIdentifierStart} and {@code javaJavaIdentifierPart} to match - * a valid name. - *

- * (?m)^public class is needed to match the class at the beginning of a line and avoid matching - * "public class" inside javadoc comment as in templates/junit_soft_assertions_entry_point_class_template.txt. - *

- * Description of the pattern: - *

    - *
  1. (?m)^ beginning of line in multi-line mode
  2. - *
  3. public class[\\s]+ the "public class" followed by one or more whitespace (either tabs, space or new lines).
  4. - *
  5. (?<CLASSNAME>...) create a named group that would match a Java identifier (here the class name).
  6. - *
  7. \\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}* match said identifier using character class.
  8. - *
- * - * @see java.util.regex.Pattern - * @see Character#isJavaIdentifierStart - * @see Character#isJavaIdentifierPart - */ - private static final Pattern CLASS_NAME_PATTERN = Pattern - .compile("(?m)^public class[\\s]+(?\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)\\b"); - - private static final Set> EMPTY_HIERARCHY = new HashSet<>(); - - private static final String NON_PUBLIC_FIELD_VALUE_EXTRACTION = "org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue(\"%s\", %s.class, actual)"; - // S is used in custom_abstract_assertion_class_template.txt - private static final String ABSTRACT_ASSERT_SELF_TYPE = "S"; - - // assertions classes are generated in their package directory starting from targetBaseDirectory. - // ex : com.nba.Player -> targetBaseDirectory/com/nba/PlayerAssert.java - private File targetBaseDirectory = Paths.get(".").toFile(); - private TemplateRegistry templateRegistry;// the pattern to search for - private boolean generateAssertionsForAllFields = false; - private String generatedAssertionsPackage = null; - - /** - * Creates a new {@link BaseAssertionGenerator} with default templates directory. - * - * @throws IOException if some template file could not be found or read - */ - public BaseAssertionGenerator() throws IOException { - this(TEMPLATES_DIR); - } - - /** - * Creates a new {@link BaseAssertionGenerator} with the templates from the given directory. - * - * @param templatesDirectory path where to find templates - * @throws IOException if some template file could not be found or read - */ - public BaseAssertionGenerator(String templatesDirectory) throws IOException { - templateRegistry = DefaultTemplateRegistryProducer.create(templatesDirectory); - } - - public void setDirectoryWhereAssertionFilesAreGenerated(File targetBaseDirectory) { - this.targetBaseDirectory = targetBaseDirectory; - } - - public void setGenerateAssertionsForAllFields(boolean generateAssertionsForAllFields) { - this.generateAssertionsForAllFields = generateAssertionsForAllFields; - } - - public void setGeneratedAssertionsPackage(String generatedAssertionsPackage) { - checkGivenPackageIsValid(generatedAssertionsPackage); - this.generatedAssertionsPackage = generatedAssertionsPackage; - } - - private void checkGivenPackageIsValid(String generatedAssertionsPackage) { - Validate.isTrue(isNotBlank(generatedAssertionsPackage), "The given package '%s' must not be blank", - generatedAssertionsPackage); - Validate.isTrue(!containsWhitespace(generatedAssertionsPackage), "The given package '%s' must not contain blank character", - generatedAssertionsPackage); - } - - @Override - public File generateCustomAssertionFor(ClassDescription classDescription) throws IOException { - // Assertion content - String assertionFileContent = generateCustomAssertionContentFor(classDescription); - // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package - String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); - buildDirectory(directoryWhereToCreateAssertFiles); - return createFile(assertionFileContent, classDescription.getAssertClassFilename(), directoryWhereToCreateAssertFiles); - } - - private String getDirectoryWhereToCreateAssertFilesFor(ClassDescription classDescription) { - return getDirectoryPathCorrespondingToPackage(determinePackageName(classDescription)); - } - - @Override - public File[] generateHierarchicalCustomAssertionFor(ClassDescription classDescription, - Set> allClasses) throws IOException { - // Assertion content - String[] assertionFileContent = generateHierarchicalCustomAssertionContentFor(classDescription, allClasses); - // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package - String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); - buildDirectory(directoryWhereToCreateAssertFiles); - // create assertion files - File[] assertionClassFiles = new File[2]; - final String concreteAssertClassFileName = classDescription.getAssertClassFilename(); - final String abstractAssertClassFileName = classDescription.getAbstractAssertClassFilename(); - assertionClassFiles[0] = createFile(assertionFileContent[0], abstractAssertClassFileName, directoryWhereToCreateAssertFiles); - assertionClassFiles[1] = createFile(assertionFileContent[1], concreteAssertClassFileName, directoryWhereToCreateAssertFiles); - return assertionClassFiles; - } - - @Override - public String[] generateHierarchicalCustomAssertionContentFor(ClassDescription classDescription, - Set> classes) { - // use abstract class template first - String abstractAssertClassContent = templateRegistry.getTemplate(ABSTRACT_ASSERT_CLASS).getContent(); - StringBuilder abstractAssertClassContentBuilder = new StringBuilder(abstractAssertClassContent); - - // generate assertion method for each property with a public getter or field - generateAssertionsForDeclaredGettersOf(abstractAssertClassContentBuilder, classDescription); - generateAssertionsForDeclaredFieldsOf(abstractAssertClassContentBuilder, classDescription); - - // close class with } - abstractAssertClassContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); - - // use concrete class template for the subclass of the generated abstract assert - String concreteAssertClassContent = templateRegistry.getTemplate(HIERARCHICAL_ASSERT_CLASS).getContent(); - - // return a String array with the actual generated content of the assertion class hierarchy - String[] assertionClassesContent = new String[2]; - assertionClassesContent[0] = fillAbstractAssertClassTemplate(abstractAssertClassContentBuilder.toString(), classDescription, - classes); - assertionClassesContent[1] = fillConcreteAssertClassTemplate(concreteAssertClassContent, classDescription); - return assertionClassesContent; - } - - private String switchToComparableAssertIfPossible(String content, ClassDescription classDescription) { - return classDescription.implementsComparable() - ? replace(content, "AbstractObjectAssert", "AbstractComparableAssert") - : content; - } - - private String fillAbstractAssertClassTemplate(String abstractAssertClassTemplate, ClassDescription classDescription, - Set> classes) { - return fillAssertClassTemplate(abstractAssertClassTemplate, classDescription, classes, false); - } - - private String fillAssertClassTemplate(String template, ClassDescription classDescription, - Set> classesHierarchy, boolean concrete) { - // Add any AssertJ needed imports only, other types are used with their fully qualified names to avoid a compilation - // error when two types have the same name. - TreeSet classesToImport = new TreeSet<>(); - // we import the class to assert in case the generated assertions are put in a different package than the class to assert, - // listNeededImports will remove it if if was not needed. - // in case of nested class, we must only import the outer class ! - classesToImport.add(classDescription.getFullyQualifiedOuterClassName()); - if (template.contains("Assertions.")) classesToImport.add("org.assertj.core.api.Assertions"); - if (template.contains("Objects.")) { - int totalObjects = StringUtils.countMatches(template, "Objects."); - int areEqualObjects = StringUtils.countMatches(template, "Objects.deepEquals"); - int areEqualArraysObjects = StringUtils.countMatches(template, "Objects.deepEqualsArrays"); - - int totalDeprecated = areEqualObjects + areEqualArraysObjects; - - if (totalDeprecated > 0) { - classesToImport.add("java.util.Objects"); - } - - if (totalObjects > totalDeprecated) { - classesToImport.add("org.assertj.core.util.Objects"); - } - } - if (template.contains("Iterables.")) classesToImport.add("org.assertj.core.internal.Iterables"); - - // Add assertion supertype to imports if needed (for abstract assertions hierarchy) - // we need a FQN if the parent class is in a different package than the child class, if not listNeededImports will optimize it - final String parentAssertClassName = classesHierarchy.contains(classDescription.getSuperType()) - ? classDescription.getFullyQualifiedParentAssertClassName() - : "org.assertj.core.api.AbstractObjectAssert"; - if (classesHierarchy.contains(classDescription.getSuperType())) { - classesToImport.add(parentAssertClassName); - } - - final String customAssertionClass = concrete ? classDescription.getAssertClassName() - : classDescription.getAbstractAssertClassName(); - final String selfType = concrete ? customAssertionClass : ABSTRACT_ASSERT_SELF_TYPE; - final String myself = concrete ? "this" : "myself"; - - template = replace(template, PACKAGE, determinePackageName(classDescription)); - template = replace(template, CUSTOM_ASSERTION_CLASS, customAssertionClass); - // use a simple parent class name as we have already imported it - // className could be a nested class like "OuterClass.NestedClass", in that case assert class will be OuterClassNestedClass - template = replace(template, ABSTRACT_SUPER_ASSERTION_CLASS, getTypeNameWithoutDots(parentAssertClassName)); - if (template.contains("AbstractObjectAssert")) classesToImport.add("org.assertj.core.api.AbstractObjectAssert"); - - template = replace(template, CLASS_TO_ASSERT, classDescription.getClassNameWithOuterClass()); - template = replace(template, SELF_TYPE, selfType); - template = replace(template, MYSELF, myself); - String neededImports = listNeededImports(classesToImport, determinePackageName(classDescription)); - template = replace(template, IMPORTS, neededImports.isEmpty() ? "" : LINE_SEPARATOR + neededImports); - - // in case the domain class is Comparable we want the assert class to inherit from AbstractComparableAssert - template = switchToComparableAssertIfPossible(template, classDescription); - - return template; - } - - private String determinePackageName(ClassDescription classDescription) { - return generatedAssertionsPackage == null ? classDescription.getPackageName() : generatedAssertionsPackage; - } - - private String fillConcreteAssertClassTemplate(String template, ClassDescription classDescription) { - return fillAssertClassTemplate(template, classDescription, EMPTY_HIERARCHY, true); - } - - @Override - public String generateCustomAssertionContentFor(ClassDescription classDescription) { - - // use class template first - String classTemplateContent = templateRegistry.getTemplate(ASSERT_CLASS).getContent(); - StringBuilder assertionFileContentBuilder = new StringBuilder(classTemplateContent); - - // generate assertion method for each property with a public getter - generateAssertionsForGettersOf(assertionFileContentBuilder, classDescription); - generateAssertionsForFieldsOf(assertionFileContentBuilder, classDescription); - - // close class with } - assertionFileContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); - - return fillConcreteAssertClassTemplate(assertionFileContentBuilder.toString(), classDescription); - } - - @Override - public String generateAssertionsEntryPointClassContentFor(final Set classDescriptionSet, - AssertionsEntryPointType assertionsEntryPointType, - String entryPointClassPackage) { - if (noClassDescriptionsGiven(classDescriptionSet)) return ""; - Template assertionEntryPointMethodTemplate = chooseAssertionEntryPointMethodTemplate(assertionsEntryPointType); - Template assertionsEntryPointClassTemplate = chooseAssertionEntryPointClassTemplate(assertionsEntryPointType); - return generateAssertionsEntryPointClassContent(classDescriptionSet, assertionsEntryPointClassTemplate, - assertionEntryPointMethodTemplate, entryPointClassPackage); - } - - private Template chooseAssertionEntryPointMethodTemplate(final AssertionsEntryPointType assertionsEntryPointType) { - switch (assertionsEntryPointType) { - case SOFT: - case JUNIT_SOFT: - case AUTO_CLOSEABLE_SOFT: - return templateRegistry.getTemplate(Type.SOFT_ENTRY_POINT_METHOD_ASSERTION); - case BDD: - return templateRegistry.getTemplate(Type.BDD_ENTRY_POINT_METHOD_ASSERTION); - case BDD_SOFT: - case JUNIT_BDD_SOFT: - case AUTO_CLOSEABLE_BDD_SOFT: - return templateRegistry.getTemplate(Type.BDD_SOFT_ENTRY_POINT_METHOD_ASSERTION); - default: - return templateRegistry.getTemplate(Type.ASSERTION_ENTRY_POINT); - } - } - - private Template chooseAssertionEntryPointClassTemplate(final AssertionsEntryPointType assertionsEntryPointType) { - switch (assertionsEntryPointType) { - case SOFT: - return templateRegistry.getTemplate(Type.SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case JUNIT_SOFT: - return templateRegistry.getTemplate(Type.JUNIT_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case AUTO_CLOSEABLE_SOFT: - return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case BDD: - return templateRegistry.getTemplate(Type.BDD_ASSERTIONS_ENTRY_POINT_CLASS); - case BDD_SOFT: - return templateRegistry.getTemplate(Type.BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case JUNIT_BDD_SOFT: - return templateRegistry.getTemplate(Type.JUNIT_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case AUTO_CLOSEABLE_BDD_SOFT: - return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - default: - return templateRegistry.getTemplate(Type.ASSERTIONS_ENTRY_POINT_CLASS); - } - } - - @Override - public File generateAssertionsEntryPointClassFor(final Set classDescriptionSet, - AssertionsEntryPointType assertionsEntryPointType, - String entryPointClassPackage) throws IOException { - if (noClassDescriptionsGiven(classDescriptionSet)) return null; - String assertionsEntryPointFileContent = generateAssertionsEntryPointClassContentFor(classDescriptionSet, - assertionsEntryPointType, - entryPointClassPackage); - String fileName = determineFileName(assertionsEntryPointFileContent, assertionsEntryPointType); - return createAssertionsFileFor(classDescriptionSet, assertionsEntryPointFileContent, fileName, - entryPointClassPackage); - } - - private String determineFileName(String assertionsEntryPointFileContent, - AssertionsEntryPointType assertionsEntryPointType) { - // expecting the class name to be here : "class " - Matcher classNameMatcher = CLASS_NAME_PATTERN.matcher(assertionsEntryPointFileContent); - // if we find a match return it - if (classNameMatcher.find()) return classNameMatcher.group("CLASSNAME") + ".java"; - // otherwise use the default name - return assertionsEntryPointType.getFileName(); - } - - private String generateAssertionsEntryPointClassContent(final Set classDescriptionSet, - final Template entryPointAssertionsClassTemplate, - final Template entryPointAssertionMethodTemplate, - String entryPointClassPackage) { - String entryPointAssertionsClassContent = entryPointAssertionsClassTemplate.getContent(); - // resolve template markers - String classPackage = isEmpty(entryPointClassPackage) - ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) - : entryPointClassPackage; - entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, PACKAGE, classPackage); - - String allEntryPointsAssertionContent = generateAssertionEntryPointMethodsFor(classDescriptionSet, - entryPointAssertionMethodTemplate); - entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, ALL_ASSERTIONS_ENTRY_POINTS, - allEntryPointsAssertionContent); - return entryPointAssertionsClassContent; - } - - /** - * create the assertions entry point file, located in its package directory starting from targetBaseDirectory. - *

- * If assertionsClassPackage is not set, we use the common base package of the given classes, if some classe are in - * a.b.c package and others in a.b.c.d, then entry point class will be in a.b.c. - *

- * - * @param classDescriptionSet used to determine the assertions class package - * @param fileContent assertions entry point file content - * @param fileName assertions entry point file name - * @param assertionsClassPackage the entry point class package - automatically determined if null. - * @return the created assertions entry point file - * @throws IOException if file can't be created. - */ - private File createAssertionsFileFor(final Set classDescriptionSet, final String fileContent, - final String fileName, final String assertionsClassPackage) throws IOException { - String classPackage = isEmpty(assertionsClassPackage) - ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) - : assertionsClassPackage; - String assertionsDirectory = getDirectoryPathCorrespondingToPackage(classPackage); - // build any needed directories - buildDirectory(assertionsDirectory); - return createFile(fileContent, fileName, assertionsDirectory); - } - - private String generateAssertionEntryPointMethodsFor(final Set classDescriptionSet, - Template assertionEntryPointMethodTemplate) { - // sort ClassDescription according to their class name. - SortedSet sortedClassDescriptionSet = new TreeSet<>(classDescriptionSet); - // generate for each classDescription the entry point method, e.g. assertThat(MyClass) or then(MyClass) - StringBuilder allAssertThatsContentBuilder = new StringBuilder(); - final String lineSeparator = System.lineSeparator(); - for (ClassDescription classDescription : sortedClassDescriptionSet) { - String assertionEntryPointMethodContent = assertionEntryPointMethodTemplate.getContent(); - // resolve class assert (ex: PlayerAssert) - // in case of inner classes like Movie.PublicCategory, class assert will be MoviePublicCategoryAssert - assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CUSTOM_ASSERTION_CLASS, - classDescription.getFullyQualifiedAssertClassName()); - // resolve class (ex: Player) - // in case of inner classes like Movie.PublicCategory use class name with outer class i.e. Movie.PublicCategory. - assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CLASS_TO_ASSERT, - classDescription.getFullyQualifiedClassName()); - - allAssertThatsContentBuilder.append(lineSeparator).append(assertionEntryPointMethodContent); - } - return allAssertThatsContentBuilder.toString(); - } - - private String determineBestEntryPointsAssertionsClassPackage(final Set classDescriptionSet) { - if (generatedAssertionsPackage != null) { - return generatedAssertionsPackage; - } - - SortedSet packages = new TreeSet<>(ORDER_BY_INCREASING_LENGTH); - for (ClassDescription classDescription : classDescriptionSet) { - packages.add(classDescription.getPackageName()); - } - // takes the base package of all given classes assuming they all belong to a common package, i.e a.b.c. over a.b.c.d - // this can certainly be improved ... - return packages.first(); - } - - /** - * Returns the target directory path where the assertions file for given classDescription will be created. - * - * @param packageName package name - * @return the target directory path corresponding to the given package. - */ - private String getDirectoryPathCorrespondingToPackage(final String packageName) { - return targetBaseDirectory + File.separator + packageName.replace('.', File.separatorChar); - } - - private static String listNeededImports(Set typesToImport, String classPackage) { - StringBuilder imports = new StringBuilder(); - for (String type : typesToImport) { - if (isImportNeeded(type, classPackage)) { - imports.append(format(IMPORT_LINE, type, LINE_SEPARATOR)); - } - } - return imports.toString(); - } - - private static boolean isImportNeeded(String type, String classPackage) { - // no need to import type belonging to the same package - if (Objects.equals(classPackage, packageOf(type))) return false; - try { - Class clazz = Class.forName(type); - // primitive and java.lang.* are available by default - return !(clazz.isPrimitive() || isJavaLangType(clazz)); - } catch (ClassNotFoundException e) { - // occurs for abstract types (ex: AbstractXXXAssert) or types to generate that are unknown - return true; - } - } - - protected void generateAssertionsForGettersOf(StringBuilder contentBuilder, ClassDescription classDescription) { - generateAssertionsForGetters(contentBuilder, classDescription.getGettersDescriptions(), classDescription); - } - - protected void generateAssertionsForDeclaredGettersOf(StringBuilder contentBuilder, - ClassDescription classDescription) { - generateAssertionsForGetters(contentBuilder, classDescription.getDeclaredGettersDescriptions(), classDescription); - } - - protected void generateAssertionsForGetters(StringBuilder assertionsForGetters, Set getters, - ClassDescription classDescription) { - for (GetterDescription getter : getters) { - String assertionContent = assertionContentForProperty(getter, classDescription); - assertionsForGetters.append(assertionContent).append(LINE_SEPARATOR); - } - } - - protected void generateAssertionsForFieldsOf(StringBuilder contentBuilder, ClassDescription classDescription) { - generateAssertionsForFields(contentBuilder, classDescription.getFieldsDescriptions(), classDescription); - } - - protected void generateAssertionsForDeclaredFieldsOf(StringBuilder contentBuilder, - ClassDescription classDescription) { - generateAssertionsForFields(contentBuilder, classDescription.getDeclaredFieldsDescriptions(), - classDescription); - } - - protected void generateAssertionsForFields(StringBuilder assertionsForPublicFields, - Set fields, ClassDescription classDescription) { - for (FieldDescription field : fields) { - if (generateAssertionsForAllFields || field.isPublic()) { - String assertionContent = assertionContentForField(field, classDescription); - // assertion can be empty if we have a getter for the field - if (!assertionContent.isEmpty()) { - assertionsForPublicFields.append(assertionContent).append(LINE_SEPARATOR); - } - } - } - } - - private String assertionContentForField(FieldDescription field, ClassDescription classDescription) { - - if (classDescription.hasGetterForField(field)) { - // the assertion has already been generated using the getter to read the field - return ""; - } - - final String fieldName = field.getName(); - String assertionContent = baseAssertionContentFor(field, classDescription); - - // we reuse template for properties to have consistent assertions for property and field but change the way we get - // the value since it's a field and not a property: - assertionContent = assertionContent.replace("${getter}()", PROPERTY_WITH_LOWERCASE_FIRST_CHAR); - // - remove also ${throws} and ${throws_javadoc} as it does not make sense for a field - assertionContent = remove(assertionContent, THROWS); - assertionContent = remove(assertionContent, THROWS_JAVADOC); - - if (!field.isPublic()) { - // if field is not public, we need to use reflection to get its value, ex : - // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("grade", Grade.class, actual); - assertionContent = assertionContent.replace("actual." + PROPERTY_WITH_LOWERCASE_FIRST_CHAR, - format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, - PROPERTY_WITH_LOWERCASE_FIRST_CHAR, PROPERTY_TYPE)); - } - if (field.isPredicate()) { - assertionContent = fillAssertionContentForPredicateField(field, assertionContent); - } - assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(field.getName())); - assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(field)); - assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, - field.getAssertTypeName(determinePackageName(classDescription))); - assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(field)); - assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, fieldName); - // It should not be possible to have a field that is a keyword - compiler won't allow it. - assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(fieldName)); - return assertionContent; - } - - private String getTypeName(DataDescription fieldOrGetter) { - if (generatedAssertionsPackage != null) { - // if the user has chosen to generate assertions in a given package we assume that - return fieldOrGetter.getFullyQualifiedTypeName(); - } - // returns a simple class name if the field or getter type is in the same package as its owning type which is the package - // where the - // Assert class is generated. - return fieldOrGetter.getTypeName(); - } - - private String fillAssertionContentForPredicateField(FieldDescription field, String assertionContent) { - if (field.isPublic()) { - assertionContent = assertionContent.replace("actual." + PREDICATE + "()", - "actual." + field.getOriginalMember().getName()); - } else { - // if field is not public, we need to use reflection to get its value, ex : - // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("active", Boolean.class, actual); - assertionContent = assertionContent.replace("actual." + PREDICATE + "()", - format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, - field.getOriginalMember().getName(), "Boolean")); - } - assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, - field.getPredicateForJavadoc()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, - field.getNegativePredicateForJavadoc()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - field.getPredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - field.getPredicateForErrorMessagePart2()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - field.getNegativePredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - field.getNegativePredicateForErrorMessagePart2()); - assertionContent = replace(assertionContent, PREDICATE, field.getPredicate()); - assertionContent = replace(assertionContent, PREDICATE_NEG, field.getNegativePredicate()); - return assertionContent; - } - - static private String unclashName(String unsafe) { - return JAVA_KEYWORDS.contains(unsafe) || "actual".equals(unsafe) ? "expected" + capitalize(unsafe) : unsafe; - } - - private String assertionContentForProperty(GetterDescription getter, ClassDescription classDescription) { - String assertionContent = baseAssertionContentFor(getter, classDescription); - - assertionContent = declareExceptions(getter, assertionContent); - - String propertyName = getter.getName(); - if (getter.isPredicate()) { - assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, - getter.getPredicateForJavadoc()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, - getter.getNegativePredicateForJavadoc()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - getter.getPredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - getter.getPredicateForErrorMessagePart2()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - getter.getNegativePredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - getter.getNegativePredicateForErrorMessagePart2()); - assertionContent = replace(assertionContent, PREDICATE, getter.getOriginalMember().getName()); - assertionContent = replace(assertionContent, PREDICATE_NEG, getter.getNegativePredicate()); - } - assertionContent = replace(assertionContent, PROPERTY_GETTER_CALL, getter.getOriginalMember().getName()); - assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(propertyName)); - assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(getter)); - assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, - getter.getAssertTypeName(determinePackageName(classDescription))); - assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(getter)); - assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, propertyName); - assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(propertyName)); - return assertionContent; - } - - /** - * The assertion content that is common to field and property (getter), the specific content part is handled - * afterwards. - * - * @param fieldOrProperty field or property - * @return the base assertion content - */ - private String baseAssertionContentFor(DataDescription fieldOrProperty, ClassDescription classDescription) { - String assertionContent = templateRegistry.getTemplate(Type.HAS).getContent(); - if (fieldOrProperty.isPredicate()) { - Type type = determinePredicateType(fieldOrProperty, classDescription); - assertionContent = templateRegistry.getTemplate(type).getContent(); - } else if (fieldOrProperty.isIterableType()) { - assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ITERABLE).getContent(), ELEMENT_TYPE, - fieldOrProperty.getElementTypeName()); - assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, - fieldOrProperty.getElementAssertTypeName()); - } else if (fieldOrProperty.isArrayType()) { - assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ARRAY).getContent(), ELEMENT_TYPE, - fieldOrProperty.getElementTypeName()); - assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, - fieldOrProperty.getElementAssertTypeName()); - } else if (fieldOrProperty.isRealNumberType()) { - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_REAL_NUMBER_WRAPPER - : Type.HAS_FOR_REAL_NUMBER; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } else if (fieldOrProperty.isWholeNumberType()) { - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_WHOLE_NUMBER_WRAPPER - : Type.HAS_FOR_WHOLE_NUMBER; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } else if (fieldOrProperty.isCharType()) { - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_CHARACTER : Type.HAS_FOR_CHAR; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } else if (fieldOrProperty.isPrimitiveType()) { - // use case : boolean getFoo -> not a predicate, but a primitive valueType - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_PRIMITIVE_WRAPPER : Type.HAS_FOR_PRIMITIVE; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } - return assertionContent; - } - - /** - * Determine whether we need to generate negative predicate assertions, for example if the class contains isValid and - * isNotValid methods, we must not generate the negative assertion for isValid as it will be done when generating - * assertions for isNotValid - */ - private Type determinePredicateType(final DataDescription fieldOrProperty, final ClassDescription classDescription) { - if (hasAlreadyNegativePredicate(fieldOrProperty, classDescription)) { - return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER_WITHOUT_NEGATION : Type.IS_WITHOUT_NEGATION; - } - return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER : Type.IS; - } - - private boolean hasAlreadyNegativePredicate(final DataDescription fieldOrProperty, - final ClassDescription classDescription) { - for (final GetterDescription getterDescription : classDescription.getGettersDescriptions()) { - if (getterDescription.getOriginalMember().getName().equals(fieldOrProperty.getNegativePredicate())) return true; - } - return false; - } - - /** - * Handle case where getter throws an exception. - * - * @param getter method we want to declare exception for - * @param assertionContent the assertion content to enrich - * @return assertion content with thrown exceptions - */ - private String declareExceptions(GetterDescription getter, String assertionContent) { - StringBuilder throwsClause = new StringBuilder(); - StringBuilder throwsJavaDoc = new StringBuilder(); - boolean first = true; - for (TypeToken exception : getter.getExceptions()) { - if (first) throwsClause.append("throws "); - else throwsClause.append(", "); - first = false; - String exceptionName = getTypeDeclaration(exception); - throwsClause.append(exceptionName); - throwsJavaDoc.append(LINE_SEPARATOR).append(" * @throws ").append(exceptionName); - throwsJavaDoc.append(" if actual.").append("${getter}() throws one."); - } - if (!getter.getExceptions().isEmpty()) throwsClause.append(' '); - - assertionContent = assertionContent.replace(THROWS_JAVADOC, throwsJavaDoc.toString()); - assertionContent = assertionContent.replace(THROWS, throwsClause.toString()); - return assertionContent; - } - - private void fillFile(String customAssertionContent, File assertionJavaFile) throws IOException { - try (FileWriter fileWriter = new FileWriter(assertionJavaFile, false)) { - fileWriter.write(customAssertionContent); - } - } - - private File createFile(String fileContent, String fileName, String targetDirectory) throws IOException { - File file = new File(targetDirectory, fileName); - - // Ignore the result as it only returns false when the file existed previously which is not wrong. - // noinspection ResultOfMethodCallIgnored - file.createNewFile(); - fillFile(fileContent, file); - return file; - } - - private static boolean noClassDescriptionsGiven(final Set classDescriptionSet) { - return classDescriptionSet == null || classDescriptionSet.isEmpty(); - } - - private static void buildDirectory(String directoryName) { - // Ignore the result as it only returns true iff the dir was created, false is not bad. - File directory = new File(directoryName); - if (!directory.exists()) directory.mkdirs(); - } - - @Override - public void register(Template template) { - templateRegistry.register(template); - } + static final String TEMPLATES_DIR = "templates" + File.separator; + private static final String GENERATED = "${generatedAnnotation}"; + private static final String IMPORT_LINE = "import %s;%s"; + private static final String PREDICATE = "${predicate}"; + private static final String PREDICATE_NEG = "${neg_predicate}"; + private static final String PREDICATE_FOR_JAVADOC = "${predicate_for_javadoc}"; + private static final String NEGATIVE_PREDICATE_FOR_JAVADOC = "${negative_predicate_for_javadoc}"; + private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = "${predicate_for_error_message_part1}"; + private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = "${predicate_for_error_message_part2}"; + private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = + "${negative_predicate_for_error_message_part1}"; + private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = + "${negative_predicate_for_error_message_part2}"; + private static final String PROPERTY_WITH_UPPERCASE_FIRST_CHAR = "${Property}"; + private static final String PROPERTY_GETTER_CALL = "${getter}"; + private static final String PROPERTY_WITH_LOWERCASE_FIRST_CHAR = "${property}"; + private static final String PROPERTY_WITH_SAFE = "${property_safe}"; + private static final String PACKAGE = "${package}"; + private static final String PROPERTY_TYPE = "${propertyType}"; + private static final String PROPERTY_SIMPLE_TYPE = "${propertySimpleType}"; + private static final String PROPERTY_ASSERT_TYPE = "${propertyAssertType}"; + private static final String CLASS_TO_ASSERT = "${class_to_assert}"; + private static final String CUSTOM_ASSERTION_CLASS = "${custom_assertion_class}"; + private static final String ABSTRACT_SUPER_ASSERTION_CLASS = "${super_assertion_class}"; + private static final String SELF_TYPE = "${self_type}"; + private static final String MYSELF = "${myself}"; + private static final String ELEMENT_TYPE = "${elementType}"; + private static final String ELEMENT_ASSERT_TYPE = "${elementAssertType}"; + private static final String ALL_ASSERTIONS_ENTRY_POINTS = "${all_assertions_entry_points}"; + private static final String IMPORTS = "${imports}"; + private static final String THROWS = "${throws}"; + private static final String THROWS_JAVADOC = "${throws_javadoc}"; + private static final String LINE_SEPARATOR = "\n"; + + private static final Comparator ORDER_BY_INCREASING_LENGTH = Comparator.comparingInt(String::length); + + private static final Set JAVA_KEYWORDS = newHashSet("abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + // This one's not strictly required because you can't have + // a property called "class" + "class", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extends", + "false", + "final", + "finally", + "float", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "null", + "package", + "protected", + "private", + "public", + "return", + "short", + "static", + "strictfp", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "void", + "volatile", + "while"); + + /** + * This regexp shall match a java class's name inside an user template. + *

+ * For this, we use the two character class {@code javaJavaIdentifierStart} and {@code javaJavaIdentifierPart} to match a valid + * name. + *

+ * (?m)^public class is needed to match the class at the beginning of a line and avoid matching "public class" + * inside javadoc comment as in templates/junit_soft_assertions_entry_point_class_template.txt. + *

+ * Description of the pattern: + *

    + *
  1. (?m)^ beginning of line in multi-line mode
  2. + *
  3. public class[\\s]+ the "public class" followed by one or more whitespace (either tabs, space or new + * lines).
  4. + *
  5. (?<CLASSNAME>...) create a named group that would match a Java identifier (here the class name).
  6. + *
  7. \\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}* match said identifier using character class.
  8. + *
+ * + * @see java.util.regex.Pattern + * @see Character#isJavaIdentifierStart + * @see Character#isJavaIdentifierPart + */ + private static final Pattern CLASS_NAME_PATTERN = Pattern + .compile("(?m)^public class[\\s]+(?\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)\\b"); + + private static final Set> EMPTY_HIERARCHY = new HashSet<>(); + + private static final String NON_PUBLIC_FIELD_VALUE_EXTRACTION = + "org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue(\"%s\", %s.class, actual)"; + // S is used in custom_abstract_assertion_class_template.txt + private static final String ABSTRACT_ASSERT_SELF_TYPE = "S"; + + // assertions classes are generated in their package directory starting from targetBaseDirectory. + // ex : com.nba.Player -> targetBaseDirectory/com/nba/PlayerAssert.java + private File targetBaseDirectory = Paths.get(".").toFile(); + private final TemplateRegistry templateRegistry;// the pattern to search for + private boolean generateAssertionsForAllFields = false; + private String generatedAssertionsPackage = null; + private GeneratedAnnotationSource generatedAnnotationSource = JAVAX; + + /** + * Creates a new {@link BaseAssertionGenerator} with default templates directory. + * + * @throws IOException if some template file could not be found or read + */ + public BaseAssertionGenerator() throws IOException { + this(TEMPLATES_DIR); + } + + /** + * Creates a new {@link BaseAssertionGenerator} with the templates from the given directory. + * + * @param templatesDirectory path where to find templates + * @throws IOException if some template file could not be found or read + */ + public BaseAssertionGenerator(String templatesDirectory) throws IOException { + templateRegistry = DefaultTemplateRegistryProducer.create(templatesDirectory); + } + + public void setDirectoryWhereAssertionFilesAreGenerated(File targetBaseDirectory) { + this.targetBaseDirectory = targetBaseDirectory; + } + + public void setGenerateAssertionsForAllFields(boolean generateAssertionsForAllFields) { + this.generateAssertionsForAllFields = generateAssertionsForAllFields; + } + + public void setGeneratedAssertionsPackage(String generatedAssertionsPackage) { + checkGivenPackageIsValid(generatedAssertionsPackage); + this.generatedAssertionsPackage = generatedAssertionsPackage; + } + + private void checkGivenPackageIsValid(String generatedAssertionsPackage) { + Validate.isTrue(isNotBlank(generatedAssertionsPackage), "The given package '%s' must not be blank", + generatedAssertionsPackage); + Validate.isTrue(!containsWhitespace(generatedAssertionsPackage), + "The given package '%s' must not contain blank character", + generatedAssertionsPackage); + } + + @Override + public File generateCustomAssertionFor(ClassDescription classDescription) throws IOException { + // Assertion content + String assertionFileContent = generateCustomAssertionContentFor(classDescription); + // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package + String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); + buildDirectory(directoryWhereToCreateAssertFiles); + return createFile(assertionFileContent, classDescription.getAssertClassFilename(), directoryWhereToCreateAssertFiles); + } + + private String getDirectoryWhereToCreateAssertFilesFor(ClassDescription classDescription) { + return getDirectoryPathCorrespondingToPackage(determinePackageName(classDescription)); + } + + @Override + public File[] generateHierarchicalCustomAssertionFor(ClassDescription classDescription, + Set> allClasses) throws IOException { + // Assertion content + String[] assertionFileContent = generateHierarchicalCustomAssertionContentFor(classDescription, allClasses); + // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package + String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); + buildDirectory(directoryWhereToCreateAssertFiles); + // create assertion files + File[] assertionClassFiles = new File[2]; + final String concreteAssertClassFileName = classDescription.getAssertClassFilename(); + final String abstractAssertClassFileName = classDescription.getAbstractAssertClassFilename(); + assertionClassFiles[0] = + createFile(assertionFileContent[0], abstractAssertClassFileName, directoryWhereToCreateAssertFiles); + assertionClassFiles[1] = + createFile(assertionFileContent[1], concreteAssertClassFileName, directoryWhereToCreateAssertFiles); + return assertionClassFiles; + } + + @Override + public String[] generateHierarchicalCustomAssertionContentFor(ClassDescription classDescription, + Set> classes) { + // use abstract class template first + String abstractAssertClassContent = templateRegistry.getTemplate(ABSTRACT_ASSERT_CLASS).getContent(); + StringBuilder abstractAssertClassContentBuilder = new StringBuilder(abstractAssertClassContent); + + // generate assertion method for each property with a public getter or field + generateAssertionsForDeclaredGettersOf(abstractAssertClassContentBuilder, classDescription); + generateAssertionsForDeclaredFieldsOf(abstractAssertClassContentBuilder, classDescription); + + // close class with } + abstractAssertClassContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); + + // use concrete class template for the subclass of the generated abstract assert + String concreteAssertClassContent = templateRegistry.getTemplate(HIERARCHICAL_ASSERT_CLASS).getContent(); + + // return a String array with the actual generated content of the assertion class hierarchy + String[] assertionClassesContent = new String[2]; + assertionClassesContent[0] = + fillAbstractAssertClassTemplate(abstractAssertClassContentBuilder.toString(), classDescription, + classes); + assertionClassesContent[1] = fillConcreteAssertClassTemplate(concreteAssertClassContent, classDescription); + return assertionClassesContent; + } + + private String switchToComparableAssertIfPossible(String content, ClassDescription classDescription) { + return classDescription.implementsComparable() + ? replace(content, "AbstractObjectAssert", "AbstractComparableAssert") + : content; + } + + private String fillAbstractAssertClassTemplate(String abstractAssertClassTemplate, ClassDescription classDescription, + Set> classes) { + return fillAssertClassTemplate(abstractAssertClassTemplate, classDescription, classes, false); + } + + private String fillAssertClassTemplate(String template, ClassDescription classDescription, + Set> classesHierarchy, boolean concrete) { + // Add any AssertJ needed imports only, other types are used with their fully qualified names to avoid a compilation + // error when two types have the same name. + TreeSet classesToImport = new TreeSet<>(); + // we import the class to assert in case the generated assertions are put in a different package than the class to assert, + // listNeededImports will remove it if if was not needed. + // in case of nested class, we must only import the outer class ! + classesToImport.add(classDescription.getFullyQualifiedOuterClassName()); + if (template.contains("Assertions.")) { + classesToImport.add("org.assertj.core.api.Assertions"); + } + if (template.contains("Objects.")) { + int totalObjects = StringUtils.countMatches(template, "Objects."); + int areEqualObjects = StringUtils.countMatches(template, "Objects.deepEquals"); + int areEqualArraysObjects = StringUtils.countMatches(template, "Objects.deepEqualsArrays"); + + int totalDeprecated = areEqualObjects + areEqualArraysObjects; + + if (totalDeprecated > 0) { + classesToImport.add("java.util.Objects"); + } + + if (totalObjects > totalDeprecated) { + classesToImport.add("org.assertj.core.util.Objects"); + } + } + if (template.contains("Iterables.")) { + classesToImport.add("org.assertj.core.internal.Iterables"); + } + + // Add assertion supertype to imports if needed (for abstract assertions hierarchy) + // we need a FQN if the parent class is in a different package than the child class, if not listNeededImports will optimize + // it + final String parentAssertClassName = classesHierarchy.contains(classDescription.getSuperType()) + ? classDescription.getFullyQualifiedParentAssertClassName() + : "org.assertj.core.api.AbstractObjectAssert"; + if (classesHierarchy.contains(classDescription.getSuperType())) { + classesToImport.add(parentAssertClassName); + } + + final String customAssertionClass = concrete ? classDescription.getAssertClassName() + : classDescription.getAbstractAssertClassName(); + final String selfType = concrete ? customAssertionClass : ABSTRACT_ASSERT_SELF_TYPE; + final String myself = concrete ? "this" : "myself"; + + template = replace(template, GENERATED, determineGeneratedSignature()); + template = replace(template, PACKAGE, determinePackageName(classDescription)); + template = replace(template, CUSTOM_ASSERTION_CLASS, customAssertionClass); + // use a simple parent class name as we have already imported it + // className could be a nested class like "OuterClass.NestedClass", in that case assert class will be OuterClassNestedClass + template = replace(template, ABSTRACT_SUPER_ASSERTION_CLASS, getTypeNameWithoutDots(parentAssertClassName)); + if (template.contains("AbstractObjectAssert")) { + classesToImport.add("org.assertj.core.api.AbstractObjectAssert"); + } + + template = replace(template, CLASS_TO_ASSERT, classDescription.getClassNameWithOuterClass()); + template = replace(template, SELF_TYPE, selfType); + template = replace(template, MYSELF, myself); + String neededImports = listNeededImports(classesToImport, determinePackageName(classDescription)); + template = replace(template, IMPORTS, neededImports.isEmpty() ? "" : LINE_SEPARATOR + neededImports); + + // in case the domain class is Comparable we want the assert class to inherit from AbstractComparableAssert + template = switchToComparableAssertIfPossible(template, classDescription); + + return template; + } + + private String determineGeneratedSignature() { + if (generatedAnnotationSource == JAVAX) { + return "@javax.annotation.Generated(value=\"assertj-assertions-generator\")"; + } + if (generatedAnnotationSource == JAKARTA) { + return "@jakarta.annotation.Generated(value=\"assertj-assertions-generator\")"; + } + return ""; + } + + private String determinePackageName(ClassDescription classDescription) { + return generatedAssertionsPackage == null ? classDescription.getPackageName() : generatedAssertionsPackage; + } + + private String fillConcreteAssertClassTemplate(String template, ClassDescription classDescription) { + return fillAssertClassTemplate(template, classDescription, EMPTY_HIERARCHY, true); + } + + @Override + public String generateCustomAssertionContentFor(ClassDescription classDescription) { + + // use class template first + String classTemplateContent = templateRegistry.getTemplate(ASSERT_CLASS).getContent(); + StringBuilder assertionFileContentBuilder = new StringBuilder(classTemplateContent); + + // generate assertion method for each property with a public getter + generateAssertionsForGettersOf(assertionFileContentBuilder, classDescription); + generateAssertionsForFieldsOf(assertionFileContentBuilder, classDescription); + + // close class with } + assertionFileContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); + + return fillConcreteAssertClassTemplate(assertionFileContentBuilder.toString(), classDescription); + } + + @Override + public String generateAssertionsEntryPointClassContentFor(final Set classDescriptionSet, + AssertionsEntryPointType assertionsEntryPointType, + String entryPointClassPackage) { + if (noClassDescriptionsGiven(classDescriptionSet)) { + return ""; + } + Template assertionEntryPointMethodTemplate = chooseAssertionEntryPointMethodTemplate(assertionsEntryPointType); + Template assertionsEntryPointClassTemplate = chooseAssertionEntryPointClassTemplate(assertionsEntryPointType); + return generateAssertionsEntryPointClassContent(classDescriptionSet, assertionsEntryPointClassTemplate, + assertionEntryPointMethodTemplate, entryPointClassPackage); + } + + private Template chooseAssertionEntryPointMethodTemplate(final AssertionsEntryPointType assertionsEntryPointType) { + switch (assertionsEntryPointType) { + case SOFT: + case JUNIT_SOFT: + case AUTO_CLOSEABLE_SOFT: + return templateRegistry.getTemplate(Type.SOFT_ENTRY_POINT_METHOD_ASSERTION); + case BDD: + return templateRegistry.getTemplate(Type.BDD_ENTRY_POINT_METHOD_ASSERTION); + case BDD_SOFT: + case JUNIT_BDD_SOFT: + case AUTO_CLOSEABLE_BDD_SOFT: + return templateRegistry.getTemplate(Type.BDD_SOFT_ENTRY_POINT_METHOD_ASSERTION); + default: + return templateRegistry.getTemplate(Type.ASSERTION_ENTRY_POINT); + } + } + + private Template chooseAssertionEntryPointClassTemplate(final AssertionsEntryPointType assertionsEntryPointType) { + switch (assertionsEntryPointType) { + case SOFT: + return templateRegistry.getTemplate(Type.SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case JUNIT_SOFT: + return templateRegistry.getTemplate(Type.JUNIT_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case AUTO_CLOSEABLE_SOFT: + return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case BDD: + return templateRegistry.getTemplate(Type.BDD_ASSERTIONS_ENTRY_POINT_CLASS); + case BDD_SOFT: + return templateRegistry.getTemplate(Type.BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case JUNIT_BDD_SOFT: + return templateRegistry.getTemplate(Type.JUNIT_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case AUTO_CLOSEABLE_BDD_SOFT: + return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + default: + return templateRegistry.getTemplate(Type.ASSERTIONS_ENTRY_POINT_CLASS); + } + } + + @Override + public File generateAssertionsEntryPointClassFor(final Set classDescriptionSet, + AssertionsEntryPointType assertionsEntryPointType, + String entryPointClassPackage) throws IOException { + if (noClassDescriptionsGiven(classDescriptionSet)) { + return null; + } + String assertionsEntryPointFileContent = generateAssertionsEntryPointClassContentFor(classDescriptionSet, + assertionsEntryPointType, + entryPointClassPackage); + String fileName = determineFileName(assertionsEntryPointFileContent, assertionsEntryPointType); + return createAssertionsFileFor(classDescriptionSet, assertionsEntryPointFileContent, fileName, + entryPointClassPackage); + } + + private String determineFileName(String assertionsEntryPointFileContent, + AssertionsEntryPointType assertionsEntryPointType) { + // expecting the class name to be here : "class " + Matcher classNameMatcher = CLASS_NAME_PATTERN.matcher(assertionsEntryPointFileContent); + // if we find a match return it + if (classNameMatcher.find()) { + return classNameMatcher.group("CLASSNAME") + ".java"; + } + // otherwise use the default name + return assertionsEntryPointType.getFileName(); + } + + private String generateAssertionsEntryPointClassContent(final Set classDescriptionSet, + final Template entryPointAssertionsClassTemplate, + final Template entryPointAssertionMethodTemplate, + String entryPointClassPackage) { + String entryPointAssertionsClassContent = entryPointAssertionsClassTemplate.getContent(); + // resolve template markers + entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, GENERATED, determineGeneratedSignature()); + String classPackage = isEmpty(entryPointClassPackage) + ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) + : entryPointClassPackage; + entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, PACKAGE, classPackage); + + String allEntryPointsAssertionContent = generateAssertionEntryPointMethodsFor(classDescriptionSet, + entryPointAssertionMethodTemplate); + entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, ALL_ASSERTIONS_ENTRY_POINTS, + allEntryPointsAssertionContent); + return entryPointAssertionsClassContent; + } + + /** + * create the assertions entry point file, located in its package directory starting from targetBaseDirectory. + *

+ * If assertionsClassPackage is not set, we use the common base package of the given classes, if some classe are in a.b.c + * package and others in a.b.c.d, then entry point class will be in a.b.c. + *

+ * + * @param classDescriptionSet used to determine the assertions class package + * @param fileContent assertions entry point file content + * @param fileName assertions entry point file name + * @param assertionsClassPackage the entry point class package - automatically determined if null. + * @return the created assertions entry point file + * @throws IOException if file can't be created. + */ + private File createAssertionsFileFor(final Set classDescriptionSet, final String fileContent, + final String fileName, final String assertionsClassPackage) throws IOException { + String classPackage = isEmpty(assertionsClassPackage) + ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) + : assertionsClassPackage; + String assertionsDirectory = getDirectoryPathCorrespondingToPackage(classPackage); + // build any needed directories + buildDirectory(assertionsDirectory); + return createFile(fileContent, fileName, assertionsDirectory); + } + + private String generateAssertionEntryPointMethodsFor(final Set classDescriptionSet, + Template assertionEntryPointMethodTemplate) { + // sort ClassDescription according to their class name. + SortedSet sortedClassDescriptionSet = new TreeSet<>(classDescriptionSet); + // generate for each classDescription the entry point method, e.g. assertThat(MyClass) or then(MyClass) + StringBuilder allAssertThatsContentBuilder = new StringBuilder(); + final String lineSeparator = System.lineSeparator(); + for (ClassDescription classDescription : sortedClassDescriptionSet) { + String assertionEntryPointMethodContent = assertionEntryPointMethodTemplate.getContent(); + // resolve class assert (ex: PlayerAssert) + // in case of inner classes like Movie.PublicCategory, class assert will be MoviePublicCategoryAssert + assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CUSTOM_ASSERTION_CLASS, + classDescription.getFullyQualifiedAssertClassName()); + // resolve class (ex: Player) + // in case of inner classes like Movie.PublicCategory use class name with outer class i.e. Movie.PublicCategory. + assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CLASS_TO_ASSERT, + classDescription.getFullyQualifiedClassName()); + + allAssertThatsContentBuilder.append(lineSeparator).append(assertionEntryPointMethodContent); + } + return allAssertThatsContentBuilder.toString(); + } + + private String determineBestEntryPointsAssertionsClassPackage(final Set classDescriptionSet) { + if (generatedAssertionsPackage != null) { + return generatedAssertionsPackage; + } + + SortedSet packages = new TreeSet<>(ORDER_BY_INCREASING_LENGTH); + for (ClassDescription classDescription : classDescriptionSet) { + packages.add(classDescription.getPackageName()); + } + // takes the base package of all given classes assuming they all belong to a common package, i.e a.b.c. over a.b.c.d + // this can certainly be improved ... + return packages.first(); + } + + /** + * Returns the target directory path where the assertions file for given classDescription will be created. + * + * @param packageName package name + * @return the target directory path corresponding to the given package. + */ + private String getDirectoryPathCorrespondingToPackage(final String packageName) { + return targetBaseDirectory + File.separator + packageName.replace('.', File.separatorChar); + } + + private static String listNeededImports(Set typesToImport, String classPackage) { + StringBuilder imports = new StringBuilder(); + for (String type : typesToImport) { + if (isImportNeeded(type, classPackage)) { + imports.append(format(IMPORT_LINE, type, LINE_SEPARATOR)); + } + } + return imports.toString(); + } + + private static boolean isImportNeeded(String type, String classPackage) { + // no need to import type belonging to the same package + if (Objects.equals(classPackage, packageOf(type))) { + return false; + } + try { + Class clazz = Class.forName(type); + // primitive and java.lang.* are available by default + return !(clazz.isPrimitive() || isJavaLangType(clazz)); + } + catch (ClassNotFoundException e) { + // occurs for abstract types (ex: AbstractXXXAssert) or types to generate that are unknown + return true; + } + } + + protected void generateAssertionsForGettersOf(StringBuilder contentBuilder, ClassDescription classDescription) { + generateAssertionsForGetters(contentBuilder, classDescription.getGettersDescriptions(), classDescription); + } + + protected void generateAssertionsForDeclaredGettersOf(StringBuilder contentBuilder, + ClassDescription classDescription) { + generateAssertionsForGetters(contentBuilder, classDescription.getDeclaredGettersDescriptions(), classDescription); + } + + protected void generateAssertionsForGetters(StringBuilder assertionsForGetters, Set getters, + ClassDescription classDescription) { + for (GetterDescription getter : getters) { + String assertionContent = assertionContentForProperty(getter, classDescription); + assertionsForGetters.append(assertionContent).append(LINE_SEPARATOR); + } + } + + protected void generateAssertionsForFieldsOf(StringBuilder contentBuilder, ClassDescription classDescription) { + generateAssertionsForFields(contentBuilder, classDescription.getFieldsDescriptions(), classDescription); + } + + protected void generateAssertionsForDeclaredFieldsOf(StringBuilder contentBuilder, + ClassDescription classDescription) { + generateAssertionsForFields(contentBuilder, classDescription.getDeclaredFieldsDescriptions(), + classDescription); + } + + protected void generateAssertionsForFields(StringBuilder assertionsForPublicFields, + Set fields, ClassDescription classDescription) { + for (FieldDescription field : fields) { + if (generateAssertionsForAllFields || field.isPublic()) { + String assertionContent = assertionContentForField(field, classDescription); + // assertion can be empty if we have a getter for the field + if (!assertionContent.isEmpty()) { + assertionsForPublicFields.append(assertionContent).append(LINE_SEPARATOR); + } + } + } + } + + private String assertionContentForField(FieldDescription field, ClassDescription classDescription) { + + if (classDescription.hasGetterForField(field)) { + // the assertion has already been generated using the getter to read the field + return ""; + } + + final String fieldName = field.getName(); + String assertionContent = baseAssertionContentFor(field, classDescription); + + // we reuse template for properties to have consistent assertions for property and field but change the way we get + // the value since it's a field and not a property: + assertionContent = assertionContent.replace("${getter}()", PROPERTY_WITH_LOWERCASE_FIRST_CHAR); + // - remove also ${throws} and ${throws_javadoc} as it does not make sense for a field + assertionContent = remove(assertionContent, THROWS); + assertionContent = remove(assertionContent, THROWS_JAVADOC); + + if (!field.isPublic()) { + // if field is not public, we need to use reflection to get its value, ex : + // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("grade", Grade.class, actual); + assertionContent = assertionContent.replace("actual." + PROPERTY_WITH_LOWERCASE_FIRST_CHAR, + format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, + PROPERTY_WITH_LOWERCASE_FIRST_CHAR, PROPERTY_TYPE)); + } + if (field.isPredicate()) { + assertionContent = fillAssertionContentForPredicateField(field, assertionContent); + } + assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(field.getName())); + assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(field)); + assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, + field.getAssertTypeName(determinePackageName(classDescription))); + assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(field)); + assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, fieldName); + // It should not be possible to have a field that is a keyword - compiler won't allow it. + assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(fieldName)); + return assertionContent; + } + + private String getTypeName(DataDescription fieldOrGetter) { + if (generatedAssertionsPackage != null) { + // if the user has chosen to generate assertions in a given package we assume that + return fieldOrGetter.getFullyQualifiedTypeName(); + } + // returns a simple class name if the field or getter type is in the same package as its owning type which is the package + // where the + // Assert class is generated. + return fieldOrGetter.getTypeName(); + } + + private String fillAssertionContentForPredicateField(FieldDescription field, String assertionContent) { + if (field.isPublic()) { + assertionContent = assertionContent.replace("actual." + PREDICATE + "()", + "actual." + field.getOriginalMember().getName()); + } + else { + // if field is not public, we need to use reflection to get its value, ex : + // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("active", Boolean.class, actual); + assertionContent = assertionContent.replace("actual." + PREDICATE + "()", + format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, + field.getOriginalMember().getName(), "Boolean")); + } + assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, + field.getPredicateForJavadoc()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, + field.getNegativePredicateForJavadoc()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + field.getPredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + field.getPredicateForErrorMessagePart2()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + field.getNegativePredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + field.getNegativePredicateForErrorMessagePart2()); + assertionContent = replace(assertionContent, PREDICATE, field.getPredicate()); + assertionContent = replace(assertionContent, PREDICATE_NEG, field.getNegativePredicate()); + return assertionContent; + } + + static private String unclashName(String unsafe) { + return JAVA_KEYWORDS.contains(unsafe) || "actual".equals(unsafe) ? "expected" + capitalize(unsafe) : unsafe; + } + + private String assertionContentForProperty(GetterDescription getter, ClassDescription classDescription) { + String assertionContent = baseAssertionContentFor(getter, classDescription); + + assertionContent = declareExceptions(getter, assertionContent); + + String propertyName = getter.getName(); + if (getter.isPredicate()) { + assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, + getter.getPredicateForJavadoc()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, + getter.getNegativePredicateForJavadoc()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + getter.getPredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + getter.getPredicateForErrorMessagePart2()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + getter.getNegativePredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + getter.getNegativePredicateForErrorMessagePart2()); + assertionContent = replace(assertionContent, PREDICATE, getter.getOriginalMember().getName()); + assertionContent = replace(assertionContent, PREDICATE_NEG, getter.getNegativePredicate()); + } + assertionContent = replace(assertionContent, PROPERTY_GETTER_CALL, getter.getOriginalMember().getName()); + assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(propertyName)); + assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(getter)); + assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, + getter.getAssertTypeName(determinePackageName(classDescription))); + assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(getter)); + assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, propertyName); + assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(propertyName)); + return assertionContent; + } + + /** + * The assertion content that is common to field and property (getter), the specific content part is handled afterwards. + * + * @param fieldOrProperty field or property + * @return the base assertion content + */ + private String baseAssertionContentFor(DataDescription fieldOrProperty, ClassDescription classDescription) { + String assertionContent = templateRegistry.getTemplate(Type.HAS).getContent(); + if (fieldOrProperty.isPredicate()) { + Type type = determinePredicateType(fieldOrProperty, classDescription); + assertionContent = templateRegistry.getTemplate(type).getContent(); + } + else if (fieldOrProperty.isIterableType()) { + assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ITERABLE).getContent(), ELEMENT_TYPE, + fieldOrProperty.getElementTypeName()); + assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, + fieldOrProperty.getElementAssertTypeName()); + } + else if (fieldOrProperty.isArrayType()) { + assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ARRAY).getContent(), ELEMENT_TYPE, + fieldOrProperty.getElementTypeName()); + assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, + fieldOrProperty.getElementAssertTypeName()); + } + else if (fieldOrProperty.isRealNumberType()) { + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_REAL_NUMBER_WRAPPER + : Type.HAS_FOR_REAL_NUMBER; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } + else if (fieldOrProperty.isWholeNumberType()) { + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_WHOLE_NUMBER_WRAPPER + : Type.HAS_FOR_WHOLE_NUMBER; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } + else if (fieldOrProperty.isCharType()) { + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_CHARACTER : Type.HAS_FOR_CHAR; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } + else if (fieldOrProperty.isPrimitiveType()) { + // use case : boolean getFoo -> not a predicate, but a primitive valueType + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_PRIMITIVE_WRAPPER : Type.HAS_FOR_PRIMITIVE; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } + return assertionContent; + } + + /** + * Determine whether we need to generate negative predicate assertions, for example if the class contains isValid and isNotValid + * methods, we must not generate the negative assertion for isValid as it will be done when generating assertions for isNotValid + */ + private Type determinePredicateType(final DataDescription fieldOrProperty, final ClassDescription classDescription) { + if (hasAlreadyNegativePredicate(fieldOrProperty, classDescription)) { + return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER_WITHOUT_NEGATION : Type.IS_WITHOUT_NEGATION; + } + return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER : Type.IS; + } + + private boolean hasAlreadyNegativePredicate(final DataDescription fieldOrProperty, + final ClassDescription classDescription) { + for (final GetterDescription getterDescription : classDescription.getGettersDescriptions()) { + if (getterDescription.getOriginalMember().getName().equals(fieldOrProperty.getNegativePredicate())) { + return true; + } + } + return false; + } + + /** + * Handle case where getter throws an exception. + * + * @param getter method we want to declare exception for + * @param assertionContent the assertion content to enrich + * @return assertion content with thrown exceptions + */ + private String declareExceptions(GetterDescription getter, String assertionContent) { + StringBuilder throwsClause = new StringBuilder(); + StringBuilder throwsJavaDoc = new StringBuilder(); + boolean first = true; + for (TypeToken exception : getter.getExceptions()) { + if (first) { + throwsClause.append("throws "); + } + else { + throwsClause.append(", "); + } + first = false; + String exceptionName = getTypeDeclaration(exception); + throwsClause.append(exceptionName); + throwsJavaDoc.append(LINE_SEPARATOR).append(" * @throws ").append(exceptionName); + throwsJavaDoc.append(" if actual.").append("${getter}() throws one."); + } + if (!getter.getExceptions().isEmpty()) { + throwsClause.append(' '); + } + + assertionContent = assertionContent.replace(THROWS_JAVADOC, throwsJavaDoc.toString()); + assertionContent = assertionContent.replace(THROWS, throwsClause.toString()); + return assertionContent; + } + + private void fillFile(String customAssertionContent, File assertionJavaFile) throws IOException { + try (FileWriter fileWriter = new FileWriter(assertionJavaFile, false)) { + fileWriter.write(customAssertionContent); + } + } + + private File createFile(String fileContent, String fileName, String targetDirectory) throws IOException { + File file = new File(targetDirectory, fileName); + + // Ignore the result as it only returns false when the file existed previously which is not wrong. + // noinspection ResultOfMethodCallIgnored + file.createNewFile(); + fillFile(fileContent, file); + return file; + } + + private static boolean noClassDescriptionsGiven(final Set classDescriptionSet) { + return classDescriptionSet == null || classDescriptionSet.isEmpty(); + } + + private static void buildDirectory(String directoryName) { + // Ignore the result as it only returns true iff the dir was created, false is not bad. + File directory = new File(directoryName); + if (!directory.exists()) { + directory.mkdirs(); + } + } + + @Override + public void register(Template template) { + templateRegistry.register(template); + } + + public void setGeneratedAnnotationSource(GeneratedAnnotationSource generatedAnnotationSource) { + this.generatedAnnotationSource = generatedAnnotationSource; + } } diff --git a/src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java b/src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java new file mode 100644 index 0000000..2cae575 --- /dev/null +++ b/src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java @@ -0,0 +1,19 @@ +/* + * 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. + * + * Copyright 2012-2021 the original author or authors. + */ +package org.assertj.assertions.generator; + +public enum GeneratedAnnotationSource { + JAVAX, + JAKARTA, + NONE; +} diff --git a/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt index 7bde364..834f41e 100644 --- a/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt @@ -19,7 +19,7 @@ package ${package}; * } * */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class AutoCloseableSoftAssertions extends org.assertj.core.api.SoftAssertions implements AutoCloseable { ${all_assertions_entry_points} @Override diff --git a/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt b/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt index e8ac7e3..a4bd26c 100644 --- a/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for BDD assertions of different data types. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class BddAssertions { ${all_assertions_entry_points} /** diff --git a/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt index 7f1a146..f23c3e2 100644 --- a/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for BDD soft assertions of different data types. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class BDDSoftAssertions extends org.assertj.core.api.BDDSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/custom_abstract_assertion_class_template.txt b/src/main/resources/templates/custom_abstract_assertion_class_template.txt index 662ff1b..ed444ca 100644 --- a/src/main/resources/templates/custom_abstract_assertion_class_template.txt +++ b/src/main/resources/templates/custom_abstract_assertion_class_template.txt @@ -3,7 +3,7 @@ ${imports} /** * Abstract base class for {@link ${class_to_assert}} specific assertions - Generated by CustomAssertionGenerator. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public abstract class ${custom_assertion_class}, A extends ${class_to_assert}> extends ${super_assertion_class} { /** diff --git a/src/main/resources/templates/custom_assertion_class_template.txt b/src/main/resources/templates/custom_assertion_class_template.txt index e638dd4..eaedcc0 100644 --- a/src/main/resources/templates/custom_assertion_class_template.txt +++ b/src/main/resources/templates/custom_assertion_class_template.txt @@ -3,7 +3,7 @@ ${imports} /** * {@link ${class_to_assert}} specific assertions - Generated by CustomAssertionGenerator. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class ${custom_assertion_class} extends AbstractObjectAssert<${custom_assertion_class}, ${class_to_assert}> { /** diff --git a/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt b/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt index d5230d2..1ebc6d1 100644 --- a/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt +++ b/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt @@ -6,7 +6,7 @@ ${imports} * Although this class is not final to allow Soft assertions proxy, if you wish to extend it, * extend {@link Abstract${custom_assertion_class}} instead. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class ${custom_assertion_class} extends Abstract${custom_assertion_class}<${custom_assertion_class}, ${class_to_assert}> { /** diff --git a/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt index 103e18b..ba97404 100644 --- a/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt @@ -18,7 +18,7 @@ package ${package}; * } * } */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class JUnitBDDSoftAssertions extends org.assertj.core.api.JUnitBDDSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt index 1a0a6ed..c8cbb2d 100644 --- a/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt @@ -18,7 +18,7 @@ package ${package}; * } * } */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class JUnitSoftAssertions extends org.assertj.core.api.JUnitSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/soft_assertions_entry_point_class_template.txt index 7b1b0d4..27ef2a9 100644 --- a/src/main/resources/templates/soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/soft_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for soft assertions of different data types. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class SoftAssertions extends org.assertj.core.api.SoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/standard_assertions_entry_point_class_template.txt b/src/main/resources/templates/standard_assertions_entry_point_class_template.txt index a264498..9d6904a 100644 --- a/src/main/resources/templates/standard_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/standard_assertions_entry_point_class_template.txt @@ -4,7 +4,7 @@ package ${package}; * Entry point for assertions of different data types. Each method in this class is a static factory for the * type-specific assertion objects. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class Assertions { ${all_assertions_entry_points} /** diff --git a/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java b/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java index 3d2cc94..836535b 100644 --- a/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java +++ b/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java @@ -80,7 +80,7 @@ public void should_generate_correctly_standard_assertions_entry_point_class_for_ STANDARD, "org"); // THEN String expectedContent = readExpectedContentFromFile("AssertionsForClassesWithSameName.expected.txt"); - assertThat(assertionsEntryPointContent).isEqualTo(expectedContent); + assertThat(assertionsEntryPointContent).isEqualToIgnoringNewLines(expectedContent); } @Test diff --git a/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java b/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java index 74d9084..90d87d6 100644 --- a/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java +++ b/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java @@ -213,7 +213,7 @@ private static String getClasspathFromClassloader(ClassLoader currentClassloader while (true) { // We only know how to extract classpaths from URLClassloaders. if (currentClassloader instanceof URLClassLoader) classloaders.add((URLClassLoader) currentClassloader); - else throw new IllegalArgumentException("Classpath for compilation could not be extracted as classloader is not a URLClassloader"); +// else throw new IllegalArgumentException("Classpath for compilation could not be extracted as classloader is not a URLClassloader"); if (currentClassloader == systemClassLoader) break; else currentClassloader = currentClassloader.getParent(); diff --git a/src/test/resources/customtemplates/my_assertion_entry_point_class.txt b/src/test/resources/customtemplates/my_assertion_entry_point_class.txt index fc210ab..fac28b4 100644 --- a/src/test/resources/customtemplates/my_assertion_entry_point_class.txt +++ b/src/test/resources/customtemplates/my_assertion_entry_point_class.txt @@ -4,7 +4,7 @@ package ${package}; * Entry point for assertions of different data types. Each method in this class is a static factory for the * type-specific assertion objects. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class MyAssertions { ${all_assertions_entry_points} /** From e592a13bc376e57668846f64f07112c675c49f2e Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Thu, 20 Jan 2022 15:59:41 +0100 Subject: [PATCH 2/4] Revert "#194: Make the Generated annotation configurable. You can now choose between javax.annotation.Generated and jakarta.annotation.Generated. Or if you really do not want any no annotation (not recommended)." This reverts commit 3d0a7eb6477d1b83acf143472c4b2ae46e1a2392. --- pom.xml | 6 - .../generator/BaseAssertionGenerator.java | 1607 ++++++++--------- .../generator/GeneratedAnnotationSource.java | 19 - ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ...stom_abstract_assertion_class_template.txt | 2 +- .../custom_assertion_class_template.txt | 2 +- ..._hierarchical_assertion_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- .../AssertionsEntryPointGeneratorTest.java | 2 +- .../generator/GenerationPathHandler.java | 2 +- .../my_assertion_entry_point_class.txt | 2 +- 16 files changed, 787 insertions(+), 871 deletions(-) delete mode 100644 src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java diff --git a/pom.xml b/pom.xml index 77dfb55..b39bee3 100644 --- a/pom.xml +++ b/pom.xml @@ -51,12 +51,6 @@ 4.11 test
- - javax.annotation - javax.annotation-api - 1.3.2 - test - commons-io diff --git a/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java b/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java index d103343..ca96069 100644 --- a/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java +++ b/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java @@ -20,8 +20,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.remove; import static org.apache.commons.lang3.StringUtils.replace; -import static org.assertj.assertions.generator.GeneratedAnnotationSource.JAKARTA; -import static org.assertj.assertions.generator.GeneratedAnnotationSource.JAVAX; import static org.assertj.assertions.generator.Template.Type.ABSTRACT_ASSERT_CLASS; import static org.assertj.assertions.generator.Template.Type.ASSERT_CLASS; import static org.assertj.assertions.generator.Template.Type.HIERARCHICAL_ASSERT_CLASS; @@ -55,835 +53,778 @@ public class BaseAssertionGenerator implements AssertionGenerator, AssertionsEntryPointGenerator { - static final String TEMPLATES_DIR = "templates" + File.separator; - private static final String GENERATED = "${generatedAnnotation}"; - private static final String IMPORT_LINE = "import %s;%s"; - private static final String PREDICATE = "${predicate}"; - private static final String PREDICATE_NEG = "${neg_predicate}"; - private static final String PREDICATE_FOR_JAVADOC = "${predicate_for_javadoc}"; - private static final String NEGATIVE_PREDICATE_FOR_JAVADOC = "${negative_predicate_for_javadoc}"; - private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = "${predicate_for_error_message_part1}"; - private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = "${predicate_for_error_message_part2}"; - private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = - "${negative_predicate_for_error_message_part1}"; - private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = - "${negative_predicate_for_error_message_part2}"; - private static final String PROPERTY_WITH_UPPERCASE_FIRST_CHAR = "${Property}"; - private static final String PROPERTY_GETTER_CALL = "${getter}"; - private static final String PROPERTY_WITH_LOWERCASE_FIRST_CHAR = "${property}"; - private static final String PROPERTY_WITH_SAFE = "${property_safe}"; - private static final String PACKAGE = "${package}"; - private static final String PROPERTY_TYPE = "${propertyType}"; - private static final String PROPERTY_SIMPLE_TYPE = "${propertySimpleType}"; - private static final String PROPERTY_ASSERT_TYPE = "${propertyAssertType}"; - private static final String CLASS_TO_ASSERT = "${class_to_assert}"; - private static final String CUSTOM_ASSERTION_CLASS = "${custom_assertion_class}"; - private static final String ABSTRACT_SUPER_ASSERTION_CLASS = "${super_assertion_class}"; - private static final String SELF_TYPE = "${self_type}"; - private static final String MYSELF = "${myself}"; - private static final String ELEMENT_TYPE = "${elementType}"; - private static final String ELEMENT_ASSERT_TYPE = "${elementAssertType}"; - private static final String ALL_ASSERTIONS_ENTRY_POINTS = "${all_assertions_entry_points}"; - private static final String IMPORTS = "${imports}"; - private static final String THROWS = "${throws}"; - private static final String THROWS_JAVADOC = "${throws_javadoc}"; - private static final String LINE_SEPARATOR = "\n"; - - private static final Comparator ORDER_BY_INCREASING_LENGTH = Comparator.comparingInt(String::length); - - private static final Set JAVA_KEYWORDS = newHashSet("abstract", - "assert", - "boolean", - "break", - "byte", - "case", - "catch", - "char", - // This one's not strictly required because you can't have - // a property called "class" - "class", - "const", - "continue", - "default", - "do", - "double", - "else", - "enum", - "extends", - "false", - "final", - "finally", - "float", - "for", - "goto", - "if", - "implements", - "import", - "instanceof", - "int", - "interface", - "long", - "native", - "new", - "null", - "package", - "protected", - "private", - "public", - "return", - "short", - "static", - "strictfp", - "super", - "switch", - "synchronized", - "this", - "throw", - "throws", - "transient", - "true", - "try", - "void", - "volatile", - "while"); - - /** - * This regexp shall match a java class's name inside an user template. - *

- * For this, we use the two character class {@code javaJavaIdentifierStart} and {@code javaJavaIdentifierPart} to match a valid - * name. - *

- * (?m)^public class is needed to match the class at the beginning of a line and avoid matching "public class" - * inside javadoc comment as in templates/junit_soft_assertions_entry_point_class_template.txt. - *

- * Description of the pattern: - *

    - *
  1. (?m)^ beginning of line in multi-line mode
  2. - *
  3. public class[\\s]+ the "public class" followed by one or more whitespace (either tabs, space or new - * lines).
  4. - *
  5. (?<CLASSNAME>...) create a named group that would match a Java identifier (here the class name).
  6. - *
  7. \\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}* match said identifier using character class.
  8. - *
- * - * @see java.util.regex.Pattern - * @see Character#isJavaIdentifierStart - * @see Character#isJavaIdentifierPart - */ - private static final Pattern CLASS_NAME_PATTERN = Pattern - .compile("(?m)^public class[\\s]+(?\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)\\b"); - - private static final Set> EMPTY_HIERARCHY = new HashSet<>(); - - private static final String NON_PUBLIC_FIELD_VALUE_EXTRACTION = - "org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue(\"%s\", %s.class, actual)"; - // S is used in custom_abstract_assertion_class_template.txt - private static final String ABSTRACT_ASSERT_SELF_TYPE = "S"; - - // assertions classes are generated in their package directory starting from targetBaseDirectory. - // ex : com.nba.Player -> targetBaseDirectory/com/nba/PlayerAssert.java - private File targetBaseDirectory = Paths.get(".").toFile(); - private final TemplateRegistry templateRegistry;// the pattern to search for - private boolean generateAssertionsForAllFields = false; - private String generatedAssertionsPackage = null; - private GeneratedAnnotationSource generatedAnnotationSource = JAVAX; - - /** - * Creates a new {@link BaseAssertionGenerator} with default templates directory. - * - * @throws IOException if some template file could not be found or read - */ - public BaseAssertionGenerator() throws IOException { - this(TEMPLATES_DIR); - } - - /** - * Creates a new {@link BaseAssertionGenerator} with the templates from the given directory. - * - * @param templatesDirectory path where to find templates - * @throws IOException if some template file could not be found or read - */ - public BaseAssertionGenerator(String templatesDirectory) throws IOException { - templateRegistry = DefaultTemplateRegistryProducer.create(templatesDirectory); - } - - public void setDirectoryWhereAssertionFilesAreGenerated(File targetBaseDirectory) { - this.targetBaseDirectory = targetBaseDirectory; - } - - public void setGenerateAssertionsForAllFields(boolean generateAssertionsForAllFields) { - this.generateAssertionsForAllFields = generateAssertionsForAllFields; - } - - public void setGeneratedAssertionsPackage(String generatedAssertionsPackage) { - checkGivenPackageIsValid(generatedAssertionsPackage); - this.generatedAssertionsPackage = generatedAssertionsPackage; - } - - private void checkGivenPackageIsValid(String generatedAssertionsPackage) { - Validate.isTrue(isNotBlank(generatedAssertionsPackage), "The given package '%s' must not be blank", - generatedAssertionsPackage); - Validate.isTrue(!containsWhitespace(generatedAssertionsPackage), - "The given package '%s' must not contain blank character", - generatedAssertionsPackage); - } - - @Override - public File generateCustomAssertionFor(ClassDescription classDescription) throws IOException { - // Assertion content - String assertionFileContent = generateCustomAssertionContentFor(classDescription); - // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package - String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); - buildDirectory(directoryWhereToCreateAssertFiles); - return createFile(assertionFileContent, classDescription.getAssertClassFilename(), directoryWhereToCreateAssertFiles); - } - - private String getDirectoryWhereToCreateAssertFilesFor(ClassDescription classDescription) { - return getDirectoryPathCorrespondingToPackage(determinePackageName(classDescription)); - } - - @Override - public File[] generateHierarchicalCustomAssertionFor(ClassDescription classDescription, - Set> allClasses) throws IOException { - // Assertion content - String[] assertionFileContent = generateHierarchicalCustomAssertionContentFor(classDescription, allClasses); - // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package - String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); - buildDirectory(directoryWhereToCreateAssertFiles); - // create assertion files - File[] assertionClassFiles = new File[2]; - final String concreteAssertClassFileName = classDescription.getAssertClassFilename(); - final String abstractAssertClassFileName = classDescription.getAbstractAssertClassFilename(); - assertionClassFiles[0] = - createFile(assertionFileContent[0], abstractAssertClassFileName, directoryWhereToCreateAssertFiles); - assertionClassFiles[1] = - createFile(assertionFileContent[1], concreteAssertClassFileName, directoryWhereToCreateAssertFiles); - return assertionClassFiles; - } - - @Override - public String[] generateHierarchicalCustomAssertionContentFor(ClassDescription classDescription, - Set> classes) { - // use abstract class template first - String abstractAssertClassContent = templateRegistry.getTemplate(ABSTRACT_ASSERT_CLASS).getContent(); - StringBuilder abstractAssertClassContentBuilder = new StringBuilder(abstractAssertClassContent); - - // generate assertion method for each property with a public getter or field - generateAssertionsForDeclaredGettersOf(abstractAssertClassContentBuilder, classDescription); - generateAssertionsForDeclaredFieldsOf(abstractAssertClassContentBuilder, classDescription); - - // close class with } - abstractAssertClassContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); - - // use concrete class template for the subclass of the generated abstract assert - String concreteAssertClassContent = templateRegistry.getTemplate(HIERARCHICAL_ASSERT_CLASS).getContent(); - - // return a String array with the actual generated content of the assertion class hierarchy - String[] assertionClassesContent = new String[2]; - assertionClassesContent[0] = - fillAbstractAssertClassTemplate(abstractAssertClassContentBuilder.toString(), classDescription, - classes); - assertionClassesContent[1] = fillConcreteAssertClassTemplate(concreteAssertClassContent, classDescription); - return assertionClassesContent; - } - - private String switchToComparableAssertIfPossible(String content, ClassDescription classDescription) { - return classDescription.implementsComparable() - ? replace(content, "AbstractObjectAssert", "AbstractComparableAssert") - : content; - } - - private String fillAbstractAssertClassTemplate(String abstractAssertClassTemplate, ClassDescription classDescription, - Set> classes) { - return fillAssertClassTemplate(abstractAssertClassTemplate, classDescription, classes, false); - } - - private String fillAssertClassTemplate(String template, ClassDescription classDescription, - Set> classesHierarchy, boolean concrete) { - // Add any AssertJ needed imports only, other types are used with their fully qualified names to avoid a compilation - // error when two types have the same name. - TreeSet classesToImport = new TreeSet<>(); - // we import the class to assert in case the generated assertions are put in a different package than the class to assert, - // listNeededImports will remove it if if was not needed. - // in case of nested class, we must only import the outer class ! - classesToImport.add(classDescription.getFullyQualifiedOuterClassName()); - if (template.contains("Assertions.")) { - classesToImport.add("org.assertj.core.api.Assertions"); - } - if (template.contains("Objects.")) { - int totalObjects = StringUtils.countMatches(template, "Objects."); - int areEqualObjects = StringUtils.countMatches(template, "Objects.deepEquals"); - int areEqualArraysObjects = StringUtils.countMatches(template, "Objects.deepEqualsArrays"); - - int totalDeprecated = areEqualObjects + areEqualArraysObjects; - - if (totalDeprecated > 0) { - classesToImport.add("java.util.Objects"); - } - - if (totalObjects > totalDeprecated) { - classesToImport.add("org.assertj.core.util.Objects"); - } - } - if (template.contains("Iterables.")) { - classesToImport.add("org.assertj.core.internal.Iterables"); - } - - // Add assertion supertype to imports if needed (for abstract assertions hierarchy) - // we need a FQN if the parent class is in a different package than the child class, if not listNeededImports will optimize - // it - final String parentAssertClassName = classesHierarchy.contains(classDescription.getSuperType()) - ? classDescription.getFullyQualifiedParentAssertClassName() - : "org.assertj.core.api.AbstractObjectAssert"; - if (classesHierarchy.contains(classDescription.getSuperType())) { - classesToImport.add(parentAssertClassName); - } - - final String customAssertionClass = concrete ? classDescription.getAssertClassName() - : classDescription.getAbstractAssertClassName(); - final String selfType = concrete ? customAssertionClass : ABSTRACT_ASSERT_SELF_TYPE; - final String myself = concrete ? "this" : "myself"; - - template = replace(template, GENERATED, determineGeneratedSignature()); - template = replace(template, PACKAGE, determinePackageName(classDescription)); - template = replace(template, CUSTOM_ASSERTION_CLASS, customAssertionClass); - // use a simple parent class name as we have already imported it - // className could be a nested class like "OuterClass.NestedClass", in that case assert class will be OuterClassNestedClass - template = replace(template, ABSTRACT_SUPER_ASSERTION_CLASS, getTypeNameWithoutDots(parentAssertClassName)); - if (template.contains("AbstractObjectAssert")) { - classesToImport.add("org.assertj.core.api.AbstractObjectAssert"); - } - - template = replace(template, CLASS_TO_ASSERT, classDescription.getClassNameWithOuterClass()); - template = replace(template, SELF_TYPE, selfType); - template = replace(template, MYSELF, myself); - String neededImports = listNeededImports(classesToImport, determinePackageName(classDescription)); - template = replace(template, IMPORTS, neededImports.isEmpty() ? "" : LINE_SEPARATOR + neededImports); - - // in case the domain class is Comparable we want the assert class to inherit from AbstractComparableAssert - template = switchToComparableAssertIfPossible(template, classDescription); - - return template; - } - - private String determineGeneratedSignature() { - if (generatedAnnotationSource == JAVAX) { - return "@javax.annotation.Generated(value=\"assertj-assertions-generator\")"; - } - if (generatedAnnotationSource == JAKARTA) { - return "@jakarta.annotation.Generated(value=\"assertj-assertions-generator\")"; - } - return ""; - } - - private String determinePackageName(ClassDescription classDescription) { - return generatedAssertionsPackage == null ? classDescription.getPackageName() : generatedAssertionsPackage; - } - - private String fillConcreteAssertClassTemplate(String template, ClassDescription classDescription) { - return fillAssertClassTemplate(template, classDescription, EMPTY_HIERARCHY, true); - } - - @Override - public String generateCustomAssertionContentFor(ClassDescription classDescription) { - - // use class template first - String classTemplateContent = templateRegistry.getTemplate(ASSERT_CLASS).getContent(); - StringBuilder assertionFileContentBuilder = new StringBuilder(classTemplateContent); - - // generate assertion method for each property with a public getter - generateAssertionsForGettersOf(assertionFileContentBuilder, classDescription); - generateAssertionsForFieldsOf(assertionFileContentBuilder, classDescription); - - // close class with } - assertionFileContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); - - return fillConcreteAssertClassTemplate(assertionFileContentBuilder.toString(), classDescription); - } - - @Override - public String generateAssertionsEntryPointClassContentFor(final Set classDescriptionSet, - AssertionsEntryPointType assertionsEntryPointType, - String entryPointClassPackage) { - if (noClassDescriptionsGiven(classDescriptionSet)) { - return ""; - } - Template assertionEntryPointMethodTemplate = chooseAssertionEntryPointMethodTemplate(assertionsEntryPointType); - Template assertionsEntryPointClassTemplate = chooseAssertionEntryPointClassTemplate(assertionsEntryPointType); - return generateAssertionsEntryPointClassContent(classDescriptionSet, assertionsEntryPointClassTemplate, - assertionEntryPointMethodTemplate, entryPointClassPackage); - } - - private Template chooseAssertionEntryPointMethodTemplate(final AssertionsEntryPointType assertionsEntryPointType) { - switch (assertionsEntryPointType) { - case SOFT: - case JUNIT_SOFT: - case AUTO_CLOSEABLE_SOFT: - return templateRegistry.getTemplate(Type.SOFT_ENTRY_POINT_METHOD_ASSERTION); - case BDD: - return templateRegistry.getTemplate(Type.BDD_ENTRY_POINT_METHOD_ASSERTION); - case BDD_SOFT: - case JUNIT_BDD_SOFT: - case AUTO_CLOSEABLE_BDD_SOFT: - return templateRegistry.getTemplate(Type.BDD_SOFT_ENTRY_POINT_METHOD_ASSERTION); - default: - return templateRegistry.getTemplate(Type.ASSERTION_ENTRY_POINT); - } - } - - private Template chooseAssertionEntryPointClassTemplate(final AssertionsEntryPointType assertionsEntryPointType) { - switch (assertionsEntryPointType) { - case SOFT: - return templateRegistry.getTemplate(Type.SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case JUNIT_SOFT: - return templateRegistry.getTemplate(Type.JUNIT_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case AUTO_CLOSEABLE_SOFT: - return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case BDD: - return templateRegistry.getTemplate(Type.BDD_ASSERTIONS_ENTRY_POINT_CLASS); - case BDD_SOFT: - return templateRegistry.getTemplate(Type.BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case JUNIT_BDD_SOFT: - return templateRegistry.getTemplate(Type.JUNIT_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - case AUTO_CLOSEABLE_BDD_SOFT: - return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); - default: - return templateRegistry.getTemplate(Type.ASSERTIONS_ENTRY_POINT_CLASS); - } - } - - @Override - public File generateAssertionsEntryPointClassFor(final Set classDescriptionSet, - AssertionsEntryPointType assertionsEntryPointType, - String entryPointClassPackage) throws IOException { - if (noClassDescriptionsGiven(classDescriptionSet)) { - return null; - } - String assertionsEntryPointFileContent = generateAssertionsEntryPointClassContentFor(classDescriptionSet, - assertionsEntryPointType, - entryPointClassPackage); - String fileName = determineFileName(assertionsEntryPointFileContent, assertionsEntryPointType); - return createAssertionsFileFor(classDescriptionSet, assertionsEntryPointFileContent, fileName, - entryPointClassPackage); - } - - private String determineFileName(String assertionsEntryPointFileContent, - AssertionsEntryPointType assertionsEntryPointType) { - // expecting the class name to be here : "class " - Matcher classNameMatcher = CLASS_NAME_PATTERN.matcher(assertionsEntryPointFileContent); - // if we find a match return it - if (classNameMatcher.find()) { - return classNameMatcher.group("CLASSNAME") + ".java"; - } - // otherwise use the default name - return assertionsEntryPointType.getFileName(); - } - - private String generateAssertionsEntryPointClassContent(final Set classDescriptionSet, - final Template entryPointAssertionsClassTemplate, - final Template entryPointAssertionMethodTemplate, - String entryPointClassPackage) { - String entryPointAssertionsClassContent = entryPointAssertionsClassTemplate.getContent(); - // resolve template markers - entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, GENERATED, determineGeneratedSignature()); - String classPackage = isEmpty(entryPointClassPackage) - ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) - : entryPointClassPackage; - entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, PACKAGE, classPackage); - - String allEntryPointsAssertionContent = generateAssertionEntryPointMethodsFor(classDescriptionSet, - entryPointAssertionMethodTemplate); - entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, ALL_ASSERTIONS_ENTRY_POINTS, - allEntryPointsAssertionContent); - return entryPointAssertionsClassContent; - } - - /** - * create the assertions entry point file, located in its package directory starting from targetBaseDirectory. - *

- * If assertionsClassPackage is not set, we use the common base package of the given classes, if some classe are in a.b.c - * package and others in a.b.c.d, then entry point class will be in a.b.c. - *

- * - * @param classDescriptionSet used to determine the assertions class package - * @param fileContent assertions entry point file content - * @param fileName assertions entry point file name - * @param assertionsClassPackage the entry point class package - automatically determined if null. - * @return the created assertions entry point file - * @throws IOException if file can't be created. - */ - private File createAssertionsFileFor(final Set classDescriptionSet, final String fileContent, - final String fileName, final String assertionsClassPackage) throws IOException { - String classPackage = isEmpty(assertionsClassPackage) - ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) - : assertionsClassPackage; - String assertionsDirectory = getDirectoryPathCorrespondingToPackage(classPackage); - // build any needed directories - buildDirectory(assertionsDirectory); - return createFile(fileContent, fileName, assertionsDirectory); - } - - private String generateAssertionEntryPointMethodsFor(final Set classDescriptionSet, - Template assertionEntryPointMethodTemplate) { - // sort ClassDescription according to their class name. - SortedSet sortedClassDescriptionSet = new TreeSet<>(classDescriptionSet); - // generate for each classDescription the entry point method, e.g. assertThat(MyClass) or then(MyClass) - StringBuilder allAssertThatsContentBuilder = new StringBuilder(); - final String lineSeparator = System.lineSeparator(); - for (ClassDescription classDescription : sortedClassDescriptionSet) { - String assertionEntryPointMethodContent = assertionEntryPointMethodTemplate.getContent(); - // resolve class assert (ex: PlayerAssert) - // in case of inner classes like Movie.PublicCategory, class assert will be MoviePublicCategoryAssert - assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CUSTOM_ASSERTION_CLASS, - classDescription.getFullyQualifiedAssertClassName()); - // resolve class (ex: Player) - // in case of inner classes like Movie.PublicCategory use class name with outer class i.e. Movie.PublicCategory. - assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CLASS_TO_ASSERT, - classDescription.getFullyQualifiedClassName()); - - allAssertThatsContentBuilder.append(lineSeparator).append(assertionEntryPointMethodContent); - } - return allAssertThatsContentBuilder.toString(); - } - - private String determineBestEntryPointsAssertionsClassPackage(final Set classDescriptionSet) { - if (generatedAssertionsPackage != null) { - return generatedAssertionsPackage; - } - - SortedSet packages = new TreeSet<>(ORDER_BY_INCREASING_LENGTH); - for (ClassDescription classDescription : classDescriptionSet) { - packages.add(classDescription.getPackageName()); - } - // takes the base package of all given classes assuming they all belong to a common package, i.e a.b.c. over a.b.c.d - // this can certainly be improved ... - return packages.first(); - } - - /** - * Returns the target directory path where the assertions file for given classDescription will be created. - * - * @param packageName package name - * @return the target directory path corresponding to the given package. - */ - private String getDirectoryPathCorrespondingToPackage(final String packageName) { - return targetBaseDirectory + File.separator + packageName.replace('.', File.separatorChar); - } - - private static String listNeededImports(Set typesToImport, String classPackage) { - StringBuilder imports = new StringBuilder(); - for (String type : typesToImport) { - if (isImportNeeded(type, classPackage)) { - imports.append(format(IMPORT_LINE, type, LINE_SEPARATOR)); - } - } - return imports.toString(); - } - - private static boolean isImportNeeded(String type, String classPackage) { - // no need to import type belonging to the same package - if (Objects.equals(classPackage, packageOf(type))) { - return false; - } - try { - Class clazz = Class.forName(type); - // primitive and java.lang.* are available by default - return !(clazz.isPrimitive() || isJavaLangType(clazz)); - } - catch (ClassNotFoundException e) { - // occurs for abstract types (ex: AbstractXXXAssert) or types to generate that are unknown - return true; - } - } - - protected void generateAssertionsForGettersOf(StringBuilder contentBuilder, ClassDescription classDescription) { - generateAssertionsForGetters(contentBuilder, classDescription.getGettersDescriptions(), classDescription); - } - - protected void generateAssertionsForDeclaredGettersOf(StringBuilder contentBuilder, - ClassDescription classDescription) { - generateAssertionsForGetters(contentBuilder, classDescription.getDeclaredGettersDescriptions(), classDescription); - } - - protected void generateAssertionsForGetters(StringBuilder assertionsForGetters, Set getters, - ClassDescription classDescription) { - for (GetterDescription getter : getters) { - String assertionContent = assertionContentForProperty(getter, classDescription); - assertionsForGetters.append(assertionContent).append(LINE_SEPARATOR); - } - } - - protected void generateAssertionsForFieldsOf(StringBuilder contentBuilder, ClassDescription classDescription) { - generateAssertionsForFields(contentBuilder, classDescription.getFieldsDescriptions(), classDescription); - } - - protected void generateAssertionsForDeclaredFieldsOf(StringBuilder contentBuilder, - ClassDescription classDescription) { - generateAssertionsForFields(contentBuilder, classDescription.getDeclaredFieldsDescriptions(), - classDescription); - } - - protected void generateAssertionsForFields(StringBuilder assertionsForPublicFields, - Set fields, ClassDescription classDescription) { - for (FieldDescription field : fields) { - if (generateAssertionsForAllFields || field.isPublic()) { - String assertionContent = assertionContentForField(field, classDescription); - // assertion can be empty if we have a getter for the field - if (!assertionContent.isEmpty()) { - assertionsForPublicFields.append(assertionContent).append(LINE_SEPARATOR); - } - } - } - } - - private String assertionContentForField(FieldDescription field, ClassDescription classDescription) { - - if (classDescription.hasGetterForField(field)) { - // the assertion has already been generated using the getter to read the field - return ""; - } - - final String fieldName = field.getName(); - String assertionContent = baseAssertionContentFor(field, classDescription); - - // we reuse template for properties to have consistent assertions for property and field but change the way we get - // the value since it's a field and not a property: - assertionContent = assertionContent.replace("${getter}()", PROPERTY_WITH_LOWERCASE_FIRST_CHAR); - // - remove also ${throws} and ${throws_javadoc} as it does not make sense for a field - assertionContent = remove(assertionContent, THROWS); - assertionContent = remove(assertionContent, THROWS_JAVADOC); - - if (!field.isPublic()) { - // if field is not public, we need to use reflection to get its value, ex : - // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("grade", Grade.class, actual); - assertionContent = assertionContent.replace("actual." + PROPERTY_WITH_LOWERCASE_FIRST_CHAR, - format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, - PROPERTY_WITH_LOWERCASE_FIRST_CHAR, PROPERTY_TYPE)); - } - if (field.isPredicate()) { - assertionContent = fillAssertionContentForPredicateField(field, assertionContent); - } - assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(field.getName())); - assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(field)); - assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, - field.getAssertTypeName(determinePackageName(classDescription))); - assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(field)); - assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, fieldName); - // It should not be possible to have a field that is a keyword - compiler won't allow it. - assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(fieldName)); - return assertionContent; - } - - private String getTypeName(DataDescription fieldOrGetter) { - if (generatedAssertionsPackage != null) { - // if the user has chosen to generate assertions in a given package we assume that - return fieldOrGetter.getFullyQualifiedTypeName(); - } - // returns a simple class name if the field or getter type is in the same package as its owning type which is the package - // where the - // Assert class is generated. - return fieldOrGetter.getTypeName(); - } - - private String fillAssertionContentForPredicateField(FieldDescription field, String assertionContent) { - if (field.isPublic()) { - assertionContent = assertionContent.replace("actual." + PREDICATE + "()", - "actual." + field.getOriginalMember().getName()); - } - else { - // if field is not public, we need to use reflection to get its value, ex : - // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("active", Boolean.class, actual); - assertionContent = assertionContent.replace("actual." + PREDICATE + "()", - format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, - field.getOriginalMember().getName(), "Boolean")); - } - assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, - field.getPredicateForJavadoc()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, - field.getNegativePredicateForJavadoc()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - field.getPredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - field.getPredicateForErrorMessagePart2()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - field.getNegativePredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - field.getNegativePredicateForErrorMessagePart2()); - assertionContent = replace(assertionContent, PREDICATE, field.getPredicate()); - assertionContent = replace(assertionContent, PREDICATE_NEG, field.getNegativePredicate()); - return assertionContent; - } - - static private String unclashName(String unsafe) { - return JAVA_KEYWORDS.contains(unsafe) || "actual".equals(unsafe) ? "expected" + capitalize(unsafe) : unsafe; - } - - private String assertionContentForProperty(GetterDescription getter, ClassDescription classDescription) { - String assertionContent = baseAssertionContentFor(getter, classDescription); - - assertionContent = declareExceptions(getter, assertionContent); - - String propertyName = getter.getName(); - if (getter.isPredicate()) { - assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, - getter.getPredicateForJavadoc()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, - getter.getNegativePredicateForJavadoc()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - getter.getPredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - getter.getPredicateForErrorMessagePart2()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, - getter.getNegativePredicateForErrorMessagePart1()); - assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, - getter.getNegativePredicateForErrorMessagePart2()); - assertionContent = replace(assertionContent, PREDICATE, getter.getOriginalMember().getName()); - assertionContent = replace(assertionContent, PREDICATE_NEG, getter.getNegativePredicate()); - } - assertionContent = replace(assertionContent, PROPERTY_GETTER_CALL, getter.getOriginalMember().getName()); - assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(propertyName)); - assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(getter)); - assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, - getter.getAssertTypeName(determinePackageName(classDescription))); - assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(getter)); - assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, propertyName); - assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(propertyName)); - return assertionContent; - } - - /** - * The assertion content that is common to field and property (getter), the specific content part is handled afterwards. - * - * @param fieldOrProperty field or property - * @return the base assertion content - */ - private String baseAssertionContentFor(DataDescription fieldOrProperty, ClassDescription classDescription) { - String assertionContent = templateRegistry.getTemplate(Type.HAS).getContent(); - if (fieldOrProperty.isPredicate()) { - Type type = determinePredicateType(fieldOrProperty, classDescription); - assertionContent = templateRegistry.getTemplate(type).getContent(); - } - else if (fieldOrProperty.isIterableType()) { - assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ITERABLE).getContent(), ELEMENT_TYPE, - fieldOrProperty.getElementTypeName()); - assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, - fieldOrProperty.getElementAssertTypeName()); - } - else if (fieldOrProperty.isArrayType()) { - assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ARRAY).getContent(), ELEMENT_TYPE, - fieldOrProperty.getElementTypeName()); - assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, - fieldOrProperty.getElementAssertTypeName()); - } - else if (fieldOrProperty.isRealNumberType()) { - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_REAL_NUMBER_WRAPPER - : Type.HAS_FOR_REAL_NUMBER; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } - else if (fieldOrProperty.isWholeNumberType()) { - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_WHOLE_NUMBER_WRAPPER - : Type.HAS_FOR_WHOLE_NUMBER; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } - else if (fieldOrProperty.isCharType()) { - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_CHARACTER : Type.HAS_FOR_CHAR; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } - else if (fieldOrProperty.isPrimitiveType()) { - // use case : boolean getFoo -> not a predicate, but a primitive valueType - Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_PRIMITIVE_WRAPPER : Type.HAS_FOR_PRIMITIVE; - assertionContent = templateRegistry.getTemplate(type).getContent(); - } - return assertionContent; - } - - /** - * Determine whether we need to generate negative predicate assertions, for example if the class contains isValid and isNotValid - * methods, we must not generate the negative assertion for isValid as it will be done when generating assertions for isNotValid - */ - private Type determinePredicateType(final DataDescription fieldOrProperty, final ClassDescription classDescription) { - if (hasAlreadyNegativePredicate(fieldOrProperty, classDescription)) { - return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER_WITHOUT_NEGATION : Type.IS_WITHOUT_NEGATION; - } - return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER : Type.IS; - } - - private boolean hasAlreadyNegativePredicate(final DataDescription fieldOrProperty, - final ClassDescription classDescription) { - for (final GetterDescription getterDescription : classDescription.getGettersDescriptions()) { - if (getterDescription.getOriginalMember().getName().equals(fieldOrProperty.getNegativePredicate())) { - return true; - } - } - return false; - } - - /** - * Handle case where getter throws an exception. - * - * @param getter method we want to declare exception for - * @param assertionContent the assertion content to enrich - * @return assertion content with thrown exceptions - */ - private String declareExceptions(GetterDescription getter, String assertionContent) { - StringBuilder throwsClause = new StringBuilder(); - StringBuilder throwsJavaDoc = new StringBuilder(); - boolean first = true; - for (TypeToken exception : getter.getExceptions()) { - if (first) { - throwsClause.append("throws "); - } - else { - throwsClause.append(", "); - } - first = false; - String exceptionName = getTypeDeclaration(exception); - throwsClause.append(exceptionName); - throwsJavaDoc.append(LINE_SEPARATOR).append(" * @throws ").append(exceptionName); - throwsJavaDoc.append(" if actual.").append("${getter}() throws one."); - } - if (!getter.getExceptions().isEmpty()) { - throwsClause.append(' '); - } - - assertionContent = assertionContent.replace(THROWS_JAVADOC, throwsJavaDoc.toString()); - assertionContent = assertionContent.replace(THROWS, throwsClause.toString()); - return assertionContent; - } - - private void fillFile(String customAssertionContent, File assertionJavaFile) throws IOException { - try (FileWriter fileWriter = new FileWriter(assertionJavaFile, false)) { - fileWriter.write(customAssertionContent); - } - } - - private File createFile(String fileContent, String fileName, String targetDirectory) throws IOException { - File file = new File(targetDirectory, fileName); - - // Ignore the result as it only returns false when the file existed previously which is not wrong. - // noinspection ResultOfMethodCallIgnored - file.createNewFile(); - fillFile(fileContent, file); - return file; - } - - private static boolean noClassDescriptionsGiven(final Set classDescriptionSet) { - return classDescriptionSet == null || classDescriptionSet.isEmpty(); - } - - private static void buildDirectory(String directoryName) { - // Ignore the result as it only returns true iff the dir was created, false is not bad. - File directory = new File(directoryName); - if (!directory.exists()) { - directory.mkdirs(); - } - } - - @Override - public void register(Template template) { - templateRegistry.register(template); - } - - public void setGeneratedAnnotationSource(GeneratedAnnotationSource generatedAnnotationSource) { - this.generatedAnnotationSource = generatedAnnotationSource; - } + static final String TEMPLATES_DIR = "templates" + File.separator; + private static final String IMPORT_LINE = "import %s;%s"; + private static final String PREDICATE = "${predicate}"; + private static final String PREDICATE_NEG = "${neg_predicate}"; + private static final String PREDICATE_FOR_JAVADOC = "${predicate_for_javadoc}"; + private static final String NEGATIVE_PREDICATE_FOR_JAVADOC = "${negative_predicate_for_javadoc}"; + private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = "${predicate_for_error_message_part1}"; + private static final String PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = "${predicate_for_error_message_part2}"; + private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1 = "${negative_predicate_for_error_message_part1}"; + private static final String NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2 = "${negative_predicate_for_error_message_part2}"; + private static final String PROPERTY_WITH_UPPERCASE_FIRST_CHAR = "${Property}"; + private static final String PROPERTY_GETTER_CALL = "${getter}"; + private static final String PROPERTY_WITH_LOWERCASE_FIRST_CHAR = "${property}"; + private static final String PROPERTY_WITH_SAFE = "${property_safe}"; + private static final String PACKAGE = "${package}"; + private static final String PROPERTY_TYPE = "${propertyType}"; + private static final String PROPERTY_SIMPLE_TYPE = "${propertySimpleType}"; + private static final String PROPERTY_ASSERT_TYPE = "${propertyAssertType}"; + private static final String CLASS_TO_ASSERT = "${class_to_assert}"; + private static final String CUSTOM_ASSERTION_CLASS = "${custom_assertion_class}"; + private static final String ABSTRACT_SUPER_ASSERTION_CLASS = "${super_assertion_class}"; + private static final String SELF_TYPE = "${self_type}"; + private static final String MYSELF = "${myself}"; + private static final String ELEMENT_TYPE = "${elementType}"; + private static final String ELEMENT_ASSERT_TYPE = "${elementAssertType}"; + private static final String ALL_ASSERTIONS_ENTRY_POINTS = "${all_assertions_entry_points}"; + private static final String IMPORTS = "${imports}"; + private static final String THROWS = "${throws}"; + private static final String THROWS_JAVADOC = "${throws_javadoc}"; + private static final String LINE_SEPARATOR = "\n"; + + private static final Comparator ORDER_BY_INCREASING_LENGTH = Comparator.comparingInt(String::length); + + private static final Set JAVA_KEYWORDS = newHashSet("abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + // This one's not strictly required because you can't have + // a property called "class" + "class", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extends", + "false", + "final", + "finally", + "float", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "null", + "package", + "protected", + "private", + "public", + "return", + "short", + "static", + "strictfp", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "void", + "volatile", + "while"); + + /** + * This regexp shall match a java class's name inside an user template. + *

+ * For this, we use the two character class {@code javaJavaIdentifierStart} and {@code javaJavaIdentifierPart} to match + * a valid name. + *

+ * (?m)^public class is needed to match the class at the beginning of a line and avoid matching + * "public class" inside javadoc comment as in templates/junit_soft_assertions_entry_point_class_template.txt. + *

+ * Description of the pattern: + *

    + *
  1. (?m)^ beginning of line in multi-line mode
  2. + *
  3. public class[\\s]+ the "public class" followed by one or more whitespace (either tabs, space or new lines).
  4. + *
  5. (?<CLASSNAME>...) create a named group that would match a Java identifier (here the class name).
  6. + *
  7. \\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}* match said identifier using character class.
  8. + *
+ * + * @see java.util.regex.Pattern + * @see Character#isJavaIdentifierStart + * @see Character#isJavaIdentifierPart + */ + private static final Pattern CLASS_NAME_PATTERN = Pattern + .compile("(?m)^public class[\\s]+(?\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)\\b"); + + private static final Set> EMPTY_HIERARCHY = new HashSet<>(); + + private static final String NON_PUBLIC_FIELD_VALUE_EXTRACTION = "org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue(\"%s\", %s.class, actual)"; + // S is used in custom_abstract_assertion_class_template.txt + private static final String ABSTRACT_ASSERT_SELF_TYPE = "S"; + + // assertions classes are generated in their package directory starting from targetBaseDirectory. + // ex : com.nba.Player -> targetBaseDirectory/com/nba/PlayerAssert.java + private File targetBaseDirectory = Paths.get(".").toFile(); + private TemplateRegistry templateRegistry;// the pattern to search for + private boolean generateAssertionsForAllFields = false; + private String generatedAssertionsPackage = null; + + /** + * Creates a new {@link BaseAssertionGenerator} with default templates directory. + * + * @throws IOException if some template file could not be found or read + */ + public BaseAssertionGenerator() throws IOException { + this(TEMPLATES_DIR); + } + + /** + * Creates a new {@link BaseAssertionGenerator} with the templates from the given directory. + * + * @param templatesDirectory path where to find templates + * @throws IOException if some template file could not be found or read + */ + public BaseAssertionGenerator(String templatesDirectory) throws IOException { + templateRegistry = DefaultTemplateRegistryProducer.create(templatesDirectory); + } + + public void setDirectoryWhereAssertionFilesAreGenerated(File targetBaseDirectory) { + this.targetBaseDirectory = targetBaseDirectory; + } + + public void setGenerateAssertionsForAllFields(boolean generateAssertionsForAllFields) { + this.generateAssertionsForAllFields = generateAssertionsForAllFields; + } + + public void setGeneratedAssertionsPackage(String generatedAssertionsPackage) { + checkGivenPackageIsValid(generatedAssertionsPackage); + this.generatedAssertionsPackage = generatedAssertionsPackage; + } + + private void checkGivenPackageIsValid(String generatedAssertionsPackage) { + Validate.isTrue(isNotBlank(generatedAssertionsPackage), "The given package '%s' must not be blank", + generatedAssertionsPackage); + Validate.isTrue(!containsWhitespace(generatedAssertionsPackage), "The given package '%s' must not contain blank character", + generatedAssertionsPackage); + } + + @Override + public File generateCustomAssertionFor(ClassDescription classDescription) throws IOException { + // Assertion content + String assertionFileContent = generateCustomAssertionContentFor(classDescription); + // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package + String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); + buildDirectory(directoryWhereToCreateAssertFiles); + return createFile(assertionFileContent, classDescription.getAssertClassFilename(), directoryWhereToCreateAssertFiles); + } + + private String getDirectoryWhereToCreateAssertFilesFor(ClassDescription classDescription) { + return getDirectoryPathCorrespondingToPackage(determinePackageName(classDescription)); + } + + @Override + public File[] generateHierarchicalCustomAssertionFor(ClassDescription classDescription, + Set> allClasses) throws IOException { + // Assertion content + String[] assertionFileContent = generateHierarchicalCustomAssertionContentFor(classDescription, allClasses); + // Create the assertion file in targetBaseDirectory + either the given package or in the class to assert package + String directoryWhereToCreateAssertFiles = getDirectoryWhereToCreateAssertFilesFor(classDescription); + buildDirectory(directoryWhereToCreateAssertFiles); + // create assertion files + File[] assertionClassFiles = new File[2]; + final String concreteAssertClassFileName = classDescription.getAssertClassFilename(); + final String abstractAssertClassFileName = classDescription.getAbstractAssertClassFilename(); + assertionClassFiles[0] = createFile(assertionFileContent[0], abstractAssertClassFileName, directoryWhereToCreateAssertFiles); + assertionClassFiles[1] = createFile(assertionFileContent[1], concreteAssertClassFileName, directoryWhereToCreateAssertFiles); + return assertionClassFiles; + } + + @Override + public String[] generateHierarchicalCustomAssertionContentFor(ClassDescription classDescription, + Set> classes) { + // use abstract class template first + String abstractAssertClassContent = templateRegistry.getTemplate(ABSTRACT_ASSERT_CLASS).getContent(); + StringBuilder abstractAssertClassContentBuilder = new StringBuilder(abstractAssertClassContent); + + // generate assertion method for each property with a public getter or field + generateAssertionsForDeclaredGettersOf(abstractAssertClassContentBuilder, classDescription); + generateAssertionsForDeclaredFieldsOf(abstractAssertClassContentBuilder, classDescription); + + // close class with } + abstractAssertClassContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); + + // use concrete class template for the subclass of the generated abstract assert + String concreteAssertClassContent = templateRegistry.getTemplate(HIERARCHICAL_ASSERT_CLASS).getContent(); + + // return a String array with the actual generated content of the assertion class hierarchy + String[] assertionClassesContent = new String[2]; + assertionClassesContent[0] = fillAbstractAssertClassTemplate(abstractAssertClassContentBuilder.toString(), classDescription, + classes); + assertionClassesContent[1] = fillConcreteAssertClassTemplate(concreteAssertClassContent, classDescription); + return assertionClassesContent; + } + + private String switchToComparableAssertIfPossible(String content, ClassDescription classDescription) { + return classDescription.implementsComparable() + ? replace(content, "AbstractObjectAssert", "AbstractComparableAssert") + : content; + } + + private String fillAbstractAssertClassTemplate(String abstractAssertClassTemplate, ClassDescription classDescription, + Set> classes) { + return fillAssertClassTemplate(abstractAssertClassTemplate, classDescription, classes, false); + } + + private String fillAssertClassTemplate(String template, ClassDescription classDescription, + Set> classesHierarchy, boolean concrete) { + // Add any AssertJ needed imports only, other types are used with their fully qualified names to avoid a compilation + // error when two types have the same name. + TreeSet classesToImport = new TreeSet<>(); + // we import the class to assert in case the generated assertions are put in a different package than the class to assert, + // listNeededImports will remove it if if was not needed. + // in case of nested class, we must only import the outer class ! + classesToImport.add(classDescription.getFullyQualifiedOuterClassName()); + if (template.contains("Assertions.")) classesToImport.add("org.assertj.core.api.Assertions"); + if (template.contains("Objects.")) { + int totalObjects = StringUtils.countMatches(template, "Objects."); + int areEqualObjects = StringUtils.countMatches(template, "Objects.deepEquals"); + int areEqualArraysObjects = StringUtils.countMatches(template, "Objects.deepEqualsArrays"); + + int totalDeprecated = areEqualObjects + areEqualArraysObjects; + + if (totalDeprecated > 0) { + classesToImport.add("java.util.Objects"); + } + + if (totalObjects > totalDeprecated) { + classesToImport.add("org.assertj.core.util.Objects"); + } + } + if (template.contains("Iterables.")) classesToImport.add("org.assertj.core.internal.Iterables"); + + // Add assertion supertype to imports if needed (for abstract assertions hierarchy) + // we need a FQN if the parent class is in a different package than the child class, if not listNeededImports will optimize it + final String parentAssertClassName = classesHierarchy.contains(classDescription.getSuperType()) + ? classDescription.getFullyQualifiedParentAssertClassName() + : "org.assertj.core.api.AbstractObjectAssert"; + if (classesHierarchy.contains(classDescription.getSuperType())) { + classesToImport.add(parentAssertClassName); + } + + final String customAssertionClass = concrete ? classDescription.getAssertClassName() + : classDescription.getAbstractAssertClassName(); + final String selfType = concrete ? customAssertionClass : ABSTRACT_ASSERT_SELF_TYPE; + final String myself = concrete ? "this" : "myself"; + + template = replace(template, PACKAGE, determinePackageName(classDescription)); + template = replace(template, CUSTOM_ASSERTION_CLASS, customAssertionClass); + // use a simple parent class name as we have already imported it + // className could be a nested class like "OuterClass.NestedClass", in that case assert class will be OuterClassNestedClass + template = replace(template, ABSTRACT_SUPER_ASSERTION_CLASS, getTypeNameWithoutDots(parentAssertClassName)); + if (template.contains("AbstractObjectAssert")) classesToImport.add("org.assertj.core.api.AbstractObjectAssert"); + + template = replace(template, CLASS_TO_ASSERT, classDescription.getClassNameWithOuterClass()); + template = replace(template, SELF_TYPE, selfType); + template = replace(template, MYSELF, myself); + String neededImports = listNeededImports(classesToImport, determinePackageName(classDescription)); + template = replace(template, IMPORTS, neededImports.isEmpty() ? "" : LINE_SEPARATOR + neededImports); + + // in case the domain class is Comparable we want the assert class to inherit from AbstractComparableAssert + template = switchToComparableAssertIfPossible(template, classDescription); + + return template; + } + + private String determinePackageName(ClassDescription classDescription) { + return generatedAssertionsPackage == null ? classDescription.getPackageName() : generatedAssertionsPackage; + } + + private String fillConcreteAssertClassTemplate(String template, ClassDescription classDescription) { + return fillAssertClassTemplate(template, classDescription, EMPTY_HIERARCHY, true); + } + + @Override + public String generateCustomAssertionContentFor(ClassDescription classDescription) { + + // use class template first + String classTemplateContent = templateRegistry.getTemplate(ASSERT_CLASS).getContent(); + StringBuilder assertionFileContentBuilder = new StringBuilder(classTemplateContent); + + // generate assertion method for each property with a public getter + generateAssertionsForGettersOf(assertionFileContentBuilder, classDescription); + generateAssertionsForFieldsOf(assertionFileContentBuilder, classDescription); + + // close class with } + assertionFileContentBuilder.append(LINE_SEPARATOR).append("}").append(LINE_SEPARATOR); + + return fillConcreteAssertClassTemplate(assertionFileContentBuilder.toString(), classDescription); + } + + @Override + public String generateAssertionsEntryPointClassContentFor(final Set classDescriptionSet, + AssertionsEntryPointType assertionsEntryPointType, + String entryPointClassPackage) { + if (noClassDescriptionsGiven(classDescriptionSet)) return ""; + Template assertionEntryPointMethodTemplate = chooseAssertionEntryPointMethodTemplate(assertionsEntryPointType); + Template assertionsEntryPointClassTemplate = chooseAssertionEntryPointClassTemplate(assertionsEntryPointType); + return generateAssertionsEntryPointClassContent(classDescriptionSet, assertionsEntryPointClassTemplate, + assertionEntryPointMethodTemplate, entryPointClassPackage); + } + + private Template chooseAssertionEntryPointMethodTemplate(final AssertionsEntryPointType assertionsEntryPointType) { + switch (assertionsEntryPointType) { + case SOFT: + case JUNIT_SOFT: + case AUTO_CLOSEABLE_SOFT: + return templateRegistry.getTemplate(Type.SOFT_ENTRY_POINT_METHOD_ASSERTION); + case BDD: + return templateRegistry.getTemplate(Type.BDD_ENTRY_POINT_METHOD_ASSERTION); + case BDD_SOFT: + case JUNIT_BDD_SOFT: + case AUTO_CLOSEABLE_BDD_SOFT: + return templateRegistry.getTemplate(Type.BDD_SOFT_ENTRY_POINT_METHOD_ASSERTION); + default: + return templateRegistry.getTemplate(Type.ASSERTION_ENTRY_POINT); + } + } + + private Template chooseAssertionEntryPointClassTemplate(final AssertionsEntryPointType assertionsEntryPointType) { + switch (assertionsEntryPointType) { + case SOFT: + return templateRegistry.getTemplate(Type.SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case JUNIT_SOFT: + return templateRegistry.getTemplate(Type.JUNIT_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case AUTO_CLOSEABLE_SOFT: + return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case BDD: + return templateRegistry.getTemplate(Type.BDD_ASSERTIONS_ENTRY_POINT_CLASS); + case BDD_SOFT: + return templateRegistry.getTemplate(Type.BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case JUNIT_BDD_SOFT: + return templateRegistry.getTemplate(Type.JUNIT_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + case AUTO_CLOSEABLE_BDD_SOFT: + return templateRegistry.getTemplate(Type.AUTO_CLOSEABLE_BDD_SOFT_ASSERTIONS_ENTRY_POINT_CLASS); + default: + return templateRegistry.getTemplate(Type.ASSERTIONS_ENTRY_POINT_CLASS); + } + } + + @Override + public File generateAssertionsEntryPointClassFor(final Set classDescriptionSet, + AssertionsEntryPointType assertionsEntryPointType, + String entryPointClassPackage) throws IOException { + if (noClassDescriptionsGiven(classDescriptionSet)) return null; + String assertionsEntryPointFileContent = generateAssertionsEntryPointClassContentFor(classDescriptionSet, + assertionsEntryPointType, + entryPointClassPackage); + String fileName = determineFileName(assertionsEntryPointFileContent, assertionsEntryPointType); + return createAssertionsFileFor(classDescriptionSet, assertionsEntryPointFileContent, fileName, + entryPointClassPackage); + } + + private String determineFileName(String assertionsEntryPointFileContent, + AssertionsEntryPointType assertionsEntryPointType) { + // expecting the class name to be here : "class " + Matcher classNameMatcher = CLASS_NAME_PATTERN.matcher(assertionsEntryPointFileContent); + // if we find a match return it + if (classNameMatcher.find()) return classNameMatcher.group("CLASSNAME") + ".java"; + // otherwise use the default name + return assertionsEntryPointType.getFileName(); + } + + private String generateAssertionsEntryPointClassContent(final Set classDescriptionSet, + final Template entryPointAssertionsClassTemplate, + final Template entryPointAssertionMethodTemplate, + String entryPointClassPackage) { + String entryPointAssertionsClassContent = entryPointAssertionsClassTemplate.getContent(); + // resolve template markers + String classPackage = isEmpty(entryPointClassPackage) + ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) + : entryPointClassPackage; + entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, PACKAGE, classPackage); + + String allEntryPointsAssertionContent = generateAssertionEntryPointMethodsFor(classDescriptionSet, + entryPointAssertionMethodTemplate); + entryPointAssertionsClassContent = replace(entryPointAssertionsClassContent, ALL_ASSERTIONS_ENTRY_POINTS, + allEntryPointsAssertionContent); + return entryPointAssertionsClassContent; + } + + /** + * create the assertions entry point file, located in its package directory starting from targetBaseDirectory. + *

+ * If assertionsClassPackage is not set, we use the common base package of the given classes, if some classe are in + * a.b.c package and others in a.b.c.d, then entry point class will be in a.b.c. + *

+ * + * @param classDescriptionSet used to determine the assertions class package + * @param fileContent assertions entry point file content + * @param fileName assertions entry point file name + * @param assertionsClassPackage the entry point class package - automatically determined if null. + * @return the created assertions entry point file + * @throws IOException if file can't be created. + */ + private File createAssertionsFileFor(final Set classDescriptionSet, final String fileContent, + final String fileName, final String assertionsClassPackage) throws IOException { + String classPackage = isEmpty(assertionsClassPackage) + ? determineBestEntryPointsAssertionsClassPackage(classDescriptionSet) + : assertionsClassPackage; + String assertionsDirectory = getDirectoryPathCorrespondingToPackage(classPackage); + // build any needed directories + buildDirectory(assertionsDirectory); + return createFile(fileContent, fileName, assertionsDirectory); + } + + private String generateAssertionEntryPointMethodsFor(final Set classDescriptionSet, + Template assertionEntryPointMethodTemplate) { + // sort ClassDescription according to their class name. + SortedSet sortedClassDescriptionSet = new TreeSet<>(classDescriptionSet); + // generate for each classDescription the entry point method, e.g. assertThat(MyClass) or then(MyClass) + StringBuilder allAssertThatsContentBuilder = new StringBuilder(); + final String lineSeparator = System.lineSeparator(); + for (ClassDescription classDescription : sortedClassDescriptionSet) { + String assertionEntryPointMethodContent = assertionEntryPointMethodTemplate.getContent(); + // resolve class assert (ex: PlayerAssert) + // in case of inner classes like Movie.PublicCategory, class assert will be MoviePublicCategoryAssert + assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CUSTOM_ASSERTION_CLASS, + classDescription.getFullyQualifiedAssertClassName()); + // resolve class (ex: Player) + // in case of inner classes like Movie.PublicCategory use class name with outer class i.e. Movie.PublicCategory. + assertionEntryPointMethodContent = replace(assertionEntryPointMethodContent, CLASS_TO_ASSERT, + classDescription.getFullyQualifiedClassName()); + + allAssertThatsContentBuilder.append(lineSeparator).append(assertionEntryPointMethodContent); + } + return allAssertThatsContentBuilder.toString(); + } + + private String determineBestEntryPointsAssertionsClassPackage(final Set classDescriptionSet) { + if (generatedAssertionsPackage != null) { + return generatedAssertionsPackage; + } + + SortedSet packages = new TreeSet<>(ORDER_BY_INCREASING_LENGTH); + for (ClassDescription classDescription : classDescriptionSet) { + packages.add(classDescription.getPackageName()); + } + // takes the base package of all given classes assuming they all belong to a common package, i.e a.b.c. over a.b.c.d + // this can certainly be improved ... + return packages.first(); + } + + /** + * Returns the target directory path where the assertions file for given classDescription will be created. + * + * @param packageName package name + * @return the target directory path corresponding to the given package. + */ + private String getDirectoryPathCorrespondingToPackage(final String packageName) { + return targetBaseDirectory + File.separator + packageName.replace('.', File.separatorChar); + } + + private static String listNeededImports(Set typesToImport, String classPackage) { + StringBuilder imports = new StringBuilder(); + for (String type : typesToImport) { + if (isImportNeeded(type, classPackage)) { + imports.append(format(IMPORT_LINE, type, LINE_SEPARATOR)); + } + } + return imports.toString(); + } + + private static boolean isImportNeeded(String type, String classPackage) { + // no need to import type belonging to the same package + if (Objects.equals(classPackage, packageOf(type))) return false; + try { + Class clazz = Class.forName(type); + // primitive and java.lang.* are available by default + return !(clazz.isPrimitive() || isJavaLangType(clazz)); + } catch (ClassNotFoundException e) { + // occurs for abstract types (ex: AbstractXXXAssert) or types to generate that are unknown + return true; + } + } + + protected void generateAssertionsForGettersOf(StringBuilder contentBuilder, ClassDescription classDescription) { + generateAssertionsForGetters(contentBuilder, classDescription.getGettersDescriptions(), classDescription); + } + + protected void generateAssertionsForDeclaredGettersOf(StringBuilder contentBuilder, + ClassDescription classDescription) { + generateAssertionsForGetters(contentBuilder, classDescription.getDeclaredGettersDescriptions(), classDescription); + } + + protected void generateAssertionsForGetters(StringBuilder assertionsForGetters, Set getters, + ClassDescription classDescription) { + for (GetterDescription getter : getters) { + String assertionContent = assertionContentForProperty(getter, classDescription); + assertionsForGetters.append(assertionContent).append(LINE_SEPARATOR); + } + } + + protected void generateAssertionsForFieldsOf(StringBuilder contentBuilder, ClassDescription classDescription) { + generateAssertionsForFields(contentBuilder, classDescription.getFieldsDescriptions(), classDescription); + } + + protected void generateAssertionsForDeclaredFieldsOf(StringBuilder contentBuilder, + ClassDescription classDescription) { + generateAssertionsForFields(contentBuilder, classDescription.getDeclaredFieldsDescriptions(), + classDescription); + } + + protected void generateAssertionsForFields(StringBuilder assertionsForPublicFields, + Set fields, ClassDescription classDescription) { + for (FieldDescription field : fields) { + if (generateAssertionsForAllFields || field.isPublic()) { + String assertionContent = assertionContentForField(field, classDescription); + // assertion can be empty if we have a getter for the field + if (!assertionContent.isEmpty()) { + assertionsForPublicFields.append(assertionContent).append(LINE_SEPARATOR); + } + } + } + } + + private String assertionContentForField(FieldDescription field, ClassDescription classDescription) { + + if (classDescription.hasGetterForField(field)) { + // the assertion has already been generated using the getter to read the field + return ""; + } + + final String fieldName = field.getName(); + String assertionContent = baseAssertionContentFor(field, classDescription); + + // we reuse template for properties to have consistent assertions for property and field but change the way we get + // the value since it's a field and not a property: + assertionContent = assertionContent.replace("${getter}()", PROPERTY_WITH_LOWERCASE_FIRST_CHAR); + // - remove also ${throws} and ${throws_javadoc} as it does not make sense for a field + assertionContent = remove(assertionContent, THROWS); + assertionContent = remove(assertionContent, THROWS_JAVADOC); + + if (!field.isPublic()) { + // if field is not public, we need to use reflection to get its value, ex : + // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("grade", Grade.class, actual); + assertionContent = assertionContent.replace("actual." + PROPERTY_WITH_LOWERCASE_FIRST_CHAR, + format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, + PROPERTY_WITH_LOWERCASE_FIRST_CHAR, PROPERTY_TYPE)); + } + if (field.isPredicate()) { + assertionContent = fillAssertionContentForPredicateField(field, assertionContent); + } + assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(field.getName())); + assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(field)); + assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, + field.getAssertTypeName(determinePackageName(classDescription))); + assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(field)); + assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, fieldName); + // It should not be possible to have a field that is a keyword - compiler won't allow it. + assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(fieldName)); + return assertionContent; + } + + private String getTypeName(DataDescription fieldOrGetter) { + if (generatedAssertionsPackage != null) { + // if the user has chosen to generate assertions in a given package we assume that + return fieldOrGetter.getFullyQualifiedTypeName(); + } + // returns a simple class name if the field or getter type is in the same package as its owning type which is the package + // where the + // Assert class is generated. + return fieldOrGetter.getTypeName(); + } + + private String fillAssertionContentForPredicateField(FieldDescription field, String assertionContent) { + if (field.isPublic()) { + assertionContent = assertionContent.replace("actual." + PREDICATE + "()", + "actual." + field.getOriginalMember().getName()); + } else { + // if field is not public, we need to use reflection to get its value, ex : + // org.assertj.core.util.introspection.FieldSupport.EXTRACTION.fieldValue("active", Boolean.class, actual); + assertionContent = assertionContent.replace("actual." + PREDICATE + "()", + format(NON_PUBLIC_FIELD_VALUE_EXTRACTION, + field.getOriginalMember().getName(), "Boolean")); + } + assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, + field.getPredicateForJavadoc()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, + field.getNegativePredicateForJavadoc()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + field.getPredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + field.getPredicateForErrorMessagePart2()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + field.getNegativePredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + field.getNegativePredicateForErrorMessagePart2()); + assertionContent = replace(assertionContent, PREDICATE, field.getPredicate()); + assertionContent = replace(assertionContent, PREDICATE_NEG, field.getNegativePredicate()); + return assertionContent; + } + + static private String unclashName(String unsafe) { + return JAVA_KEYWORDS.contains(unsafe) || "actual".equals(unsafe) ? "expected" + capitalize(unsafe) : unsafe; + } + + private String assertionContentForProperty(GetterDescription getter, ClassDescription classDescription) { + String assertionContent = baseAssertionContentFor(getter, classDescription); + + assertionContent = declareExceptions(getter, assertionContent); + + String propertyName = getter.getName(); + if (getter.isPredicate()) { + assertionContent = assertionContent.replace(PREDICATE_FOR_JAVADOC, + getter.getPredicateForJavadoc()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_JAVADOC, + getter.getNegativePredicateForJavadoc()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + getter.getPredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + getter.getPredicateForErrorMessagePart2()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART1, + getter.getNegativePredicateForErrorMessagePart1()); + assertionContent = assertionContent.replace(NEGATIVE_PREDICATE_FOR_FOR_ERROR_MESSAGE_PART2, + getter.getNegativePredicateForErrorMessagePart2()); + assertionContent = replace(assertionContent, PREDICATE, getter.getOriginalMember().getName()); + assertionContent = replace(assertionContent, PREDICATE_NEG, getter.getNegativePredicate()); + } + assertionContent = replace(assertionContent, PROPERTY_GETTER_CALL, getter.getOriginalMember().getName()); + assertionContent = replace(assertionContent, PROPERTY_WITH_UPPERCASE_FIRST_CHAR, capitalize(propertyName)); + assertionContent = replace(assertionContent, PROPERTY_SIMPLE_TYPE, getTypeName(getter)); + assertionContent = replace(assertionContent, PROPERTY_ASSERT_TYPE, + getter.getAssertTypeName(determinePackageName(classDescription))); + assertionContent = replace(assertionContent, PROPERTY_TYPE, getTypeName(getter)); + assertionContent = replace(assertionContent, PROPERTY_WITH_LOWERCASE_FIRST_CHAR, propertyName); + assertionContent = replace(assertionContent, PROPERTY_WITH_SAFE, unclashName(propertyName)); + return assertionContent; + } + + /** + * The assertion content that is common to field and property (getter), the specific content part is handled + * afterwards. + * + * @param fieldOrProperty field or property + * @return the base assertion content + */ + private String baseAssertionContentFor(DataDescription fieldOrProperty, ClassDescription classDescription) { + String assertionContent = templateRegistry.getTemplate(Type.HAS).getContent(); + if (fieldOrProperty.isPredicate()) { + Type type = determinePredicateType(fieldOrProperty, classDescription); + assertionContent = templateRegistry.getTemplate(type).getContent(); + } else if (fieldOrProperty.isIterableType()) { + assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ITERABLE).getContent(), ELEMENT_TYPE, + fieldOrProperty.getElementTypeName()); + assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, + fieldOrProperty.getElementAssertTypeName()); + } else if (fieldOrProperty.isArrayType()) { + assertionContent = replace(templateRegistry.getTemplate(Type.HAS_FOR_ARRAY).getContent(), ELEMENT_TYPE, + fieldOrProperty.getElementTypeName()); + assertionContent = replace(assertionContent, ELEMENT_ASSERT_TYPE, + fieldOrProperty.getElementAssertTypeName()); + } else if (fieldOrProperty.isRealNumberType()) { + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_REAL_NUMBER_WRAPPER + : Type.HAS_FOR_REAL_NUMBER; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } else if (fieldOrProperty.isWholeNumberType()) { + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_WHOLE_NUMBER_WRAPPER + : Type.HAS_FOR_WHOLE_NUMBER; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } else if (fieldOrProperty.isCharType()) { + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_CHARACTER : Type.HAS_FOR_CHAR; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } else if (fieldOrProperty.isPrimitiveType()) { + // use case : boolean getFoo -> not a predicate, but a primitive valueType + Type type = fieldOrProperty.isPrimitiveWrapperType() ? Type.HAS_FOR_PRIMITIVE_WRAPPER : Type.HAS_FOR_PRIMITIVE; + assertionContent = templateRegistry.getTemplate(type).getContent(); + } + return assertionContent; + } + + /** + * Determine whether we need to generate negative predicate assertions, for example if the class contains isValid and + * isNotValid methods, we must not generate the negative assertion for isValid as it will be done when generating + * assertions for isNotValid + */ + private Type determinePredicateType(final DataDescription fieldOrProperty, final ClassDescription classDescription) { + if (hasAlreadyNegativePredicate(fieldOrProperty, classDescription)) { + return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER_WITHOUT_NEGATION : Type.IS_WITHOUT_NEGATION; + } + return fieldOrProperty.isPrimitiveWrapperType() ? Type.IS_WRAPPER : Type.IS; + } + + private boolean hasAlreadyNegativePredicate(final DataDescription fieldOrProperty, + final ClassDescription classDescription) { + for (final GetterDescription getterDescription : classDescription.getGettersDescriptions()) { + if (getterDescription.getOriginalMember().getName().equals(fieldOrProperty.getNegativePredicate())) return true; + } + return false; + } + + /** + * Handle case where getter throws an exception. + * + * @param getter method we want to declare exception for + * @param assertionContent the assertion content to enrich + * @return assertion content with thrown exceptions + */ + private String declareExceptions(GetterDescription getter, String assertionContent) { + StringBuilder throwsClause = new StringBuilder(); + StringBuilder throwsJavaDoc = new StringBuilder(); + boolean first = true; + for (TypeToken exception : getter.getExceptions()) { + if (first) throwsClause.append("throws "); + else throwsClause.append(", "); + first = false; + String exceptionName = getTypeDeclaration(exception); + throwsClause.append(exceptionName); + throwsJavaDoc.append(LINE_SEPARATOR).append(" * @throws ").append(exceptionName); + throwsJavaDoc.append(" if actual.").append("${getter}() throws one."); + } + if (!getter.getExceptions().isEmpty()) throwsClause.append(' '); + + assertionContent = assertionContent.replace(THROWS_JAVADOC, throwsJavaDoc.toString()); + assertionContent = assertionContent.replace(THROWS, throwsClause.toString()); + return assertionContent; + } + + private void fillFile(String customAssertionContent, File assertionJavaFile) throws IOException { + try (FileWriter fileWriter = new FileWriter(assertionJavaFile, false)) { + fileWriter.write(customAssertionContent); + } + } + + private File createFile(String fileContent, String fileName, String targetDirectory) throws IOException { + File file = new File(targetDirectory, fileName); + + // Ignore the result as it only returns false when the file existed previously which is not wrong. + // noinspection ResultOfMethodCallIgnored + file.createNewFile(); + fillFile(fileContent, file); + return file; + } + + private static boolean noClassDescriptionsGiven(final Set classDescriptionSet) { + return classDescriptionSet == null || classDescriptionSet.isEmpty(); + } + + private static void buildDirectory(String directoryName) { + // Ignore the result as it only returns true iff the dir was created, false is not bad. + File directory = new File(directoryName); + if (!directory.exists()) directory.mkdirs(); + } + + @Override + public void register(Template template) { + templateRegistry.register(template); + } } diff --git a/src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java b/src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java deleted file mode 100644 index 2cae575..0000000 --- a/src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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. - * - * Copyright 2012-2021 the original author or authors. - */ -package org.assertj.assertions.generator; - -public enum GeneratedAnnotationSource { - JAVAX, - JAKARTA, - NONE; -} diff --git a/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt index 834f41e..7bde364 100644 --- a/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/auto_closeable_soft_assertions_entry_point_class_template.txt @@ -19,7 +19,7 @@ package ${package}; * } * */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class AutoCloseableSoftAssertions extends org.assertj.core.api.SoftAssertions implements AutoCloseable { ${all_assertions_entry_points} @Override diff --git a/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt b/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt index a4bd26c..e8ac7e3 100644 --- a/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for BDD assertions of different data types. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class BddAssertions { ${all_assertions_entry_points} /** diff --git a/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt index f23c3e2..7f1a146 100644 --- a/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for BDD soft assertions of different data types. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class BDDSoftAssertions extends org.assertj.core.api.BDDSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/custom_abstract_assertion_class_template.txt b/src/main/resources/templates/custom_abstract_assertion_class_template.txt index ed444ca..662ff1b 100644 --- a/src/main/resources/templates/custom_abstract_assertion_class_template.txt +++ b/src/main/resources/templates/custom_abstract_assertion_class_template.txt @@ -3,7 +3,7 @@ ${imports} /** * Abstract base class for {@link ${class_to_assert}} specific assertions - Generated by CustomAssertionGenerator. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public abstract class ${custom_assertion_class}, A extends ${class_to_assert}> extends ${super_assertion_class} { /** diff --git a/src/main/resources/templates/custom_assertion_class_template.txt b/src/main/resources/templates/custom_assertion_class_template.txt index eaedcc0..e638dd4 100644 --- a/src/main/resources/templates/custom_assertion_class_template.txt +++ b/src/main/resources/templates/custom_assertion_class_template.txt @@ -3,7 +3,7 @@ ${imports} /** * {@link ${class_to_assert}} specific assertions - Generated by CustomAssertionGenerator. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class ${custom_assertion_class} extends AbstractObjectAssert<${custom_assertion_class}, ${class_to_assert}> { /** diff --git a/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt b/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt index 1ebc6d1..d5230d2 100644 --- a/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt +++ b/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt @@ -6,7 +6,7 @@ ${imports} * Although this class is not final to allow Soft assertions proxy, if you wish to extend it, * extend {@link Abstract${custom_assertion_class}} instead. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class ${custom_assertion_class} extends Abstract${custom_assertion_class}<${custom_assertion_class}, ${class_to_assert}> { /** diff --git a/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt index ba97404..103e18b 100644 --- a/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt @@ -18,7 +18,7 @@ package ${package}; * } * } */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class JUnitBDDSoftAssertions extends org.assertj.core.api.JUnitBDDSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt index c8cbb2d..1a0a6ed 100644 --- a/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt @@ -18,7 +18,7 @@ package ${package}; * } * } */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class JUnitSoftAssertions extends org.assertj.core.api.JUnitSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/soft_assertions_entry_point_class_template.txt index 27ef2a9..7b1b0d4 100644 --- a/src/main/resources/templates/soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/soft_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for soft assertions of different data types. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class SoftAssertions extends org.assertj.core.api.SoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/standard_assertions_entry_point_class_template.txt b/src/main/resources/templates/standard_assertions_entry_point_class_template.txt index 9d6904a..a264498 100644 --- a/src/main/resources/templates/standard_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/standard_assertions_entry_point_class_template.txt @@ -4,7 +4,7 @@ package ${package}; * Entry point for assertions of different data types. Each method in this class is a static factory for the * type-specific assertion objects. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class Assertions { ${all_assertions_entry_points} /** diff --git a/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java b/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java index 836535b..3d2cc94 100644 --- a/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java +++ b/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java @@ -80,7 +80,7 @@ public void should_generate_correctly_standard_assertions_entry_point_class_for_ STANDARD, "org"); // THEN String expectedContent = readExpectedContentFromFile("AssertionsForClassesWithSameName.expected.txt"); - assertThat(assertionsEntryPointContent).isEqualToIgnoringNewLines(expectedContent); + assertThat(assertionsEntryPointContent).isEqualTo(expectedContent); } @Test diff --git a/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java b/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java index 90d87d6..74d9084 100644 --- a/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java +++ b/src/test/java/org/assertj/assertions/generator/GenerationPathHandler.java @@ -213,7 +213,7 @@ private static String getClasspathFromClassloader(ClassLoader currentClassloader while (true) { // We only know how to extract classpaths from URLClassloaders. if (currentClassloader instanceof URLClassLoader) classloaders.add((URLClassLoader) currentClassloader); -// else throw new IllegalArgumentException("Classpath for compilation could not be extracted as classloader is not a URLClassloader"); + else throw new IllegalArgumentException("Classpath for compilation could not be extracted as classloader is not a URLClassloader"); if (currentClassloader == systemClassLoader) break; else currentClassloader = currentClassloader.getParent(); diff --git a/src/test/resources/customtemplates/my_assertion_entry_point_class.txt b/src/test/resources/customtemplates/my_assertion_entry_point_class.txt index fac28b4..fc210ab 100644 --- a/src/test/resources/customtemplates/my_assertion_entry_point_class.txt +++ b/src/test/resources/customtemplates/my_assertion_entry_point_class.txt @@ -4,7 +4,7 @@ package ${package}; * Entry point for assertions of different data types. Each method in this class is a static factory for the * type-specific assertion objects. */ -${generatedAnnotation} +@javax.annotation.Generated(value="assertj-assertions-generator") public class MyAssertions { ${all_assertions_entry_points} /** From 4a9b2c1122c686779cda9edc105d6edd9643995c Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Thu, 20 Jan 2022 16:22:29 +0100 Subject: [PATCH 3/4] #194: cleaned up the autoformatting mess. --- .../generator/BaseAssertionGenerator.java | 20 +++++++++++++++++++ .../generator/GeneratedAnnotationSource.java | 19 ++++++++++++++++++ ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ...stom_abstract_assertion_class_template.txt | 2 +- .../custom_assertion_class_template.txt | 2 +- ..._hierarchical_assertion_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- ..._assertions_entry_point_class_template.txt | 2 +- .../AssertionsEntryPointGeneratorTest.java | 2 +- 13 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/assertj/assertions/generator/GeneratedAnnotationSource.java diff --git a/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java b/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java index ca96069..f1808e4 100644 --- a/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java +++ b/src/main/java/org/assertj/assertions/generator/BaseAssertionGenerator.java @@ -20,6 +20,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.remove; import static org.apache.commons.lang3.StringUtils.replace; +import static org.assertj.assertions.generator.GeneratedAnnotationSource.JAKARTA; +import static org.assertj.assertions.generator.GeneratedAnnotationSource.JAVAX; import static org.assertj.assertions.generator.Template.Type.ABSTRACT_ASSERT_CLASS; import static org.assertj.assertions.generator.Template.Type.ASSERT_CLASS; import static org.assertj.assertions.generator.Template.Type.HIERARCHICAL_ASSERT_CLASS; @@ -54,6 +56,7 @@ public class BaseAssertionGenerator implements AssertionGenerator, AssertionsEntryPointGenerator { static final String TEMPLATES_DIR = "templates" + File.separator; + private static final String GENERATED = "${generatedAnnotation}"; private static final String IMPORT_LINE = "import %s;%s"; private static final String PREDICATE = "${predicate}"; private static final String PREDICATE_NEG = "${neg_predicate}"; @@ -178,6 +181,7 @@ public class BaseAssertionGenerator implements AssertionGenerator, AssertionsEnt private TemplateRegistry templateRegistry;// the pattern to search for private boolean generateAssertionsForAllFields = false; private String generatedAssertionsPackage = null; + private GeneratedAnnotationSource generatedAnnotationSource = JAVAX; /** * Creates a new {@link BaseAssertionGenerator} with default templates directory. @@ -326,6 +330,7 @@ private String fillAssertClassTemplate(String template, ClassDescription classDe final String selfType = concrete ? customAssertionClass : ABSTRACT_ASSERT_SELF_TYPE; final String myself = concrete ? "this" : "myself"; + template = replace(template, GENERATED, determineGeneratedSignature()); template = replace(template, PACKAGE, determinePackageName(classDescription)); template = replace(template, CUSTOM_ASSERTION_CLASS, customAssertionClass); // use a simple parent class name as we have already imported it @@ -345,6 +350,16 @@ private String fillAssertClassTemplate(String template, ClassDescription classDe return template; } + private String determineGeneratedSignature() { + if (generatedAnnotationSource == JAVAX) { + return "@javax.annotation.Generated(value=\"assertj-assertions-generator\")"; + } + if (generatedAnnotationSource == JAKARTA) { + return "@jakarta.annotation.Generated(value=\"assertj-assertions-generator\")"; + } + return ""; + } + private String determinePackageName(ClassDescription classDescription) { return generatedAssertionsPackage == null ? classDescription.getPackageName() : generatedAssertionsPackage; } @@ -451,6 +466,7 @@ private String generateAssertionsEntryPointClassContent(final Set */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class AutoCloseableSoftAssertions extends org.assertj.core.api.SoftAssertions implements AutoCloseable { ${all_assertions_entry_points} @Override diff --git a/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt b/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt index e8ac7e3..a4bd26c 100644 --- a/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/bdd_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for BDD assertions of different data types. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class BddAssertions { ${all_assertions_entry_points} /** diff --git a/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt index 7f1a146..f23c3e2 100644 --- a/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/bdd_soft_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for BDD soft assertions of different data types. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class BDDSoftAssertions extends org.assertj.core.api.BDDSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/custom_abstract_assertion_class_template.txt b/src/main/resources/templates/custom_abstract_assertion_class_template.txt index 662ff1b..ed444ca 100644 --- a/src/main/resources/templates/custom_abstract_assertion_class_template.txt +++ b/src/main/resources/templates/custom_abstract_assertion_class_template.txt @@ -3,7 +3,7 @@ ${imports} /** * Abstract base class for {@link ${class_to_assert}} specific assertions - Generated by CustomAssertionGenerator. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public abstract class ${custom_assertion_class}, A extends ${class_to_assert}> extends ${super_assertion_class} { /** diff --git a/src/main/resources/templates/custom_assertion_class_template.txt b/src/main/resources/templates/custom_assertion_class_template.txt index e638dd4..eaedcc0 100644 --- a/src/main/resources/templates/custom_assertion_class_template.txt +++ b/src/main/resources/templates/custom_assertion_class_template.txt @@ -3,7 +3,7 @@ ${imports} /** * {@link ${class_to_assert}} specific assertions - Generated by CustomAssertionGenerator. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class ${custom_assertion_class} extends AbstractObjectAssert<${custom_assertion_class}, ${class_to_assert}> { /** diff --git a/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt b/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt index d5230d2..1ebc6d1 100644 --- a/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt +++ b/src/main/resources/templates/custom_hierarchical_assertion_class_template.txt @@ -6,7 +6,7 @@ ${imports} * Although this class is not final to allow Soft assertions proxy, if you wish to extend it, * extend {@link Abstract${custom_assertion_class}} instead. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class ${custom_assertion_class} extends Abstract${custom_assertion_class}<${custom_assertion_class}, ${class_to_assert}> { /** diff --git a/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt index 103e18b..ba97404 100644 --- a/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/junit_bdd_soft_assertions_entry_point_class_template.txt @@ -18,7 +18,7 @@ package ${package}; * } * } */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class JUnitBDDSoftAssertions extends org.assertj.core.api.JUnitBDDSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt index 1a0a6ed..c8cbb2d 100644 --- a/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/junit_soft_assertions_entry_point_class_template.txt @@ -18,7 +18,7 @@ package ${package}; * } * } */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class JUnitSoftAssertions extends org.assertj.core.api.JUnitSoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/soft_assertions_entry_point_class_template.txt b/src/main/resources/templates/soft_assertions_entry_point_class_template.txt index 7b1b0d4..27ef2a9 100644 --- a/src/main/resources/templates/soft_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/soft_assertions_entry_point_class_template.txt @@ -3,7 +3,7 @@ package ${package}; /** * Entry point for soft assertions of different data types. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class SoftAssertions extends org.assertj.core.api.SoftAssertions { ${all_assertions_entry_points} } diff --git a/src/main/resources/templates/standard_assertions_entry_point_class_template.txt b/src/main/resources/templates/standard_assertions_entry_point_class_template.txt index a264498..9d6904a 100644 --- a/src/main/resources/templates/standard_assertions_entry_point_class_template.txt +++ b/src/main/resources/templates/standard_assertions_entry_point_class_template.txt @@ -4,7 +4,7 @@ package ${package}; * Entry point for assertions of different data types. Each method in this class is a static factory for the * type-specific assertion objects. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +${generatedAnnotation} public class Assertions { ${all_assertions_entry_points} /** diff --git a/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java b/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java index 3d2cc94..836535b 100644 --- a/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java +++ b/src/test/java/org/assertj/assertions/generator/AssertionsEntryPointGeneratorTest.java @@ -80,7 +80,7 @@ public void should_generate_correctly_standard_assertions_entry_point_class_for_ STANDARD, "org"); // THEN String expectedContent = readExpectedContentFromFile("AssertionsForClassesWithSameName.expected.txt"); - assertThat(assertionsEntryPointContent).isEqualTo(expectedContent); + assertThat(assertionsEntryPointContent).isEqualToIgnoringNewLines(expectedContent); } @Test From 72bd0961b9a3d13a4ef2f14d66f41e891145a5f6 Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Wed, 26 Jan 2022 12:07:49 +0100 Subject: [PATCH 4/4] #194: Changed a test to have the jakarta annotation under test. --- pom.xml | 6 ++++++ .../assertions/generator/AssertionGeneratorTest.java | 11 ++++++++--- src/test/resources/AbstractPlayerAssert.expected.txt | 2 +- src/test/resources/PlayerAssert.expected.txt | 2 +- src/test/resources/PlayerAssert.flat.expected.txt | 2 +- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index b39bee3..065b30c 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,12 @@ 1.2.3
+ + jakarta.annotation + jakarta.annotation-api + 2.0.0 + test + org.assertj assertj-core diff --git a/src/test/java/org/assertj/assertions/generator/AssertionGeneratorTest.java b/src/test/java/org/assertj/assertions/generator/AssertionGeneratorTest.java index 1b5e21f..b0ae1a1 100644 --- a/src/test/java/org/assertj/assertions/generator/AssertionGeneratorTest.java +++ b/src/test/java/org/assertj/assertions/generator/AssertionGeneratorTest.java @@ -67,9 +67,14 @@ public void beforeEachTest() throws IOException { } @Test - public void should_generate_assertion_for_player_class() throws Exception { - verifyFlatAssertionGenerationFor(Player.class); - verifyHierarchicalAssertionGenerationFor(Player.class); + public void should_generate_assertion_for_player_class_using_jakarta_annotation() throws Exception { + try { + assertionGenerator.setGeneratedAnnotationSource( GeneratedAnnotationSource.JAKARTA ); + verifyFlatAssertionGenerationFor(Player.class); + verifyHierarchicalAssertionGenerationFor(Player.class); + }finally { + assertionGenerator.setGeneratedAnnotationSource( GeneratedAnnotationSource.JAVAX ); + } } @Test diff --git a/src/test/resources/AbstractPlayerAssert.expected.txt b/src/test/resources/AbstractPlayerAssert.expected.txt index 08b3dd1..1fa36d1 100644 --- a/src/test/resources/AbstractPlayerAssert.expected.txt +++ b/src/test/resources/AbstractPlayerAssert.expected.txt @@ -8,7 +8,7 @@ import org.assertj.core.internal.Iterables; /** * Abstract base class for {@link Player} specific assertions - Generated by CustomAssertionGenerator. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +@jakarta.annotation.Generated(value="assertj-assertions-generator") public abstract class AbstractPlayerAssert, A extends Player> extends AbstractObjectAssert { /** diff --git a/src/test/resources/PlayerAssert.expected.txt b/src/test/resources/PlayerAssert.expected.txt index 8b3f9d3..4a3e9bb 100644 --- a/src/test/resources/PlayerAssert.expected.txt +++ b/src/test/resources/PlayerAssert.expected.txt @@ -6,7 +6,7 @@ package org.assertj.assertions.generator.data.nba; * Although this class is not final to allow Soft assertions proxy, if you wish to extend it, * extend {@link AbstractPlayerAssert} instead. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +@jakarta.annotation.Generated(value="assertj-assertions-generator") public class PlayerAssert extends AbstractPlayerAssert { /** diff --git a/src/test/resources/PlayerAssert.flat.expected.txt b/src/test/resources/PlayerAssert.flat.expected.txt index e663191..112e4bb 100644 --- a/src/test/resources/PlayerAssert.flat.expected.txt +++ b/src/test/resources/PlayerAssert.flat.expected.txt @@ -8,7 +8,7 @@ import org.assertj.core.internal.Iterables; /** * {@link Player} specific assertions - Generated by CustomAssertionGenerator. */ -@javax.annotation.Generated(value="assertj-assertions-generator") +@jakarta.annotation.Generated(value="assertj-assertions-generator") public class PlayerAssert extends AbstractObjectAssert { /**