From: Robert Varga Date: Fri, 11 Mar 2022 19:09:22 +0000 (+0100) Subject: Fix identityref wildcards X-Git-Tag: v9.0.0~12 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=mdsal.git;a=commitdiff_plain;h=f735dcbc86962f6b51abecadec0c00f595649984 Fix identityref wildcards Identityref use Class 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 --- diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend index 18d0da34ba..7dfd93a551 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend @@ -254,16 +254,36 @@ class BuilderTemplate extends AbstractBuilderTemplate { if (ownGetterType instanceof ParameterizedType) { val itemType = ownGetterType.actualTypeArguments.get(0) if (Types.isListType(ownGetterType)) { - return ''' - this._«propertyName» = «CODEHELPERS.importedName».checkListFieldCast(«itemType.importedName».class, "«propertyName»", «retrieveProperty»)''' + val importedClass = importedClass(itemType) + if (importedClass !== null) { + return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass) + } + return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName) } if (Types.isSetType(ownGetterType)) { - return ''' - this._«propertyName» = «CODEHELPERS.importedName».checkSetFieldCast(«itemType.importedName».class, "«propertyName»", «retrieveProperty»)''' + val importedClass = importedClass(itemType) + if (importedClass !== null) { + return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCastIdentity", importedClass) + } + return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName) + } + if (Types.CLASS.equals(ownGetterType)) { + return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.importedName) } } - return ''' - this._«propertyName» = «CODEHELPERS.importedName».checkFieldCast(«ownGetterType.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 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 index 0000000000..2c214bfaee --- /dev/null +++ b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/Mdsal732Test.java @@ -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.checkSetFieldCastIdentity(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.checkSetFieldCastIdentity(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 index 0000000000..e528c3ec9c --- /dev/null +++ b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal732/mdsal732.yang @@ -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; + } +} + diff --git a/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java index aba378e49c..ff57748811 100644 --- a/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java +++ b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java @@ -383,6 +383,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 @Nullable Class checkFieldCastIdentity( + final @NonNull Class 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. * @@ -401,7 +426,24 @@ public final class CodeHelpers { } /** - * Utility method for checking whether the items of target list is compatible. + * 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 @Nullable List> checkListFieldCastIdentity( + final @NonNull Class requiredClass, final @NonNull String fieldName, final @Nullable List list) { + checkCollectionFieldIdentity(requiredClass, fieldName, list); + return (List>) list; + } + + /** + * Utility method for checking whether the items of target set is compatible. * * @param requiredClass Required item class * @param fieldName name of the field being filled @@ -417,6 +459,23 @@ public final class CodeHelpers { return (Set) set; } + /** + * Utility method for checking whether the items of a target set are compatible with a particular identity class. + * + * @param requiredClass Required class + * @param fieldName name of the field being filled + * @param set Set, which items should be checked + * @return Type-checked Set + * @throws IllegalArgumentException if a set item is not a Class representing {@code requiredClass} + * @throws NullPointerException if {@code requiredClass} or {@code fieldName} is null + */ + @SuppressWarnings("unchecked") + public static @Nullable Set> checkSetFieldCastIdentity( + final @NonNull Class requiredClass, final @NonNull String fieldName, final @Nullable Set set) { + checkCollectionFieldIdentity(requiredClass, fieldName, set); + return (Set>) set; + } + @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "Internal NPE->IAE conversion") private static void checkCollectionField(final @NonNull Class requiredClass, @@ -431,6 +490,20 @@ public final class CodeHelpers { } } + @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", + justification = "Internal NPE->IAE conversion") + private static void checkCollectionFieldIdentity(final @NonNull Class requiredClass, + final @NonNull String fieldName, final @Nullable Collection collection) { + if (collection != null) { + try { + collection.forEach(item -> ((Class) item).asSubclass(requiredClass)); + } catch (ClassCastException | NullPointerException e) { + throw new IllegalArgumentException("Invalid input item for property \"" + requireNonNull(fieldName) + + "\"", e); + } + } + } + /** * The constant '31' is the result of folding this code: *
diff --git a/binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java b/binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java
index a3d81ea2d2..102ce23fcb 100644
--- a/binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java
+++ b/binding/yang-binding/src/test/java/org/opendaylight/yangtools/yang/binding/CodeHelpersTest.java
@@ -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;
@@ -29,6 +32,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));
@@ -45,6 +66,29 @@ public class CodeHelpersTest {
         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));
+    }
+
     @Test
     public void testCheckSetFieldCast() {
         assertNull(CodeHelpers.checkSetFieldCast(CodeHelpersTest.class, "foo", null));
@@ -60,4 +104,35 @@ public class CodeHelpersTest {
             () -> CodeHelpers.checkSetFieldCast(CodeHelpersTest.class, "foo", Set.of(new Object())));
         assertThat(iae.getCause(), instanceOf(ClassCastException.class));
     }
+
+    @Test
+    public void testCheckSetFieldCastIdentity() {
+        assertNull(CodeHelpers.checkSetFieldCastIdentity(Identity.class, "foo", null));
+        assertSame(Set.of(), CodeHelpers.checkSetFieldCastIdentity(Identity.class, "foo", Set.of()));
+
+        final var set = Set.of(Identity.class);
+        assertSame(set, CodeHelpers.checkSetFieldCastIdentity(Identity.class, "foo", set));
+        final var derivedSet = Set.of(DerivedIdentity.class);
+        assertSame(derivedSet, CodeHelpers.checkSetFieldCastIdentity(Identity.class, "foo", derivedSet));
+
+        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkSetFieldCastIdentity(Identity.class, "foo", Collections.singleton(null)));
+        assertThat(iae.getCause(), instanceOf(NullPointerException.class));
+
+        iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkSetFieldCastIdentity(Identity.class, "foo", Set.of(new Object())));
+        assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+
+        iae = assertThrows(IllegalArgumentException.class,
+            () -> CodeHelpers.checkSetFieldCastIdentity(Identity.class, "foo", Set.of(BaseIdentity.class)));
+        assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+    }
+
+    private interface Identity extends BaseIdentity {
+
+    }
+
+    private interface DerivedIdentity extends Identity {
+
+    }
 }