Fix identityref wildcards 91/100091/1
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 11 Mar 2022 19:09:22 +0000 (20:09 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sat, 12 Mar 2022 10:01:15 +0000 (11:01 +0100)
Identityref use Class<? extends Target> for their return types, hence
we mask unmask the ParameterizedType to Target's JavaTypeName.

JIRA: MDSAL-732
Change-Id: I96fd0ffcffcd65aa7c658ab9d881a950ba41c6cb
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit f735dcbc86962f6b51abecadec0c00f595649984)

binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/Mdsal732Test.java [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal732/mdsal732.yang [new file with mode: 0644]
binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java
binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java

index fc45d33295668b6e3f4df4a5cf2dee08adc549ea..c110cdd849007f2277e4e7fe64e996d8f4e49f80 100644 (file)
@@ -250,13 +250,32 @@ class BuilderTemplate extends AbstractBuilderTemplate {
         if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
             return "this._" + propertyName + " = " + retrieveProperty
         }
-        if (Types.isListType(ownGetterType)) {
-            val itemType = (ownGetterType as ParameterizedType).actualTypeArguments.get(0)
-            return '''
-                this._«propertyName» = «CODEHELPERS.importedName».checkListFieldCast(«itemType.importedName».class, "«propertyName»", «retrieveProperty»)'''
+        if (ownGetterType instanceof ParameterizedType) {
+            val itemType = ownGetterType.actualTypeArguments.get(0)
+            if (Types.isListType(ownGetterType)) {
+                val importedClass = importedClass(itemType)
+                if (importedClass !== null) {
+                    return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass)
+                }
+                return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
+            }
+            if (Types.CLASS.equals(ownGetterType)) {
+                return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.importedName)
+            }
         }
-        return '''
-            this._«propertyName» = «CODEHELPERS.importedName».checkFieldCast(«ownGetter.returnType.importedName».class, "«propertyName»", «retrieveProperty»)'''
+        return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
+    }
+
+    def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
+            this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
+
+    private def importedClass(Type type) {
+        if (type instanceof ParameterizedType) {
+            if (Types.CLASS.equals(type.rawType)) {
+                return type.actualTypeArguments.get(0).identifier.importedName
+            }
+        }
+        return null
     }
 
     private def List<Type> getBaseIfcs(GeneratedType type) {
diff --git a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/Mdsal732Test.java b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/Mdsal732Test.java
new file mode 100644 (file)
index 0000000..67904e5
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.java.api.generator;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class Mdsal732Test extends BaseCompilationTest {
+    private File sourcesOutputDir;
+    private File compiledOutputDir;
+
+    @Before
+    public void before() throws IOException, URISyntaxException {
+        sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal732");
+        compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal732");
+    }
+
+    @After
+    public void after() {
+        CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
+    }
+
+    @Test
+    public void testIdentityrefLeafrefSpecialization() throws IOException, URISyntaxException {
+        generateTestSources("/compilation/mdsal732", sourcesOutputDir);
+        final var xyzzyBuilder = FileSearchUtil.getFiles(sourcesOutputDir).get("XyzzyBuilder.java");
+        assertNotNull(xyzzyBuilder);
+
+        final var content = Files.readString(xyzzyBuilder.toPath());
+        FileSearchUtil.assertFileContainsConsecutiveLines(xyzzyBuilder, content,
+            "    public XyzzyBuilder(Grp arg) {",
+            "        this._foo = CodeHelpers.checkFieldCastIdentity(Foo.class, \"foo\", arg.getFoo());",
+            "        this._bar = CodeHelpers.checkListFieldCastIdentity(Foo.class, \"bar\", arg.getBar());",
+            "        this._baz = CodeHelpers.checkListFieldCastIdentity(Foo.class, \"baz\", arg.getBaz());",
+            "    }");
+        FileSearchUtil.assertFileContainsConsecutiveLines(xyzzyBuilder, content,
+            "    public void fieldsFrom(DataObject arg) {",
+            "        boolean isValidArg = false;",
+            "        if (arg instanceof Grp) {",
+            "            this._foo = CodeHelpers.checkFieldCastIdentity(Foo.class, \"foo\", ((Grp)arg).getFoo());",
+            "            this._bar = CodeHelpers.checkListFieldCastIdentity(Foo.class, \"bar\", ((Grp)arg).getBar());",
+            "            this._baz = CodeHelpers.checkListFieldCastIdentity(Foo.class, \"baz\", ((Grp)arg).getBaz());",
+            "            isValidArg = true;",
+            "        }",
+            "        CodeHelpers.validValue(isValidArg, arg, \"[Grp]\");",
+            "    }");
+
+        CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
+    }
+}
diff --git a/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal732/mdsal732.yang b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal732/mdsal732.yang
new file mode 100644 (file)
index 0000000..e528c3e
--- /dev/null
@@ -0,0 +1,38 @@
+module mdsal732 {
+  namespace mdsal732;
+  prefix mdsal732;
+
+  grouping grp {
+    leaf foo {
+      type leafref {
+        path ../xyzzy;
+      }
+    }
+
+    leaf-list bar {
+      type leafref {
+        path ../xyzzy;
+      }
+    }
+
+    leaf-list baz {
+      type leafref {
+        path ../xyzzy;
+      }
+      ordered-by user;
+    }
+  }
+
+  identity foo;
+
+  container xyzzy {
+    leaf xyzzy {
+      type identityref {
+        base foo;
+      }
+    }
+
+    uses grp;
+  }
+}
+
index 9f24fe2c45ac719f88d648e007fcf307ddd6fc8c..90050a402ebe1bf5c67708c16564c57d24ec12f5 100644 (file)
@@ -380,6 +380,31 @@ public final class CodeHelpers {
         }
     }
 
+    /**
+     * Utility method for checking whether a target object is compatible with a particular identity class.
+     *
+     * @param requiredClass Required class
+     * @param fieldName name of the field being filled
+     * @param obj Object to check, may be null
+     * @return Object cast to required class, if it class matches requirement, or null
+     * @throws IllegalArgumentException if {@code obj} is not an Class representing {@code requiredClass}
+     * @throws NullPointerException if {@code requiredClass} or {@code fieldName} is null
+     */
+    public static <T extends BaseIdentity> @Nullable Class<? extends T> checkFieldCastIdentity(
+            final @NonNull Class<T> requiredClass, final @NonNull String fieldName, final @Nullable Object obj) {
+        if (obj == null) {
+            return null;
+        }
+        checkArgument(obj instanceof Class, "Invalid input value \"%s\" for property \"%s\"", obj, fieldName);
+
+        try {
+            return ((Class<?>) obj).asSubclass(requiredClass);
+        } catch (ClassCastException e) {
+            throw new IllegalArgumentException("Invalid input value \"" + obj + "\" for property \"" + fieldName + "\"",
+                e);
+        }
+    }
+
     /**
      * Utility method for checking whether the items of target list is compatible.
      *
@@ -403,6 +428,30 @@ public final class CodeHelpers {
         return (List<T>) list;
     }
 
+    /**
+     * Utility method for checking whether the items of a target list are compatible with a particular identity class.
+     *
+     * @param requiredClass Required class
+     * @param fieldName name of the field being filled
+     * @param list List, which items should be checked
+     * @return Type-checked List
+     * @throws IllegalArgumentException if a list item is not a Class representing {@code requiredClass}
+     * @throws NullPointerException if {@code requiredClass} or {@code fieldName} is null
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends BaseIdentity> @Nullable List<Class<? extends T>> checkListFieldCastIdentity(
+            final @NonNull Class<T> requiredClass, final @NonNull String fieldName, final @Nullable List<?> list) {
+        if (list != null) {
+            try {
+                list.forEach(item -> ((Class<?>) item).asSubclass(requiredClass));
+            } catch (ClassCastException | NullPointerException e) {
+                throw new IllegalArgumentException("Invalid input item for property \"" + requireNonNull(fieldName)
+                    + "\"", e);
+            }
+        }
+        return (List<Class<? extends T>>) list;
+    }
+
     /**
      * The constant '31' is the result of folding this code:
      * <pre>
index 769318ed115dabf0218308d7fc366c27ba344bed..05a5bac3188b37ae51c2fc9825f96c35faf660d8 100644 (file)
@@ -7,7 +7,10 @@
  */
 package org.opendaylight.yangtools.yang.binding;
 
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -28,6 +31,24 @@ public class CodeHelpersTest {
         assertThat(iae.getCause(), instanceOf(ClassCastException.class));
     }
 
+    @Test
+    public void testCheckedFieldCastIdentity() {
+        assertNull(CodeHelpers.checkFieldCastIdentity(Identity.class, "foo", null));
+        assertSame(Identity.class, CodeHelpers.checkFieldCastIdentity(Identity.class, "foo", Identity.class));
+        assertSame(DerivedIdentity.class, CodeHelpers.checkFieldCastIdentity(Identity.class, "foo",
+            DerivedIdentity.class));
+
+        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkFieldCastIdentity(Identity.class, "foo", new Object()));
+        assertThat(iae.getMessage(), allOf(
+            startsWith("Invalid input value \"java.lang.Object"),
+            endsWith("\" for property \"foo\"")));
+
+        iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkFieldCastIdentity(Identity.class, "foo", BaseIdentity.class));
+        assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+    }
+
     @Test
     public void testCheckListFieldCast() {
         assertNull(CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", null));
@@ -43,4 +64,35 @@ public class CodeHelpersTest {
             () -> CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", List.of(new Object())));
         assertThat(iae.getCause(), instanceOf(ClassCastException.class));
     }
+
+    @Test
+    public void testCheckListFieldCastIdentity() {
+        assertNull(CodeHelpers.checkListFieldCastIdentity(Identity.class, "foo", null));
+        assertSame(List.of(), CodeHelpers.checkListFieldCastIdentity(Identity.class, "foo", List.of()));
+
+        final var list = List.of(Identity.class);
+        assertSame(list, CodeHelpers.checkListFieldCastIdentity(Identity.class, "foo", list));
+        final var derivedList = List.of(DerivedIdentity.class);
+        assertSame(derivedList, CodeHelpers.checkListFieldCastIdentity(Identity.class, "foo", derivedList));
+
+        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkListFieldCastIdentity(Identity.class, "foo", Collections.singletonList(null)));
+        assertThat(iae.getCause(), instanceOf(NullPointerException.class));
+
+        iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkListFieldCastIdentity(Identity.class, "foo", List.of(new Object())));
+        assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+
+        iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkListFieldCastIdentity(Identity.class, "foo", List.of(BaseIdentity.class)));
+        assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+    }
+
+    private interface Identity extends BaseIdentity {
+
+    }
+
+    private interface DerivedIdentity extends Identity {
+
+    }
 }