From 443e1e4b474f95e7346f8c588d9ca9c7cc831a19 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 14 Jul 2021 13:15:23 +0200 Subject: [PATCH] Move mapModulesByIetfYangLibraryYang() This method has only a single production caller, migrate it to that class, making reducing utility overloads. Change-Id: I2c210e93797fbe5575c524e54c6c3ad6841b950f Signed-off-by: Robert Varga --- .../handlers/SchemaContextHandler.java | 172 +++++++++++++++++- .../mapping/RestconfMappingNodeUtil.java | 169 ----------------- .../mapping/RestconfMappingNodeUtilTest.java | 3 +- 3 files changed, 173 insertions(+), 171 deletions(-) diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java index d4d26d86b2..a6e9313e51 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/handlers/SchemaContextHandler.java @@ -9,7 +9,11 @@ package org.opendaylight.restconf.nb.rfc8040.handlers; import static java.util.Objects.requireNonNull; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PostConstruct; @@ -26,14 +30,34 @@ import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary; import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.RestconfState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.Module.ConformanceType; import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.Revision; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder; import org.opendaylight.yangtools.yang.data.api.schema.tree.ConflictingModificationAppliedException; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.model.api.Deviation; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextListener; +import org.opendaylight.yangtools.yang.model.api.FeatureDefinition; import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.ModuleLike; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.Submodule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,7 +104,7 @@ public class SchemaContextHandler implements EffectiveModelContextListener, Auto schemaContext = requireNonNull(context); if (context.findModule(IetfYangLibrary.MODULE_QNAME).isPresent()) { - putData(RestconfMappingNodeUtil.mapModulesByIetfYangLibraryYang(context.getModules(), context, + putData(mapModulesByIetfYangLibraryYang(context.getModules(), context, String.valueOf(this.moduleSetId.incrementAndGet()))); } @@ -119,4 +143,150 @@ public class SchemaContextHandler implements EffectiveModelContextListener, Auto } } } + + + /** + * Map data from modules to {@link NormalizedNode}. + * + * @param modules modules for mapping + * @param context schema context + * @param moduleSetId module-set-id of actual set + * @return mapped data as {@link NormalizedNode} + */ + @VisibleForTesting + public static ContainerNode mapModulesByIetfYangLibraryYang(final Collection modules, + final SchemaContext context, final String moduleSetId) { + final CollectionNodeBuilder mapBuilder = Builders.orderedMapBuilder() + .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.MODULE_QNAME_LIST)); + for (final Module module : context.getModules()) { + fillMapByModules(mapBuilder, IetfYangLibrary.MODULE_QNAME_LIST, false, module, context); + } + return Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(ModulesState.QNAME)) + .withChild(ImmutableNodes.leafNode(IetfYangLibrary.MODULE_SET_ID_LEAF_QNAME, moduleSetId)) + .withChild(mapBuilder.build()).build(); + } + + + /** + * Map data by the specific module or submodule. + * + * @param mapBuilder ordered list builder for children + * @param mapQName QName corresponding to the list builder + * @param isSubmodule true if module is specified as submodule, false otherwise + * @param module specific module or submodule + * @param context schema context + */ + private static void fillMapByModules(final CollectionNodeBuilder mapBuilder, + final QName mapQName, final boolean isSubmodule, final ModuleLike module, final SchemaContext context) { + final DataContainerNodeBuilder mapEntryBuilder = + newCommonLeafsMapEntryBuilder(mapQName, module); + + mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_SCHEMA_LEAF_QNAME, + IetfYangLibrary.BASE_URI_OF_SCHEMA + module.getName() + "/" + // FIXME: orElse(null) does not seem appropriate here + + module.getQNameModule().getRevision().map(Revision::toString).orElse(null))); + + if (!isSubmodule) { + mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAMESPACE_LEAF_QNAME, + module.getNamespace().toString())); + + // features - not mandatory + if (module.getFeatures() != null && !module.getFeatures().isEmpty()) { + addFeatureLeafList(mapEntryBuilder, module.getFeatures()); + } + // deviations - not mandatory + final ConformanceType conformance; + if (module.getDeviations() != null && !module.getDeviations().isEmpty()) { + addDeviationList(module, mapEntryBuilder, context); + conformance = ConformanceType.Implement; + } else { + conformance = ConformanceType.Import; + } + mapEntryBuilder.withChild( + ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_CONFORMANCE_LEAF_QNAME, conformance.getName())); + + // submodules - not mandatory + if (module.getSubmodules() != null && !module.getSubmodules().isEmpty()) { + addSubmodules(module, mapEntryBuilder, context); + } + } + mapBuilder.withChild(mapEntryBuilder.build()); + } + + /** + * Mapping submodules of specific module. + * + * @param module module with submodules + * @param mapEntryBuilder mapEntryBuilder of parent for mapping children + * @param ietfYangLibraryModule ietf-yang-library module + * @param context schema context + */ + private static void addSubmodules(final ModuleLike module, + final DataContainerNodeBuilder mapEntryBuilder, + final SchemaContext context) { + final CollectionNodeBuilder mapBuilder = Builders.orderedMapBuilder() + .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME)); + + for (final Submodule submodule : module.getSubmodules()) { + fillMapByModules(mapBuilder, IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME, true, submodule, + context); + } + mapEntryBuilder.withChild(mapBuilder.build()); + } + + /** + * Mapping deviations of specific module. + * + * @param module + * module with deviations + * @param mapEntryBuilder + * mapEntryBuilder of parent for mapping children + * @param context + * schema context + */ + private static void addDeviationList(final ModuleLike module, + final DataContainerNodeBuilder mapEntryBuilder, + final SchemaContext context) { + final CollectionNodeBuilder deviations = Builders.mapBuilder() + .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME)); + for (final Deviation deviation : module.getDeviations()) { + final List ids = deviation.getTargetPath().getNodeIdentifiers(); + final QName lastComponent = ids.get(ids.size() - 1); + + deviations.withChild(newCommonLeafsMapEntryBuilder(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME, + context.findModule(lastComponent.getModule()).get()) + .build()); + } + mapEntryBuilder.withChild(deviations.build()); + } + + /** + * Mapping features of specific module. + * + * @param mapEntryBuilder mapEntryBuilder of parent for mapping children + * @param features features of specific module + */ + private static void addFeatureLeafList( + final DataContainerNodeBuilder mapEntryBuilder, + final Collection features) { + final ListNodeBuilder> leafSetBuilder = Builders.leafSetBuilder() + .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_FEATURE_LEAF_LIST_QNAME)); + for (final FeatureDefinition feature : features) { + leafSetBuilder.withChildValue(feature.getQName().getLocalName()); + } + mapEntryBuilder.withChild(leafSetBuilder.build()); + } + + private static DataContainerNodeBuilder newCommonLeafsMapEntryBuilder( + final QName qname, final ModuleLike module) { + final var name = module.getName(); + final var revision = module.getQNameModule().getRevision().map(Revision::toString).orElse(""); + return Builders.mapEntryBuilder() + .withNodeIdentifier(NodeIdentifierWithPredicates.of(qname, Map.of( + IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name, + IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision))) + .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name)) + .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision)); + } } diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtil.java index 80c875dd70..0ec9ecd07f 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtil.java @@ -16,45 +16,28 @@ import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Collection; -import java.util.List; -import java.util.Map; import java.util.Optional; import org.opendaylight.restconf.common.errors.RestconfDocumentedException; import org.opendaylight.restconf.nb.rfc8040.Rfc8040; -import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.RestconfState; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.Capabilities; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.streams.Stream; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.streams.stream.Access; -import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState; -import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.Module.ConformanceType; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.Revision; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode; -import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode; -import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode; -import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder; import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; -import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; -import org.opendaylight.yangtools.yang.model.api.Deviation; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; -import org.opendaylight.yangtools.yang.model.api.FeatureDefinition; import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.ModuleLike; import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; -import org.opendaylight.yangtools.yang.model.api.Submodule; /** * Util class for mapping nodes. @@ -78,158 +61,6 @@ public final class RestconfMappingNodeUtil { // Hidden on purpose } - /** - * Map data from modules to {@link NormalizedNode}. - * - * @param modules modules for mapping - * @param context schema context - * @param moduleSetId module-set-id of actual set - * @return mapped data as {@link NormalizedNode} - */ - public static ContainerNode mapModulesByIetfYangLibraryYang(final Collection modules, - final SchemaContext context, final String moduleSetId) { - final CollectionNodeBuilder mapBuilder = Builders.orderedMapBuilder() - .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.MODULE_QNAME_LIST)); - for (final Module module : context.getModules()) { - fillMapByModules(mapBuilder, IetfYangLibrary.MODULE_QNAME_LIST, false, module, context); - } - return Builders.containerBuilder() - .withNodeIdentifier(new NodeIdentifier(ModulesState.QNAME)) - .withChild(ImmutableNodes.leafNode(IetfYangLibrary.MODULE_SET_ID_LEAF_QNAME, moduleSetId)) - .withChild(mapBuilder.build()).build(); - } - - /** - * Map data by the specific module or submodule. - * - * @param mapBuilder - * ordered list builder for children - * @param mapQName - * QName corresponding to the list builder - * @param isSubmodule - * true if module is specified as submodule, false otherwise - * @param module - * specific module or submodule - * @param context - * schema context - */ - private static void fillMapByModules(final CollectionNodeBuilder mapBuilder, - final QName mapQName, final boolean isSubmodule, final ModuleLike module, final SchemaContext context) { - final DataContainerNodeBuilder mapEntryBuilder = - newCommonLeafsMapEntryBuilder(mapQName, module); - - mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_SCHEMA_LEAF_QNAME, - IetfYangLibrary.BASE_URI_OF_SCHEMA + module.getName() + "/" - // FIXME: orElse(null) does not seem appropriate here - + module.getQNameModule().getRevision().map(Revision::toString).orElse(null))); - - if (!isSubmodule) { - mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAMESPACE_LEAF_QNAME, - module.getNamespace().toString())); - - // features - not mandatory - if (module.getFeatures() != null && !module.getFeatures().isEmpty()) { - addFeatureLeafList(mapEntryBuilder, module.getFeatures()); - } - // deviations - not mandatory - final ConformanceType conformance; - if (module.getDeviations() != null && !module.getDeviations().isEmpty()) { - addDeviationList(module, mapEntryBuilder, context); - conformance = ConformanceType.Implement; - } else { - conformance = ConformanceType.Import; - } - mapEntryBuilder.withChild( - ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_CONFORMANCE_LEAF_QNAME, conformance.getName())); - - // submodules - not mandatory - if (module.getSubmodules() != null && !module.getSubmodules().isEmpty()) { - addSubmodules(module, mapEntryBuilder, context); - } - } - mapBuilder.withChild(mapEntryBuilder.build()); - } - - /** - * Mapping submodules of specific module. - * - * @param module - * module with submodules - * @param mapEntryBuilder - * mapEntryBuilder of parent for mapping children - * @param ietfYangLibraryModule - * ietf-yang-library module - * @param context - * schema context - */ - private static void addSubmodules(final ModuleLike module, - final DataContainerNodeBuilder mapEntryBuilder, - final SchemaContext context) { - final CollectionNodeBuilder mapBuilder = Builders.orderedMapBuilder() - .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME)); - - for (final Submodule submodule : module.getSubmodules()) { - fillMapByModules(mapBuilder, IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME, true, submodule, - context); - } - mapEntryBuilder.withChild(mapBuilder.build()); - } - - /** - * Mapping deviations of specific module. - * - * @param module - * module with deviations - * @param mapEntryBuilder - * mapEntryBuilder of parent for mapping children - * @param context - * schema context - */ - private static void addDeviationList(final ModuleLike module, - final DataContainerNodeBuilder mapEntryBuilder, - final SchemaContext context) { - final CollectionNodeBuilder deviations = Builders.mapBuilder() - .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME)); - for (final Deviation deviation : module.getDeviations()) { - final List ids = deviation.getTargetPath().getNodeIdentifiers(); - final QName lastComponent = ids.get(ids.size() - 1); - - deviations.withChild(newCommonLeafsMapEntryBuilder(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME, - context.findModule(lastComponent.getModule()).get()) - .build()); - } - mapEntryBuilder.withChild(deviations.build()); - } - - /** - * Mapping features of specific module. - * - * @param mapEntryBuilder mapEntryBuilder of parent for mapping children - * @param features features of specific module - */ - private static void addFeatureLeafList( - final DataContainerNodeBuilder mapEntryBuilder, - final Collection features) { - final ListNodeBuilder> leafSetBuilder = Builders.leafSetBuilder() - .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_FEATURE_LEAF_LIST_QNAME)); - for (final FeatureDefinition feature : features) { - leafSetBuilder.withChildValue(feature.getQName().getLocalName()); - } - mapEntryBuilder.withChild(leafSetBuilder.build()); - } - - private static DataContainerNodeBuilder newCommonLeafsMapEntryBuilder( - final QName qname, final ModuleLike module) { - final var name = module.getName(); - final var revision = module.getQNameModule().getRevision().map(Revision::toString).orElse(""); - return Builders.mapEntryBuilder() - .withNodeIdentifier(NodeIdentifierWithPredicates.of(qname, Map.of( - IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name, - IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision))) - .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name)) - .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision)); - } - /** * Map capabilites by ietf-restconf-monitoring. * diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtilTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtilTest.java index 56aa718dc2..00d67e1fa3 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtilTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/utils/mapping/RestconfMappingNodeUtilTest.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.opendaylight.restconf.nb.rfc8040.Rfc8040; import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary; import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils; +import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler; import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.RestconfState; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.Capabilities; @@ -74,7 +75,7 @@ public class RestconfMappingNodeUtilTest { @Test public void restconfMappingNodeTest() { // write modules into list module in Restconf - final ContainerNode mods = RestconfMappingNodeUtil.mapModulesByIetfYangLibraryYang( + final ContainerNode mods = SchemaContextHandler.mapModulesByIetfYangLibraryYang( RestconfMappingNodeUtilTest.modules, schemaContext, "1"); // verify loaded modules -- 2.36.6