diff --git a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Equals.java b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Equals.java index 909c1a02..7988f663 100644 --- a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Equals.java +++ b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/Equals.java @@ -15,14 +15,14 @@ */ 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. + * The Equals annotation on a bean should generate an equals method. + * The method will be created in [BeanName]Utils class as a static method: + * public static boolean BeanNameUtils.equals(BeanName this, Object other) * * @author Elif Kurtay * @since 1.3 @@ -31,10 +31,4 @@ @Retention(RUNTIME) @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) public @interface Equals { - - /** - * @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/HashCode.java b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/HashCode.java index 9abbbc20..119fd3ae 100644 --- a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/HashCode.java +++ b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/HashCode.java @@ -15,14 +15,14 @@ */ 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. + * The HashCode annotation on a bean should generate a hashCode method. + * The method will be created in [BeanName]Utils class as a static method: + * public static int BeanNameUtils.hashCode(BeanName object) * * @author Elif Kurtay * @since 1.3 @@ -31,10 +31,4 @@ @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 index 4f603d84..1c52ec55 100644 --- a/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/ToString.java +++ b/sourcegen-annotations/src/main/java/io/micronaut/sourcegen/annotations/ToString.java @@ -15,14 +15,14 @@ */ 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. + * The ToString annotation on a bean should generate a toString method. + * The method will be created in [BeanName]Utils class as a static method: + * public static String BeanNameUtils.toString(BeanName object) * * @author Elif Kurtay * @since 1.3 @@ -31,10 +31,4 @@ @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 feefda29..fd8aa6fd 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 @@ -32,10 +32,13 @@ import java.util.*; import javax.lang.model.element.Modifier; -import static io.micronaut.sourcegen.generator.visitors.BuilderAnnotationVisitor.*; - /** - * The visitor that generates the util functions of a bean. + * The visitor that generates the Utils class of a bean. + * The Utils class can have functions substituting toString, equals, and hashcode. + * However, each method needs to be annotated to be generated. + * \@ToString annotation for toString function + * \@Equals annotation for equals function + * \@HashCode annotation for hashCode function * * @author Elif Kurtay * @since 1.3 @@ -77,10 +80,10 @@ public void visitClass(ClassElement element, VisitorContext context) { ClassDef.ClassDefBuilder utilsBuilder = ClassDef.builder(utilsClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL); - // TODO: should I check for properties that have getters? + // TODO: add checks for properties that have getters? List properties = element.getBeanProperties(); - // create the utils functions + // create the utils functions if they are annotated if (element.hasStereotype(ToString.class)) { createToStringMethod(utilsBuilder, element.getSimpleName(), properties); } @@ -126,7 +129,9 @@ public void visitClass(ClassElement element, VisitorContext context) { } } - /* toString method + /* + Creates a toString method with signature: + public static String BeanNameUtils.toString(BeanName object) */ private static void createToStringMethod(ClassDef.ClassDefBuilder classDefBuilder, String objectName, List properties) { List statements = new ArrayList<>(); @@ -180,20 +185,22 @@ private static void createToStringMethod(ClassDef.ClassDefBuilder classDefBuilde classDefBuilder.addMethod(method); } - /* complete equals method + /* + Creates an equals method with signature: + public static boolean BeanNameUtils.equals(BeanName object1, Object object2) */ private static void createEqualsMethod(ClassDef.ClassDefBuilder classDefBuilder, String objectName, List properties) { MethodDef method = MethodDef.builder("equals") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(boolean.class) - .addParameter("firstObject", TypeDef.of(Object.class)) + .addParameter("first", ClassTypeDef.of(objectName)) .addParameter("secondObject", TypeDef.of(Object.class)) .build((self, parameterDef) -> { // local variables needed 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()); + VariableDef firstObject = parameterDef.get(0).asVariable(); VariableDef.Local secondObject = new VariableDef.Local("second", classname.type()); VariableDef.Local bothNullCondition = new VariableDef.Local("bothNullCondition", TypeDef.of(boolean.class)); VariableDef.Local equalsCondition = new VariableDef.Local("equalsCondition", TypeDef.of(boolean.class)); @@ -207,15 +214,12 @@ private static void createEqualsMethod(ClassDef.ClassDefBuilder classDefBuilder, .asConditionIf(ExpressionDef.constant(true).returning()), new StatementDef.DefineAndAssign( isCorrectInstance, - new ExpressionDef.Condition(" && ", - parameterDef.get(0).asVariable().asCondition(" instanceof ", classname), - parameterDef.get(1).asVariable().asCondition(" instanceof ", classname)) + parameterDef.get(1).asVariable().asCondition(" instanceof ", classname) ), new StatementDef.If( new ExpressionDef.Condition(" == ", isCorrectInstance, ExpressionDef.constant(false)), ExpressionDef.constant(false).returning() ), - new StatementDef.DefineAndAssign(firstObject, new ExpressionDef.Cast(classname.type(), parameterDef.get(0).asVariable())), new StatementDef.DefineAndAssign(secondObject, new ExpressionDef.Cast(classname.type(), parameterDef.get(1).asVariable())), new StatementDef.DefineAndAssign(bothNullCondition, ExpressionDef.constant(false)), new StatementDef.DefineAndAssign(equalsCondition, ExpressionDef.constant(false)), @@ -287,7 +291,9 @@ private static void createEqualsMethod(ClassDef.ClassDefBuilder classDefBuilder, classDefBuilder.addMethod(method); } - /* complete hashCode method + /* + Creates a hashCode method with signature: + public static int BeanNameUtils.hashCode(BeanName object) */ private static void createHashCodeMethod(ClassDef.ClassDefBuilder classDefBuilder, String objectName, List properties) { MethodDef method = MethodDef.builder("hashCode") 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 b980ea22..f5f3e14f 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 @@ -15,7 +15,6 @@ */ package io.micronaut.sourcegen.example; - //tag::clazz[] import io.micronaut.sourcegen.annotations.Equals; import io.micronaut.sourcegen.annotations.HashCode; @@ -27,20 +26,32 @@ @Equals @HashCode public class Person4 { + public enum Title { + MRS, + MR, + MS + } + private long id; + private Title title; private String name; private byte[] bytes; - public Person4(long id, String name, byte[] bytes) { + public Person4(long id, Title title, String name, byte[] bytes) { this.id = id; + this.title = title; this.name = name; - this.bytes = Arrays.copyOf(bytes, bytes.length); + this.bytes = (bytes != null) ? Arrays.copyOf(bytes, bytes.length) : null; } public long getId() { return id; } + public Title getTitle() { + return title; + } + public String getName() { return name; } @@ -48,5 +59,20 @@ public String getName() { public byte[] getBytes() { return bytes; } + + @Override + public String toString() { + return Person4Utils.toString(this); + } + + @Override + public int hashCode() { + return Person4Utils.hashCode(this); + } + + @Override + public boolean equals(Object obj) { + return Person4Utils.equals(this, obj); + } } //end::clazz[] diff --git a/test-suite-java/src/test/java/io/micronaut/sourcegen/example/PersonUtilsTest.java b/test-suite-java/src/test/java/io/micronaut/sourcegen/example/PersonUtilsTest.java index f86c736e..a6dd8bed 100644 --- a/test-suite-java/src/test/java/io/micronaut/sourcegen/example/PersonUtilsTest.java +++ b/test-suite-java/src/test/java/io/micronaut/sourcegen/example/PersonUtilsTest.java @@ -25,34 +25,63 @@ public class PersonUtilsTest { @Test public void testToString() { - var person = new Person4(123L, "Cédric", new byte[]{1,2,3}); + var person = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + assertNotNull(Person4Utils.toString(person)); - assertTrue(Person4Utils.toString(person).contains("Person4[")); + assertTrue(person.toString().contains("Person4[")); + assertEquals("Person4[id=123, title=MR, name=Cédric, bytes=[1, 2, 3]]", person.toString()); } + @Test + public void testEqualsWithCorrectObjects() { + var person = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + var personSame = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + var personDiffPrimitive = new Person4(124L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + var personDiffEnum = new Person4(123L, Person4.Title.MRS,"Cédric", new byte[]{1,2,3}); + var personDiffObject = new Person4(123L, Person4.Title.MR,"Cédric Jr.", new byte[]{1,2,3}); + var personDiffArray = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,4}); + + assertNotNull(Person4Utils.equals(person, personSame)); + + assertTrue(person.equals(person)); + assertTrue(person.equals(personSame)); + + assertFalse(person.equals(personDiffPrimitive)); + assertFalse(person.equals(personDiffEnum)); + assertFalse(person.equals(personDiffObject)); + assertFalse(person.equals(personDiffArray)); + } @Test - public void testEquals() { - var person = new Person4(123L, "Cédric", new byte[]{1,2,3}); - var personSame = new Person4(123L, "Cédric", new byte[]{1,2,3}); - var personDiffAll = new Person4(124L, "Cédric 2", new byte[]{1,2,3, 4}); - var personDiffPrimitive = new Person4(124L, "Cédric", new byte[]{1,2,3}); - var personDiffObject = new Person4(123L, "Cédric", new byte[]{1,2,3, 4}); - - assertTrue(Person4Utils.equals(person, personSame)); - assertTrue(Person4Utils.equals(person, person)); - assertFalse(Person4Utils.equals(person, personDiffAll)); - assertFalse(Person4Utils.equals(person, personDiffPrimitive)); - assertFalse(Person4Utils.equals(person, personDiffObject)); + public void testEqualsWithNulls() { + var person = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + var personDoubleNull1 = new Person4(123L, Person4.Title.MR,null, new byte[]{1,2,3}); + var personDoubleNull2 = new Person4(123L, Person4.Title.MR,null, new byte[]{1,2,3}); + var personSingleNull = new Person4(124L, Person4.Title.MR,"Cédric", null); + + assertFalse(person.equals(null)); + assertFalse(person.equals(new Object())); + + assertTrue(personDoubleNull1.equals(personDoubleNull2)); + assertFalse(personSingleNull.equals(person)); + assertFalse(person.equals(personSingleNull)); } @Test public void testHashCode() { - var person = new Person4(123L, "Cédric", new byte[]{1,2,3}); - var personSame = new Person4(123L, "Cédric", new byte[]{1,2,3}); - var personDiffAll = new Person4(124L, "Cédric 2", new byte[]{1,2,3, 4}); - assertEquals(Person4Utils.hashCode(person), Person4Utils.hashCode(personSame)); - assertNotEquals(Person4Utils.hashCode(person), Person4Utils.hashCode(personDiffAll)); - } + var person = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + var personSame = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + var personDiffPrimitive = new Person4(124L, Person4.Title.MR,"Cédric", new byte[]{1,2,3}); + var personDiffEnum = new Person4(123L, Person4.Title.MRS,"Cédric", new byte[]{1,2,3}); + var personDiffObject = new Person4(123L, Person4.Title.MR,"Cédric Jr.", new byte[]{1,2,3}); + var personDiffArray = new Person4(123L, Person4.Title.MR,"Cédric", new byte[]{1,2,4}); + assertNotNull(Person4Utils.hashCode(person)); + assertEquals(person.hashCode(), person.hashCode()); + assertEquals(person.hashCode(), personSame.hashCode()); + assertNotEquals(person.hashCode(), personDiffPrimitive.hashCode()); + assertNotEquals(person.hashCode(), personDiffEnum.hashCode()); + assertNotEquals(person.hashCode(), personDiffObject.hashCode()); + assertNotEquals(person.hashCode(), personDiffArray.hashCode()); + } }