From 12fa234a87b5aae52ed9013534f50079de77278c Mon Sep 17 00:00:00 2001 From: Jeff Trent Date: Thu, 26 Jan 2023 15:33:49 -0500 Subject: [PATCH 1/2] Builder updates, fixes and enhancements --- .../main/java/io/helidon/builder/Builder.java | 18 ++- .../processor/spi/DefaultTypeInfo.java | 42 +++++- .../builder/processor/spi/TypeInfo.java | 12 +- .../spi/TypeInfoCreatorProvider.java | 7 +- builder/processor-tools/pom.xml | 8 +- .../builder/processor/tools/BodyContext.java | 55 +++++-- .../tools/BuilderTemplateHelper.java | 44 ------ .../processor/tools/BuilderTypeTools.java | 135 ++++++++++++------ .../tools/DefaultBuilderCreatorProvider.java | 82 ++++++++--- .../processor/tools/GenerateJavadoc.java | 14 +- .../processor/tools/GenerateMethod.java | 107 ++++++++++++-- .../builder/processor/tools/Versions.java | 43 ++++++ .../builder/processor/BuilderProcessor.java | 5 +- builder/tests/builder/pom.xml | 13 +- .../builder/test/testsubjects/Container.java | 3 +- .../builder/test/testsubjects/EdgeCases.java | 21 ++- .../builder/test/testsubjects/MapCase.java | 78 ++++++++++ .../packageprivates/PackagePrivate.java | 28 ++++ .../packageprivates/package-info.java | 20 +++ .../builder/src/main/java/module-info.java | 3 +- .../helidon/builder/test/EdgeCasesTest.java | 37 ++++- .../io/helidon/builder/test/MapCaseTest.java | 108 ++++++++++++++ .../builder/test/PickleBarrelTest.java | 4 +- .../test/testsubjects/PackagePrivateTest.java | 28 ++++ .../test/nodeps/NoDepsInterceptedBean.java | 5 +- pico/builder-config/pom.xml | 4 +- .../processor/ConfigBeanBuilderCreator.java | 15 +- .../builder/config/processor/Versions.java | 43 ++++++ .../pico/tools/ModuleInfoDescriptor.java | 4 +- pico/types/pom.xml | 7 +- .../pico/types/DefaultAnnotationAndValue.java | 20 ++- .../helidon/pico/types/DefaultTypeName.java | 25 +++- .../pico/types/DefaultTypedElementName.java | 68 +++++++-- .../helidon/pico/types/TypedElementName.java | 26 +++- .../test/DefaultAnnotationAndValueTest.java | 18 ++- .../pico/types/test/DefaultTypeNameTest.java | 19 ++- 36 files changed, 980 insertions(+), 189 deletions(-) delete mode 100644 builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTemplateHelper.java create mode 100644 builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/Versions.java create mode 100644 builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MapCase.java create mode 100644 builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/PackagePrivate.java create mode 100644 builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/package-info.java create mode 100644 builder/tests/builder/src/test/java/io/helidon/builder/test/MapCaseTest.java create mode 100644 builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/PackagePrivateTest.java create mode 100644 pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/Versions.java diff --git a/builder/builder/src/main/java/io/helidon/builder/Builder.java b/builder/builder/src/main/java/io/helidon/builder/Builder.java index 2e9a23d4bf9..a57f6fe60d2 100644 --- a/builder/builder/src/main/java/io/helidon/builder/Builder.java +++ b/builder/builder/src/main/java/io/helidon/builder/Builder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,8 @@ */ @SuppressWarnings("rawtypes") @Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) +// note: runtime retention needed for cases when derived builders are inherited across modules +@Retention(RetentionPolicy.RUNTIME) @BuilderTrigger public @interface Builder { @@ -67,6 +68,11 @@ */ boolean DEFAULT_ALLOW_NULLS = false; + /** + * The default value for {@link #includeGeneratedAnnotation()}. + */ + boolean DEFAULT_INCLUDE_GENERATED_ANNOTATION = false; + /** * The default list type used for the generated class implementation for any references to {@link java.util.List} is found * on the methods of the {@link Builder}-annotation interface. @@ -180,6 +186,14 @@ */ boolean allowNulls() default DEFAULT_ALLOW_NULLS; + /** + * Should the code generated types included the {@code Generated} annotation. Including this annotation will require an + * additional module dependency on your modules to include {@code jakarta.annotation-api}. + * + * @return true to include the Generated annotation + */ + boolean includeGeneratedAnnotation() default DEFAULT_INCLUDE_GENERATED_ANNOTATION; + /** * The interceptor implementation type. See {@link BuilderInterceptor} for further details. Any interceptor applied will be called * prior to validation. The interceptor implementation can be any lambda-like implementation for the {@link BuilderInterceptor} diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeInfo.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeInfo.java index 1548cb9cd9b..eb9a5bb5ebe 100644 --- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeInfo.java +++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/DefaultTypeInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,11 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import io.helidon.pico.types.AnnotationAndValue; import io.helidon.pico.types.TypeName; @@ -36,6 +38,7 @@ public class DefaultTypeInfo implements TypeInfo { private final List elementInfo; private final List otherElementInfo; private final TypeInfo superTypeInfo; + private final Set modifierNames; /** * Default constructor taking the builder as an argument. @@ -50,6 +53,7 @@ protected DefaultTypeInfo(Builder b) { this.elementInfo = List.copyOf(b.elementInfo); this.otherElementInfo = List.copyOf(b.otherElementInfo); this.superTypeInfo = b.superTypeInfo; + this.modifierNames = Set.copyOf(b.modifierNames); } /** @@ -91,6 +95,11 @@ public Optional superTypeInfo() { return Optional.ofNullable(superTypeInfo); } + @Override + public Set modifierNames() { + return modifierNames; + } + @Override public String toString() { return getClass().getSimpleName() + "(" + toStringInner() + ")"; @@ -105,7 +114,8 @@ protected String toStringInner() { return "typeName=" + typeName() + ", elementInfo=" + elementInfo() + ", annotations=" + annotations() - + ", superTypeInfo=" + superTypeInfo(); + + ", superTypeInfo=" + superTypeInfo() + + ", modifierNames=" + modifierNames(); } /** @@ -115,6 +125,7 @@ public static class Builder implements io.helidon.common.Builder annotations = new ArrayList<>(); private final List elementInfo = new ArrayList<>(); private final List otherElementInfo = new ArrayList<>(); + private final Set modifierNames = new LinkedHashSet<>(); private TypeName typeName; private String typeKind; @@ -229,7 +240,32 @@ public Builder otherElementInfo(Collection val) { */ public Builder addOtherElementInfo(TypedElementName val) { Objects.requireNonNull(val); - otherElementInfo.add(Objects.requireNonNull(val)); + otherElementInfo.add(val); + return this; + } + + /** + * Sets the modifiers to val. + * + * @param val the value + * @return this fluent builder + */ + public Builder modifierNames(Collection val) { + Objects.requireNonNull(val); + this.modifierNames.clear(); + this.modifierNames.addAll(val); + return this; + } + + /** + * Adds a single modifier val. + * + * @param val the value + * @return this fluent builder + */ + public Builder addModifierName(String val) { + Objects.requireNonNull(val); + modifierNames.add(val); return this; } diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfo.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfo.java index 73eadde6c37..361cdf81166 100644 --- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfo.java +++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import io.helidon.pico.types.AnnotationAndValue; import io.helidon.pico.types.TypeName; @@ -67,8 +68,15 @@ public interface TypeInfo { /** * The parent/super class for this type info. * - * @return the super type. + * @return the super type */ Optional superTypeInfo(); + /** + * Element modifiers. + * + * @return element modifiers + */ + Set modifierNames(); + } diff --git a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java index b6902144b89..9093d87669e 100644 --- a/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java +++ b/builder/processor-spi/src/main/java/io/helidon/builder/processor/spi/TypeInfoCreatorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; -import io.helidon.pico.types.AnnotationAndValue; import io.helidon.pico.types.TypeName; /** @@ -34,13 +33,13 @@ public interface TypeInfoCreatorProvider { /** * Creates a {@link TypeInfo}. * - * @param annotation the annotation that triggered the creation + * @param annoTypeName the annotation type name that triggered the creation * @param typeName the type name that is being processed that is annotated with the triggering annotation * @param element the element representative of the typeName * @param processingEnv the processing environment * @return the type info associated with the arguments being processed, or empty if not able to process the type */ - Optional createTypeInfo(AnnotationAndValue annotation, + Optional createTypeInfo(TypeName annoTypeName, TypeName typeName, TypeElement element, ProcessingEnvironment processingEnv); diff --git a/builder/processor-tools/pom.xml b/builder/processor-tools/pom.xml index 91cbc6d8a02..008d8fe273c 100644 --- a/builder/processor-tools/pom.xml +++ b/builder/processor-tools/pom.xml @@ -1,7 +1,7 @@ + + jakarta.annotation + jakarta.annotation-api + provided + io.helidon.common.testing helidon-common-testing-junit5 diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java index d2af1a286a3..8da677f0ab8 100644 --- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java +++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BodyContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import io.helidon.builder.Builder; import io.helidon.builder.processor.spi.TypeInfo; import io.helidon.pico.types.AnnotationAndValue; import io.helidon.pico.types.DefaultAnnotationAndValue; @@ -33,7 +34,6 @@ import io.helidon.pico.types.TypedElementName; import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.BUILDER_ANNO_TYPE_NAME; -import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.DEFAULT_ALLOW_NULLS; import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.DEFAULT_INCLUDE_META_ATTRIBUTES; import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.DEFAULT_LIST_TYPE; import static io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider.DEFAULT_MAP_TYPE; @@ -63,6 +63,7 @@ public class BodyContext { private final boolean requireLibraryDependencies; private final boolean beanStyleRequired; private final boolean allowNulls; + private final boolean includeGeneratedAnnotation; private final String listType; private final String mapType; private final String setType; @@ -73,6 +74,7 @@ public class BodyContext { private final String genericBuilderClassDecl; private final String genericBuilderAliasDecl; private final String genericBuilderAcceptAliasDecl; + private final String publicOrPackagePrivateDecl; private final TypeName interceptorTypeName; private final String interceptorCreateMethod; @@ -85,9 +87,9 @@ public class BodyContext { * @param builderTriggerAnnotation the builder annotation */ BodyContext(boolean doingConcreteType, - TypeName implTypeName, - TypeInfo typeInfo, - AnnotationAndValue builderTriggerAnnotation) { + TypeName implTypeName, + TypeInfo typeInfo, + AnnotationAndValue builderTriggerAnnotation) { this.doingConcreteType = doingConcreteType; this.implTypeName = implTypeName; this.typeInfo = typeInfo; @@ -98,6 +100,7 @@ public class BodyContext { this.requireLibraryDependencies = toRequireLibraryDependencies(builderTriggerAnnotation, typeInfo); this.beanStyleRequired = toRequireBeanStyle(builderTriggerAnnotation, typeInfo); this.allowNulls = toAllowNulls(builderTriggerAnnotation, typeInfo); + this.includeGeneratedAnnotation = toIncludeGeneratedAnnotation(builderTriggerAnnotation, typeInfo); this.listType = toListImplType(builderTriggerAnnotation, typeInfo); this.mapType = toMapImplType(builderTriggerAnnotation, typeInfo); this.setType = toSetImplType(builderTriggerAnnotation, typeInfo); @@ -124,6 +127,9 @@ public class BodyContext { searchForBuilderAnnotation("interceptorCreateMethod", builderTriggerAnnotation, typeInfo); this.interceptorCreateMethod = (interceptorCreateMethod == null || interceptorCreateMethod.isEmpty()) ? null : interceptorCreateMethod; + this.publicOrPackagePrivateDecl = (typeInfo.typeKind().equals("INTERFACE") + || typeInfo.modifierNames().isEmpty() + || typeInfo.modifierNames().contains("PUBLIC")) ? "public " : ""; } /** @@ -266,6 +272,16 @@ protected boolean allowNulls() { return allowNulls; } + /** + * Returns true if {@code jakarta.annotations.Generated} annotation should be generated. + * See {@link io.helidon.builder.Builder#includeGeneratedAnnotation()}. + * + * @return true if the Generated annotation should be generated on the target beans + */ + protected boolean includeGeneratedAnnotation() { + return includeGeneratedAnnotation; + } + /** * Returns the list type generated. * See {@link io.helidon.builder.Builder#listImplType()}. @@ -359,6 +375,14 @@ protected String genericBuilderAcceptAliasDecl() { return genericBuilderAcceptAliasDecl; } + /** + * Returns "public" or "" for public or package private declaration, accordingly. + * + * @return the modifier declaration + */ + public String publicOrPackagePrivateDecl() { + return publicOrPackagePrivateDecl; + } /** * Returns the interceptor implementation type name. * See {@link io.helidon.builder.Builder#interceptor()}. @@ -435,7 +459,7 @@ private static boolean hasStreamSupportOnBuilder(boolean ignoreDoingConcreteClas private static boolean toIncludeMetaAttributes(AnnotationAndValue builderTriggerAnnotation, TypeInfo typeInfo) { String val = searchForBuilderAnnotation("includeMetaAttributes", builderTriggerAnnotation, typeInfo); - return val == null ? DEFAULT_INCLUDE_META_ATTRIBUTES : Boolean.parseBoolean(val); + return (val == null) ? DEFAULT_INCLUDE_META_ATTRIBUTES : Boolean.parseBoolean(val); } /** @@ -444,7 +468,7 @@ private static boolean toIncludeMetaAttributes(AnnotationAndValue builderTrigger private static boolean toRequireLibraryDependencies(AnnotationAndValue builderTriggerAnnotation, TypeInfo typeInfo) { String val = searchForBuilderAnnotation("requireLibraryDependencies", builderTriggerAnnotation, typeInfo); - return val == null ? DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES : Boolean.parseBoolean(val); + return (val == null) ? DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES : Boolean.parseBoolean(val); } /** @@ -457,12 +481,21 @@ private static boolean toRequireBeanStyle(AnnotationAndValue builderTriggerAnnot } /** - * In support of {@link io.helidon.builder.Builder#allowNulls()} ()}. + * In support of {@link io.helidon.builder.Builder#allowNulls()}. */ private static boolean toAllowNulls(AnnotationAndValue builderTriggerAnnotation, TypeInfo typeInfo) { String val = searchForBuilderAnnotation("allowNulls", builderTriggerAnnotation, typeInfo); - return val == null ? DEFAULT_ALLOW_NULLS : Boolean.parseBoolean(val); + return (val == null) ? Builder.DEFAULT_ALLOW_NULLS : Boolean.parseBoolean(val); + } + + /** + * In support of {@link io.helidon.builder.Builder#includeGeneratedAnnotation()}. + */ + private static boolean toIncludeGeneratedAnnotation(AnnotationAndValue builderTriggerAnnotation, + TypeInfo typeInfo) { + String val = searchForBuilderAnnotation("includeGeneratedAnnotation", builderTriggerAnnotation, typeInfo); + return (val == null) ? Builder.DEFAULT_INCLUDE_GENERATED_ANNOTATION : Boolean.parseBoolean(val); } /** @@ -475,7 +508,7 @@ private static String toListImplType(AnnotationAndValue builderTriggerAnnotation } /** - * In support of {@link io.helidon.builder.Builder#mapImplType()} ()}. + * In support of {@link io.helidon.builder.Builder#mapImplType()}. */ private static String toMapImplType(AnnotationAndValue builderTriggerAnnotation, TypeInfo typeInfo) { @@ -528,7 +561,7 @@ private static void gatherAllAttributeNames(BodyContext ctx, } if (Objects.isNull(ctx.parentTypeName.get()) - && superTypeInfo.typeKind().equals("INTERFACE")) { + && superTypeInfo.typeKind().equals(DefaultBuilderCreatorProvider.INTERFACE)) { ctx.parentTypeName.set(superTypeInfo.typeName()); } else if (Objects.isNull(ctx.parentAnnotationType.get()) && superTypeInfo.typeKind().equals("ANNOTATION_TYPE")) { diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTemplateHelper.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTemplateHelper.java deleted file mode 100644 index bdb9ccc4a6b..00000000000 --- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTemplateHelper.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.builder.processor.tools; - -class BuilderTemplateHelper { - - private BuilderTemplateHelper() { - } - - public static String getDefaultGeneratedSticker(String generatorClassTypeName) { - String[] generatedSticker = new String[] { - "generator=" + generatorClassTypeName - }; - - StringBuilder result = new StringBuilder("// Generated("); - int i = 0; - for (String s : generatedSticker) { - if (i++ > 0) { - result.append(", "); - } - result.append("\"").append(s).append("\""); - } - if (result.length() > 0) { - result.append(")"); - } - - return result.toString(); - } - -} diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java index 5b178e8b91f..e28ce4000bc 100644 --- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java +++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/BuilderTypeTools.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,11 +62,12 @@ */ @Weight(Weighted.DEFAULT_WEIGHT - 1) public class BuilderTypeTools implements TypeInfoCreatorProvider { - private static final boolean ACCEPT_ABSTRACT_CLASS_TARGETS = true; /** - * Default constructor. + * Default constructor. Service loaded. + * + * @deprecated */ // note: this needs to remain public since it will be resolved via service loader ... @Deprecated @@ -74,20 +75,18 @@ public BuilderTypeTools() { } @Override - public Optional createTypeInfo(AnnotationAndValue annotation, - TypeName typeName, - TypeElement element, - ProcessingEnvironment processingEnv) { - Objects.requireNonNull(annotation); - Objects.requireNonNull(annotation.typeName()); - Objects.requireNonNull(typeName); - + public Optional createTypeInfo( + TypeName annotationTypeName, + TypeName typeName, + TypeElement element, + ProcessingEnvironment processingEnv) { + Objects.requireNonNull(annotationTypeName); if (typeName.name().equals(Annotation.class.getName())) { return Optional.empty(); } if (!isAcceptableBuilderTarget(element)) { - String msg = annotation.typeName() + " is not intended to be targeted to this type: " + element; + String msg = annotationTypeName + " is not intended to be targeted to this type: " + element; processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg); throw new IllegalStateException(msg); } @@ -106,6 +105,10 @@ public Optional createTypeInfo(AnnotationAndValue annotation, Collection elementInfo = toElementInfo(element, processingEnv, true); Collection otherElementInfo = toElementInfo(element, processingEnv, false); + Set modifierNames = element.getModifiers().stream() + .map(Modifier::toString) + .map(String::toUpperCase) + .collect(Collectors.toSet()); return Optional.of(DefaultTypeInfo.builder() .typeName(typeName) .typeKind(String.valueOf(element.getKind())) @@ -114,7 +117,9 @@ public Optional createTypeInfo(AnnotationAndValue annotation, processingEnv.getElementUtils())) .elementInfo(elementInfo) .otherElementInfo(otherElementInfo) - .update(it -> toTypeInfo(annotation, element, processingEnv).ifPresent(it::superTypeInfo)) + .modifierNames(modifierNames) + .update(it -> toTypeInfo(annotationTypeName, element, processingEnv) + .ifPresent(it::superTypeInfo)) .build()); } @@ -122,10 +127,11 @@ public Optional createTypeInfo(AnnotationAndValue annotation, * Determines if the target element with the {@link io.helidon.builder.Builder} annotation is an acceptable element type. * If it is not acceptable then the caller is expected to throw an exception or log an error, etc. * - * @param element the element + * @param element the element * @return true if the element is acceptable */ - protected boolean isAcceptableBuilderTarget(Element element) { + protected boolean isAcceptableBuilderTarget( + Element element) { final ElementKind kind = element.getKind(); final Set modifiers = element.getModifiers(); boolean isAcceptable = (kind == ElementKind.INTERFACE @@ -143,9 +149,10 @@ protected boolean isAcceptableBuilderTarget(Element element) { * @param wantWhatWeCanAccept pass true to get the elements we can accept to process, false for the other ones * @return the collection of typed elements */ - protected Collection toElementInfo(TypeElement element, - ProcessingEnvironment processingEnv, - boolean wantWhatWeCanAccept) { + protected Collection toElementInfo( + TypeElement element, + ProcessingEnvironment processingEnv, + boolean wantWhatWeCanAccept) { return element.getEnclosedElements().stream() .filter(it -> it.getKind() == ElementKind.METHOD) .map(ExecutableElement.class::cast) @@ -161,14 +168,16 @@ protected Collection toElementInfo(TypeElement element, * @param ee the executable element * @return true if not able to accept */ - protected boolean canAccept(ExecutableElement ee) { + protected boolean canAccept( + ExecutableElement ee) { Set mods = ee.getModifiers(); return mods.contains(Modifier.ABSTRACT); } - private Optional toTypeInfo(AnnotationAndValue annotation, - TypeElement element, - ProcessingEnvironment processingEnv) { + private Optional toTypeInfo( + TypeName annotationTypeName, + TypeElement element, + ProcessingEnvironment processingEnv) { List ifaces = element.getInterfaces(); if (ifaces.size() > 1) { processingEnv.getMessager() @@ -182,7 +191,7 @@ private Optional toTypeInfo(AnnotationAndValue annotation, return Optional.empty(); } - return createTypeInfo(annotation, + return createTypeInfo(annotationTypeName, createTypeNameFromElement(parent.orElseThrow()).orElseThrow(), parent.orElseThrow(), processingEnv); @@ -194,7 +203,8 @@ private Optional toTypeInfo(AnnotationAndValue annotation, * @param typeMirror the type mirror * @return the type element */ - public static Optional toTypeElement(TypeMirror typeMirror) { + public static Optional toTypeElement( + TypeMirror typeMirror) { if (TypeKind.DECLARED == typeMirror.getKind()) { TypeElement te = (TypeElement) ((DeclaredType) typeMirror).asElement(); return (te.toString().equals(Object.class.getName())) ? Optional.empty() : Optional.of(te); @@ -202,13 +212,25 @@ public static Optional toTypeElement(TypeMirror typeMirror) { return Optional.empty(); } + /** + * Creates a name from a declared type during annotation processing. + * + * @param type the element type + * @return the associated type name instance + */ + public static Optional createTypeNameFromDeclaredType( + DeclaredType type) { + return createTypeNameFromElement(type.asElement()); + } + /** * Creates a name from an element type during annotation processing. * * @param type the element type * @return the associated type name instance */ - public static Optional createTypeNameFromElement(Element type) { + public static Optional createTypeNameFromElement( + Element type) { if (type instanceof VariableElement) { return createTypeNameFromMirror(type.asType()); } @@ -238,7 +260,8 @@ public static Optional createTypeNameFromElement(Element type) * @param typeMirror the type mirror * @return the type name associated with the type mirror, or empty for generic type variables */ - public static Optional createTypeNameFromMirror(TypeMirror typeMirror) { + public static Optional createTypeNameFromMirror( + TypeMirror typeMirror) { TypeKind kind = typeMirror.getKind(); if (kind.isPrimitive()) { Class type; @@ -315,8 +338,9 @@ public static Optional createTypeNameFromMirror(TypeMirror type * @param ams the collection to search through * @return the annotation mirror, or empty if not found */ - public static Optional findAnnotationMirror(String annotationType, - Collection ams) { + public static Optional findAnnotationMirror( + String annotationType, + Collection ams) { return ams.stream() .filter(it -> annotationType.equals(it.getAnnotationType().toString())) .findFirst(); @@ -329,8 +353,9 @@ public static Optional findAnnotationMirror(String a * @param elements the elements * @return the new instance or empty if the annotation mirror passed is invalid */ - public static Optional createAnnotationAndValueFromMirror(AnnotationMirror am, - Elements elements) { + public static Optional createAnnotationAndValueFromMirror( + AnnotationMirror am, + Elements elements) { Optional val = createTypeNameFromMirror(am.getAnnotationType()); return val.map(it -> DefaultAnnotationAndValue.create(it, extractValues(am, elements))); @@ -343,8 +368,9 @@ public static Optional createAnnotationAndValueFromMirror(An * @param elements the elements * @return the list of annotations extracted from the element */ - public static List createAnnotationAndValueListFromElement(Element e, - Elements elements) { + public static List createAnnotationAndValueListFromElement( + Element e, + Elements elements) { return e.getAnnotationMirrors().stream().map(it -> createAnnotationAndValueFromMirror(it, elements)) .filter(Optional::isPresent) .map(Optional::orElseThrow) @@ -358,8 +384,9 @@ public static List createAnnotationAndValueListFromElement(E * @param elements the optional elements * @return the extracted values */ - public static Map extractValues(AnnotationMirror am, - Elements elements) { + public static Map extractValues( + AnnotationMirror am, + Elements elements) { return extractValues(elements.getElementValuesWithDefaults(am)); } @@ -369,7 +396,8 @@ public static Map extractValues(AnnotationMirror am, * @param values the element values * @return the extracted values */ - public static Map extractValues(Map values) { + public static Map extractValues( + Map values) { Map result = new LinkedHashMap<>(); values.forEach((el, val) -> { String name = el.getSimpleName().toString(); @@ -382,19 +410,21 @@ public static Map extractValues(Map componentTypeNames = null; String defaultValue = null; - List elementTypeAnnotations = Collections.emptyList(); + List elementTypeAnnotations = List.of(); + Set modifierNames = Set.of(); if (v instanceof ExecutableElement) { ExecutableElement ee = (ExecutableElement) v; TypeMirror returnType = ee.getReturnType(); @@ -417,16 +447,21 @@ public static TypedElementName createTypedElementNameFromElement(Element v, .mapBlankArrayToNull(true) .mapEmptyStringToNull(true) .mapToSourceDeclaration(true), null); + modifierNames = ee.getModifiers().stream() + .map(Modifier::toString) + .collect(Collectors.toSet()); } - componentTypeNames = componentTypeNames == null ? List.of() : componentTypeNames; + componentTypeNames = (componentTypeNames == null) ? List.of() : componentTypeNames; return DefaultTypedElementName.builder() .typeName(type) .componentTypeNames(componentTypeNames) .elementName(v.getSimpleName().toString()) + .elementKind(v.getKind().name()) .defaultValue(defaultValue) .annotations(createAnnotationAndValueListFromElement(v, elements)) .elementTypeAnnotations(elementTypeAnnotations) + .modifierNames(modifierNames) .build(); } @@ -436,8 +471,22 @@ public static TypedElementName createTypedElementNameFromElement(Element v, * @param val the value to check * @return true if the value provided is non-null and non-blank. */ - static boolean hasNonBlankValue(String val) { - return Objects.nonNull(val) && !val.isBlank(); + static boolean hasNonBlankValue( + String val) { + return (val != null) && !val.isBlank(); } + /** + * Produces the generated sticker annotation attribute contents. + * + * @param generatorClassTypeName the generator class type name + * @param versionId the generator version identifier + * @return the generated sticker + */ + public static String generatedStickerFor( + String generatorClassTypeName, + String versionId) { + return "value = \"" + Objects.requireNonNull(generatorClassTypeName) + + "\", comments = \"version=" + versionId + "\""; + } } diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java index 965c992e5d3..cd38db23996 100644 --- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java +++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/DefaultBuilderCreatorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; @@ -57,9 +58,9 @@ */ @Weight(Weighted.DEFAULT_WEIGHT - 1) // allow all other creators to take precedence over us... public class DefaultBuilderCreatorProvider implements BuilderCreatorProvider { + static final String INTERFACE = "INTERFACE"; static final boolean DEFAULT_INCLUDE_META_ATTRIBUTES = true; static final boolean DEFAULT_REQUIRE_LIBRARY_DEPENDENCIES = true; - static final boolean DEFAULT_ALLOW_NULLS = false; static final String DEFAULT_IMPL_PREFIX = Builder.DEFAULT_IMPL_PREFIX; static final String DEFAULT_ABSTRACT_IMPL_PREFIX = Builder.DEFAULT_ABSTRACT_IMPL_PREFIX; static final String DEFAULT_SUFFIX = Builder.DEFAULT_SUFFIX; @@ -339,6 +340,9 @@ protected void appendHeader(StringBuilder builder, builder.append("import java.util.Map;\n"); builder.append("import java.util.Set;\n"); builder.append("import java.util.Objects;\n\n"); + if (ctx.includeGeneratedAnnotation()) { + builder.append("import jakarta.annotation.Generated;\n\n"); + } appendExtraImports(builder, ctx); builder.append("/**\n"); @@ -346,10 +350,15 @@ protected void appendHeader(StringBuilder builder, builder.append(" * ").append(type).append(" implementation w/ builder for {@link "); builder.append(ctx.typeInfo().typeName()).append("}.\n"); builder.append(" */\n"); - builder.append(BuilderTemplateHelper.getDefaultGeneratedSticker(getClass().getSimpleName())).append("\n"); + if (!ctx.includeGeneratedAnnotation()) { + builder.append("// "); + } + builder.append("@Generated(") + .append(generatedStickerFor(ctx)) + .append(")\n"); builder.append("@SuppressWarnings(\"unchecked\")\t\n"); appendAnnotations(builder, ctx.typeInfo().annotations(), ""); - builder.append("public "); + builder.append(ctx.publicOrPackagePrivateDecl()); if (!ctx.doingConcreteType()) { builder.append("abstract "); } @@ -390,6 +399,16 @@ protected void appendHeader(StringBuilder builder, builder.append(" {\n"); } + /** + * Returns the {@code Generated} sticker to be added. + * + * @param ctx the context + * @return the generated sticker. + */ + protected String generatedStickerFor(BodyContext ctx) { + return BuilderTypeTools.generatedStickerFor(getClass().getName(), Versions.CURRENT_BUILDER_VERSION); + } + /** * Returns any extra 'extends' type name that should be on the main generated type at the base level. * @@ -733,8 +752,11 @@ protected void maybeAppendSingularSetter(StringBuilder builder, String beanAttributeName, boolean isList, boolean isMap, boolean isSet) { String singularVal = toValue(Singular.class, method, false, false).orElse(null); - if (Objects.nonNull(singularVal) && (isList || isMap || isSet)) { - char[] methodName = reverseBeanName(singularVal.isBlank() ? maybeSingularFormOf(beanAttributeName) : singularVal); + if ((singularVal != null) && (isList || isMap || isSet)) { + char[] methodName = reverseBeanName(beanAttributeName); + appendSetter(builder, ctx, beanAttributeName, new String(methodName), method, false, GenerateMethod.SINGULAR_PREFIX); + + methodName = reverseBeanName(singularVal.isBlank() ? maybeSingularFormOf(beanAttributeName) : singularVal); GenerateMethod.singularSetter(builder, ctx, method, beanAttributeName, methodName); } } @@ -767,6 +789,16 @@ protected void appendSetter(StringBuilder mainBuilder, String beanAttributeName, String methodName, TypedElementName method) { + appendSetter(mainBuilder, ctx, beanAttributeName, methodName, method, true, ""); + } + + private void appendSetter(StringBuilder mainBuilder, + BodyContext ctx, + String beanAttributeName, + String methodName, + TypedElementName method, + boolean doClear, + String prefixName) { TypeName typeName = method.typeName(); boolean isList = typeName.isList(); boolean isMap = !isList && typeName.isMap(); @@ -774,28 +806,38 @@ protected void appendSetter(StringBuilder mainBuilder, boolean upLevel = isSet || isList; StringBuilder builder = new StringBuilder(); - GenerateJavadoc.setter(builder, beanAttributeName, method); - builder.append("\t\tpublic ").append(ctx.genericBuilderAliasDecl()).append(" ").append(methodName).append("(") + GenerateJavadoc.setter(builder, beanAttributeName); + builder.append("\t\tpublic ").append(ctx.genericBuilderAliasDecl()).append(" ") + .append(prefixName) + .append(methodName).append("(") .append(toGenerics(method, upLevel)).append(" val) {\n"); /* Assign field, or update collection */ - builder.append("\t\t\tthis.") - .append(beanAttributeName); + if (doClear) { + builder.append("\t\t\tthis.") + .append(beanAttributeName); + } if (isList) { - builder.append(".clear();\n"); + if (doClear) { + builder.append(".clear();\n"); + } builder.append("\t\t\tthis.") .append(beanAttributeName) .append(".addAll(").append(maybeRequireNonNull(ctx, "val")).append(");\n"); } else if (isMap) { - builder.append(".clear();\n"); + if (doClear) { + builder.append(".clear();\n"); + } builder.append("\t\t\tthis.") .append(beanAttributeName) .append(".putAll(").append(maybeRequireNonNull(ctx, "val")).append(");\n"); } else if (isSet) { - builder.append(".clear();\n"); + if (doClear) { + builder.append(".clear();\n"); + } builder.append("\t\t\tthis.") .append(beanAttributeName) .append(".addAll(").append(maybeRequireNonNull(ctx, "val")).append(");\n"); @@ -817,7 +859,7 @@ protected void appendSetter(StringBuilder mainBuilder, mainBuilder.append(builder); - if (typeName.isOptional() && !typeName.typeArguments().isEmpty()) { + if (prefixName.isBlank() && typeName.isOptional() && !typeName.typeArguments().isEmpty()) { TypeName genericType = typeName.typeArguments().get(0); appendDirectNonOptionalSetter(mainBuilder, ctx, beanAttributeName, method, methodName, genericType); } @@ -969,7 +1011,9 @@ private static String toGenerics(TypeName typeName, boolean upLevelToCollection, int depth) { if (typeName.typeArguments().isEmpty()) { - return (typeName.array() || Optional.class.getName().equals(typeName.name())) + return (typeName.array() + || Optional.class.getName().equals(typeName.name()) + || (typeName.wildcard() && depth > 0)) ? typeName.fqName() : typeName.name(); } @@ -1275,7 +1319,7 @@ private String collectionType(BodyContext ctx, TypeName type) { private void appendBuilderHeader(StringBuilder builder, BodyContext ctx) { GenerateJavadoc.builderClass(builder, ctx); - builder.append("\tpublic "); + builder.append("\t").append(ctx.publicOrPackagePrivateDecl()); if (!ctx.doingConcreteType()) { builder.append("abstract "); } @@ -1479,7 +1523,9 @@ private void appendHashCodeAndEquals(StringBuilder builder, builder.append("\t\tboolean equals = true;\n"); } for (TypedElementName method : ctx.allTypeInfos()) { - builder.append("\t\tequals &= Objects.equals(").append(method.elementName()).append("(), other.") + String equalsClass = method.typeName().array() ? Arrays.class.getName() : "Objects"; + builder.append("\t\tequals &= ").append(equalsClass).append(".equals(") + .append(method.elementName()).append("(), other.") .append(method.elementName()).append("());\n"); } builder.append("\t\treturn equals;\n"); @@ -1570,6 +1616,8 @@ private void appendDefaultValueAssignment(StringBuilder builder, boolean isCharArr = type.fqName().equals("char[]"); if ((isString || isCharArr) && !defaultVal.startsWith("\"")) { builder.append("\""); + } else if (!type.primitive() && !type.name().startsWith("java.")) { + builder.append(type.name()).append("."); } builder.append(defaultVal); diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java index 29b400ad841..da8401018d3 100644 --- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java +++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateJavadoc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.builder.processor.tools; +import io.helidon.pico.types.TypeName; import io.helidon.pico.types.TypedElementName; final class GenerateJavadoc { @@ -120,29 +121,26 @@ static void innerToString(StringBuilder builder) { } static void setter(StringBuilder builder, - String beanAttributeName, - TypedElementName method) { + String beanAttributeName) { builder.append("\t\t/**\n"); builder.append("\t\t * Setter for '").append(beanAttributeName).append("'.\n"); builder.append("\t\t *\n"); builder.append("\t\t * @param val the new value\n"); builder.append("\t\t * @return this fluent builder\n"); - builder.append("\t\t * @see #").append(method.elementName()).append("()\n"); builder.append("\t\t */\n"); } static void singularSetter(StringBuilder builder, - TypedElementName method, + TypeName methodTypeName, String beanAttributeName) { builder.append("\t\t/**\n"); - builder.append("\t\t * Singular setter for '").append(beanAttributeName).append("'.\n"); + builder.append("\t\t * Setter for '").append(beanAttributeName).append("'.\n"); builder.append("\t\t *\n"); - if (method.typeName().isMap()) { + if (methodTypeName.isMap()) { builder.append("\t\t * @param key the key\n"); } builder.append("\t\t * @param val the new value\n"); builder.append("\t\t * @return this fluent builder\n"); - builder.append("\t\t * @see #").append(method.elementName()).append("()\n"); builder.append("\t\t */\n"); } diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java index cb3d72a8654..89def95e341 100644 --- a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java +++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/GenerateMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,15 @@ package io.helidon.builder.processor.tools; import java.util.List; +import java.util.Objects; import java.util.Optional; import io.helidon.pico.types.TypeName; import io.helidon.pico.types.TypedElementName; final class GenerateMethod { + static final String SINGULAR_PREFIX = "add"; + private GenerateMethod() { } @@ -49,7 +52,7 @@ static void stringToCharSetter(StringBuilder builder, String beanAttributeName, TypedElementName method, String methodName) { - GenerateJavadoc.setter(builder, beanAttributeName, method); + GenerateJavadoc.setter(builder, beanAttributeName); builder.append("\t\tpublic ").append(ctx.genericBuilderAliasDecl()).append(" ").append(methodName) .append("(String val) {\n"); builder.append("\t\t\tObjects.requireNonNull(val);\n"); @@ -72,7 +75,7 @@ static void nonOptionalSetter(StringBuilder builder, TypedElementName method, String methodName, TypeName genericType) { - GenerateJavadoc.setter(builder, beanAttributeName, method); + GenerateJavadoc.setter(builder, beanAttributeName); builder.append("\t\tpublic ") .append(ctx.genericBuilderAliasDecl()) .append(" ") @@ -98,18 +101,52 @@ static void singularSetter(StringBuilder builder, TypedElementName method, String beanAttributeName, char[] methodName) { - GenerateJavadoc.singularSetter(builder, method, beanAttributeName); + TypeName typeName = method.typeName(); + TypeName mapValueType = mapValueTypeNameOf(typeName); + builder.append( + oneSingularSetter(ctx, + typeName, + toGenericsDecl(method, false, mapValueType), + beanAttributeName, + methodName, + false, + mapValueType)); + + // check if the values of the map are collection types, and overload that style method as well for singular usage + if (mapValueType != null && !mapValueType.typeArguments().isEmpty()) { + if (mapValueType.isSet() || mapValueType.isList()) { + TypeName singularMapValueType = Objects.requireNonNull(mapValueType.typeArguments().get(0)); + builder.append( + oneSingularSetter(ctx, + typeName, + toGenericsDecl(method, true, singularMapValueType), + beanAttributeName, + methodName, + true, + mapValueType)); + } + } + } + + private static StringBuilder oneSingularSetter(BodyContext ctx, + TypeName typeName, + String genericDecl, + String beanAttributeName, + char[] methodName, + boolean forceUseComputeStyle, + TypeName mapValueType) { + StringBuilder builder = new StringBuilder(); + GenerateJavadoc.singularSetter(builder, typeName, beanAttributeName); // builder method declaration for "addSomething()" builder.append("\t\tpublic ") .append(ctx.genericBuilderAliasDecl()) - .append(" add") + .append(" ").append(SINGULAR_PREFIX) .append(methodName) .append("(") - .append(toGenericsDecl(method)) + .append(genericDecl) .append(") {\n"); // body of the method - TypeName typeName = method.typeName(); if (typeName.isMap()) { builder.append("\t\t\tObjects.requireNonNull(key);\n"); } @@ -119,18 +156,67 @@ static void singularSetter(StringBuilder builder, if (typeName.isList() || typeName.isSet()) { builder.append(".add(val);\n"); } else { // isMap - builder.append(".put(key, val);\n"); + boolean useComputeStyle = forceUseComputeStyle + || (mapValueType != null && (mapValueType.isSet() || mapValueType.isList() || mapValueType.isMap())); + if (useComputeStyle) { + builder.append(".compute(key, (k, v) -> {\n"); + builder.append("\t\t\t\tif (v == null) {\n"); + builder.append("\t\t\t\t\tv = new "); + if (mapValueType.isSet()) { + builder.append(ctx.setType()); + } else if (mapValueType.isList()) { + builder.append(ctx.listType()); + } else if (mapValueType.isMap()) { + builder.append(ctx.mapType()); + } else { + throw new IllegalStateException("unhandled singular type: " + mapValueType); + } + builder.append("<>();\n"); + builder.append("\t\t\t\t}\n"); + if (forceUseComputeStyle) { + if (mapValueType.isSet() || mapValueType.isList()) { + builder.append("\t\t\t\t((java.util.Collection) v).add(val);\n"); + } else if (mapValueType.isMap()) { + builder.append("\t\t\t\t((java.util.Map) v).put(k, val);\n"); + } else { + throw new IllegalStateException("unhandled singular type: " + mapValueType); + } + } else { + if (mapValueType.isSet() || mapValueType.isList()) { + builder.append("\t\t\t\t((java.util.Collection) v).addAll(val);\n"); + } else if (mapValueType.isMap()) { + builder.append("\t\t\t\t((java.util.Map) v).putAll(val);\n"); + } else { + builder.append("\t\t\t\t((java.util.Map) v).put(k, val);\n"); + } + } + builder.append("\t\t\t\treturn v;\n"); + builder.append("\t\t\t});\n"); + } else { + builder.append(".put(key, val);\n"); + } } builder.append("\t\t\treturn identity();\n"); builder.append("\t\t}\n\n"); + return builder; + } + + private static TypeName mapValueTypeNameOf(TypeName typeName) { + return (typeName.isMap() && typeName.typeArguments().size() > 1) ? typeName.typeArguments().get(1) : null; } - private static String toGenericsDecl(TypedElementName method) { + private static String toGenericsDecl(TypedElementName method, + boolean useSingluarMapValues, + TypeName mapValueType) { List compTypeNames = method.typeName().typeArguments(); if (1 == compTypeNames.size()) { return avoidWildcard(compTypeNames.get(0)) + " val"; } else if (2 == compTypeNames.size()) { - return avoidWildcard(compTypeNames.get(0)) + " key, " + avoidWildcard(compTypeNames.get(1)) + " val"; + if (useSingluarMapValues) { + return avoidWildcard(compTypeNames.get(0)) + " key, " + avoidWildcard(mapValueType) + " val"; + } else { + return avoidWildcard(compTypeNames.get(0)) + " key, " + avoidWildcard(compTypeNames.get(1)) + " val"; + } } return "Object val"; } @@ -138,4 +224,5 @@ private static String toGenericsDecl(TypedElementName method) { private static String avoidWildcard(TypeName typeName) { return typeName.wildcard() ? typeName.name() : typeName.fqName(); } + } diff --git a/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/Versions.java b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/Versions.java new file mode 100644 index 00000000000..fdc0e886937 --- /dev/null +++ b/builder/processor-tools/src/main/java/io/helidon/builder/processor/tools/Versions.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.builder.processor.tools; + +/** + * Keeps track of the Builder Interop Versions. + *

+ * Since Builder performs code-generation, each previously generated artifact version may need to be discoverable in order to + * determine interoperability with previous release versions. This class will only track version changes for anything that might + * affect interoperability - it will not be rev'ed for general code enhancements and fixes. + *

+ * Please note that this version is completely independent of the Helidon version and other features and modules within Helidon. + */ +public class Versions { + + /** + * Version 1 - the initial release of Builder. + */ + public static final String BUILDER_VERSION_1 = "1"; + + /** + * The current release is {@link #BUILDER_VERSION_1}. + */ + public static final String CURRENT_BUILDER_VERSION = BUILDER_VERSION_1; + + private Versions() { + } + +} diff --git a/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java b/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java index 19977c8d4ff..70609de1e62 100644 --- a/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java +++ b/builder/processor/src/main/java/io/helidon/builder/processor/BuilderProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -157,7 +157,8 @@ protected void process(Class annoType, AnnotationAndValue builderAnnotation = BuilderTypeTools .createAnnotationAndValueFromMirror(am, elementUtils).get(); TypeName typeName = BuilderTypeTools.createTypeNameFromElement(element).orElse(null); - Optional typeInfo = tools.createTypeInfo(builderAnnotation, typeName, (TypeElement) element, processingEnv); + Optional typeInfo = tools + .createTypeInfo(builderAnnotation.typeName(), typeName, (TypeElement) element, processingEnv); if (typeInfo.isEmpty()) { String msg = "Nothing to process, skipping: " + element; LOGGER.log(System.Logger.Level.WARNING, msg); diff --git a/builder/tests/builder/pom.xml b/builder/tests/builder/pom.xml index c82616d4352..f30ef47580c 100644 --- a/builder/tests/builder/pom.xml +++ b/builder/tests/builder/pom.xml @@ -1,7 +1,7 @@ + + + jakarta.annotation + jakarta.annotation-api + provided + com.fasterxml.jackson.core jackson-annotations @@ -71,6 +77,11 @@ helidon-common-testing-junit5 test + + org.mockito + mockito-core + test + diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Container.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Container.java index aea7f35643c..9acc88a0fc3 100644 --- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Container.java +++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/Container.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,7 @@ enum Type { * * @return type of container if specified */ + @ConfiguredOption("PLASTIC") Optional type(); } diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java index accccf9851f..24005215d30 100644 --- a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java +++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/EdgeCases.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package io.helidon.builder.test.testsubjects; +import java.util.List; +import java.util.Map; import java.util.Optional; import io.helidon.builder.Builder; +import io.helidon.builder.Singular; import io.helidon.config.metadata.ConfiguredOption; /** @@ -43,4 +46,20 @@ public interface EdgeCases { @ConfiguredOption("-1") Optional optionalIntegerWithDefault(); + /** + * Validates the conversion of ? to Object. + * + * @return ignored, here for testing purposes only + */ + @Singular + List listOfObjects(); + + /** + * Validates the conversion of ? to Object. + * + * @return ignored, here for testing purposes only + */ + @Singular + Map mapOfEdgeCases(); + } diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MapCase.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MapCase.java new file mode 100644 index 00000000000..38ef6d7ed95 --- /dev/null +++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/MapCase.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.builder.test.testsubjects; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.helidon.builder.Builder; +import io.helidon.builder.Singular; + +/** + * Demonstrate singular properties of maps and builders. + */ +@Builder(implPrefix = "Test") +public abstract class MapCase { + + /** + * For Testing. + * + * @return for testing + */ + @Singular + public abstract Map stringToString(); + + /** + * For Testing. + * + * @return for testing + */ + @Singular("Dependency") + public abstract Map> stringToDependencies(); + + /** + * For Testing. + * + * @return for testing + */ + @Singular + public abstract Map> stringToDependencyMap(); + + /** + * For Testing. + * + * @return for testing + */ + @Singular + public abstract Map> stringToStringList(); + + + /** + * Test example only. + */ + public interface Dependency { + + /** + * Test example only. + * + * @return the name + */ + String name(); + } + +} diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/PackagePrivate.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/PackagePrivate.java new file mode 100644 index 00000000000..00adc2bc3ca --- /dev/null +++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/PackagePrivate.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.builder.test.testsubjects.packageprivates; + +import io.helidon.builder.Builder; + +/** + * Here to ensure the generated classes and methods all stay package private! + */ +@Builder +abstract class PackagePrivate { + abstract String name(); + abstract boolean booleanVal(); +} diff --git a/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/package-info.java b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/package-info.java new file mode 100644 index 00000000000..ad85979ef11 --- /dev/null +++ b/builder/tests/builder/src/main/java/io/helidon/builder/test/testsubjects/packageprivates/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Package private testing. + */ +package io.helidon.builder.test.testsubjects.packageprivates; diff --git a/builder/tests/builder/src/main/java/module-info.java b/builder/tests/builder/src/main/java/module-info.java index dec300d64ae..b22d0e07083 100644 --- a/builder/tests/builder/src/main/java/module-info.java +++ b/builder/tests/builder/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ * Helidon Builder Test module. */ module io.helidon.builder.test.builder { + requires static jakarta.annotation; requires static com.fasterxml.jackson.annotation; requires static io.helidon.config.metadata; diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/EdgeCasesTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/EdgeCasesTest.java index 34af5acce2a..7e6e1845d70 100644 --- a/builder/tests/builder/src/test/java/io/helidon/builder/test/EdgeCasesTest.java +++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/EdgeCasesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,25 @@ package io.helidon.builder.test; +import java.util.List; +import java.util.Map; +import java.util.Set; + import io.helidon.builder.test.testsubjects.DefaultEdgeCases; +import io.helidon.builder.test.testsubjects.EdgeCases; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; class EdgeCasesTest { @Test - void testIt() { - DefaultEdgeCases val = DefaultEdgeCases.builder().build(); + void testBasics() { + EdgeCases val = DefaultEdgeCases.builder().build(); assertThat(val.optionalIntegerWithDefault().get(), is(-1)); assertThat(val.optionalStringWithDefault().get(), equalTo("test")); @@ -37,4 +43,29 @@ void testIt() { assertThat(val.optionalStringWithDefault().get(), equalTo("test")); } + @Test + void listOfObjects() { + List listOfGenericObjects = List.of("test1"); + EdgeCases val = DefaultEdgeCases.builder() + .listOfObjects(listOfGenericObjects) + .addListOfObject("test2") + .build(); + assertThat(val.listOfObjects(), equalTo(List.of("test1", "test2"))); + } + + @Test + void mapOfEdgeCases() { + AnotherEdgeCase anotherEdgeCase = mock(AnotherEdgeCase.class); + Map mapOfEdgeCases = Map.of("test1", anotherEdgeCase); + EdgeCases val = DefaultEdgeCases.builder() + .mapOfEdgeCases(mapOfEdgeCases) + .addMapOfEdgeCase("test2", anotherEdgeCase) + .build(); + assertThat(val.mapOfEdgeCases().keySet(), equalTo(Set.of("test1", "test2"))); + } + + + interface AnotherEdgeCase extends EdgeCases { + } + } diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/MapCaseTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/MapCaseTest.java new file mode 100644 index 00000000000..1f260471761 --- /dev/null +++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/MapCaseTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.builder.test; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.helidon.builder.test.testsubjects.MapCase; +import io.helidon.builder.test.testsubjects.TestMapCase; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasKey; + +class MapCaseTest { + + @Test + void testIt() { + TestMapCase.Builder builder = TestMapCase.builder(); + + assertThat(builder.stringToString(), + sameInstance(builder.stringToString())); + assertThat(builder.stringToDependencies(), + sameInstance(builder.stringToDependencies())); + assertThat(builder.stringToDependencyMap(), + sameInstance(builder.stringToDependencyMap())); + + // allow direct mutation + builder.stringToString().put("a", "1"); + assertThat(builder.stringToString().get("a"), + equalTo("1")); + + // allow replacement of the map + LinkedHashMap> map = new LinkedHashMap<>(); + LinkedHashSet mSet = new LinkedHashSet<>(); + mSet.add(Mockito.mock(MapCase.Dependency.class)); + map.put("m", mSet); + builder.stringToDependencies(map); + assertThat(builder.stringToDependencies(), + hasKey("m")); + assertThat(builder.stringToDependencies().size(), + equalTo(1)); + builder.stringToDependencies(Map.of("n", Set.of(Mockito.mock(MapCase.Dependency.class)))); + assertThat(builder.stringToDependencies(), + hasKey("n")); + assertThat(builder.stringToDependencies().size(), + equalTo(1)); + builder.stringToDependencies(map); + + // allow augmentation of the collection within the maps - see how we understand the values are collections that we add to + // instead of replace + builder.addDependency("m", Set.of(Mockito.mock(MapCase.Dependency.class))); + assertThat(builder.stringToDependencies().get("m").size(), + equalTo(2)); + // true singular version on the value parameter as well + builder.addDependency("n", Mockito.mock(MapCase.Dependency.class)); + assertThat(builder.stringToDependencies().get("n").size(), + equalTo(1)); + assertThat(builder.stringToDependencies().size(), + equalTo(2)); + + // map of maps + builder.addStringToDependencyMap("p", Map.of("1", Mockito.mock(MapCase.Dependency.class))); + assertThat(builder.stringToDependencyMap().get("p").size(), + equalTo(1)); + builder.addStringToDependencyMap("p", Map.of("2", Mockito.mock(MapCase.Dependency.class))); + assertThat(builder.stringToDependencyMap().get("p").size(), + equalTo(2)); + assertThat(builder.stringToDependencyMap().size(), + equalTo(1)); + + // map of lists + builder.addStringToStringList("1", List.of("a", "b")); + builder.addStringToStringList("1", List.of("c", "d")); + builder.addStringToStringList("1", "e"); + builder.addStringToStringList("2", "x"); + + assertThat(builder.stringToStringList().size(), + equalTo(2)); + assertThat(builder.stringToStringList().get("1"), + contains("a", "b", "c", "d", "e")); + assertThat(builder.stringToStringList().get("2"), + contains("x")); + } + +} diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/PickleBarrelTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/PickleBarrelTest.java index be72513a1b7..c75935e6c02 100644 --- a/builder/tests/builder/src/test/java/io/helidon/builder/test/PickleBarrelTest.java +++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/PickleBarrelTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,6 @@ void testIt() { PickleBarrel pickleBarrel = pickleBarrelBuilder.addPickle(pickle).id("123").build(); assertThat(pickleBarrel.toString(), - equalTo("PickleBarrel(id=123, type=Optional.empty, pickles=[Pickle(type=DILL, size=Optional[MEDIUM])])")); + equalTo("PickleBarrel(id=123, type=Optional[PLASTIC], pickles=[Pickle(type=DILL, size=Optional[MEDIUM])])")); } } diff --git a/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/PackagePrivateTest.java b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/PackagePrivateTest.java new file mode 100644 index 00000000000..b5beed9ba0b --- /dev/null +++ b/builder/tests/builder/src/test/java/io/helidon/builder/test/testsubjects/PackagePrivateTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.builder.test.testsubjects; + +import org.junit.jupiter.api.Test; + +class PackagePrivateTest { + + @Test + void testIt() { + + } + +} diff --git a/builder/tests/nodeps/src/main/java/io/helidon/builder/test/nodeps/NoDepsInterceptedBean.java b/builder/tests/nodeps/src/main/java/io/helidon/builder/test/nodeps/NoDepsInterceptedBean.java index 4ee82189d1e..19d64792580 100644 --- a/builder/tests/nodeps/src/main/java/io/helidon/builder/test/nodeps/NoDepsInterceptedBean.java +++ b/builder/tests/nodeps/src/main/java/io/helidon/builder/test/nodeps/NoDepsInterceptedBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,8 @@ */ @Builder(requireLibraryDependencies = false, interceptor = NoDepsBeanBuilderInterceptor.class, - interceptorCreateMethod = "create") + interceptorCreateMethod = "create", + includeGeneratedAnnotation = false) public interface NoDepsInterceptedBean { /** diff --git a/pico/builder-config/pom.xml b/pico/builder-config/pom.xml index f5a7de0346e..e49619e0294 100644 --- a/pico/builder-config/pom.xml +++ b/pico/builder-config/pom.xml @@ -1,7 +1,7 @@ + tests diff --git a/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/ConfigBeanBuilderCreator.java b/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/ConfigBeanBuilderCreator.java index c1e3b2969df..235ffd69d60 100644 --- a/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/ConfigBeanBuilderCreator.java +++ b/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/ConfigBeanBuilderCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import io.helidon.builder.processor.spi.TypeInfo; import io.helidon.builder.processor.tools.BodyContext; +import io.helidon.builder.processor.tools.BuilderTypeTools; import io.helidon.builder.processor.tools.DefaultBuilderCreatorProvider; import io.helidon.common.Weight; import io.helidon.common.Weighted; @@ -85,6 +86,16 @@ protected void preValidate(TypeName implTypeName, // assertNoAnnotation(ConfiguredBy.class.getName(), typeInfo); assertNoAnnotation(jakarta.inject.Singleton.class.getName(), typeInfo); assertNoAnnotation("javax.inject.Singleton", typeInfo); + + if (!typeInfo.typeKind().equals("INTERFACE")) { + throw new IllegalStateException("@" + builderAnnotation.typeName().className() + + " is only supported on interface types: " + typeInfo.typeName()); + } + } + + @Override + protected String generatedStickerFor(BodyContext ctx) { + return BuilderTypeTools.generatedStickerFor(getClass().getName(), Versions.CURRENT_PICO_CONFIG_BUILDER_VERSION); } @Override @@ -113,7 +124,7 @@ protected void appendExtraImports(StringBuilder builder, builder.append("import ").append(Config.class.getName()).append(";\n"); builder.append("import ").append(ConfigResolver.class.getName()).append(";\n"); - builder.append("import ").append(ConfigBeanBuilderValidator.class.getName()).append(";\n"); + builder.append("import ").append(ConfigBeanBuilderValidator.class.getName()).append(";\n\n"); } @Override diff --git a/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/Versions.java b/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/Versions.java new file mode 100644 index 00000000000..8dc1b56f74c --- /dev/null +++ b/pico/builder-config/processor/src/main/java/io/helidon/pico/builder/config/processor/Versions.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.pico.builder.config.processor; + +/** + * Keeps track of the Pico Config Builder Interop Versions. + *

+ * Since Pico Config Builder performs code-generation, each previously generated artifact version may need to be discoverable in + * order to determine interoperability with previous release versions. This class will only track version changes for anything + * that might affect interoperability - it will not be rev'ed for general code enhancements and fixes. + *

+ * Please note that this version is completely independent of the Helidon version and other features and modules within Helidon. + */ +public class Versions { + + /** + * Version 1 - the initial release of Builder. + */ + public static final String PICO_CONFIG_BUILDER_VERSION_1 = "1"; + + /** + * The current release is {@link #PICO_CONFIG_BUILDER_VERSION_1}. + */ + public static final String CURRENT_PICO_CONFIG_BUILDER_VERSION = PICO_CONFIG_BUILDER_VERSION_1; + + private Versions() { + } + +} diff --git a/pico/tools/src/main/java/io/helidon/pico/tools/ModuleInfoDescriptor.java b/pico/tools/src/main/java/io/helidon/pico/tools/ModuleInfoDescriptor.java index a776a1e60a7..46f05c5d8fa 100644 --- a/pico/tools/src/main/java/io/helidon/pico/tools/ModuleInfoDescriptor.java +++ b/pico/tools/src/main/java/io/helidon/pico/tools/ModuleInfoDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,7 +136,7 @@ enum Ordering { * * @return the ordering */ - @ConfiguredOption("Ordering.NATURAL") + @ConfiguredOption("NATURAL") Ordering ordering(); /** diff --git a/pico/types/pom.xml b/pico/types/pom.xml index 61525667bfb..cddaaef102a 100644 --- a/pico/types/pom.xml +++ b/pico/types/pom.xml @@ -1,7 +1,7 @@