RFC8040 'rc:yang-data' support for mdsal binding generator 81/97381/32
authorRuslan Kashapov <ruslan.kashapov@pantheon.tech>
Tue, 13 Dec 2022 10:34:08 +0000 (12:34 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 31 Jan 2023 23:18:22 +0000 (00:18 +0100)
New naming strategy is introduced to generate artifact names out of
yang-data argument: if name is yang identifier compliant the camel-case
transformation used, otherwise non-compliant characters are encoded.

JIRA: MDSAL-675
JIRA: MDSAL-808
Change-Id: I6644f550a378cd176e5f53a201ee6e70b32c6410
Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
27 files changed:
binding/mdsal-binding-generator/pom.xml
binding/mdsal-binding-generator/src/main/java/module-info.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractCompositeGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/ModuleGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/StatementNamespace.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataGenerator.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataNamingStrategy.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/rt/DefaultYangDataRuntimeType.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal675Test.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/yang-data-models/ietf-restconf.yang [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-demo.yang [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-naming.yang [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGenerator.java
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/YangModuleInfoTemplate.xtend
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/CompilationTest.java
binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/ietf-restconf.yang [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/yang-data-demo.yang [new file with mode: 0644]
binding/mdsal-binding-model-ri/src/main/java/org/opendaylight/mdsal/binding/model/ri/BindingTypes.java
binding/mdsal-binding-runtime-api/pom.xml
binding/mdsal-binding-runtime-api/src/main/java/module-info.java
binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/mdsal/binding/runtime/api/YangDataRuntimeType.java [new file with mode: 0644]
binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/naming/BindingMapping.java
binding/mdsal-binding-spec-util/src/test/java/org/opendaylight/mdsal/binding/spec/naming/BindingMappingTest.java
binding/mdsal-binding-test-model/pom.xml
binding/mdsal-binding-test-model/src/main/yang/yang-data-demo.yang [new file with mode: 0644]
binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/CodeHelpers.java

index 6327c0dc9d36e2790ecc82c64191e3aa8860beb9..15e274b8c0a93f201d8185a90e7418ea143304a7 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>odlext-model-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc8040-model-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-model-spi</artifactId>
index 4d641e39fe3e10163dcef66a9df9603ff7b4dbe4..b3c7d56a99bdc379a660581c23b9b9133b86ea57 100644 (file)
@@ -30,6 +30,7 @@ module org.opendaylight.mdsal.binding.generator {
     requires org.opendaylight.yangtools.yang.model.ri;
     requires org.opendaylight.yangtools.yang.model.util;
     requires org.opendaylight.yangtools.odlext.model.api;
+    requires org.opendaylight.yangtools.rfc8040.model.api;
     requires org.slf4j;
 
     // Annotations
index 55352d60e8cf5461763d326bf6a0268ea1ba3c0b..93f8dbde3c1ad4fe9fa36b6c33d567e85335f61c 100644 (file)
@@ -25,6 +25,7 @@ import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilde
 import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.model.api.AddedByUsesAware;
 import org.opendaylight.yangtools.yang.model.api.CopyableNode;
@@ -554,6 +555,10 @@ public abstract class AbstractCompositeGenerator<S extends EffectiveStatement<?,
                         tmpAug.add(new UsesAugmentGenerator(usesAug, uses, this));
                     }
                 }
+            } else if (stmt instanceof YangDataEffectiveStatement yangData) {
+                if (this instanceof ModuleGenerator moduleGen) {
+                    tmp.add(YangDataGenerator.of(yangData, moduleGen));
+                }
             } else {
                 LOG.trace("Ignoring statement {}", stmt);
             }
index edb1aee7dde9b5a2aab2e0c68ee9c84302dbcade..1ed6d5ed2aa2e786c7a3e28c42acb7f6cb3ee1bf 100644 (file)
@@ -109,6 +109,12 @@ public final class ModuleGenerator extends AbstractCompositeGenerator<ModuleEffe
             Map.entry(yangModuleInfo, localName.getLocalName()));
     }
 
+    // FIXME: use YangDataName
+    void addNameConstant(final GeneratedTypeBuilderBase<?> builder, final String templateName) {
+        builder.addConstant(BindingTypes.YANG_DATA_NAME, BindingMapping.NAME_STATIC_FIELD_NAME,
+            Map.entry(yangModuleInfo, templateName));
+    }
+
     @Override
     CompositeRuntimeTypeBuilder<ModuleEffectiveStatement, ModuleRuntimeType> createBuilder(
             final ModuleEffectiveStatement statement) {
index c74e65682f58611e028b78a0c8ccb12436bcef80..12dcdaceb5ddf8e7ef59e4fa8f74b1ebf9296260 100644 (file)
@@ -33,6 +33,12 @@ enum StatementNamespace {
      * The namespace of all {@code grouping} statements, bullet 6.
      */
     GROUPING("$G"),
+    /**
+     * The namespace of all RFC8040 {@code ietf-restconf:yang-data} statements. These sit outside of the usual YANG
+     * statement namespaces, but may overlap in the usual case when template names conform to YANG {@code identifier}
+     * rules.
+     */
+    YANG_DATA("$YD"),
     /**
      * All other processed statements. Includes {@code augment}, and {@code schema tree} statements.
      */
diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataGenerator.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataGenerator.java
new file mode 100644 (file)
index 0000000..a69705e
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2021 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.generator.impl.reactor;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.generator.impl.reactor.CollisionDomain.Member;
+import org.opendaylight.mdsal.binding.generator.impl.rt.DefaultYangDataRuntimeType;
+import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
+import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
+import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
+import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.YangDataRuntimeType;
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.UnresolvedQName;
+import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+
+/**
+ * Generator corresponding to a {@code rc:yang-data} statement.
+ */
+abstract sealed class YangDataGenerator
+        extends AbstractCompositeGenerator<YangDataEffectiveStatement, YangDataRuntimeType> {
+    private static final class WithIdentifier extends YangDataGenerator {
+        private final @NonNull Unqualified identifier;
+
+        WithIdentifier(final YangDataEffectiveStatement statement, final ModuleGenerator parent,
+                final Unqualified identifier) {
+            super(statement, parent);
+            this.identifier = requireNonNull(identifier);
+        }
+
+        @Override
+        CamelCaseNamingStrategy createNamingStrategy() {
+            return new CamelCaseNamingStrategy(namespace(), identifier);
+        }
+    }
+
+    private static final class WithString extends YangDataGenerator {
+        WithString(final YangDataEffectiveStatement statement, final ModuleGenerator parent) {
+            super(statement, parent);
+        }
+
+        @Override
+        YangDataNamingStrategy createNamingStrategy() {
+            return new YangDataNamingStrategy(statement().argument());
+        }
+    }
+
+    private YangDataGenerator(final YangDataEffectiveStatement statement, final ModuleGenerator parent) {
+        super(statement, parent);
+    }
+
+    static @NonNull YangDataGenerator of(final YangDataEffectiveStatement statement, final ModuleGenerator parent) {
+        // yang-data's argument is not guaranteed to comply with YANG 'identifier', but it usually does. If it does, we
+        // use the usual mechanics, but if it does not, we have to deal with any old string, similar to what we do for
+        // bit names. Here we decide which path to take.
+        final String templateName = statement.argument();
+        final var identifier = UnresolvedQName.tryLocalName(templateName);
+        return identifier != null ? new WithIdentifier(statement, parent, identifier)
+            : new WithString(statement, parent);
+    }
+
+    @Override
+    final void pushToInference(final SchemaInferenceStack dataTree) {
+        final QNameModule moduleQName = currentModule().getQName().getModule();
+        dataTree.enterYangData(moduleQName, statement().argument());
+    }
+
+    @Override
+    final ClassPlacement classPlacement() {
+        return ClassPlacement.TOP_LEVEL;
+    }
+
+    @Override
+    final Member createMember(final CollisionDomain domain) {
+        return domain.addPrimary(this, createNamingStrategy());
+    }
+
+    abstract @NonNull ClassNamingStrategy createNamingStrategy();
+
+    @Override
+    final StatementNamespace namespace() {
+        return StatementNamespace.YANG_DATA;
+    }
+
+    @Override
+    final GeneratedType createTypeImpl(final TypeBuilderFactory builderFactory) {
+        final GeneratedTypeBuilder builder = builderFactory.newGeneratedTypeBuilder(typeName());
+
+        builder.addImplementsType(BindingTypes.yangData(builder));
+        addUsesInterfaces(builder, builderFactory);
+        addConcreteInterfaceMethods(builder);
+
+        addGetterMethods(builder, builderFactory);
+
+        final var module = currentModule();
+        module.addNameConstant(builder, statement().argument());
+
+        builder.setModuleName(module.statement().argument().getLocalName());
+        builderFactory.addCodegenInformation(module, statement(), builder);
+
+        return builder.build();
+    }
+
+    @Override
+    final CompositeRuntimeTypeBuilder<YangDataEffectiveStatement, YangDataRuntimeType> createBuilder(
+            final YangDataEffectiveStatement statement) {
+        return new CompositeRuntimeTypeBuilder<>(statement) {
+            @Override
+            YangDataRuntimeType build(final GeneratedType type, final YangDataEffectiveStatement statement,
+                    final List<RuntimeType> children, final List<AugmentRuntimeType> augments) {
+                return new DefaultYangDataRuntimeType(type, statement, children);
+            }
+        };
+    }
+
+    @Override
+    final void addAsGetterMethod(final GeneratedTypeBuilderBase<?> builder, final TypeBuilderFactory builderFactory) {
+        // is not a part of any structure
+    }
+}
diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataNamingStrategy.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataNamingStrategy.java
new file mode 100644 (file)
index 0000000..1bec0ff
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o. 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.generator.impl.reactor;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
+
+/**
+ * Naming strategy for {@code ietf-restconf:yang-data} template which has a generic string not matching YANG identifier.
+ */
+@NonNullByDefault
+final class YangDataNamingStrategy extends ClassNamingStrategy {
+    private final String javaIdentifier;
+
+    YangDataNamingStrategy(final String templateName) {
+        javaIdentifier = BindingMapping.mapYangDataName(templateName);
+    }
+
+    @Override
+    String simpleClassName() {
+        return javaIdentifier;
+    }
+
+    @Override
+    @Nullable ClassNamingStrategy fallback() {
+        // javaIdentifier is guaranteed to be unique, there is no need for fallback
+        return null;
+    }
+
+    @Override
+    String rootName() {
+        return javaIdentifier;
+    }
+
+    @Override
+    String childPackage() {
+        // javaIdentifier is always unique and provides an identifier which is suitable for package naming as well,
+        //  except we need to further expand the package name so it does not class with the class.
+        // Since the strategy escapes '$', appending one cannot clash.
+        return javaIdentifier + '$';
+    }
+
+    @Override
+    ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+        return helper.add("javaIdentifier", javaIdentifier);
+    }
+}
diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/rt/DefaultYangDataRuntimeType.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/rt/DefaultYangDataRuntimeType.java
new file mode 100644 (file)
index 0000000..7ae8c2b
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021 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.generator.impl.rt;
+
+import com.google.common.annotations.Beta;
+import java.util.List;
+import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.YangDataRuntimeType;
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
+
+@Beta
+public final class DefaultYangDataRuntimeType extends AbstractCompositeRuntimeType<YangDataEffectiveStatement>
+        implements YangDataRuntimeType {
+    public DefaultYangDataRuntimeType(final GeneratedType bindingType, final YangDataEffectiveStatement statement,
+            final List<RuntimeType> children) {
+        super(bindingType, statement, children);
+    }
+}
diff --git a/binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal675Test.java b/binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal675Test.java
new file mode 100644 (file)
index 0000000..1d14357
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech s.r.o. 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.generator.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.in;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+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.ri.BaseYangTypes;
+import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
+import org.opendaylight.mdsal.binding.model.ri.Types;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class Mdsal675Test {
+    private static final String PACKAGE = "org.opendaylight.yang.gen.v1.urn.test.yang.data.demo.norev.";
+    private static final String PACKAGE2 = "org.opendaylight.yang.gen.v1.urn.test.yang.data.naming.norev.";
+    private static final String MODULE_CLASS_NAME = PACKAGE + "YangDataDemoData";
+    private static final String ROOT_CONTAINER_CLASS_NAME = PACKAGE + "RootContainer";
+    private static final Map<String, Type> INTERFACE_METHODS =
+            Map.of("implementedInterface", Types.typeForClass(Class.class));
+
+    @Test
+    public void yangDataGen() {
+        final List<GeneratedType> allGenTypes = DefaultBindingGenerator.generateFor(
+                YangParserTestUtils.parseYangResources(Mdsal675Test.class,
+                        "/yang-data-models/ietf-restconf.yang", "/yang-data-models/yang-data-demo.yang"));
+        assertNotNull(allGenTypes);
+        assertEquals(29, allGenTypes.size());
+        final Map<String, GeneratedType> genTypesMap = allGenTypes.stream()
+                .collect(ImmutableMap.toImmutableMap(type -> type.getIdentifier().toString(), type -> type));
+
+        // ensure generated yang-data classes contain getters for inner structure types
+
+        // yang-data > container
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithContainer"),
+                assertGenType(genTypesMap, PACKAGE + "yang.data.with.container.ContainerFromYangData"),
+                List.of("getContainerFromYangData", "nonnullContainerFromYangData"));
+        // yang-data > list
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithList"),
+                Types.listTypeFor(assertGenType(genTypesMap, PACKAGE + "yang.data.with.list.ListFromYangData")),
+                List.of("getListFromYangData", "nonnullListFromYangData"));
+        // yang-data > leaf
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithLeaf"),
+                BaseYangTypes.STRING_TYPE,
+                List.of("getLeafFromYangData", "requireLeafFromYangData"));
+        // yang-data > leaf-list
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithLeafList"),
+                Types.setTypeFor(BaseYangTypes.STRING_TYPE),
+                List.of("getLeafListFromYangData", "requireLeafListFromYangData"));
+        // yang-data > anydata
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithAnydata"),
+                assertGenType(genTypesMap, PACKAGE + "yang.data.with.anydata.AnydataFromYangData"),
+                List.of("getAnydataFromYangData", "requireAnydataFromYangData"));
+        // yang-data > anyxml
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithAnyxml"),
+                assertGenType(genTypesMap, PACKAGE + "yang.data.with.anyxml.AnyxmlFromYangData"),
+                List.of("getAnyxmlFromYangData", "requireAnyxmlFromYangData"));
+
+        // ensure generated yang-data classes extending inner group so group content is reachable
+
+        // yang-data > uses > group > container
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithContainerFromGroup"),
+                assertGenType(genTypesMap, PACKAGE + "GrpForContainer"),
+                assertGenType(genTypesMap, PACKAGE + "grp._for.container.ContainerFromGroup"),
+                List.of("getContainerFromGroup", "nonnullContainerFromGroup"));
+        // yang-data > uses > group > list
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithListFromGroup"),
+                assertGenType(genTypesMap, PACKAGE + "GrpForList"),
+                Types.listTypeFor(assertGenType(genTypesMap, PACKAGE + "grp._for.list.ListFromGroup")),
+                List.of("getListFromGroup", "nonnullListFromGroup")
+        );
+        // yang-data > uses > group > leaf
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithLeafFromGroup"),
+                assertGenType(genTypesMap, PACKAGE + "GrpForLeaf"),
+                BaseYangTypes.UINT32_TYPE,
+                List.of("getLeafFromGroup", "requireLeafFromGroup"));
+        // yang-data > uses > group > leaf-list
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithLeafListFromGroup"),
+                assertGenType(genTypesMap, PACKAGE + "GrpForLeafList"),
+                Types.setTypeFor(BaseYangTypes.UINT32_TYPE),
+                List.of("getLeafListFromGroup", "requireLeafListFromGroup"));
+        // yang-data > uses > group > anydata
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithAnydataFromGroup"),
+                assertGenType(genTypesMap, PACKAGE + "GrpForAnydata"),
+                assertGenType(genTypesMap, PACKAGE + "grp._for.anydata.AnydataFromGroup"),
+                List.of("getAnydataFromGroup", "requireAnydataFromGroup"));
+        // yang-data > uses > group > anyxml
+        assertYangDataGenType(
+                assertGenType(genTypesMap, PACKAGE + "YangDataWithAnyxmlFromGroup"),
+                assertGenType(genTypesMap, PACKAGE + "GrpForAnyxml"),
+                assertGenType(genTypesMap, PACKAGE + "grp._for.anyxml.AnyxmlFromGroup"),
+                List.of("getAnyxmlFromGroup", "requireAnyxmlFromGroup"));
+
+        // ensure module class has only getter for root container
+        final GeneratedType moduleType = assertGenType(genTypesMap, MODULE_CLASS_NAME);
+        assertNotNull(moduleType.getMethodDefinitions());
+        assertEquals(List.of("getRootContainer"),
+                moduleType.getMethodDefinitions().stream().map(MethodSignature::getName)
+                        .filter(methodName -> methodName.startsWith("get")).toList());
+
+        // ensure yang-data at non-top level is ignored (no getters in parent container)
+        GeneratedType rootContainerType = assertGenType(genTypesMap, ROOT_CONTAINER_CLASS_NAME);
+        assertNotNull(rootContainerType.getMethodDefinitions());
+        assertTrue(rootContainerType.getMethodDefinitions().stream()
+                .filter(method -> method.getName().startsWith("get"))
+                .findFirst().isEmpty());
+    }
+
+    @Test
+    public void yangDataNamingStrategy() {
+        final List<GeneratedType> allGenTypes = DefaultBindingGenerator.generateFor(
+                YangParserTestUtils.parseYangResources(Mdsal675Test.class,
+                        "/yang-data-models/ietf-restconf.yang", "/yang-data-models/yang-data-naming.yang"));
+        assertNotNull(allGenTypes);
+        assertEquals(22, allGenTypes.size());
+        final Set<String> genTypeNames =
+                allGenTypes.stream().map(type -> type.getIdentifier().toString()).collect(Collectors.toSet());
+
+        // template name is not compliant to YANG identifier -> char encoding used, name starts with $ char
+        // a) latin1, but not a valid Java identifier
+        assertTrue(genTypeNames.contains(PACKAGE2 + "$ľaľa$20$ho$2C$$20$papľuha$2C$$20$ogrcal$20$mi$20$krpce$21$"));
+        // b) cyrillic, but a valid Java identifier
+        assertTrue(genTypeNames.contains(PACKAGE2 + "привет"));
+
+        // template name is compliant to yang identifier -> camel-case used
+        assertTrue(genTypeNames.contains(PACKAGE2 + "IdentifierCompliantName"));
+
+        // name collision with a typedef
+        assertTrue(genTypeNames.contains(PACKAGE2 + "Collision1$T"));
+        assertTrue(genTypeNames.contains(PACKAGE2 + "collision1.Collision1"));
+        assertTrue(genTypeNames.contains(PACKAGE2 + "Collision1$YD"));
+
+        // name collision with top level container
+        assertTrue(genTypeNames.contains(PACKAGE2 + "Collision2"));
+        assertTrue(genTypeNames.contains(PACKAGE2 + "Collision2$YD"));
+
+        // name collision with group used
+        assertTrue(genTypeNames.contains(PACKAGE2 + "Collision3$G"));
+        assertTrue(genTypeNames.contains(PACKAGE2 + "Collision3$YD"));
+
+        // rc:yang-data .-/#
+        assertTrue(genTypeNames.contains(PACKAGE2 + "$$2E$$2D$$2F$$23$"));
+        assertTrue(genTypeNames.contains(PACKAGE2 + "$$2e$$2d$$2f$$23$$.Foo"));
+
+        // rc:yang-data -./#
+        assertTrue(genTypeNames.contains(PACKAGE2 + "$$2D$$2E$$2F$$23$"));
+        assertTrue(genTypeNames.contains(PACKAGE2 + "$$2d$$2e$$2f$$23$$.Foo"));
+    }
+
+    private static GeneratedType assertGenType(final Map<String, GeneratedType> genTypesMap, final String className) {
+        final var ret = genTypesMap.get(className);
+        assertNotNull("no type generated: " + className, ret);
+        return ret;
+    }
+
+    private static void assertYangDataGenType(final GeneratedType yangDataType, final Type contentType,
+            final List<String> getterMethods) {
+        assertImplements(yangDataType, BindingTypes.yangData(yangDataType));
+        INTERFACE_METHODS.forEach((name, type) -> assertHasMethod(yangDataType, name, type));
+        for (final String methodName : getterMethods) {
+            assertHasMethod(yangDataType, methodName, contentType);
+        }
+    }
+
+    private static void assertYangDataGenType(final GeneratedType yangDataType, final GeneratedType groupType,
+            final Type contentType, final List<String> getterMethods) {
+        assertImplements(yangDataType, BindingTypes.yangData(yangDataType));
+        assertImplements(yangDataType, groupType);
+        INTERFACE_METHODS.forEach((name, type) -> assertHasMethod(yangDataType, name, type));
+        for (final String methodName : getterMethods) {
+            assertHasMethod(groupType, methodName, contentType);
+        }
+    }
+
+    private static void assertHasMethod(final GeneratedType genType, final String methodName,
+            final Type returnType) {
+        assertTrue("no expected method " + methodName + " returning " + returnType,
+            genType.getMethodDefinitions().stream().anyMatch(
+                method -> methodName.equals(method.getName()) && returnType.equals(method.getReturnType())));
+    }
+
+    private static void assertImplements(final GeneratedType genType, final Type implementedType) {
+        assertThat(implementedType, in(genType.getImplements()));
+    }
+
+}
diff --git a/binding/mdsal-binding-generator/src/test/resources/yang-data-models/ietf-restconf.yang b/binding/mdsal-binding-generator/src/test/resources/yang-data-models/ietf-restconf.yang
new file mode 100644 (file)
index 0000000..0f8d765
--- /dev/null
@@ -0,0 +1,16 @@
+module ietf-restconf {
+  yang-version 1.1;
+  namespace "urn:ietf:params:xml:ns:yang:ietf-restconf";
+  prefix "rc";
+
+  description
+    "NB: Original file minified for testing purposes";
+
+  revision 2017-01-26;
+
+  extension yang-data {
+    argument name {
+      yin-element true;
+    }
+  }
+}
\ No newline at end of file
diff --git a/binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-demo.yang b/binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-demo.yang
new file mode 100644 (file)
index 0000000..18b314f
--- /dev/null
@@ -0,0 +1,109 @@
+module yang-data-demo {
+  yang-version 1.1;
+  namespace "urn:test:yang:data:demo";
+  prefix ydd;
+
+  import ietf-restconf { prefix rc; }
+
+  rc:yang-data yang-data-with-container {
+    container container-from-yang-data {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  rc:yang-data yang-data-with-list {
+    list list-from-yang-data {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  rc:yang-data yang-data-with-leaf {
+    leaf leaf-from-yang-data {
+      type string;
+    }
+  }
+
+  rc:yang-data yang-data-with-leaf-list {
+    leaf-list leaf-list-from-yang-data {
+      type string;
+    }
+  }
+
+  rc:yang-data yang-data-with-anydata {
+    anydata anydata-from-yang-data;
+  }
+
+  rc:yang-data yang-data-with-anyxml {
+    anyxml anyxml-from-yang-data;
+  }
+
+  rc:yang-data yang-data-with-container-from-group {
+    uses grp-for-container;
+  }
+
+  rc:yang-data yang-data-with-list-from-group {
+    uses grp-for-list;
+  }
+
+  rc:yang-data yang-data-with-leaf-from-group {
+    uses grp-for-leaf;
+  }
+
+  rc:yang-data yang-data-with-leaf-list-from-group {
+    uses grp-for-leaf-list;
+  }
+
+  rc:yang-data yang-data-with-anydata-from-group {
+    uses grp-for-anydata;
+  }
+
+  rc:yang-data yang-data-with-anyxml-from-group {
+    uses grp-for-anyxml;
+  }
+
+  grouping grp-for-container {
+    container container-from-group {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  grouping grp-for-list {
+    list list-from-group {
+      leaf num {
+        type uint32;
+      }
+    }
+  }
+
+  grouping grp-for-leaf {
+    leaf leaf-from-group {
+      type uint32;
+    }
+  }
+
+  grouping grp-for-leaf-list {
+    leaf-list leaf-list-from-group {
+      type uint32;
+    }
+  }
+
+  grouping grp-for-anydata {
+    anydata anydata-from-group;
+  }
+
+  grouping grp-for-anyxml{
+    anyxml anyxml-from-group;
+  }
+
+  container root-container {
+    rc:yang-data "yang-data-ignored" {
+      container ignored;
+    }
+  }
+}
diff --git a/binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-naming.yang b/binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-naming.yang
new file mode 100644 (file)
index 0000000..b1be361
--- /dev/null
@@ -0,0 +1,73 @@
+module yang-data-naming {
+  yang-version 1.1;
+  namespace "urn:test:yang:data:naming";
+  prefix ydn;
+
+  import ietf-restconf { prefix rc; }
+
+  rc:yang-data "ľaľa ho, papľuha, ogrcal mi krpce!" {
+    container krpce {
+      leaf moje {
+        type boolean;
+      }
+      leaf ogrcanie {
+        type boolean;
+      }
+    }
+  }
+
+  rc:yang-data привет {
+    container cyrillic {
+      leaf ja {
+        type boolean;
+      }
+    }
+  }
+
+  rc:yang-data "identifier-compliant-name" {
+    container cont {
+      leaf lf {
+        type boolean;
+      }
+    }
+  }
+
+  rc:yang-data collision1 {
+    container collision1;
+  }
+
+  typedef collision1 {
+    type string;
+  }
+
+  rc:yang-data collision2 {
+    container some;
+  }
+
+  container collision2;
+
+  rc:yang-data collision3 {
+    uses collision3;
+  }
+
+  grouping collision3 {
+    container some;
+  }
+
+  rc:yang-data .-/# {
+    container foo {
+      leaf bar {
+        type string;
+      }
+    }
+  }
+
+  rc:yang-data -./# {
+    list foo {
+      key baz;
+      leaf baz {
+        type uint32;
+      }
+    }
+  }
+}
index d57fab88a597ff29df4f0832345b9d12ebaa9e81..2728e7c8b0d04bc66062f5a447d9038839b4c595 100644 (file)
@@ -301,6 +301,12 @@ abstract class BaseTemplate extends JavaFileTemplate {
              * YANG identifier of the statement represented by this class.
              */
             public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
+        «ELSEIF BindingMapping.NAME_STATIC_FIELD_NAME.equals(c.name)»
+            «val entry = c.value as Entry<JavaTypeName, String>»
+            /**
+             * Yang Data template name of the statement represented by this class.
+             */
+            public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_YANGDATANAMEOF_METHOD_NAME»("«entry.value»");
         «ELSEIF BindingMapping.VALUE_STATIC_FIELD_NAME.equals(c.name) && BaseIdentity.equals(c.value)»
             «val typeName = c.type.importedName»
             «val override = OVERRIDE.importedName»
index b031a957617a917a4fe0c555637dd13fab63c889..7babdde669b733cba64bf987bee6b4a3302cbc4f 100644 (file)
@@ -19,6 +19,7 @@ import org.opendaylight.mdsal.binding.model.ri.generated.type.builder.CodegenGen
 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
 import org.opendaylight.yangtools.yang.binding.Augmentable;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.YangData;
 
 /**
  * Transformator of the data from the virtual form to JAVA programming language. The result source code represent java
@@ -27,6 +28,7 @@ import org.opendaylight.yangtools.yang.binding.Augmentation;
 public final class BuilderGenerator implements CodeGenerator {
     private static final JavaTypeName AUGMENTABLE = JavaTypeName.create(Augmentable.class);
     private static final JavaTypeName AUGMENTATION = JavaTypeName.create(Augmentation.class);
+    private static final JavaTypeName YANG_DATA = JavaTypeName.create(YangData.class);
 
     /**
      * Passes via list of implemented types in <code>type</code>.
@@ -40,7 +42,7 @@ public final class BuilderGenerator implements CodeGenerator {
             for (Type t : generated.getImplements()) {
                 // "rpc" and "grouping" elements do not implement Augmentable
                 final JavaTypeName name = t.getIdentifier();
-                if (name.equals(AUGMENTABLE) || name.equals(AUGMENTATION)) {
+                if (name.equals(AUGMENTABLE) || name.equals(AUGMENTATION) || name.equals(YANG_DATA)) {
                     return true;
                 }
             }
index c6840aa3ccc9adcf95be0f58cf956bd506c56c20..9bb9a6acb5f75def3f7246b214926329da7a9133 100644 (file)
@@ -12,6 +12,7 @@ import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMappin
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.MODEL_BINDING_PROVIDER_CLASS_NAME
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.MODULE_INFO_CLASS_NAME
 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.MODULE_INFO_YANGDATANAMEOF_METHOD_NAME
 
 import com.google.common.base.Preconditions
 import com.google.common.collect.ImmutableSet
@@ -22,6 +23,7 @@ import java.util.Set
 import java.util.TreeMap
 import java.util.function.Function
 import org.eclipse.xtend.lib.annotations.Accessors
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataSchemaNode
 import org.opendaylight.yangtools.yang.binding.YangModuleInfo
 import org.opendaylight.yangtools.yang.common.Revision
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext
@@ -64,6 +66,7 @@ final class YangModuleInfoTemplate {
     val Module module
     val EffectiveModelContext ctx
     val Function<ModuleLike, Optional<String>> moduleFilePathResolver
+    val boolean hasYangData
 
     var importedTypes = CORE_IMPORT_STR
 
@@ -80,6 +83,7 @@ final class YangModuleInfoTemplate {
         this.moduleFilePathResolver = moduleFilePathResolver
         packageName = module.QNameModule.rootPackageName;
         modelBindingProviderName = '''«packageName».«MODEL_BINDING_PROVIDER_CLASS_NAME»'''
+        hasYangData = module.unknownSchemaNodes.stream.anyMatch([s | s instanceof YangDataSchemaNode])
     }
 
     def String generate() {
@@ -113,12 +117,27 @@ final class YangModuleInfoTemplate {
                  *
                  * @param localName local name
                  * @return A QName
-                 * @throws NullPointerException if {@code localName} is null
-                 * @throws IllegalArgumentException if localName is not a valid YANG identifier
+                 * @throws NullPointerException if {@code localName} is {@code null}
+                 * @throws IllegalArgumentException if {@code localName} is not a valid YANG identifier
                  */
                 public static @NonNull QName «MODULE_INFO_QNAMEOF_METHOD_NAME»(final String localName) {
                     return QName.create(NAME, localName).intern();
                 }
+            «IF hasYangData»
+
+                /**
+                 * Create an interned {@link YangDataName} with specified {@code templateName} and namespace/revision of
+                 * this module.
+                 *
+                 * @param templateName template name
+                 * @return A YangDataName
+                 * @throws NullPointerException if {@code templateName} is {@code null}
+                 * @throws IllegalArgumentException if {@code templateName} is empty
+                 */
+                public static @NonNull YangDataName «MODULE_INFO_YANGDATANAMEOF_METHOD_NAME»(final String templateName) {
+                    return new YangDataName(NAME.getModule(), templateName).intern();
+                }
+            «ENDIF»
 
                 «classBody(module, MODULE_INFO_CLASS_NAME, submodules)»
             }
@@ -127,6 +146,9 @@ final class YangModuleInfoTemplate {
             package «packageName»;
 
             «importedTypes»
+            «IF hasYangData»
+            import org.opendaylight.yangtools.yang.common.YangDataName;
+            «ENDIF»
 
             «body»
         '''.toString
index 4c240dd885c3c4d891d7934bf6aeb319c8ce3017..53ce2472609e67828a4f7c76927a454a3d320219 100644 (file)
@@ -792,7 +792,7 @@ public class CompilationTest extends BaseCompilationTest {
         generateTestSources("/compilation/union-string-pattern", sourcesOutputDir);
         CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
 
-        final ClassLoader loader = new URLClassLoader(new URL[] { compiledOutputDir.toURI().toURL() });
+        final ClassLoader loader = new URLClassLoader(new URL[]{compiledOutputDir.toURI().toURL()});
         final Class<?> fooClass = Class.forName(CompilationTestUtils.BASE_PKG + ".foo.norev.Foo", true, loader);
 
         final Field patterns = fooClass.getDeclaredField(TypeConstants.PATTERN_CONSTANT_NAME);
@@ -801,6 +801,67 @@ public class CompilationTest extends BaseCompilationTest {
         CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
     }
 
+    @Test
+    public void yangDataCompilation() throws Exception {
+        final File sourcesOutputDir = CompilationTestUtils.generatorOutput("yang-data-gen");
+        final File compiledOutputDir = CompilationTestUtils.compilerOutput("yang-data-gen");
+
+        generateTestSources("/compilation/yang-data-gen", sourcesOutputDir);
+        CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
+
+        final ClassLoader loader = new URLClassLoader(new URL[]{compiledOutputDir.toURI().toURL()});
+        final List<String> artifactNames = List.of(
+                // module with top level container
+                "$YangModuleInfoImpl", "YangDataDemoData", "RootContainer", "RootContainerBuilder",
+
+                // yang-data artifacts
+                "YangDataWithContainer", "YangDataWithContainerBuilder",
+                "YangDataWithList", "YangDataWithListBuilder",
+                "YangDataWithLeaf", "YangDataWithLeafBuilder",
+                "YangDataWithLeafList", "YangDataWithLeafListBuilder",
+                "YangDataWithAnydata", "YangDataWithAnydataBuilder",
+                "YangDataWithAnyxml", "YangDataWithAnyxmlBuilder",
+
+                // yang-data content artifacts
+                "yang.data.with.container.ContainerFromYangData",
+                "yang.data.with.container.ContainerFromYangDataBuilder",
+                "yang.data.with.list.ListFromYangData", "yang.data.with.list.ListFromYangDataBuilder",
+                "yang.data.with.anydata.AnydataFromYangData", "yang.data.with.anyxml.AnyxmlFromYangData",
+
+                // yang-data artifacts using groups
+                "YangDataWithContainerFromGroup", "YangDataWithContainerFromGroupBuilder",
+                "YangDataWithListFromGroup", "YangDataWithListFromGroupBuilder",
+                "YangDataWithLeafFromGroup", "YangDataWithLeafFromGroupBuilder",
+                "YangDataWithLeafListFromGroup", "YangDataWithLeafListFromGroupBuilder",
+                "YangDataWithAnydataFromGroup", "YangDataWithAnydataFromGroupBuilder",
+                "YangDataWithAnyxmlFromGroup", "YangDataWithAnyxmlFromGroupBuilder",
+
+                // group artifacts
+                "GrpForContainer", "GrpForList", "GrpForLeaf", "GrpForLeafList", "GrpForAnydata", "GrpForAnyxml",
+
+                // group content artifacts
+                "grp._for.container.ContainerFromGroup", "grp._for.container.ContainerFromGroupBuilder",
+                "grp._for.list.ListFromGroup", "grp._for.list.ListFromGroupBuilder",
+                "grp._for.anydata.AnydataFromGroup", "grp._for.anyxml.AnyxmlFromGroup",
+
+                // artifacts for non-ascii template naming: yang data artifact, inner container + builder
+                "$ľaľaho$20$papľuhu", "$ľaľaho$20$papľuhuBuilder",
+                "$ľaľaho$20$papľuhu$.LatinNaming", "$ľaľaho$20$papľuhu$.LatinNamingBuilder",
+                "привет", "приветBuilder", "привет$.CyrillicNaming", "привет$.CyrillicNamingBuilder"
+        );
+
+        for (String name : artifactNames) {
+            final String className = CompilationTestUtils.BASE_PKG + ".urn.test.yang.data.demo.rev220222." + name;
+            // ensure class source is generated
+            final String srcPath = className.replace('.', File.separatorChar) + ".java";
+            assertTrue(srcPath + " exists", new File(sourcesOutputDir, srcPath).exists());
+            // ensure class is loadable
+            Class.forName(className, true, loader);
+        }
+
+        CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
+    }
+
     private static void testReturnTypeIdentityref(final Class<?> clazz, final String methodName,
             final String returnTypeStr) throws NoSuchMethodException {
         Class<?> returnType = clazz.getMethod(methodName).getReturnType();
diff --git a/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/ietf-restconf.yang b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/ietf-restconf.yang
new file mode 100644 (file)
index 0000000..0f8d765
--- /dev/null
@@ -0,0 +1,16 @@
+module ietf-restconf {
+  yang-version 1.1;
+  namespace "urn:ietf:params:xml:ns:yang:ietf-restconf";
+  prefix "rc";
+
+  description
+    "NB: Original file minified for testing purposes";
+
+  revision 2017-01-26;
+
+  extension yang-data {
+    argument name {
+      yin-element true;
+    }
+  }
+}
\ No newline at end of file
diff --git a/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/yang-data-demo.yang b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/yang-data-demo.yang
new file mode 100644 (file)
index 0000000..cc56c0c
--- /dev/null
@@ -0,0 +1,119 @@
+module yang-data-demo {
+  yang-version 1.1;
+  namespace "urn:test:yang:data:demo";
+  prefix ydd;
+
+  import ietf-restconf {
+    prefix rc;
+  }
+
+  revision 2022-02-22;
+
+  rc:yang-data yang-data-with-container {
+    container container-from-yang-data {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  rc:yang-data yang-data-with-list {
+    list list-from-yang-data {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  rc:yang-data yang-data-with-leaf {
+    leaf leaf-from-yang-data {
+      type string;
+    }
+  }
+
+  rc:yang-data yang-data-with-leaf-list {
+    leaf-list leaf-list-from-yang-data {
+      type string;
+    }
+  }
+
+  rc:yang-data yang-data-with-anydata {
+    anydata anydata-from-yang-data;
+  }
+
+  rc:yang-data yang-data-with-anyxml {
+    anyxml anyxml-from-yang-data;
+  }
+
+  rc:yang-data yang-data-with-container-from-group {
+    uses grp-for-container;
+  }
+
+  rc:yang-data yang-data-with-list-from-group {
+    uses grp-for-list;
+  }
+
+  rc:yang-data yang-data-with-leaf-from-group {
+    uses grp-for-leaf;
+  }
+
+  rc:yang-data yang-data-with-leaf-list-from-group {
+    uses grp-for-leaf-list;
+  }
+
+  rc:yang-data yang-data-with-anydata-from-group {
+    uses grp-for-anydata;
+  }
+
+  rc:yang-data yang-data-with-anyxml-from-group {
+    uses grp-for-anyxml;
+  }
+
+  grouping grp-for-container {
+    container container-from-group {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  grouping grp-for-list {
+    list list-from-group {
+      leaf num {
+        type uint32;
+      }
+    }
+  }
+
+  grouping grp-for-leaf {
+    leaf leaf-from-group {
+      type uint32;
+    }
+  }
+
+  grouping grp-for-leaf-list {
+    leaf-list leaf-list-from-group {
+      type uint32;
+    }
+  }
+
+  grouping grp-for-anydata {
+    anydata anydata-from-group;
+  }
+
+  grouping grp-for-anyxml {
+    anyxml anyxml-from-group;
+  }
+
+  container root-container {
+    rc:yang-data "yang-data-ignored";
+  }
+
+  rc:yang-data "ľaľaho papľuhu" {
+    container latin-naming;
+  }
+
+  rc:yang-data привет {
+    container cyrillic-naming;
+  }
+}
index 881a5c7e1331a934a563326e68ebae47b4ae3766..d241ae41df0b7054236c9a88119531e64abbc305 100644 (file)
@@ -47,10 +47,12 @@ import org.opendaylight.yangtools.yang.binding.RpcOutput;
 import org.opendaylight.yangtools.yang.binding.RpcService;
 import org.opendaylight.yangtools.yang.binding.ScalarTypeObject;
 import org.opendaylight.yangtools.yang.binding.UnionTypeObject;
+import org.opendaylight.yangtools.yang.binding.YangData;
 import org.opendaylight.yangtools.yang.binding.YangFeature;
 import org.opendaylight.yangtools.yang.binding.annotations.RoutingContext;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.YangDataName;
 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
 
 public final class BindingTypes {
@@ -71,6 +73,7 @@ public final class BindingTypes {
     public static final ConcreteType UNION_TYPE_OBJECT = typeForClass(UnionTypeObject.class);
     public static final ConcreteType INSTANCE_IDENTIFIER = typeForClass(InstanceIdentifier.class);
     public static final ConcreteType KEYED_INSTANCE_IDENTIFIER = typeForClass(KeyedInstanceIdentifier.class);
+    public static final ConcreteType YANG_DATA_NAME = typeForClass(YangDataName.class);
 
     // This is an annotation, we are current just referencing the type
     public static final JavaTypeName ROUTING_CONTEXT = JavaTypeName.create(RoutingContext.class);
@@ -95,6 +98,7 @@ public final class BindingTypes {
     private static final ConcreteType RPC = typeForClass(Rpc.class);
     private static final ConcreteType RPC_RESULT = typeForClass(RpcResult.class);
     private static final ConcreteType YANG_FEATURE = typeForClass(YangFeature.class);
+    private static final ConcreteType YANG_DATA = typeForClass(YangData.class);
 
     private BindingTypes() {
 
@@ -107,7 +111,7 @@ public final class BindingTypes {
      * @param input Type input type
      * @param output Type output type
      * @return A parameterized type corresponding to {@code Action<Parent, Input, Output>}
-     * @throws NullPointerException if any argument is is null
+     * @throws NullPointerException if any argument is {@code null}
      */
     public static ParameterizedType action(final Type parent, final Type input, final Type output) {
         return parameterizedTypeFor(ACTION, instanceIdentifier(parent), input, output);
@@ -121,7 +125,7 @@ public final class BindingTypes {
      * @param input Type input type
      * @param output Type output type
      * @return A parameterized type corresponding to {@code KeyedListAction<ParentKey, Parent, Input, Output>}
-     * @throws NullPointerException if any argument is is null
+     * @throws NullPointerException if any argument is {@code null}
      */
     public static ParameterizedType keyedListAction(final Type parent, final Type keyType, final Type input,
             final Type output) {
@@ -133,7 +137,7 @@ public final class BindingTypes {
      *
      * @param concreteType The concrete type of this notification
      * @return A parameterized type corresponding to {@code Notification<ConcreteType>}
-     * @throws NullPointerException if any argument is is null
+     * @throws NullPointerException if any argument is {@code null}
      */
     public static ParameterizedType notification(final Type concreteType) {
         return parameterizedTypeFor(NOTIFICATION, concreteType);
@@ -145,7 +149,7 @@ public final class BindingTypes {
      * @param concreteType The concrete type of this notification
      * @param parent Type of parent defining the notification
      * @return A parameterized type corresponding to {@code InstanceNotification<ConcreteType, Parent>}
-     * @throws NullPointerException if {@code parent} is is null
+     * @throws NullPointerException if {@code parent} is {@code null}
      */
     public static ParameterizedType instanceNotification(final Type concreteType, final Type parent) {
         return parameterizedTypeFor(INSTANCE_NOTIFICATION, concreteType, parent);
@@ -158,7 +162,7 @@ public final class BindingTypes {
      * @param parent Type of parent defining the notification
      * @param keyType Type of parent's key
      * @return A parameterized type corresponding to {@code KeyedInstanceNotification<ConcreteType, ParentKey, Parent>}
-     * @throws NullPointerException if any argument is is null
+     * @throws NullPointerException if any argument is {@code null}
      */
     public static ParameterizedType keyedListNotification(final Type concreteType, final Type parent,
             final Type keyType) {
@@ -170,7 +174,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code Augmentable<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static @NonNull ParameterizedType augmentable(final Type type) {
         return parameterizedTypeFor(AUGMENTABLE, type);
@@ -181,7 +185,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code Augmentation<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static @NonNull ParameterizedType augmentation(final Type type) {
         return parameterizedTypeFor(AUGMENTATION, type);
@@ -192,7 +196,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code ChildOf<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType childOf(final Type type) {
         return parameterizedTypeFor(CHILD_OF, type);
@@ -203,7 +207,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code ChoiceIn<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType choiceIn(final Type type) {
         return parameterizedTypeFor(CHOICE_IN, type);
@@ -214,7 +218,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code Identifier<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType identifier(final Type type) {
         return parameterizedTypeFor(IDENTIFIER, type);
@@ -225,7 +229,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code Identifiable<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType identifiable(final Type type) {
         return parameterizedTypeFor(IDENTIFIABLE, type);
@@ -236,7 +240,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code InstanceIdentifier<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType instanceIdentifier(final Type type) {
         return parameterizedTypeFor(INSTANCE_IDENTIFIER, type);
@@ -248,7 +252,7 @@ public final class BindingTypes {
      * @param type Type for which to specialize
      * @param keyType Type of key
      * @return A parameterized type corresponding to {@code KeyedInstanceIdentifier<Type, KeyType>}
-     * @throws NullPointerException if any argument is is null
+     * @throws NullPointerException if any argument is is {@code null}
      */
     public static ParameterizedType keyedInstanceIdentifier(final Type type, final Type keyType) {
         return parameterizedTypeFor(KEYED_INSTANCE_IDENTIFIER, type, keyType);
@@ -259,7 +263,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code OpaqueObject<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType opaqueObject(final Type type) {
         return parameterizedTypeFor(OPAQUE_OBJECT, type);
@@ -282,7 +286,7 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code RpcResult<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType rpcResult(final Type type) {
         return parameterizedTypeFor(RPC_RESULT, type);
@@ -293,19 +297,30 @@ public final class BindingTypes {
      *
      * @param type Type for which to specialize
      * @return A parameterized type corresponding to {@code ScalarTypeObject<Type>}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     public static ParameterizedType scalarTypeObject(final Type type) {
         return parameterizedTypeFor(SCALAR_TYPE_OBJECT, type);
     }
 
+    /**
+     * Type specializing {@link YangData} for a particular type.
+     *
+     * @param concreteType The concrete type of this notification
+     * @return A parameterized type corresponding to {@code YangData<Type>}
+     * @throws NullPointerException if any argument is is {@code null}
+     */
+    public static ParameterizedType yangData(final Type concreteType) {
+        return parameterizedTypeFor(YANG_DATA, concreteType);
+    }
+
     /**
      * Type specializing {@link YangFeature} for a particular type.
      *
      * @param concreteType The concrete type of this notification
      * @param parent Type of parent defining the notification
      * @return A parameterized type corresponding to {@code YangFeature<Type, DataRootType>}
-     * @throws NullPointerException if any argument is is null
+     * @throws NullPointerException if any argument is is {@code null}
      */
     public static ParameterizedType yangFeature(final Type concreteType, final Type parent) {
         return parameterizedTypeFor(YANG_FEATURE, concreteType, parent);
@@ -354,7 +369,7 @@ public final class BindingTypes {
      *
      * @param type Parameterized type
      * @return Augmentable target, or null if {@code type} does not match the result of {@link #augmentation(Type)}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     @Beta
     public static @Nullable Type extractAugmentable(final ParameterizedType type) {
@@ -375,7 +390,7 @@ public final class BindingTypes {
      *
      * @param type Parameterized type
      * @return Identifiable target, or null if {@code type} does not match the result of {@link #identifier(Type)}
-     * @throws NullPointerException if {@code type} is null
+     * @throws NullPointerException if {@code type} is {@code null}
      */
     @Beta
     public static @Nullable Type extractIdentifiable(final ParameterizedType type) {
index e1e62bda28f9b14c8847f34fd9f4b85ea9435a6c..6d9ca58505e767796161f8a91c42d6d57943a662 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-model-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>rfc8040-model-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-repo-api</artifactId>
index 3c37cf6762d63dc145d4ee823874d9604ee6fa47..5a12f0f3d25557568da8d74414881e0805757044 100644 (file)
@@ -14,6 +14,7 @@ module org.opendaylight.mdsal.binding.runtime.api {
     requires transitive org.opendaylight.yangtools.yang.binding;
     requires transitive org.opendaylight.yangtools.yang.repo.api;
     requires transitive org.opendaylight.yangtools.yang.repo.spi;
+    requires transitive org.opendaylight.yangtools.rfc8040.model.api;
     requires transitive org.opendaylight.mdsal.binding.model.api;
     requires org.slf4j;
 
diff --git a/binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/mdsal/binding/runtime/api/YangDataRuntimeType.java b/binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/mdsal/binding/runtime/api/YangDataRuntimeType.java
new file mode 100644 (file)
index 0000000..84da505
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech s.r.o. 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.runtime.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
+
+@NonNullByDefault
+public interface YangDataRuntimeType extends CompositeRuntimeType {
+    @Override
+    YangDataEffectiveStatement statement();
+}
index 6de5d251d02338bfbec3c37afc488666a1e29755..81cf8aab123d1dfe8ec191d3bbbd8d9cf3d151d2 100644 (file)
@@ -70,7 +70,11 @@ public final class BindingMapping {
     public static final @NonNull String NOTIFICATION_LISTENER_SUFFIX = "Listener";
     public static final @NonNull String BUILDER_SUFFIX = "Builder";
     public static final @NonNull String KEY_SUFFIX = "Key";
+    // ietf-restconf:yang-data, i.e. YangDataName
+    public static final @NonNull String NAME_STATIC_FIELD_NAME = "NAME";
+    // everything that can have a QName (e.g. identifier bound to a namespace)
     public static final @NonNull String QNAME_STATIC_FIELD_NAME = "QNAME";
+    // concrete extensible contracts, for example 'feature', 'identity' and similar
     public static final @NonNull String VALUE_STATIC_FIELD_NAME = "VALUE";
     public static final @NonNull String PACKAGE_PREFIX = "org.opendaylight.yang.gen.v1";
     public static final @NonNull String AUGMENTATION_FIELD = "augmentation";
@@ -83,6 +87,7 @@ public final class BindingMapping {
 
     public static final @NonNull String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl";
     public static final @NonNull String MODULE_INFO_QNAMEOF_METHOD_NAME = "qnameOf";
+    public static final @NonNull String MODULE_INFO_YANGDATANAMEOF_METHOD_NAME = "yangDataNameOf";
     public static final @NonNull String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider";
 
     /**
@@ -426,6 +431,19 @@ public final class BindingMapping {
         return javaToYang.inverse();
     }
 
+    /**
+     * Builds class name representing yang-data template name which is not yang identifier compliant.
+     *
+     * @param templateName template name
+     * @return Java class name
+     * @throws NullPointerException if {@code templateName} is {@code null}
+     * @throws IllegalArgumentException if (@code templateName} is empty
+     */
+    // TODO: take YangDataName once we have it readily available
+    public static String mapYangDataName(final String templateName) {
+        return mapEnumAssignedName(templateName);
+    }
+
     // See https://docs.oracle.com/javase/specs/jls/se16/html/jls-3.html#jls-3.8
     // TODO: we are being conservative here, but should differentiate TypeIdentifier and UnqualifiedMethodIdentifier,
     //       which have different exclusions
index 29ab3491cacde7e3d6928bbf58dcb620d61a613b..64299d9dea2c3e3909c9db3f513c30ed9b16e6b8 100644 (file)
@@ -114,4 +114,16 @@ public class BindingMappingTest {
 
         assertEquals(expected, BindingMapping.mapEnumAssignedNames(yang));
     }
+
+    @Test
+    public void yangDataMapping() {
+        // single ascii compliant non-conflicting word - remain as is
+        assertEquals("single", BindingMapping.mapYangDataName("single"));
+        // ascii compliant - non-compliany chars only encoded
+        assertEquals("$abc$20$cde", BindingMapping.mapYangDataName("abc cde"));
+        // latin1 compliant -> latin chars normalized, non-compliant chars are encoded
+        assertEquals("$ľaľaho$20$papľuhu", BindingMapping.mapYangDataName("ľaľaho papľuhu"));
+        // latin1 non-compliant - all non-compliant characters encoded
+        assertEquals("$привет$20$papľuhu", BindingMapping.mapYangDataName("привет papľuhu"));
+    }
 }
index 3c5cde78114a7eaa31aa4cd492bdec0bdd9401c6..48dcec56a1cbe18810bb7273d3314d1774f1eb2c 100644 (file)
             <groupId>org.opendaylight.mdsal.model</groupId>
             <artifactId>yang-ext</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal.binding.model.ietf</groupId>
+            <artifactId>rfc8040</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.kohsuke.metainf-services</groupId>
             <artifactId>metainf-services</artifactId>
diff --git a/binding/mdsal-binding-test-model/src/main/yang/yang-data-demo.yang b/binding/mdsal-binding-test-model/src/main/yang/yang-data-demo.yang
new file mode 100644 (file)
index 0000000..4d7e049
--- /dev/null
@@ -0,0 +1,119 @@
+module yang-data-demo {
+  yang-version 1.1;
+  namespace "urn:test:yang:data:demo";
+  prefix ydd;
+
+  import ietf-restconf {
+    prefix rc;
+  }
+
+  revision 2022-02-22;
+
+  rc:yang-data yang-data-with-container {
+    container container-from-yang-data {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  rc:yang-data yang-data-with-list {
+    list list-from-yang-data {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  rc:yang-data yang-data-with-leaf {
+    leaf leaf-from-yang-data {
+      type string;
+    }
+  }
+
+  rc:yang-data yang-data-with-leaf-list {
+    leaf-list leaf-list-from-yang-data {
+      type string;
+    }
+  }
+
+  rc:yang-data yang-data-with-anydata {
+    anydata anydata-from-yang-data;
+  }
+
+  rc:yang-data yang-data-with-anyxml {
+    anyxml anyxml-from-yang-data;
+  }
+
+  rc:yang-data yang-data-with-container-from-group {
+    uses grp-for-container;
+  }
+
+  rc:yang-data yang-data-with-list-from-group {
+    uses grp-for-list;
+  }
+
+  rc:yang-data yang-data-with-leaf-from-group {
+    uses grp-for-leaf;
+  }
+
+  rc:yang-data yang-data-with-leaf-list-from-group {
+    uses grp-for-leaf-list;
+  }
+
+  rc:yang-data yang-data-with-anydata-from-group {
+    uses grp-for-anydata;
+  }
+
+  rc:yang-data yang-data-with-anyxml-from-group {
+    uses grp-for-anyxml;
+  }
+
+  grouping grp-for-container {
+    container container-from-group {
+      leaf str {
+        type string;
+      }
+    }
+  }
+
+  grouping grp-for-list {
+    list list-from-group {
+      leaf num {
+        type uint32;
+      }
+    }
+  }
+
+  grouping grp-for-leaf {
+    leaf leaf-from-group {
+      type uint32;
+    }
+  }
+
+  grouping grp-for-leaf-list {
+    leaf-list leaf-list-from-group {
+      type uint32;
+    }
+  }
+
+  grouping grp-for-anydata {
+    anydata anydata-from-group;
+  }
+
+  grouping grp-for-anyxml{
+    anyxml anyxml-from-group;
+  }
+
+  container root-container {
+    rc:yang-data "yang-data-ignored";
+  }
+
+  rc:yang-data ./# {
+    container foo;
+  }
+
+  rc:yang-data /.# {
+    list foo;
+  }
+}
index 58ee21f082f75f8af2d0675c5f4f9e7616d3a9a5..303fb84c4c791b6383edb34f2a932ba80dd73ea6 100644 (file)
@@ -415,16 +415,16 @@ public final class CodeHelpers {
     }
 
     /**
-     * Utility method for checking whether a target object is a compatible DataObject.
+     * Utility method for checking whether a target object is a compatible {@link BindingContract}.
      *
-     * @param requiredClass Required DataObject class
+     * @param requiredClass Required BindingContract 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,
+    public static <T extends BindingContract<?>> @Nullable T checkCast(final @NonNull Class<T> requiredClass,
             final @Nullable Object obj) {
-        return obj instanceof DataObject && requiredClass.equals(((DataObject) obj).implementedInterface())
+        return obj instanceof BindingContract<?> contract && requiredClass.equals(contract.implementedInterface())
             ? requiredClass.cast(obj) : null;
     }