Generate bindingEquals() and use it in generated implementations 45/90745/32
authorIlya Igushev <illia.ihushev@pantheon.tech>
Fri, 3 Jul 2020 08:07:22 +0000 (08:07 +0000)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 20 Jul 2020 16:10:48 +0000 (18:10 +0200)
As we are gearing towards generating default methods in interfaces,
we need the ability to completely analyze all a target type from
a template, not from a generator.

This ability was previously available to Builder*Template, now it
is part of JavaFileTemplate and hence can be picked up by
InterfaceTemplate as needed.

JIRA: MDSAL-473
Change-Id: Ife60dadb9b66dc01df04ebeba11d82c8806f2236
Signed-off-by: illia.ihushev <illia.ihushev@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
binding/mdsal-binding-generator-impl/src/main/java/org/opendaylight/mdsal/binding/generator/impl/AbstractTypeGenerator.java
binding/mdsal-binding-generator-impl/src/test/java/org/opendaylight/mdsal/binding/generator/impl/GenEnumResolvingTest.java
binding/mdsal-binding-generator-impl/src/test/java/org/opendaylight/mdsal/binding/generator/impl/GeneratedTypesTest.java
binding/mdsal-binding-generator-impl/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal320Test.java
binding/mdsal-binding-generator-impl/src/test/java/org/opendaylight/mdsal/binding/generator/impl/UsesTest.java
binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/Types.java
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderImplTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGeneratorTest.java
binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/naming/BindingMapping.java
binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java

index f99944b403861ca69758553c9ba57fa44dc7d226..dd52ed7d2c97cd455032d8d27b3478f0e18fede1 100644 (file)
@@ -42,6 +42,7 @@ import static org.opendaylight.mdsal.binding.model.util.Types.classType;
 import static org.opendaylight.mdsal.binding.model.util.Types.listTypeFor;
 import static org.opendaylight.mdsal.binding.model.util.Types.listenableFutureTypeFor;
 import static org.opendaylight.mdsal.binding.model.util.Types.mapTypeFor;
+import static org.opendaylight.mdsal.binding.model.util.Types.primitiveBooleanType;
 import static org.opendaylight.mdsal.binding.model.util.Types.primitiveIntType;
 import static org.opendaylight.mdsal.binding.model.util.Types.primitiveVoidType;
 import static org.opendaylight.mdsal.binding.model.util.Types.wildcardTypeFor;
@@ -2064,6 +2065,10 @@ abstract class AbstractTypeGenerator {
             .setAccessModifier(AccessModifier.PUBLIC)
             .setStatic(true)
             .setReturnType(primitiveIntType());
+        typeBuilder.addMethod(BindingMapping.BINDING_EQUALS_NAME)
+            .setAccessModifier(AccessModifier.PUBLIC)
+            .setStatic(true)
+            .setReturnType(primitiveBooleanType());
         typeBuilder.addMethod(BindingMapping.BINDING_TO_STRING_NAME)
             .setAccessModifier(AccessModifier.PUBLIC)
             .setStatic(true)
index ff72e5dc58486edebbcfe86df8e49627ab51ceb0..2d77d0db450ab2254df441bbb193b993afe90749 100644 (file)
@@ -66,7 +66,7 @@ public class GenEnumResolvingTest {
         assertNotNull("Generated Interface cannot contain NULL reference for Method Signature Definitions!", methods);
 
         // FIXME: split this into getter/default/static asserts
-        assertEquals(18, methods.size());
+        assertEquals(19, methods.size());
         Enumeration ianaIfType = null;
         for (final MethodSignature method : methods) {
             if (method.getName().equals("getType")) {
@@ -121,7 +121,7 @@ public class GenEnumResolvingTest {
         assertNotNull("Generated Type Interface cannot contain NULL reference to Enumeration types!", methods);
 
         // FIXME: split this into getter/default/static asserts
-        assertEquals(8, methods.size());
+        assertEquals(9, methods.size());
         for (final MethodSignature method : methods) {
             if (method.getName().equals("getLinkUpDownTrapEnable")) {
                 linkUpDownTrapEnable = method.getReturnType();
index 55d27789648eb22f17e47fcb1edbb094a91161cc..9ca5660a796328ea7185be2b14a5580517b7f6cd 100644 (file)
@@ -51,9 +51,9 @@ public class GeneratedTypesTest {
         assertNotNull(simpleContainer);
         assertNotNull(nestedContainer);
         // FIXME: split this into getter/default/static asserts
-        assertEquals(6, simpleContainer.getMethodDefinitions().size());
+        assertEquals(7, simpleContainer.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(5, nestedContainer.getMethodDefinitions().size());
+        assertEquals(6, nestedContainer.getMethodDefinitions().size());
 
         int getFooMethodCounter = 0;
         int getBarMethodCounter = 0;
@@ -134,9 +134,9 @@ public class GeneratedTypesTest {
         assertNotNull(simpleContainer);
         assertNotNull(nestedContainer);
         // FIXME: split this into getter/default/static asserts
-        assertEquals(6, simpleContainer.getMethodDefinitions().size());
+        assertEquals(7, simpleContainer.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(5, nestedContainer.getMethodDefinitions().size());
+        assertEquals(6, nestedContainer.getMethodDefinitions().size());
 
         int getFooMethodCounter = 0;
         int getBarMethodCounter = 0;
@@ -299,9 +299,9 @@ public class GeneratedTypesTest {
         }
 
         // FIXME: split this into getter/default/static asserts
-        assertEquals(5, listParentContainerMethodsCount);
+        assertEquals(6, listParentContainerMethodsCount);
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, listChildContainerMethodsCount);
+        assertEquals(5, listChildContainerMethodsCount);
         assertEquals(1, getSimpleListKeyMethodCount);
         assertEquals(1, listKeyClassCount);
 
@@ -327,7 +327,7 @@ public class GeneratedTypesTest {
         assertEquals(1, getBarMethodCount);
 
         // FIXME: split this into getter/default/static asserts
-        assertEquals(9, simpleListMethodsCount);
+        assertEquals(10, simpleListMethodsCount);
     }
 
     @Test
index b1d6df3a542b03c5fc65dde30a85eb9748347ef9..c9bd8bb8811f357630f4d68ae84e571d4366f60a 100644 (file)
@@ -66,6 +66,8 @@ public class Mdsal320Test {
 
         final MethodSignature bindingHashCode = it.next();
         assertEquals(BindingMapping.BINDING_HASHCODE_NAME, bindingHashCode.getName());
+        final MethodSignature bindingEquals = it.next();
+        assertEquals(BindingMapping.BINDING_EQUALS_NAME, bindingEquals.getName());
         final MethodSignature bindingToString = it.next();
         assertEquals(BindingMapping.BINDING_TO_STRING_NAME, bindingToString.getName());
 
index 46a37363f3c194b76bfad7dfaaabb75930cf092d..c36ce075bed6756f2df2b84a6eb1cbe06e43f62a 100644 (file)
@@ -106,7 +106,7 @@ public class UsesTest {
 
         containsInterface("GroupingCaseTest", caseC);
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, caseC.getMethodDefinitions().size());
+        assertEquals(4, caseC.getMethodDefinitions().size());
 
         assertEquals("Number of method in GroupingCaseTest is incorrect", 2, groupingCaseTest.getMethodDefinitions()
                 .size());
@@ -154,7 +154,7 @@ public class UsesTest {
         assertEquals("Number of method in GroupingContainerTestis incorrect", 3, groupingContainerTest
                 .getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, containerTest.getMethodDefinitions().size());
+        assertEquals(5, containerTest.getMethodDefinitions().size());
 
         containsMethods(groupingContainerTest.getMethodDefinitions(), new NameTypePattern(
                 "getLeafGroupingContainerTest1", "String"), new NameTypePattern("getLeafGroupingContainerTest2",
@@ -270,11 +270,11 @@ public class UsesTest {
         assertEquals("Number of method in GroupingListTest is incorrect", 6, groupingListTest.getMethodDefinitions()
                 .size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, listTest.getMethodDefinitions().size());
+        assertEquals(5, listTest.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, containerGroupingListTest.getMethodDefinitions().size());
+        assertEquals(5, containerGroupingListTest.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, listGroupingListTest.getMethodDefinitions().size());
+        assertEquals(5, listGroupingListTest.getMethodDefinitions().size());
 
         containsMethods(groupingListTest.getMethodDefinitions(), new NameTypePattern("getContainerGroupingListTest",
                 "ContainerGroupingListTest"), new NameTypePattern("getLeafGroupingListTest", "String"),
@@ -404,15 +404,15 @@ public class UsesTest {
         containsInterface("GroupingRpcOutputTest", rpcTestOutput);
 
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, rpcTestInput.getMethodDefinitions().size());
+        assertEquals(4, rpcTestInput.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, rpcTestOutput.getMethodDefinitions().size());
+        assertEquals(4, rpcTestOutput.getMethodDefinitions().size());
         assertEquals("Number of method in GroupingRpcInputTest is incorrect", 3, groupingRpcInputTest
                 .getMethodDefinitions().size());
         assertEquals("Number of method in GroupingRpcOutputTest is incorrect", 2, groupingRpcOutputTest
                 .getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, containerGroupingRpcInputTest.getMethodDefinitions().size());
+        assertEquals(5, containerGroupingRpcInputTest.getMethodDefinitions().size());
 
         containsMethods(groupingRpcInputTest.getMethodDefinitions(), new NameTypePattern(
                 "getContainerGroupingRpcInputTest", "ContainerGroupingRpcInputTest"), new NameTypePattern(
@@ -460,9 +460,9 @@ public class UsesTest {
         containsInterface("GroupingAugmentTest", containerAugment1);
 
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, containerAugment1.getMethodDefinitions().size());
+        assertEquals(4, containerAugment1.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, containerAugment1.getMethodDefinitions().size());
+        assertEquals(4, containerAugment1.getMethodDefinitions().size());
         assertEquals("Number of method in GroupingCaseTest is incorrect", 2, groupingAugmentTest.getMethodDefinitions()
                 .size());
 
@@ -522,11 +522,11 @@ public class UsesTest {
         containsInterface("GroupingNotificationTest", notificationTest);
 
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, notificationTest.getMethodDefinitions().size());
+        assertEquals(5, notificationTest.getMethodDefinitions().size());
         assertEquals("Number of method in GroupingNotificationTest is incorrect", 3, groupingNotificationTest
                 .getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, containerGroupingNotificationTest.getMethodDefinitions().size());
+        assertEquals(5, containerGroupingNotificationTest.getMethodDefinitions().size());
 
         containsMethods(notificationTest.getMethodDefinitions(), new NameTypePattern("getLeafNotificationTest",
                 "String"));
index 5f0727699ef4e89be4e33f6cc2c57bc43c1e320b..288fca8a3ab69c21c0c5fa7015429361a4854ac7 100644 (file)
@@ -61,6 +61,7 @@ public final class Types {
     private static final @NonNull ConcreteType LISTENABLE_FUTURE = typeForClass(ListenableFuture.class);
     private static final @NonNull ConcreteType MAP_TYPE = typeForClass(Map.class);
     private static final @NonNull ConcreteType OBJECT = typeForClass(Object.class);
+    private static final @NonNull ConcreteType PRIMITIVE_BOOLEAN = typeForClass(boolean.class);
     private static final @NonNull ConcreteType PRIMITIVE_INT = typeForClass(int.class);
     private static final @NonNull ConcreteType PRIMITIVE_VOID = typeForClass(void.class);
     private static final @NonNull ConcreteType SERIALIZABLE = typeForClass(Serializable.class);
@@ -102,6 +103,15 @@ public final class Types {
         return OBJECT;
     }
 
+    /**
+     * Returns an instance of {@link ConcreteType} which represents JAVA <code>boolean</code> type.
+     *
+     * @return <code>ConcreteType</code> instance which represents JAVA <code>boolean</code>
+     */
+    public static @NonNull ConcreteType primitiveBooleanType() {
+        return PRIMITIVE_BOOLEAN;
+    }
+
     /**
      * Returns an instance of {@link ConcreteType} which represents JAVA <code>int</code> type.
      *
index 650d15cf809f365fddf0744c6379537ad7b9823e..be38f4995cfe822447921eafe873e8684cd81ad4 100644 (file)
@@ -7,13 +7,11 @@
  */
 package org.opendaylight.mdsal.binding.java.api.generator
 
-import static org.opendaylight.mdsal.binding.model.util.BindingTypes.DATA_OBJECT
 import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
-import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_EQUALS_NAME
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_HASHCODE_NAME
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_TO_STRING_NAME
-import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
 
 import java.util.Collection
 import java.util.List
@@ -95,43 +93,7 @@ class BuilderImplTemplate extends AbstractBuilderTemplate {
         «IF !properties.empty || augmentType !== null»
             @«OVERRIDE.importedName»
             public boolean equals(«Types.objectType().importedName» obj) {
-                if (this == obj) {
-                    return true;
-                }
-                if (!(obj instanceof «DATA_OBJECT.importedName»)) {
-                    return false;
-                }
-                if (!«targetType.importedName».class.equals(((«DATA_OBJECT.importedName»)obj).«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»())) {
-                    return false;
-                }
-                «targetType.importedName» other = («targetType.importedName»)obj;
-                «FOR property : properties»
-                    «val fieldName = property.fieldName»
-                    if (!«property.importedUtilClass».equals(«fieldName», other.«property.getterName»())) {
-                        return false;
-                    }
-                «ENDFOR»
-                «IF augmentType !== null»
-                    if (getClass() == obj.getClass()) {
-                        // Simple case: we are comparing against self
-                        «type.name» otherImpl = («type.name») obj;
-                        if (!«JU_OBJECTS.importedName».equals(augmentations(), otherImpl.augmentations())) {
-                            return false;
-                        }
-                    } else {
-                        // Hard case: compare our augments with presence there...
-                        for («JU_MAP.importedName».Entry<«CLASS.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> e : augmentations().entrySet()) {
-                            if (!e.getValue().equals(other.«AUGMENTABLE_AUGMENTATION_NAME»(e.getKey()))) {
-                                return false;
-                            }
-                        }
-                        // .. and give the other one the chance to do the same
-                        if (!obj.equals(this)) {
-                            return false;
-                        }
-                    }
-                «ENDIF»
-                return true;
+                return «targetType.importedName».«BINDING_EQUALS_NAME»(this, obj);
             }
         «ENDIF»
     '''
index 23e47b6ce0e2360d0e1fb653cac9875cf5b1df27..8c3ae346644b7820304f39dc7278f48a9c374f63 100644 (file)
@@ -7,11 +7,12 @@
  */
 package org.opendaylight.mdsal.binding.java.api.generator
 
-import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
 import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.getGetterMethodForNonnull
 import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isGetterMethodName
 import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isNonnullMethodName
+import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_EQUALS_NAME
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_HASHCODE_NAME
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_TO_STRING_NAME
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
@@ -27,6 +28,7 @@ import org.opendaylight.mdsal.binding.model.api.Enumeration
 import org.opendaylight.mdsal.binding.model.api.GeneratedType
 import org.opendaylight.mdsal.binding.model.api.MethodSignature
 import org.opendaylight.mdsal.binding.model.api.Type
+import org.opendaylight.mdsal.binding.model.util.Types
 import org.opendaylight.mdsal.binding.model.util.TypeConstants
 
 /**
@@ -192,6 +194,7 @@ class InterfaceTemplate extends BaseTemplate {
 
     def private generateStaticMethod(MethodSignature method) {
         switch method.name {
+            case BINDING_EQUALS_NAME : generateBindingEquals
             case BINDING_HASHCODE_NAME : generateBindingHashCode
             case BINDING_TO_STRING_NAME : generateBindingToString
         }
@@ -222,10 +225,9 @@ class InterfaceTemplate extends BaseTemplate {
         /**
          * Default implementation of {@link «Object.importedName»#hashCode()} contract for this interface.
          * Implementations of this interface are encouraged to defer to this method to get consistent hashing
-         * results across all implementation.
+         * results across all implementations.
          *
          «IF augmentable»
-         * <p>
          * @param <T$$> implementation type, which has to also implement «AUGMENTATION_HOLDER.importedName» interface
          *              contract.
          «ENDIF»
@@ -234,9 +236,9 @@ class InterfaceTemplate extends BaseTemplate {
          * @throws «NPE.importedName» if {@code obj} is null
          */
         «IF augmentable»
-            static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<?>> int «BINDING_HASHCODE_NAME»(final @«NONNULL.importedName» T$$ obj) {
+        static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<?>> int «BINDING_HASHCODE_NAME»(final @«NONNULL.importedName» T$$ obj) {
         «ELSE»
-            static int «BINDING_HASHCODE_NAME»(final «type.fullyQualifiedName» obj) {
+        static int «BINDING_HASHCODE_NAME»(final «type.fullyQualifiedName» obj) {
         «ENDIF»
             final int prime = 31;
             int result = 1;
@@ -250,15 +252,53 @@ class InterfaceTemplate extends BaseTemplate {
         }
     '''
 
+    def private generateBindingEquals() '''
+        «val augmentable = analyzeType»
+        «IF augmentable || !typeAnalysis.value.isEmpty»
+            /**
+             * Default implementation of {@link «Object.importedName»#equals(«Object.importedName»)} contract for this interface.
+             * Implementations of this interface are encouraged to defer to this method to get consistent equality
+             * results across all implementations.
+             *
+             «IF augmentable»
+             * @param <T$$> implementation type, which has to also implement «AUGMENTATION_HOLDER.importedName» interface
+             *              contract.
+             «ENDIF»
+             * @param thisObj Object acting as the receiver of equals invocation
+             * @param obj Object acting as argument to equals invocation
+             * @return True if thisObj and obj are considered equal
+             * @throws «NPE.importedName» if {@code thisObj} is null
+             */
+            «IF augmentable»
+            static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<«type.fullyQualifiedName»>> boolean «BINDING_EQUALS_NAME»(final @«NONNULL.importedName» T$$ thisObj, final «Types.objectType().importedName» obj) {
+            «ELSE»
+            static boolean «BINDING_EQUALS_NAME»(final «type.fullyQualifiedName» thisObj, final «Types.objectType().importedName» obj) {
+            «ENDIF»
+                if (thisObj == obj) {
+                    return true;
+                }
+                final «type.fullyQualifiedName» other = «CODEHELPERS.importedName».checkCast(«type.fullyQualifiedName».class, obj);
+                if (other == null) {
+                    return false;
+                }
+                «FOR property : typeAnalysis.value»
+                    if (!«property.importedUtilClass».equals(thisObj.«property.getterName»(), other.«property.getterName»())) {
+                        return false;
+                    }
+                «ENDFOR»
+                return «IF augmentable»«CODEHELPERS.importedName».equalsAugmentations(thisObj, other)«ELSE»true«ENDIF»;
+            }
+        «ENDIF»
+    '''
+
     def generateBindingToString() '''
         «val augmentable = analyzeType»
         /**
          * Default implementation of {@link «Object.importedName»#toString()} contract for this interface.
          * Implementations of this interface are encouraged to defer to this method to get consistent string
-         * representations across all implementation.
+         * representations across all implementations.
          *
          «IF augmentable»
-         * <p>
          * @param <T$$> implementation type, which has to also implement «AUGMENTATION_HOLDER.importedName» interface
          *              contract.
          «ENDIF»
@@ -267,9 +307,9 @@ class InterfaceTemplate extends BaseTemplate {
          * @throws «NPE.importedName» if {@code obj} is null
          */
         «IF augmentable»
-            static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<«type.fullyQualifiedName»>> «STRING.importedName» «BINDING_TO_STRING_NAME»(final @«NONNULL.importedName» T$$ obj) {
+        static <T$$ extends «type.fullyQualifiedName» & «AUGMENTATION_HOLDER.importedName»<«type.fullyQualifiedName»>> «STRING.importedName» «BINDING_TO_STRING_NAME»(final @«NONNULL.importedName» T$$ obj) {
         «ELSE»
-            static «STRING.importedName» «BINDING_TO_STRING_NAME»(final «type.fullyQualifiedName» obj) {
+        static «STRING.importedName» «BINDING_TO_STRING_NAME»(final «type.fullyQualifiedName» obj) {
         «ENDIF»
             final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«type.name»");
             «FOR property : typeAnalysis.value»
@@ -288,7 +328,7 @@ class InterfaceTemplate extends BaseTemplate {
         «formatDataForJavaDoc(method, "@return " + asCode(ret.fullyQualifiedName) + " " + asCode(propertyNameFromGetter(method)) + ", or an empty list if it is not present")»
         «method.annotations.generateAnnotations»
         default «ret.importedNonNull» «name»() {
-            return «CODEHELPERS.importedName».nonnull(«getGetterMethodForNonnull(name)»());
+            return «CODEHELPERS.importedName».nonnull(«name.getGetterMethodForNonnull»());
         }
     '''
 
index 0e167acc3c0d1f65e78623ec0b864c9ad64928c9..8415c74cddec270e11d857cdc745b65d795f9c42 100644 (file)
@@ -37,7 +37,7 @@ public class BuilderGeneratorTest {
         assertEquals("/**\n"
                 + " * Default implementation of {@link Object#toString()} contract for this interface.\n"
                 + " * Implementations of this interface are encouraged to defer to this method to get consistent string"
-                + "\n * representations across all implementation.\n"
+                + "\n * representations across all implementations.\n"
                 + " *\n"
                 + " * @param obj Object for which to generate toString() result.\n"
                 + " * @return {@link String} value of data modeled by this interface.\n"
@@ -55,7 +55,7 @@ public class BuilderGeneratorTest {
         assertEquals("/**\n"
                 + " * Default implementation of {@link Object#toString()} contract for this interface.\n"
                 + " * Implementations of this interface are encouraged to defer to this method to get consistent string"
-                + "\n * representations across all implementation.\n"
+                + "\n * representations across all implementations.\n"
                 + " *\n"
                 + " * @param obj Object for which to generate toString() result.\n"
                 + " * @return {@link String} value of data modeled by this interface.\n"
@@ -72,7 +72,7 @@ public class BuilderGeneratorTest {
         assertEquals("/**\n"
                 + " * Default implementation of {@link Object#toString()} contract for this interface.\n"
                 + " * Implementations of this interface are encouraged to defer to this method to get consistent string"
-                + "\n * representations across all implementation.\n"
+                + "\n * representations across all implementations.\n"
                 + " *\n"
                 + " * @param obj Object for which to generate toString() result.\n"
                 + " * @return {@link String} value of data modeled by this interface.\n"
@@ -91,9 +91,8 @@ public class BuilderGeneratorTest {
         assertEquals("/**\n"
                 + " * Default implementation of {@link Object#toString()} contract for this interface.\n"
                 + " * Implementations of this interface are encouraged to defer to this method to get consistent string"
-                + "\n * representations across all implementation.\n"
+                + "\n * representations across all implementations.\n"
                 + " *\n"
-                + " * <p>\n"
                 + " * @param <T$$> implementation type, which has to also implement AugmentationHolder interface\n"
                 + " *              contract.\n"
                 + " * @param obj Object for which to generate toString() result.\n"
@@ -113,9 +112,8 @@ public class BuilderGeneratorTest {
         assertEquals("/**\n"
                 + " * Default implementation of {@link Object#toString()} contract for this interface.\n"
                 + " * Implementations of this interface are encouraged to defer to this method to get consistent string"
-                + "\n * representations across all implementation.\n"
+                + "\n * representations across all implementations.\n"
                 + " *\n"
-                + " * <p>\n"
                 + " * @param <T$$> implementation type, which has to also implement AugmentationHolder interface\n"
                 + " *              contract.\n"
                 + " * @param obj Object for which to generate toString() result.\n"
@@ -136,9 +134,8 @@ public class BuilderGeneratorTest {
         assertEquals("/**\n"
                 + " * Default implementation of {@link Object#toString()} contract for this interface.\n"
                 + " * Implementations of this interface are encouraged to defer to this method to get consistent string"
-                + "\n * representations across all implementation.\n"
+                + "\n * representations across all implementations.\n"
                 + " *\n"
-                + " * <p>\n"
                 + " * @param <T$$> implementation type, which has to also implement AugmentationHolder interface\n"
                 + " *              contract.\n"
                 + " * @param obj Object for which to generate toString() result.\n"
index 75c9fb4e444086b73f13f5b9cd09f4fd2dad7f30..032759fa92bff29487cdf71e9f9d457d9b68a2ad 100644 (file)
@@ -86,6 +86,12 @@ public final class BindingMapping {
      */
     public static final @NonNull String BINDING_HASHCODE_NAME = "bindingHashCode";
 
+    /**
+     * Name of default {@link Object#equals(Object)} implementation for instantiated DataObjects. Each such generated
+     * interface contains this static method.
+     */
+    public static final @NonNull String BINDING_EQUALS_NAME = "bindingEquals";
+
     /**
      * Name of default {@link Object#toString()} implementation for instantiated DataObjects. Each such generated
      * interface contains this static method.
index c51ce89361336afc47a875bea303109126464052..74a2cc0e9bee7fdc251f5049fd9b2a6b0e87df5c 100644 (file)
@@ -20,6 +20,7 @@ import java.math.BigInteger;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.regex.Pattern;
 import org.eclipse.jdt.annotation.NonNull;
@@ -380,6 +381,46 @@ public final class CodeHelpers {
         return value == null ? null : Uint64.valueOf(value);
     }
 
+    /**
+     * Utility method for checking whether a target object is a compatible DataObject.
+     *
+     * @param requiredClass Required DataObject class
+     * @param obj Object to check, may be null
+     * @return Object cast to required class, if its implemented class matches requirement, null otherwise
+     * @throws NullPointerException if {@code requiredClass} is null
+     */
+    public static <T extends DataObject> @Nullable T checkCast(final @NonNull Class<T> requiredClass,
+            final @Nullable Object obj) {
+        return obj instanceof DataObject && requiredClass.equals(((DataObject) obj).implementedInterface())
+            ? requiredClass.cast(obj) : null;
+    }
+
+    /**
+     * Utility method for comparing two augmentable objects' augmentations.
+     *
+     * @param <T> Augmentable type
+     * @param thisObj The object representing 'this'
+     * @param other The object representing 'obj'
+     * @return True if both object's augmentations are equal
+     * @throws NullPointerException if any argument is null
+     */
+    public static <T extends Augmentable<T>> boolean equalsAugmentations(final @NonNull AugmentationHolder<T> thisObj,
+            final @NonNull Augmentable<T> other) {
+        if (other instanceof AugmentationHolder) {
+            // Simple case: other object is also an AugmentationHolder
+            return thisObj.augmentations().equals(((AugmentationHolder<?>) other).augmentations());
+        }
+
+        // Hard case: compare our augments with presence there...
+        for (Entry<Class<? extends Augmentation<T>>, Augmentation<T>> e : thisObj.augmentations().entrySet()) {
+            if (!e.getValue().equals(other.augmentation(e.getKey()))) {
+                return false;
+            }
+        }
+        // .. and give the other one the chance to do the same
+        return other.equals(thisObj);
+    }
+
     /**
      * Utility for extracting augmentations from an implementation of {@link AugmentationHolder} interface.
      *