From 5dde7a42df5f0b34da6418b50d52c7a068bae1aa Mon Sep 17 00:00:00 2001 From: elifKurtay Date: Tue, 24 Sep 2024 16:14:53 +0200 Subject: [PATCH] create separate annotations --- .../annotations/{Utils.java => Equals.java} | 2 +- .../sourcegen/annotations/HashCode.java | 40 ++++++++++ .../sourcegen/annotations/ToString.java | 40 ++++++++++ .../visitors/UtilsAnnotationVisitor.java | 75 ++++++++----------- .../micronaut/sourcegen/example/Person4.java | 8 +- 5 files changed, 119 insertions(+), 46 deletions(-) rename sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/{Utils.java => Equals.java} (97%) create mode 100644 sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/HashCode.java create mode 100644 sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/ToString.java diff --git a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Utils.java b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Equals.java similarity index 97% rename from sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Utils.java rename to sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Equals.java index f7f010a1..909c1a02 100644 --- a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Utils.java +++ b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Equals.java @@ -30,7 +30,7 @@ @Retention(RUNTIME) @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) -public @interface Utils { +public @interface Equals { /** * @return Array of annotations to apply on the utils diff --git a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/HashCode.java b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/HashCode.java new file mode 100644 index 00000000..9abbbc20 --- /dev/null +++ b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/HashCode.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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.micronaut.sourcegen.annotations; + +import io.micronaut.core.annotation.Introspected; + +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * The Utils annotation on a bean should generate toString, equals and hashCode implementations. + * + * @author Elif Kurtay + * @since 1.3 + */ + +@Retention(RUNTIME) +@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) +public @interface HashCode { + + /** + * @return Array of annotations to apply on the utils + */ + Class[] annotatedWith() default Introspected.class; + +} diff --git a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/ToString.java b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/ToString.java new file mode 100644 index 00000000..4f603d84 --- /dev/null +++ b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/ToString.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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.micronaut.sourcegen.annotations; + +import io.micronaut.core.annotation.Introspected; + +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * The Utils annotation on a bean should generate toString, equals and hashCode implementations. + * + * @author Elif Kurtay + * @since 1.3 + */ + +@Retention(RUNTIME) +@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) +public @interface ToString { + + /** + * @return Array of annotations to apply on the utils + */ + Class[] annotatedWith() default Introspected.class; + +} diff --git a/sourcegen-generator/src/main/java/io/micronaut/sourcegen/generator/visitors/UtilsAnnotationVisitor.java b/sourcegen-generator/src/main/java/io/micronaut/sourcegen/generator/visitors/UtilsAnnotationVisitor.java index ce063330..feefda29 100644 --- a/sourcegen-generator/src/main/java/io/micronaut/sourcegen/generator/visitors/UtilsAnnotationVisitor.java +++ b/sourcegen-generator/src/main/java/io/micronaut/sourcegen/generator/visitors/UtilsAnnotationVisitor.java @@ -22,8 +22,9 @@ import io.micronaut.inject.processing.ProcessingException; import io.micronaut.inject.visitor.TypeElementVisitor; import io.micronaut.inject.visitor.VisitorContext; -import io.micronaut.sourcegen.annotations.Singular; -import io.micronaut.sourcegen.annotations.Utils; +import io.micronaut.sourcegen.annotations.Equals; +import io.micronaut.sourcegen.annotations.HashCode; +import io.micronaut.sourcegen.annotations.ToString; import io.micronaut.sourcegen.generator.SourceGenerator; import io.micronaut.sourcegen.generator.SourceGenerators; import io.micronaut.sourcegen.model.*; @@ -32,7 +33,6 @@ import javax.lang.model.element.Modifier; import static io.micronaut.sourcegen.generator.visitors.BuilderAnnotationVisitor.*; -import static io.micronaut.sourcegen.generator.visitors.Singulars.singularize; /** * The visitor that generates the util functions of a bean. @@ -42,13 +42,13 @@ */ @Internal -public final class UtilsAnnotationVisitor implements TypeElementVisitor { +public final class UtilsAnnotationVisitor implements TypeElementVisitor { + private static final int NULL_HASH_VALUE = 43; + private static final int TRUE_HASH_VALUE = 79; + private static final int FALSE_HASH_VALUE = 97; + private static final int HASH_MULTIPLIER = 59; private final Set processed = new HashSet<>(); - private final static int NULL_HASH_VALUE = 43; - private final static int TRUE_HASH_VALUE = 79; - private final static int FALSE_HASH_VALUE = 97; - private final static int HASH_MULTIPLIER = 59; @Override public @NonNull VisitorKind getVisitorKind() { @@ -62,7 +62,9 @@ public void start(VisitorContext visitorContext) { @Override public void visitClass(ClassElement element, VisitorContext context) { - // TODO: collect annotations, change TypeElementVisitor Utils -> Object + if (!(element.hasStereotype(ToString.class) || element.hasStereotype(Equals.class) || element.hasStereotype(HashCode.class))) { + return; + } if (processed.contains(element.getName())) { return; @@ -71,20 +73,23 @@ public void visitClass(ClassElement element, VisitorContext context) { String simpleName = element.getSimpleName() + "Utils"; String utilsClassName = element.getPackageName() + "." + simpleName; - ClassTypeDef builderType = ClassTypeDef.of(utilsClassName); - // class def and annotations ClassDef.ClassDefBuilder utilsBuilder = ClassDef.builder(utilsClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL); - addAnnotations(utilsBuilder, element.getAnnotation(Utils.class)); // TODO: should I check for properties that have getters? List properties = element.getBeanProperties(); // create the utils functions - createToStringMethod(utilsBuilder, simpleName, properties); - createEqualsMethod(utilsBuilder, simpleName, properties); - createHashCodeMethod(utilsBuilder, simpleName, properties); + if (element.hasStereotype(ToString.class)) { + createToStringMethod(utilsBuilder, element.getSimpleName(), properties); + } + if (element.hasStereotype(Equals.class)) { + createEqualsMethod(utilsBuilder, element.getSimpleName(), properties); + } + if (element.hasStereotype(HashCode.class)) { + createHashCodeMethod(utilsBuilder, element.getSimpleName(), properties); + } SourceGenerator sourceGenerator = SourceGenerators.findByLanguage(context.getLanguage()).orElse(null); if (sourceGenerator == null) { @@ -111,7 +116,7 @@ public void visitClass(ClassElement element, VisitorContext context) { } catch (Exception e) { SourceGenerators.handleFatalException( element, - Utils.class, + ToString.class, e, (exception -> { processed.remove(element.getName()); @@ -121,33 +126,19 @@ public void visitClass(ClassElement element, VisitorContext context) { } } - static void addAnnotations(ClassDef.ClassDefBuilder builder, AnnotationValue annotation) { - Optional annotatedWith = annotation.getConvertibleValues() - .get(BUILDER_ANNOTATED_WITH_MEMBER, AnnotationClassValue[].class); - if (annotatedWith.isEmpty()) { - // Apply the default annotation - builder.addAnnotation(Introspected.class); - } else { - for (AnnotationClassValue value: annotatedWith.get()) { - builder.addAnnotation(value.getName()); - } - } - } - - /* TODO: complete toString method - - Added with @ToString annotation + /* toString method */ - private static void createToStringMethod(ClassDef.ClassDefBuilder classDefBuilder, String simpleName, List properties) { + private static void createToStringMethod(ClassDef.ClassDefBuilder classDefBuilder, String objectName, List properties) { List statements = new ArrayList<>(); VariableDef.Local strBuilder = new VariableDef.Local("strBuilder", ClassTypeDef.of(StringBuilder.class)); VariableDef arrays = new VariableDef.Local("java.util.Arrays", ClassTypeDef.of(java.util.Arrays.class)); - VariableDef thisVariable = new VariableDef.MethodParameter("object", ClassTypeDef.of(simpleName.substring(0, simpleName.length() - 5))); + VariableDef thisVariable = new VariableDef.MethodParameter("object", ClassTypeDef.of(objectName)); statements.add(strBuilder.defineAndAssign(ClassTypeDef.of(StringBuilder.class).instantiate())); statements.add(strBuilder.invoke( "append", ClassTypeDef.of(strBuilder.getClass()), - ExpressionDef.constant(simpleName.substring(0, simpleName.length() - 5) + "[") + ExpressionDef.constant(objectName + "[") )); for (int i = 0; i < properties.size(); i++) { PropertyElement beanProperty = properties.get(i); @@ -183,16 +174,15 @@ private static void createToStringMethod(ClassDef.ClassDefBuilder classDefBuilde MethodDef method = MethodDef.builder("toString") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(String.class) - .addParameter("object", ClassTypeDef.of(simpleName.substring(0, simpleName.length() - 5))) + .addParameter("object", ClassTypeDef.of(objectName)) .addStatements(statements) .build(); classDefBuilder.addMethod(method); } - /* TODO: complete equals method - - Added with @Equals annotation + /* complete equals method */ - private static void createEqualsMethod(ClassDef.ClassDefBuilder classDefBuilder, String simpleName, List properties) { + private static void createEqualsMethod(ClassDef.ClassDefBuilder classDefBuilder, String objectName, List properties) { MethodDef method = MethodDef.builder("equals") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(boolean.class) @@ -200,7 +190,7 @@ private static void createEqualsMethod(ClassDef.ClassDefBuilder classDefBuilder, .addParameter("secondObject", TypeDef.of(Object.class)) .build((self, parameterDef) -> { // local variables needed - VariableDef classname = new VariableDef.Local(simpleName.substring(0, simpleName.length() - 5), ClassTypeDef.of(simpleName.substring(0, simpleName.length() - 5))); + VariableDef classname = new VariableDef.Local(objectName, ClassTypeDef.of(objectName)); VariableDef arrays = new VariableDef.Local("java.util.Arrays", ClassTypeDef.of(java.util.Arrays.class)); List statements = new ArrayList<>(); VariableDef.Local firstObject = new VariableDef.Local("first", classname.type()); @@ -297,13 +287,12 @@ private static void createEqualsMethod(ClassDef.ClassDefBuilder classDefBuilder, classDefBuilder.addMethod(method); } - /* TODO: complete hashCode method - - Added with @HashCode annotation + /* complete hashCode method */ - private static void createHashCodeMethod(ClassDef.ClassDefBuilder classDefBuilder, String simpleName, List properties) { + private static void createHashCodeMethod(ClassDef.ClassDefBuilder classDefBuilder, String objectName, List properties) { MethodDef method = MethodDef.builder("hashCode") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addParameter("object", ClassTypeDef.of(simpleName.substring(0, simpleName.length() - 5))) + .addParameter("object", ClassTypeDef.of(objectName)) .returns(int.class) .build((self, parameterDef) -> { List statements = new ArrayList<>(); diff --git a/test-suite-java/src/main/java/io/micronaut/sourcegen/example/Person4.java b/test-suite-java/src/main/java/io/micronaut/sourcegen/example/Person4.java index 73414f67..b980ea22 100644 --- a/test-suite-java/src/main/java/io/micronaut/sourcegen/example/Person4.java +++ b/test-suite-java/src/main/java/io/micronaut/sourcegen/example/Person4.java @@ -17,11 +17,15 @@ //tag::clazz[] -import io.micronaut.sourcegen.annotations.Utils; +import io.micronaut.sourcegen.annotations.Equals; +import io.micronaut.sourcegen.annotations.HashCode; +import io.micronaut.sourcegen.annotations.ToString; import java.util.Arrays; -@Utils +@ToString +@Equals +@HashCode public class Person4 { private long id; private String name;