From 163325d8770b579bb88151521c80efec9eee9bb5 Mon Sep 17 00:00:00 2001 From: Ruslan Kashapov Date: Tue, 13 Dec 2022 12:34:08 +0200 Subject: [PATCH] RFC8040 'rc:yang-data' support for mdsal binding generator 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 Signed-off-by: Robert Varga --- binding/mdsal-binding-generator/pom.xml | 4 + .../src/main/java/module-info.java | 1 + .../reactor/AbstractCompositeGenerator.java | 5 + .../impl/reactor/ModuleGenerator.java | 6 + .../impl/reactor/StatementNamespace.java | 6 + .../impl/reactor/YangDataGenerator.java | 132 +++++++++++ .../impl/reactor/YangDataNamingStrategy.java | 54 +++++ .../impl/rt/DefaultYangDataRuntimeType.java | 24 ++ .../binding/generator/impl/Mdsal675Test.java | 213 ++++++++++++++++++ .../yang-data-models/ietf-restconf.yang | 16 ++ .../yang-data-models/yang-data-demo.yang | 109 +++++++++ .../yang-data-models/yang-data-naming.yang | 73 ++++++ .../java/api/generator/BaseTemplate.xtend | 6 + .../java/api/generator/BuilderGenerator.java | 4 +- .../generator/YangModuleInfoTemplate.xtend | 26 ++- .../java/api/generator/CompilationTest.java | 63 +++++- .../yang-data-gen/ietf-restconf.yang | 16 ++ .../yang-data-gen/yang-data-demo.yang | 119 ++++++++++ .../mdsal/binding/model/ri/BindingTypes.java | 53 +++-- binding/mdsal-binding-runtime-api/pom.xml | 4 + .../src/main/java/module-info.java | 1 + .../runtime/api/YangDataRuntimeType.java | 17 ++ .../binding/spec/naming/BindingMapping.java | 18 ++ .../spec/naming/BindingMappingTest.java | 12 + binding/mdsal-binding-test-model/pom.xml | 4 + .../src/main/yang/yang-data-demo.yang | 119 ++++++++++ .../yangtools/yang/binding/CodeHelpers.java | 8 +- 27 files changed, 1086 insertions(+), 27 deletions(-) create mode 100644 binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataGenerator.java create mode 100644 binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataNamingStrategy.java create mode 100644 binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/rt/DefaultYangDataRuntimeType.java create mode 100644 binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal675Test.java create mode 100644 binding/mdsal-binding-generator/src/test/resources/yang-data-models/ietf-restconf.yang create mode 100644 binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-demo.yang create mode 100644 binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-naming.yang create mode 100644 binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/ietf-restconf.yang create mode 100644 binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/yang-data-demo.yang create mode 100644 binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/mdsal/binding/runtime/api/YangDataRuntimeType.java create mode 100644 binding/mdsal-binding-test-model/src/main/yang/yang-data-demo.yang diff --git a/binding/mdsal-binding-generator/pom.xml b/binding/mdsal-binding-generator/pom.xml index 6327c0dc9d..15e274b8c0 100644 --- a/binding/mdsal-binding-generator/pom.xml +++ b/binding/mdsal-binding-generator/pom.xml @@ -40,6 +40,10 @@ org.opendaylight.yangtools odlext-model-api + + org.opendaylight.yangtools + rfc8040-model-api + org.opendaylight.yangtools yang-model-spi diff --git a/binding/mdsal-binding-generator/src/main/java/module-info.java b/binding/mdsal-binding-generator/src/main/java/module-info.java index 4d641e39fe..b3c7d56a99 100644 --- a/binding/mdsal-binding-generator/src/main/java/module-info.java +++ b/binding/mdsal-binding-generator/src/main/java/module-info.java @@ -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 diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractCompositeGenerator.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractCompositeGenerator.java index 55352d60e8..93f8dbde3c 100644 --- a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractCompositeGenerator.java +++ b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractCompositeGenerator.java @@ -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 builder, final String templateName) { + builder.addConstant(BindingTypes.YANG_DATA_NAME, BindingMapping.NAME_STATIC_FIELD_NAME, + Map.entry(yangModuleInfo, templateName)); + } + @Override CompositeRuntimeTypeBuilder createBuilder( final ModuleEffectiveStatement statement) { diff --git a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/StatementNamespace.java b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/StatementNamespace.java index c74e65682f..12dcdaceb5 100644 --- a/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/StatementNamespace.java +++ b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/StatementNamespace.java @@ -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 index 0000000000..a69705e83b --- /dev/null +++ b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataGenerator.java @@ -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 { + 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 createBuilder( + final YangDataEffectiveStatement statement) { + return new CompositeRuntimeTypeBuilder<>(statement) { + @Override + YangDataRuntimeType build(final GeneratedType type, final YangDataEffectiveStatement statement, + final List children, final List 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 index 0000000000..1bec0ff0da --- /dev/null +++ b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/YangDataNamingStrategy.java @@ -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 index 0000000000..7ae8c2bd11 --- /dev/null +++ b/binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/rt/DefaultYangDataRuntimeType.java @@ -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 + implements YangDataRuntimeType { + public DefaultYangDataRuntimeType(final GeneratedType bindingType, final YangDataEffectiveStatement statement, + final List 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 index 0000000000..1d14357598 --- /dev/null +++ b/binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal675Test.java @@ -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 INTERFACE_METHODS = + Map.of("implementedInterface", Types.typeForClass(Class.class)); + + @Test + public void yangDataGen() { + final List 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 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 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 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 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 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 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 index 0000000000..0f8d765488 --- /dev/null +++ b/binding/mdsal-binding-generator/src/test/resources/yang-data-models/ietf-restconf.yang @@ -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 index 0000000000..18b314f479 --- /dev/null +++ b/binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-demo.yang @@ -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 index 0000000000..b1be361c99 --- /dev/null +++ b/binding/mdsal-binding-generator/src/test/resources/yang-data-models/yang-data-naming.yang @@ -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; + } + } + } +} diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend index d57fab88a5..2728e7c8b0 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend @@ -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» + /** + * 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» diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGenerator.java b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGenerator.java index b031a95761..7babdde669 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGenerator.java +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGenerator.java @@ -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 type. @@ -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; } } diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/YangModuleInfoTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/YangModuleInfoTemplate.xtend index c6840aa3cc..9bb9a6acb5 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/YangModuleInfoTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/YangModuleInfoTemplate.xtend @@ -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> 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 diff --git a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/CompilationTest.java b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/CompilationTest.java index 4c240dd885..53ce247260 100644 --- a/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/CompilationTest.java +++ b/binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/CompilationTest.java @@ -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 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 index 0000000000..0f8d765488 --- /dev/null +++ b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/ietf-restconf.yang @@ -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 index 0000000000..cc56c0cd06 --- /dev/null +++ b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/yang-data-gen/yang-data-demo.yang @@ -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; + } +} diff --git a/binding/mdsal-binding-model-ri/src/main/java/org/opendaylight/mdsal/binding/model/ri/BindingTypes.java b/binding/mdsal-binding-model-ri/src/main/java/org/opendaylight/mdsal/binding/model/ri/BindingTypes.java index 881a5c7e13..d241ae41df 100644 --- a/binding/mdsal-binding-model-ri/src/main/java/org/opendaylight/mdsal/binding/model/ri/BindingTypes.java +++ b/binding/mdsal-binding-model-ri/src/main/java/org/opendaylight/mdsal/binding/model/ri/BindingTypes.java @@ -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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} - * @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} + * @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} - * @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) { diff --git a/binding/mdsal-binding-runtime-api/pom.xml b/binding/mdsal-binding-runtime-api/pom.xml index e1e62bda28..6d9ca58505 100644 --- a/binding/mdsal-binding-runtime-api/pom.xml +++ b/binding/mdsal-binding-runtime-api/pom.xml @@ -36,6 +36,10 @@ org.opendaylight.yangtools yang-model-api + + org.opendaylight.yangtools + rfc8040-model-api + org.opendaylight.yangtools yang-repo-api diff --git a/binding/mdsal-binding-runtime-api/src/main/java/module-info.java b/binding/mdsal-binding-runtime-api/src/main/java/module-info.java index 3c37cf6762..5a12f0f3d2 100644 --- a/binding/mdsal-binding-runtime-api/src/main/java/module-info.java +++ b/binding/mdsal-binding-runtime-api/src/main/java/module-info.java @@ -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 index 0000000000..84da505fdf --- /dev/null +++ b/binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/mdsal/binding/runtime/api/YangDataRuntimeType.java @@ -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(); +} diff --git a/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/naming/BindingMapping.java b/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/naming/BindingMapping.java index 6de5d251d0..81cf8aab12 100644 --- a/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/naming/BindingMapping.java +++ b/binding/mdsal-binding-spec-util/src/main/java/org/opendaylight/mdsal/binding/spec/naming/BindingMapping.java @@ -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 diff --git a/binding/mdsal-binding-spec-util/src/test/java/org/opendaylight/mdsal/binding/spec/naming/BindingMappingTest.java b/binding/mdsal-binding-spec-util/src/test/java/org/opendaylight/mdsal/binding/spec/naming/BindingMappingTest.java index 29ab3491ca..64299d9dea 100644 --- a/binding/mdsal-binding-spec-util/src/test/java/org/opendaylight/mdsal/binding/spec/naming/BindingMappingTest.java +++ b/binding/mdsal-binding-spec-util/src/test/java/org/opendaylight/mdsal/binding/spec/naming/BindingMappingTest.java @@ -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")); + } } diff --git a/binding/mdsal-binding-test-model/pom.xml b/binding/mdsal-binding-test-model/pom.xml index 3c5cde7811..48dcec56a1 100644 --- a/binding/mdsal-binding-test-model/pom.xml +++ b/binding/mdsal-binding-test-model/pom.xml @@ -43,6 +43,10 @@ org.opendaylight.mdsal.model yang-ext + + org.opendaylight.mdsal.binding.model.ietf + rfc8040 + org.kohsuke.metainf-services metainf-services 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 index 0000000000..4d7e049a33 --- /dev/null +++ b/binding/mdsal-binding-test-model/src/main/yang/yang-data-demo.yang @@ -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; + } +} 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 58ee21f082..303fb84c4c 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 @@ -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 @Nullable T checkCast(final @NonNull Class requiredClass, + public static > @Nullable T checkCast(final @NonNull Class 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; } -- 2.36.6