diff --git a/.github/workflows/release-deploy.yml b/.github/workflows/release-deploy.yml index d361131..261c9f1 100644 --- a/.github/workflows/release-deploy.yml +++ b/.github/workflows/release-deploy.yml @@ -109,4 +109,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release create v${{ env.RELEASE_VERSION }} modules/auto-record/target/auto-record-${{ env.RELEASE_VERSION }}.jar --generate-notes --verify-tag + gh release create v${{ env.RELEASE_VERSION }} --generate-notes --verify-tag diff --git a/README.md b/README.md index 5bc101c..341942d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Java record newContext generator [![CI Verify Status](https://github.com/pawellabaj/auto-record/actions/workflows/verify.yml/badge.svg?branch=main)](https://github.com/pawellabaj/auto-record/actions/workflows/verify.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=pl.com.labaj.autorecord%3Aauto-record-project&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=pl.com.labaj.autorecord%3Aauto-record-project) -[![Sonatype Lift Status](https://lift.sonatype.com/api/badge/github.com/pawellabaj/auto-record)](https://lift.sonatype.com/results/github.com/pawellabaj/auto-record) [![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://github.com/jvm-repo-rebuild/reproducible-central#pl.com.labaj.autorecord:auto-record) [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7700/badge)](https://bestpractices.coreinfrastructure.org/projects/7700) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](.github/CODE_OF_CONDUCT.md) diff --git a/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/ClassProperty.java b/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/ClassProperty.java index 0171c1a..f7977c4 100644 --- a/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/ClassProperty.java +++ b/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/ClassProperty.java @@ -26,4 +26,6 @@ @Target({METHOD, PARAMETER, RECORD_COMPONENT}) @Retention(CLASS) -public @interface ClassProperty {} +public @interface ClassProperty { + int priority() default 0; +} diff --git a/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/SourceProperty.java b/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/SourceProperty.java index 11443c0..7c49a6d 100644 --- a/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/SourceProperty.java +++ b/modules/auto-record-tests/src/main/java/pl/com/labaj/autorecord/annotations/SourceProperty.java @@ -26,4 +26,6 @@ @Target({METHOD, PARAMETER, RECORD_COMPONENT}) @Retention(SOURCE) -public @interface SourceProperty {} +public @interface SourceProperty { + String metadata() default "default"; +} diff --git a/modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/processor/GenerationTest.java b/modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/processor/GenerationTest.java index eb69d42..77eb789 100644 --- a/modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/processor/GenerationTest.java +++ b/modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/processor/GenerationTest.java @@ -48,7 +48,8 @@ class GenerationTest { "ToStringMemoized", "ToStringMemoizedByAnnotation", "HashCodeIgnoredFieldMemoized", - "AnnotatedProperty" + "AnnotatedProperty", + "AnnotationWithValue" }) void shouldGenerateRecord(String interfaceName) { //given diff --git a/modules/auto-record-tests/src/test/resources/in/AnnotationWithValue.java b/modules/auto-record-tests/src/test/resources/in/AnnotationWithValue.java new file mode 100644 index 0000000..5e6fd4b --- /dev/null +++ b/modules/auto-record-tests/src/test/resources/in/AnnotationWithValue.java @@ -0,0 +1,44 @@ +package pl.com.labaj.autorecord.testcase; + +/*- + * 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 pl.com.labaj.autorecord.AutoRecord; +import pl.com.labaj.autorecord.Memoized; +import pl.com.labaj.autorecord.annotations.ClassProperty; +import pl.com.labaj.autorecord.annotations.SourceProperty; + +import javax.annotation.Nullable; + +import static java.util.Objects.isNull; + +@AutoRecord +interface AnnotationWithValue { + @Nullable + Integer id(); + + @SourceProperty + @ClassProperty(priority = 9) + @SuppressWarnings("DataFlowIssue") + @Memoized + default String idToString() { + if (isNull(id())) { + return "absent"; + } + + return Integer.toString(id()); + } +} diff --git a/modules/auto-record-tests/src/test/resources/out/AnnotationWithValueRecord.java b/modules/auto-record-tests/src/test/resources/out/AnnotationWithValueRecord.java new file mode 100644 index 0000000..5b1d31e --- /dev/null +++ b/modules/auto-record-tests/src/test/resources/out/AnnotationWithValueRecord.java @@ -0,0 +1,53 @@ +package pl.com.labaj.autorecord.testcase; + +/*- + * 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 static java.util.Objects.requireNonNullElseGet; + +import java.lang.Integer; +import java.lang.Override; +import java.lang.String; +import java.lang.SuppressWarnings; +import javax.annotation.Nullable; +import javax.annotation.processing.Generated; +import pl.com.labaj.autorecord.GeneratedWithAutoRecord; +import pl.com.labaj.autorecord.Memoized; +import pl.com.labaj.autorecord.annotations.ClassProperty; +import pl.com.labaj.autorecord.annotations.SourceProperty; +import pl.com.labaj.autorecord.memoizer.Memoizer; + +@Generated("pl.com.labaj.autorecord.AutoRecord") +@GeneratedWithAutoRecord +record AnnotationWithValueRecord(@Nullable Integer id, + @SourceProperty @ClassProperty(priority = 9) @Nullable Memoizer idToStringMemoizer) implements AnnotationWithValue { + AnnotationWithValueRecord { + idToStringMemoizer = requireNonNullElseGet(idToStringMemoizer, Memoizer::new); + } + + AnnotationWithValueRecord(@Nullable Integer id) { + this(id, new Memoizer<>()); + } + + @SourceProperty + @ClassProperty(priority = 9) + @SuppressWarnings({"DataFlowIssue"}) + @Memoized + @Override + public String idToString() { + return idToStringMemoizer.computeIfAbsent(AnnotationWithValue.super::idToString); + } +} diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/BuilderOptionsSubGenerator.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/BuilderOptionsSubGenerator.java index d838eef..7e34211 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/BuilderOptionsSubGenerator.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/BuilderOptionsSubGenerator.java @@ -64,7 +64,7 @@ private void addMember(StaticImports staticImports, AnnotationSpec.Builder annot if (returnType.isPrimitive()) { annotationBuilder.addMember(name, "$L", actualValue); - } else if (option.type().isEnum()) { + } else if (returnType.isEnum()) { var enumName = ((Enum) actualValue).name(); staticImports.add(returnType, enumName); annotationBuilder.addMember(name, "$L", enumName); diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java index 32944ef..f7f2ec3 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -36,14 +37,15 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; -import static java.util.function.Predicate.not; import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static org.apiguardian.api.API.Status.INTERNAL; @@ -74,23 +76,21 @@ public static List createAnnotationSpecs(List Set targets, List> annotationsToAdd, List> annotationsToExclude) { - var classNamesStream = annotationMirrors.stream() - .map(AnnotationMirror::getAnnotationType) - .map(DeclaredType::asElement) - .map(TypeElement.class::cast) - .filter(annotation -> canAnnotateElementType(annotation, targets)) - .map(ClassName::get); - var classNamesToAdd = annotationsToAdd.stream() - .map(ClassName::get); + var parentAnnotationDetails = annotationMirrors.stream() + .map(annotationMirror -> AnnotationDetails.toAnnotationDetails(annotationMirror, targets)) + .filter(Objects::nonNull); + var annotationDetailsToAdd = annotationsToAdd.stream() + .map(ClassName::get) + .map(className -> new AnnotationDetails(className, Map.of())); + var classNamesToExclude = annotationsToExclude.stream() .map(ClassName::get) .collect(toSet()); - return Stream.concat(classNamesStream, classNamesToAdd) - .filter(not(classNamesToExclude::contains)) + return Stream.concat(parentAnnotationDetails, annotationDetailsToAdd) + .filter(annotationDetails -> !classNamesToExclude.contains(annotationDetails.className())) .distinct() - .map(AnnotationSpec::builder) - .map(AnnotationSpec.Builder::build) + .map(AnnotationDetails::toAnnotationSpec) .toList(); } @@ -153,7 +153,7 @@ public static List merge(List annotations1, List var annotationBuilder = AnnotationSpec.builder(annotationlass); - record Member(String name, CodeBlock value){} + record Member(String name, CodeBlock value) {} var members = entry.getValue().stream() .map(annotationSpec -> annotationSpec.members) .map(Map::entrySet) @@ -172,4 +172,33 @@ record Member(String name, CodeBlock value){} }) .toList(); } + + private record AnnotationDetails(ClassName className, Map values) { + @Nullable + private static AnnotationDetails toAnnotationDetails(AnnotationMirror annotationMirror, Set targets) { + var annotationType = annotationMirror.getAnnotationType(); + var typeElement = (TypeElement) annotationType.asElement(); + + if (!canAnnotateElementType(typeElement, targets)) { + return null; + } + + var className = ClassName.get(typeElement); + + var values = annotationMirror.getElementValues().entrySet().stream() + .collect(toMap( + entry -> entry.getKey().getSimpleName().toString(), + entry -> (AnnotationValue) entry.getValue() + )); + + return new AnnotationDetails(className, values); + } + + private AnnotationSpec toAnnotationSpec() { + var annotationBuilder = AnnotationSpec.builder(className); + values.forEach((name, value) -> annotationBuilder.addMember(name, "$L", value.toString())); + + return annotationBuilder.build(); + } + } }