Generate bindingHashCode() and use it in generated implementations 05/84205/27
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 4 Sep 2019 15:44:12 +0000 (17:44 +0200)
committerRobert Varga <nite@hq.sk>
Mon, 20 Jul 2020 11:45:22 +0000 (11:45 +0000)
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-471
Change-Id: I278c085da195df8b4153b62381203b8ddc7bd073
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/ClassTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend
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 ff1197b8e8c0239ade7785de4b68adda8892a328..f99944b403861ca69758553c9ba57fa44dc7d226 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.primitiveIntType;
 import static org.opendaylight.mdsal.binding.model.util.Types.primitiveVoidType;
 import static org.opendaylight.mdsal.binding.model.util.Types.wildcardTypeFor;
 import static org.opendaylight.yangtools.yang.model.util.SchemaContextUtil.findDataSchemaNode;
@@ -2059,6 +2060,10 @@ abstract class AbstractTypeGenerator {
     private static void addConcreteInterfaceMethods(final GeneratedTypeBuilder typeBuilder) {
         defaultImplementedInterace(typeBuilder);
 
+        typeBuilder.addMethod(BindingMapping.BINDING_HASHCODE_NAME)
+            .setAccessModifier(AccessModifier.PUBLIC)
+            .setStatic(true)
+            .setReturnType(primitiveIntType());
         typeBuilder.addMethod(BindingMapping.BINDING_TO_STRING_NAME)
             .setAccessModifier(AccessModifier.PUBLIC)
             .setStatic(true)
index 6869fd6922af2cfb0d6c02e4188c9d174730d468..ff72e5dc58486edebbcfe86df8e49627ab51ceb0 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(17, methods.size());
+        assertEquals(18, 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(7, methods.size());
+        assertEquals(8, methods.size());
         for (final MethodSignature method : methods) {
             if (method.getName().equals("getLinkUpDownTrapEnable")) {
                 linkUpDownTrapEnable = method.getReturnType();
index 3f97c93154f698daea450c1ec0f04bbf5640a8bb..55d27789648eb22f17e47fcb1edbb094a91161cc 100644 (file)
@@ -51,9 +51,9 @@ public class GeneratedTypesTest {
         assertNotNull(simpleContainer);
         assertNotNull(nestedContainer);
         // FIXME: split this into getter/default/static asserts
-        assertEquals(5, simpleContainer.getMethodDefinitions().size());
+        assertEquals(6, simpleContainer.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, nestedContainer.getMethodDefinitions().size());
+        assertEquals(5, 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(5, simpleContainer.getMethodDefinitions().size());
+        assertEquals(6, simpleContainer.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(4, nestedContainer.getMethodDefinitions().size());
+        assertEquals(5, 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(4, listParentContainerMethodsCount);
+        assertEquals(5, listParentContainerMethodsCount);
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, listChildContainerMethodsCount);
+        assertEquals(4, 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(8, simpleListMethodsCount);
+        assertEquals(9, simpleListMethodsCount);
     }
 
     @Test
index 80546fd7a1da562878cad95513d76e7ef093ca01..b1d6df3a542b03c5fc65dde30a85eb9748347ef9 100644 (file)
@@ -64,6 +64,8 @@ public class Mdsal320Test {
         assertTrue(getImplIface.isDefault());
         assertTrue(it.hasNext());
 
+        final MethodSignature bindingHashCode = it.next();
+        assertEquals(BindingMapping.BINDING_HASHCODE_NAME, bindingHashCode.getName());
         final MethodSignature bindingToString = it.next();
         assertEquals(BindingMapping.BINDING_TO_STRING_NAME, bindingToString.getName());
 
index ee4be95b62f09898239b73c46d588843fd60bba9..46a37363f3c194b76bfad7dfaaabb75930cf092d 100644 (file)
@@ -106,7 +106,7 @@ public class UsesTest {
 
         containsInterface("GroupingCaseTest", caseC);
         // FIXME: split this into getter/default/static asserts
-        assertEquals(2, caseC.getMethodDefinitions().size());
+        assertEquals(3, 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(3, containerTest.getMethodDefinitions().size());
+        assertEquals(4, 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(3, listTest.getMethodDefinitions().size());
+        assertEquals(4, listTest.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, containerGroupingListTest.getMethodDefinitions().size());
+        assertEquals(4, containerGroupingListTest.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, listGroupingListTest.getMethodDefinitions().size());
+        assertEquals(4, 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(2, rpcTestInput.getMethodDefinitions().size());
+        assertEquals(3, rpcTestInput.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(2, rpcTestOutput.getMethodDefinitions().size());
+        assertEquals(3, 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(3, containerGroupingRpcInputTest.getMethodDefinitions().size());
+        assertEquals(4, 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(2, containerAugment1.getMethodDefinitions().size());
+        assertEquals(3, containerAugment1.getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(2, containerAugment1.getMethodDefinitions().size());
+        assertEquals(3, 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(3, notificationTest.getMethodDefinitions().size());
+        assertEquals(4, notificationTest.getMethodDefinitions().size());
         assertEquals("Number of method in GroupingNotificationTest is incorrect", 3, groupingNotificationTest
                 .getMethodDefinitions().size());
         // FIXME: split this into getter/default/static asserts
-        assertEquals(3, containerGroupingNotificationTest.getMethodDefinitions().size());
+        assertEquals(4, containerGroupingNotificationTest.getMethodDefinitions().size());
 
         containsMethods(notificationTest.getMethodDefinitions(), new NameTypePattern("getLeafNotificationTest",
                 "String"));
index 894e48291c003b5accd6a6565d57645214565ce4..5f0727699ef4e89be4e33f6cc2c57bc43c1e320b 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_INT = typeForClass(int.class);
     private static final @NonNull ConcreteType PRIMITIVE_VOID = typeForClass(void.class);
     private static final @NonNull ConcreteType SERIALIZABLE = typeForClass(Serializable.class);
     private static final @NonNull ConcreteType SET_TYPE = typeForClass(Set.class);
@@ -101,6 +102,15 @@ public final class Types {
         return OBJECT;
     }
 
+    /**
+     * Returns an instance of {@link ConcreteType} which represents JAVA <code>int</code> type.
+     *
+     * @return <code>ConcreteType</code> instance which represents JAVA <code>int</code>
+     */
+    public static @NonNull ConcreteType primitiveIntType() {
+        return PRIMITIVE_INT;
+    }
+
     /**
      * Returns an instance of {@link ConcreteType} which represents JAVA <code>void</code> type.
      *
index 18a4d9ec04e93bb7035c5a3d93b60991aeadc19f..650d15cf809f365fddf0744c6379537ad7b9823e 100644 (file)
@@ -11,6 +11,7 @@ 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_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
 
@@ -77,11 +78,7 @@ class BuilderImplTemplate extends AbstractBuilderTemplate {
                     return hash;
                 }
 
-                «hashCodeResult(properties)»
-                «IF augmentType !== null»
-                    result = prime * result + «JU_OBJECTS.importedName».hashCode(augmentations());
-                «ENDIF»
-
+                final int result = «targetType.importedName».«BINDING_HASHCODE_NAME»(this);
                 hash = result;
                 hashValid = true;
                 return result;
index 5c3ec4a824b60503d39ba808e5e870633e81f895..4a5f3913c1d0d7210cf98fef8ceed20eb621d7e9 100644 (file)
@@ -581,7 +581,11 @@ class ClassTemplate extends BaseTemplate {
             @«OVERRIDE.importedName»
             public int hashCode() {
                 «IF size != 1»
-                    «hashCodeResult(genTO.hashCodeIdentifiers)»
+                    final int prime = 31;
+                    int result = 1;
+                    «FOR property : genTO.hashCodeIdentifiers»
+                        result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
+                    «ENDFOR»
                     return result;
                 «ELSE»
                     return «CODEHELPERS.importedName».wrapperHashCode(«genTO.hashCodeIdentifiers.get(0).fieldName»);
index 619d6c2073967362bf3a87d910118bd25448a167..23e47b6ce0e2360d0e1fb653cac9875cf5b1df27 100644 (file)
@@ -12,6 +12,7 @@ import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMappin
 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.spec.naming.BindingMapping.AUGMENTATION_FIELD
+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
 
@@ -191,6 +192,7 @@ class InterfaceTemplate extends BaseTemplate {
 
     def private generateStaticMethod(MethodSignature method) {
         switch method.name {
+            case BINDING_HASHCODE_NAME : generateBindingHashCode
             case BINDING_TO_STRING_NAME : generateBindingToString
         }
     }
@@ -215,6 +217,39 @@ class InterfaceTemplate extends BaseTemplate {
         }
     '''
 
+    def private generateBindingHashCode() '''
+        «val augmentable = analyzeType»
+        /**
+         * 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.
+         *
+         «IF augmentable»
+         * <p>
+         * @param <T$$> implementation type, which has to also implement «AUGMENTATION_HOLDER.importedName» interface
+         *              contract.
+         «ENDIF»
+         * @param obj Object for which to generate hashCode() result.
+         * @return Hash code value of data modeled by this interface.
+         * @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) {
+        «ELSE»
+            static int «BINDING_HASHCODE_NAME»(final «type.fullyQualifiedName» obj) {
+        «ENDIF»
+            final int prime = 31;
+            int result = 1;
+            «FOR property : typeAnalysis.value»
+                result = prime * result + «property.importedUtilClass».hashCode(obj.«property.getterMethodName»());
+            «ENDFOR»
+            «IF augmentable»
+                result = prime * result + «CODEHELPERS.importedName».hashAugmentations(obj);
+            «ENDIF»
+            return result;
+        }
+    '''
+
     def generateBindingToString() '''
         «val augmentable = analyzeType»
         /**
index 74908b796af6669b747a4b19f01cdd3c53a4285e..75c9fb4e444086b73f13f5b9cd09f4fd2dad7f30 100644 (file)
@@ -80,6 +80,12 @@ public final class BindingMapping {
      */
     public static final @NonNull String DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME = "implementedInterface";
 
+    /**
+     * Name of default {@link Object#hashCode()} implementation for instantiated DataObjects. Each such generated
+     * interface contains this static method.
+     */
+    public static final @NonNull String BINDING_HASHCODE_NAME = "bindingHashCode";
+
     /**
      * Name of default {@link Object#toString()} implementation for instantiated DataObjects. Each such generated
      * interface contains this static method.
index fce8d8301fb59d97f437fe5ebdef35c0ed43c3a4..b8ad0134e25ab2ce7e420e965c9b44f17355977a 100644 (file)
@@ -380,6 +380,17 @@ public final class CodeHelpers {
         return value == null ? null : Uint64.valueOf(value);
     }
 
+    /**
+     * Utility for extracting augmentations from an implementation of {@link AugmentationHolder} interface.
+     *
+     * @param obj Implementation object
+     * @return hash code of augmentations
+     * @throws NullPointerException if obj is null
+     */
+    public static int hashAugmentations(final @NonNull AugmentationHolder<?> obj) {
+        return Objects.hashCode(obj.augmentations());
+    }
+
     /**
      * The constant '31' is the result of folding this code:
      * <pre>