From 9a28debea7ce46ca7b4f126e959d093655c68a45 Mon Sep 17 00:00:00 2001 From: pawellabaj Date: Mon, 24 Jul 2023 02:06:59 +0200 Subject: [PATCH] To not forget --- .../compact/AdditionalMethodBuilder.java | 91 +++++++++ .../ImmutableCollectionsExtension.java | 100 ++++++---- .../extension/compact/RecordComponent.java | 94 +++++---- .../extension/compact/StatementBuilder.java | 179 ++++++++++++++++++ .../extension/compact/StatementBuilders.java | 137 -------------- .../autorecord/extension/compact/Utils.java | 60 ------ .../pl/com/labaj/autorecord/AutoRecord.java | 3 +- .../extension/AutoRecordExtension.java | 6 +- .../CompactConstructorExtension.java | 28 +++ .../CompactConstructorSubGenerator.java | 19 ++ 10 files changed, 437 insertions(+), 280 deletions(-) create mode 100644 extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/AdditionalMethodBuilder.java create mode 100644 extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilder.java delete mode 100644 extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilders.java delete mode 100644 extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/Utils.java diff --git a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/AdditionalMethodBuilder.java b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/AdditionalMethodBuilder.java new file mode 100644 index 0000000..80eff3f --- /dev/null +++ b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/AdditionalMethodBuilder.java @@ -0,0 +1,91 @@ +package pl.com.labaj.autorecord.extension.compact; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeVariableName; +import pl.com.labaj.autorecord.context.Logger; +import pl.com.labaj.autorecord.context.StaticImports; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import java.util.Map; + +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.STATIC; +import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.ENUM_SET; +import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME; +import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.SET; +import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.SORTED_SET; + +class AdditionalMethodBuilder { + private static final Map METHOD_BUILDERS = Map.of( + SET, builderForSet() + ); + + private final Types typeUtils; + + private final Map immutableTypes; + private final Map collectionTypes; + private final StaticImports staticImports; + private final Logger logger; + + AdditionalMethodBuilder(Types typeUtils, + Map immutableTypes, + Map collectionTypes, + StaticImports staticImports, + Logger logger) { + this.typeUtils = typeUtils; + this.immutableTypes = immutableTypes; + this.collectionTypes = collectionTypes; + this.staticImports = staticImports; + this.logger = logger; + } + + MethodSpec buildMethodFor(String mark) { + return METHOD_BUILDERS.get(mark).buildMethod(typeUtils, immutableTypes, collectionTypes, staticImports, logger); + } + + private static MethodBuilder builderForSet() { + return (typeUtils, immutableTypes, collectionTypes, staticImports, logger) -> { + var typeVariableName = TypeVariableName.get("E"); + var setType = collectionTypes.get(SET); + var className = ClassName.get((TypeElement) typeUtils.asElement(setType)); + var setTypeName = ParameterizedTypeName.get(className, typeVariableName); + var parameterSpec = ParameterSpec.builder(setTypeName, "set").build(); + var methodBuilder = MethodSpec.methodBuilder("_immutableSet") + .addModifiers(PRIVATE, STATIC) + .addTypeVariable(typeVariableName) + .returns(setTypeName) + .addParameter(parameterSpec); + + var enumSetType = immutableTypes.get(ENUM_SET); + immutableTypes.values().stream() + .filter(immutableType -> typeUtils.isSubtype(immutableType, setType)) + .forEach(immutableType -> { + var isEnumSet = typeUtils.isSameType(immutableType, enumSetType); + methodBuilder.beginControlFlow("if (set instanceof $T<$L>)", immutableType, isEnumSet ? '?' : 'E') + .addStatement("return set") + .endControlFlow(); + }); + + methodBuilder.beginControlFlow("if (set instanceof $T sortedSet)", collectionTypes.get(SORTED_SET)) + .addStatement("return $T.copyOfSorted(sortedSet)", GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME) + .endControlFlow() + .addStatement("return set"); + + return methodBuilder.build(); + }; + } + + @FunctionalInterface + private static interface MethodBuilder { + MethodSpec buildMethod(Types typeUtils, + Map immutableTypes, + Map collectionTypes, + StaticImports staticImports, + Logger logger); + } +} diff --git a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/ImmutableCollectionsExtension.java b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/ImmutableCollectionsExtension.java index b8be321..ddd4ca8 100644 --- a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/ImmutableCollectionsExtension.java +++ b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/ImmutableCollectionsExtension.java @@ -18,15 +18,15 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; import pl.com.labaj.autorecord.context.Context; import pl.com.labaj.autorecord.context.Logger; import pl.com.labaj.autorecord.context.StaticImports; import pl.com.labaj.autorecord.extension.CompactConstructorExtension; -import javax.annotation.Nullable; +import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -37,36 +37,41 @@ import static java.util.stream.Collectors.toMap; import static javax.lang.model.type.TypeKind.ARRAY; import static pl.com.labaj.autorecord.extension.compact.RecordComponent.getComponentsDebugInfo; -import static pl.com.labaj.autorecord.extension.compact.RecordComponent.toExtensionRecordComponent; -import static pl.com.labaj.autorecord.extension.compact.StatementBuilders.statementBuilders; public class ImmutableCollectionsExtension implements CompactConstructorExtension { - private static final String ENUM_SET = "java.util.EnumSet"; - private static final String GUAVA_IMMUTABLE_SET = "com.google.common.collect.ImmutableSet"; + static final ClassName GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME + = ClassName.get("com.google.common.collect", "ImmutableSortedSet"); - private static final String SORTED_SET = "java.util.SortedSet"; + static final String NAVIGABLE_SET = "java.util.NavigableSet"; + static final String SORTED_SET = "java.util.SortedSet"; static final String SET = "java.util.Set"; - static final Set COLLECTION_SUPER_TYPES = Set.of( + + static final String ENUM_SET = "java.util.EnumSet"; + private static final String GUAVA_IMMUTABLE_SET = "com.google.common.collect.ImmutableSet"; + private static final Set COLLECTION_NAMES = Set.of( + NAVIGABLE_SET, SORTED_SET, SET ); private Types typeUtils; private Logger logger; - private final Set knownImmutableTypes; - private final Map, Boolean> userTypesCache = new HashMap<>(); + private final Set knownImmutableTypeNames; + private Map collectionTypes; private List componentsToProcess; + private Set additionalMethodMarks; + private Map immutableTypes; public ImmutableCollectionsExtension() { - knownImmutableTypes = new HashSet<>(); - knownImmutableTypes.add(ENUM_SET); - knownImmutableTypes.add(GUAVA_IMMUTABLE_SET); + knownImmutableTypeNames = new HashSet<>(); + knownImmutableTypeNames.add(ENUM_SET); + knownImmutableTypeNames.add(GUAVA_IMMUTABLE_SET); } @Override public void init(String[] parameters) { - knownImmutableTypes.addAll(Arrays.asList(parameters)); + knownImmutableTypeNames.addAll(Arrays.asList(parameters)); } @Override @@ -74,11 +79,36 @@ public boolean shouldGenerateCompactConstructor(boolean isGeneratedByProcessor, typeUtils = context.processingEnv().getTypeUtils(); logger = context.logger(); + var elementUtils = context.processingEnv().getElementUtils(); + + record NameType(String name, TypeMirror type) {} + immutableTypes = knownImmutableTypeNames.stream() + .map(name -> { + var typeElement = elementUtils.getTypeElement(name); + return nonNull(typeElement) ? new NameType(name, typeUtils.erasure(typeElement.asType())) : null; + }) + .filter(Objects::nonNull) + .collect(toMap( + NameType::name, + NameType::type + )); + collectionTypes = COLLECTION_NAMES.stream() + .map(name -> { + var typeElement = elementUtils.getTypeElement(name); + return nonNull(typeElement) ? new NameType(name, typeUtils.erasure(typeElement.asType())) : null; + }) + .filter(Objects::nonNull) + .collect(toMap( + NameType::name, + NameType::type + )); + + var componentBuilder = new RecordComponent.Builder(typeUtils, immutableTypes, collectionTypes, logger); var recordComponents = context.components() .stream() .filter(recordComponent -> !recordComponent.type().getKind().isPrimitive()) .filter(recordComponent -> recordComponent.type().getKind() != ARRAY) - .map(component -> toExtensionRecordComponent(typeUtils, knownImmutableTypes, component)) + .map(componentBuilder::toExtensionRecordComponent) .toList(); if (logger.isDebugEnabled()) { logger.debug("Record components:\n" + getComponentsDebugInfo(recordComponents)); @@ -101,39 +131,29 @@ public CodeBlock suffixCompactConstructorContent(Context context, StaticImports logger.note("Components to process:\n" + getComponentsDebugInfo(componentsToProcess)); } - var elementUtils = context.processingEnv().getElementUtils(); - - record NameClassName(String name, ClassName className){} - - var classNameMap = knownImmutableTypes.stream() - .map(s -> { - var typeElement = elementUtils.getTypeElement(s); - return nonNull(typeElement) ? new NameClassName(s, ClassName.get(typeElement)) : null; - }) - .filter(Objects::nonNull) - .collect(toMap(NameClassName::name, NameClassName::className)); - var codeBuilder = CodeBlock.builder(); + var statementBuilder = new StatementBuilder(typeUtils, collectionTypes, staticImports, logger); + componentsToProcess.stream() - .map(recordComponent -> statementFor(recordComponent, staticImports)) + .map(statementBuilder::statementFor) .filter(Objects::nonNull) .forEach(codeBuilder::addStatement); + additionalMethodMarks = statementBuilder.additionalMethods(); + return codeBuilder.build(); } - @Nullable - private CodeBlock statementFor(RecordComponent recordComponent, StaticImports staticImports) { - return statementBuilders().stream() - .map(builder -> builder.buildStatement(recordComponent, staticImports, logger)) - .filter(Objects::nonNull) - .findFirst() - .orElseGet(() -> { - //TODO: log debug instead of exception - var message = "Unrecognized %s type of \"%s\" recordComponent".formatted(recordComponent.declaredType(), recordComponent.name()); - logger.note(message); - return null; - }); + @Override + public List additionalMethodsToSupportCompactConstructor(Context context, StaticImports staticImports) { + if (additionalMethodMarks.isEmpty()) { + return List.of(); + } + + var additionalMethodsBuilder = new AdditionalMethodBuilder(typeUtils, immutableTypes, collectionTypes, staticImports, logger); + return additionalMethodMarks.stream() + .map(additionalMethodsBuilder::buildMethodFor) + .toList(); } } diff --git a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/RecordComponent.java b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/RecordComponent.java index 0c7dba9..c1a491b 100644 --- a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/RecordComponent.java +++ b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/RecordComponent.java @@ -16,67 +16,81 @@ * limitations under the License. */ +import pl.com.labaj.autorecord.context.Logger; + import javax.annotation.Nullable; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import java.util.List; +import java.util.Map; import java.util.Set; import static java.util.stream.Collectors.joining; import static javax.lang.model.type.TypeKind.DECLARED; -import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.COLLECTION_SUPER_TYPES; - -record RecordComponent(String name, boolean isNullable, String declaredType, boolean shouldBeProcessed) { - - static RecordComponent toExtensionRecordComponent(Types typeUtils, - Set knownImmutableTypes, - pl.com.labaj.autorecord.context.RecordComponent component) { - var isNullable = component.isAnnotatedWith(Nullable.class); - - var componentType = component.type(); - var declaredType = getQualifiedName((DeclaredType) componentType); +record RecordComponent(String name, boolean isNullable, TypeMirror declaredType, boolean shouldBeProcessed) { - var shouldBeProcessed = shouldBeProcessed(typeUtils, knownImmutableTypes, componentType, declaredType); + static String getComponentsDebugInfo(List recordComponents) { + return recordComponents.stream() + .map(RecordComponent::toString) + .collect(joining("\n")); + } - return new RecordComponent(component.name(), isNullable, declaredType, shouldBeProcessed); + @Override + public String toString() { + return name + " " + shouldBeProcessed + " : " + declaredType; } - private static boolean shouldBeProcessed(Types typeUtils, Set knownImmutableTypes, TypeMirror componentType, String declaredClass) { - if (componentType.getKind() != DECLARED) { - return true; + static class Builder { + private final Types typeUtils; + private final Set immutableTypes; + private final Set collectionTypes; + private final Logger logger; + + Builder(Types typeUtils, Map immutableTypes, Map collectionTypes, Logger logger) { + this.typeUtils = typeUtils; + this.immutableTypes = Set.copyOf(immutableTypes.values()); + this.collectionTypes = Set.copyOf(collectionTypes.values()); + this.logger = logger; } - if (knownImmutableTypes.contains(declaredClass)) { - return false; + RecordComponent toExtensionRecordComponent(pl.com.labaj.autorecord.context.RecordComponent component) { + var isNullable = component.isAnnotatedWith(Nullable.class); + var componentType = component.type(); + var declaredType = typeUtils.erasure(componentType); + var shouldBeProcessed = shouldBeProcessed(componentType); + + return new RecordComponent(component.name(), isNullable, declaredType, shouldBeProcessed); } - var superTypes = typeUtils.directSupertypes(componentType); - var hasImmutableSuperType = superTypes.stream() - .map(superType -> getQualifiedName((DeclaredType) superType)) - .anyMatch(knownImmutableTypes::contains); + private boolean shouldBeProcessed(TypeMirror componentType) { + if (componentType.getKind() != DECLARED) { + return true; + } - if (hasImmutableSuperType) { - return false; - } + var rawComponentType = typeUtils.erasure(componentType); - return COLLECTION_SUPER_TYPES.contains(declaredClass); - } + var isImmutableType = immutableTypes.stream() + .anyMatch(immutableType -> typeUtils.isSameType(rawComponentType, immutableType)); + logger.debug(componentType + " is immutable type: " + isImmutableType); - private static String getQualifiedName(DeclaredType t) { - return ((TypeElement) t.asElement()).getQualifiedName().toString(); - } + if (isImmutableType) { + return false; + } - static String getComponentsDebugInfo(List recordComponents) { - return recordComponents.stream() - .map(RecordComponent::toString) - .collect(joining("\n")); - } + var hasImmutableSuperType = immutableTypes.stream() + .anyMatch(immutableType -> typeUtils.isSubtype(rawComponentType, immutableType)); + logger.debug(componentType + " has super immutable type " + hasImmutableSuperType); - @Override - public String toString() { - return name + " " + shouldBeProcessed + " : " + declaredType; + if (hasImmutableSuperType) { + return false; + } + + var isCollection = collectionTypes.stream() + .anyMatch(collectionType -> typeUtils.isSameType(rawComponentType, collectionType)); + logger.debug(componentType + " is collection " + isCollection); + + return isCollection; + } } } diff --git a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilder.java b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilder.java new file mode 100644 index 0000000..8caea56 --- /dev/null +++ b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilder.java @@ -0,0 +1,179 @@ +package pl.com.labaj.autorecord.extension.compact; + +/*- + * Copyright © 2023 Auto Record + * + * 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. + */ + +import com.squareup.javapoet.CodeBlock; +import pl.com.labaj.autorecord.context.Logger; +import pl.com.labaj.autorecord.context.StaticImports; + +import javax.annotation.Nullable; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.NAVIGABLE_SET; +import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.SET; +import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.SORTED_SET; + +final class StatementBuilder { + + private static final List RECORD_STATEMENT_BUILDERS = List.of( + builderForSet(), + builderForSortedSet() +// builderForEnumSetImplementation(), +// builderForSetImplementation(), +// builderForList(), +// builderForListImplementation() + ); + private final Types typeUtils; + private final Map collectionTypes; + private final StaticImports staticImports; + private final Logger logger; + private Set additionalMethods = new HashSet<>(); + + StatementBuilder(Types typeUtils, Map collectionTypes, StaticImports staticImports, Logger logger) { + this.typeUtils = typeUtils; + this.collectionTypes = collectionTypes; + this.staticImports = staticImports; + this.logger = logger; + } + + @Nullable + CodeBlock statementFor(RecordComponent recordComponent) { + return RECORD_STATEMENT_BUILDERS.stream() + .map(builder -> builder.buildStatement(typeUtils, collectionTypes, recordComponent, staticImports, additionalMethods, logger)) + .filter(Objects::nonNull) + .findFirst() + .orElseGet(() -> { + //TODO: log debug instead of exception + var message = "Unrecognized %s type of \"%s\" recordComponent".formatted(recordComponent.declaredType(), recordComponent.name()); + logger.note(message); + return null; + }); + } + + public Set additionalMethods() { + return additionalMethods; + } + + private static RecordStatementBuilder builderForSet() { + return (typeUtils, collectionTypes, component, staticImports, additionalMethods, logger) -> { + var componentType = component.declaredType(); + var setType = collectionTypes.get(SET); + + if (!typeUtils.isSameType(componentType, setType)) { + return null; + } + + var format = "$1L = _immutableSet($1L)"; + if (component.isNullable()) { + staticImports.add(Objects.class, "isNull"); + format = "$1L = isNull($1L) ? null : _immutableSet($1L)"; + } + + additionalMethods.add(SET); + + return CodeBlock.of(format, component.name()); + }; + } + + private static RecordStatementBuilder builderForSortedSet() { + return (typeUtils, collectionTypes, component, staticImports, additionalMethods, logger) -> { + var componentType = component.declaredType(); + var navigableSetType = collectionTypes.get(NAVIGABLE_SET); + var sortedSetType = collectionTypes.get(SORTED_SET); + + if (!typeUtils.isSameType(componentType, navigableSetType) && !typeUtils.isSameType(componentType, sortedSetType)) { + return null; + } + + var format = "$2L = $1T.copyOfSorted($2L)"; + if (component.isNullable()) { + staticImports.add(Objects.class, "isNull"); + format = "$2L = isNull($2L) ? null : $1T.copyOfSorted($2L)"; + } + + return CodeBlock.of(format, ImmutableCollectionsExtension.GUAVA_IMMUTABLE_SORTED_SET_CLASS_NAME, component.name()); + }; + } + +// +// private static RecordStatementBuilder builderForEnumSetImplementation() { +// return (component, staticImports, logger) -> { +// if (ENUM_SET.isAssignableFrom(component.declaredType())) { +// logger.debug("The type of " + component.name() + " component is the EnumSet implementation"); +// } +// +// return null; +// }; +// } +// +// private static RecordStatementBuilder builderForSetImplementation() { +// return (component, staticImports, logger) -> { +// if (SET.isAssignableFrom(component.declaredType())) { +// logger.mandatoryWarning(ruleS1319Message(component, SET)); +// //TODO: Remove this line +// return CodeBlock.of("// " + component.name() + " --> warning"); +// } +// +// return null; +// }; +// } +// +// private static RecordStatementBuilder builderForList() { +// return (component, staticImports, logger) -> { +// if (!component.declaredType().isAssignableFrom(LIST)) { +// return null; +// } +// +// var format = "$2L = $1T.copyOf($2L)"; +// if (component.isNullable()) { +// staticImports.add(Objects.class, "isNull"); +// format = "$2L = isNull($2L) ? null : $1T.copyOf($2L)"; +// } +// +// return CodeBlock.of(format, IMMUTABLE_LIST, component.name()); +// }; +// } +// +// private static RecordStatementBuilder builderForListImplementation() { +// return (component, staticImports, logger) -> { +// if (LIST.isAssignableFrom(component.declaredType())) { +// logger.mandatoryWarning(ruleS1319Message(component, LIST)); +// //TODO: Remove this line +// return CodeBlock.of("// " + component.name() + " --> warning"); +// } +// +// return null; +// }; +// } + + @FunctionalInterface + private interface RecordStatementBuilder { + @Nullable + CodeBlock buildStatement(Types typeUtils, + Map collectionTypes, + RecordComponent recordComponent, + StaticImports staticImports, + Set additionalMethods, + Logger logger); + } +} diff --git a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilders.java b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilders.java deleted file mode 100644 index 9522811..0000000 --- a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/StatementBuilders.java +++ /dev/null @@ -1,137 +0,0 @@ -package pl.com.labaj.autorecord.extension.compact; - -/*- - * Copyright © 2023 Auto Record - * - * 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. - */ - -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import pl.com.labaj.autorecord.context.Logger; -import pl.com.labaj.autorecord.context.StaticImports; - -import javax.annotation.Nullable; -import java.util.List; -import java.util.Objects; - -import static pl.com.labaj.autorecord.extension.compact.ImmutableCollectionsExtension.SET; - -final class StatementBuilders { - - private static final List STATEMENT_BUILDERS = List.of( - builderForSet() -// builderForNavigableSet(), -// builderForEnumSetImplementation(), -// builderForSetImplementation(), -// builderForList(), -// builderForListImplementation() - ); - public static final ClassName GUAVA_IMMUTABLE_SET_CLASS_NAME = ClassName.get("com.google.common.collect", - "ImmutableSet"); - - private StatementBuilders() {} - - static List statementBuilders() { - return STATEMENT_BUILDERS; - } - - private static StatementBuilder builderForSet() { - return (component, staticImports, logger) -> { - if (!component.declaredType().equals(SET)) { - return null; - } - - var format = "$2L = $1T.copyOf($2L)"; - if (component.isNullable()) { - staticImports.add(Objects.class, "isNull"); - format = "$2L = isNull($2L) ? null : $1T.copyOf($2L)"; - } - - return CodeBlock.of(format, GUAVA_IMMUTABLE_SET_CLASS_NAME, component.name()); - }; - } -// -// private static StatementBuilder builderForNavigableSet() { -// return (component, staticImports, logger) -> { -// if (!component.declaredType().isAssignableFrom(NAVIGABLE_SET)) { -// return null; -// } -// -// var format = "$2L = $1T.copyOfSorted($2L)"; -// if (component.isNullable()) { -// staticImports.add(Objects.class, "isNull"); -// format = "$2L = isNull($2L) ? null : $1T.copyOfSorted($2L)"; -// } -// -// return CodeBlock.of(format, ImmutableSortedSet.class, component.name()); -// }; -// } -// -// private static StatementBuilder builderForEnumSetImplementation() { -// return (component, staticImports, logger) -> { -// if (ENUM_SET.isAssignableFrom(component.declaredType())) { -// logger.debug("The type of " + component.name() + " component is the EnumSet implementation"); -// } -// -// return null; -// }; -// } -// -// private static StatementBuilder builderForSetImplementation() { -// return (component, staticImports, logger) -> { -// if (SET.isAssignableFrom(component.declaredType())) { -// logger.mandatoryWarning(ruleS1319Message(component, SET)); -// //TODO: Remove this line -// return CodeBlock.of("// " + component.name() + " --> warning"); -// } -// -// return null; -// }; -// } -// -// private static StatementBuilder builderForList() { -// return (component, staticImports, logger) -> { -// if (!component.declaredType().isAssignableFrom(LIST)) { -// return null; -// } -// -// var format = "$2L = $1T.copyOf($2L)"; -// if (component.isNullable()) { -// staticImports.add(Objects.class, "isNull"); -// format = "$2L = isNull($2L) ? null : $1T.copyOf($2L)"; -// } -// -// return CodeBlock.of(format, IMMUTABLE_LIST, component.name()); -// }; -// } -// -// private static StatementBuilder builderForListImplementation() { -// return (component, staticImports, logger) -> { -// if (LIST.isAssignableFrom(component.declaredType())) { -// logger.mandatoryWarning(ruleS1319Message(component, LIST)); -// //TODO: Remove this line -// return CodeBlock.of("// " + component.name() + " --> warning"); -// } -// -// return null; -// }; -// } - - @FunctionalInterface - static - interface StatementBuilder { - @Nullable - CodeBlock buildStatement(RecordComponent recordComponent, StaticImports staticImports, Logger logger); - } -} diff --git a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/Utils.java b/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/Utils.java deleted file mode 100644 index ed8fa5c..0000000 --- a/extensions/immutable-collections/src/main/java/pl/com/labaj/autorecord/extension/compact/Utils.java +++ /dev/null @@ -1,60 +0,0 @@ -package pl.com.labaj.autorecord.extension.compact; - -/*- - * Copyright © 2023 Auto Record - * - * 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. - */ - - -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Set; - -final class Utils { - @SuppressWarnings("rawtypes") - static final Class COLLECTION = Collection.class; - @SuppressWarnings("rawtypes") - static final Class SET = Set.class; - @SuppressWarnings("rawtypes") - static final Class LIST = List.class; - @SuppressWarnings("rawtypes") - static final Class NAVIGABLE_SET = NavigableSet.class; - @SuppressWarnings("rawtypes") - static final Class ENUM_SET = EnumSet.class; - @SuppressWarnings("rawtypes") - static final Class MAP = Map.class; -// @SuppressWarnings("rawtypes") -// static final Class IMMUTABLE_SET = ImmutableSet.class; -// @SuppressWarnings("rawtypes") -// static final Class IMMUTABLE_LIST = ImmutableList.class; - private static final String RULE_S1319_FORMAT = - "[java:S1319]The type of \"%s\" component should be an interface such as \"%s\" rather than the implementation \"%s\"."; - - private Utils() {} - -// static Class loadClass(String className) { -// try { -// return Class.forName(className); -// } catch (ClassNotFoundException e) { -// throw new AutoRecordProcessorException("Cannot load " + className, e); -// } -// } - -// static String ruleS1319Message(RecordComponent component, Class interfaceClass) { -// return RULE_S1319_FORMAT.formatted(component.name(), interfaceClass.getSimpleName(), component.declaredType().asElement().getSimpleName()); -// } -} diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/AutoRecord.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/AutoRecord.java index cefdc3b..60ebf4e 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/AutoRecord.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/AutoRecord.java @@ -48,7 +48,8 @@ /** * Specifies options for the {@link AutoRecord} annotation. - *

Compatibility Note: Methods may be added to this annotation in future releases of the library. + *

+ * Compatibility Note: Methods may be added to this annotation in future releases of the library. * * @see Customization Wiki */ diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/AutoRecordExtension.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/AutoRecordExtension.java index 5f07b0a..ad266fb 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/AutoRecordExtension.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/AutoRecordExtension.java @@ -29,7 +29,8 @@ *

    *
  • {@link CompactConstructorExtension}
  • *
- *

Compatibility Note: Methods may be added to this interface in future releases of the library. + *

+ * Compatibility Note: Methods may be added to this interface in future releases of the library. * * @see Extensions Wiki * @since 2.1.0 @@ -39,7 +40,8 @@ public interface AutoRecordExtension { * Sets the parameters for the extension. *

* This method can be overridden by the extension to receive any custom parameters that may be needed during record generation. - *

Note: The method is called before any other methods. + *

+ * Note: The method is called before any other methods. * * @param parameters array of {@link String} objects representing the additional parameters for the custom extension. * @see AutoRecord.Extension#parameters() diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/CompactConstructorExtension.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/CompactConstructorExtension.java index da01b63..d7137b9 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/CompactConstructorExtension.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/extension/CompactConstructorExtension.java @@ -17,10 +17,13 @@ */ import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; import pl.com.labaj.autorecord.context.Context; import pl.com.labaj.autorecord.context.StaticImports; import pl.com.labaj.autorecord.processor.AutoRecordProcessor; +import java.util.List; + /** * Represents an extension of the {@link AutoRecordProcessor} for customizing the compact constructor generation process. *

@@ -45,6 +48,8 @@ default boolean shouldGenerateCompactConstructor(boolean isGeneratedByProcessor, /** * Provides a code to be inserted before the current content of the compact constructor. + *

+ * Note: The method is called when {@link #shouldGenerateCompactConstructor(boolean, Context)} returns {@code true}. * * @param context the {@link Context} object containing of the annotation processing. * @param staticImports the {@link StaticImports} object {@code static import} to statements that will be added into @@ -58,6 +63,8 @@ default CodeBlock prefixCompactConstructorContent(Context context, StaticImports /** * Provides a code to be inserted after the current content of the compact constructor. + * + * Note: The method is called when {@link #shouldGenerateCompactConstructor(boolean, Context)} returns {@code true}. * * @param context the {@link Context} object containing of the annotation processing. * @param staticImports the {@link StaticImports} object {@code static import} to statements that will be added into @@ -68,4 +75,25 @@ default CodeBlock prefixCompactConstructorContent(Context context, StaticImports default CodeBlock suffixCompactConstructorContent(Context context, StaticImports staticImports) { return null; } + + /** + * Provides list of additional methods that needs to be added to generated record. + *

+ * Notes: + *

    + *
  • The method is called when {@link #shouldGenerateCompactConstructor(boolean, Context)} returns {@code true}.
  • + *
  • The method is called after {@link #prefixCompactConstructorContent(Context, StaticImports)} + * and {@link #suffixCompactConstructorContent(Context, StaticImports)} methods.
  • + *
+ * + * @param context the {@link Context} object containing of the annotation processing. + * @param staticImports the {@link StaticImports} object {@code static import} to statements that will be added into + * generated record. + * @return a list of {@link MethodSpec} representing additional methods that needs to be added to generated record + * @see MethodSpec + * @since 2.2.0 + */ + default List additionalMethodsToSupportCompactConstructor(Context context, StaticImports staticImports) { + return List.of(); + } } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/CompactConstructorSubGenerator.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/CompactConstructorSubGenerator.java index ebdc700..6f2e18b 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/CompactConstructorSubGenerator.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/CompactConstructorSubGenerator.java @@ -16,6 +16,7 @@ * limitations under the License. */ +import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; @@ -27,6 +28,7 @@ import pl.com.labaj.autorecord.processor.context.ProcessorContext; import javax.annotation.Nullable; +import javax.annotation.processing.Generated; import javax.lang.model.element.Modifier; import java.util.Deque; import java.util.LinkedList; @@ -34,6 +36,7 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Stream; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static java.util.Objects.nonNull; @@ -73,6 +76,22 @@ void generate(TypeSpec.Builder recordBuilder, StaticImports staticImports) { var compactConstructor = generateCompactConstructor(staticImports, generatedByProcessor, filteredExtensions, nonNullNames); recordBuilder.compactConstructor(compactConstructor); + + filteredExtensions.stream() + .flatMap(extension -> additionalMethods(extension, staticImports)) + .forEach(recordBuilder::addMethod); + } + + private Stream additionalMethods(CompactConstructorExtension extension, StaticImports staticImports) { + var annotation = AnnotationSpec.builder(Generated.class) + .addMember("value", "$S", extension.getClass().getName()) + .build(); + var additionalMethods = extension.additionalMethodsToSupportCompactConstructor(context, staticImports); + + return additionalMethods.stream() + .map(methodSpec -> methodSpec.toBuilder() + .addAnnotation(annotation) + .build()); } private MethodSpec generateCompactConstructor(StaticImports staticImports,