Use YANG-derived prefixes 30/108030/15
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 28 Sep 2023 20:33:47 +0000 (22:33 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 29 Sep 2023 10:57:28 +0000 (12:57 +0200)
All XML examples starting from RFC6020 onwards use a module's prefix to
identify the module.

This is not something that is universally feasible to do, as the
document context may indicate a different namespace mapping, which may
be more efficient to use.

Such assignments may also conflict with already-assigned prefixes and
redefining XML namespaces increases cognitive load in analysis -- hence
we have to avoid that.

Also prefix assignments are not a global namespace, so we could easily
end up with ambigous mappings. This is unlikely, but it can easily
happen.

With an EffectiveModelContext we can perform analysis on what is valid
within that enclosed world and use that as a guide. When
NamespacePrefixes decides current RandomPrefix (which is not random at
all!) does not have a mapping, we can consult an analysis object to give
us guidance.

This patch renames RandomPrefix to 'NamespacePrefixes' and introduces
PreferredPrefixes, which can be computed from an EffectiveModelContext.

These two then act in unison to assign prefixes based on prefix
statements present in modules forming an EffectiveModelContext whenever
we need to do such a thing.

JIRA: YANGTOOLS-1544
Change-Id: Iafc5806f53d8927e46f059287e2ae3ed2c77ff26
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
16 files changed:
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/IdentityrefXmlCodec.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/InstanceIdentifierSerializer.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/InstanceIdentifierXmlCodec.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/NamespacePrefixes.java [moved from codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefix.java with 54% similarity]
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/PreferredPrefixes.java [new file with mode: 0644]
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemaAwareXMLStreamNormalizedNodeStreamWriter.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemaAwareXMLStreamWriterUtils.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemalessXMLStreamNormalizedNodeStreamWriter.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/StreamWriterFacade.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XMLStreamNormalizedNodeStreamWriter.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlCodecFactory.java
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NamespacePrefixesTest.java [moved from codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixTest.java with 66% similarity]
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/PreferredPrefixesTest.java [new file with mode: 0644]
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStreamUtilsTest.java
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1473Test.java
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1543Test.java

index fedb06261aecaaba271505e3bc26a7b9a030fbb7..6b106af50a2e7bad8cebdf409c08fa80e9e58479 100644 (file)
@@ -24,10 +24,13 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 final class IdentityrefXmlCodec implements XmlCodec<QName> {
     private final @NonNull EffectiveModelContext context;
+    private final @NonNull PreferredPrefixes pref;
     private final @NonNull QNameModule parentModule;
 
-    IdentityrefXmlCodec(final EffectiveModelContext context, final QNameModule parentModule) {
+    IdentityrefXmlCodec(final EffectiveModelContext context, final PreferredPrefixes pref,
+            final QNameModule parentModule) {
         this.context = requireNonNull(context);
+        this.pref = requireNonNull(pref);
         this.parentModule = requireNonNull(parentModule);
     }
 
@@ -55,10 +58,10 @@ final class IdentityrefXmlCodec implements XmlCodec<QName> {
 
     @Override
     public void writeValue(final XMLStreamWriter ctx, final QName value) throws XMLStreamException {
-        final var prefixes = new RandomPrefix(ctx.getNamespaceContext());
+        final var prefixes = new NamespacePrefixes(pref, ctx.getNamespaceContext());
         final var str = QNameCodecUtil.encodeQName(value, uri -> prefixes.encodePrefix(uri.getNamespace()));
 
-        for (var e : prefixes.getPrefixes()) {
+        for (var e : prefixes.emittedPrefixes()) {
             ctx.writeNamespace(e.getValue(), e.getKey().toString());
         }
         ctx.writeCharacters(str);
index 81a2cc36e8efbde56e652a67143c654244dbd827..36ded53757c9aa2e9a0906f35ad1c950b3dc7bef 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.yangtools.yang.data.codec.xml;
 
+import java.util.List;
 import java.util.Map.Entry;
 import javax.xml.namespace.NamespaceContext;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
@@ -14,15 +15,16 @@ import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.Module;
 
 final class InstanceIdentifierSerializer extends AbstractInstanceIdentifierCodec {
-    private final RandomPrefix prefixes;
+    private final NamespacePrefixes prefixes;
 
-    InstanceIdentifierSerializer(final DataSchemaContextTree dataContextTree, final NamespaceContext nsContext) {
+    InstanceIdentifierSerializer(final DataSchemaContextTree dataContextTree, final PreferredPrefixes pref,
+            final NamespaceContext nsContext) {
         super(dataContextTree);
-        prefixes = new RandomPrefix(nsContext);
+        prefixes = new NamespacePrefixes(pref, nsContext);
     }
 
-    Iterable<Entry<XMLNamespace, String>> getPrefixes() {
-        return prefixes.getPrefixes();
+    List<Entry<XMLNamespace, String>> emittedPrefixes() {
+        return prefixes.emittedPrefixes();
     }
 
     @Override
index 72d5c7fce3e22b8c0077eb704361fd9665ee5780..f803f5483a233920df2eae0942607e1310d08b0f 100644 (file)
@@ -19,9 +19,11 @@ import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 final class InstanceIdentifierXmlCodec implements XmlCodec<YangInstanceIdentifier> {
     private final @NonNull XmlCodecFactory codecFactory;
     private final DataSchemaContextTree dataContextTree;
+    private final PreferredPrefixes pref;
 
-    InstanceIdentifierXmlCodec(final XmlCodecFactory codecFactory) {
+    InstanceIdentifierXmlCodec(final XmlCodecFactory codecFactory, final PreferredPrefixes pref) {
         this.codecFactory = requireNonNull(codecFactory);
+        this.pref = requireNonNull(pref);
         dataContextTree = DataSchemaContextTree.from(codecFactory.getEffectiveModelContext());
     }
 
@@ -38,7 +40,7 @@ final class InstanceIdentifierXmlCodec implements XmlCodec<YangInstanceIdentifie
 
     @Override
     public void writeValue(final XMLStreamWriter ctx, final YangInstanceIdentifier value) throws XMLStreamException {
-        final var serializer = new InstanceIdentifierSerializer(dataContextTree, ctx.getNamespaceContext());
+        final var serializer = new InstanceIdentifierSerializer(dataContextTree, pref, ctx.getNamespaceContext());
 
         final String str;
         try {
@@ -46,7 +48,7 @@ final class InstanceIdentifierXmlCodec implements XmlCodec<YangInstanceIdentifie
         } catch (IllegalArgumentException e) {
             throw new XMLStreamException("Failed to encode instance-identifier", e);
         }
-        for (var entry : serializer.getPrefixes()) {
+        for (var entry : serializer.emittedPrefixes()) {
             ctx.writeNamespace(entry.getValue(), entry.getKey().toString());
         }
         ctx.writeCharacters(str);
similarity index 54%
rename from codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefix.java
rename to codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/NamespacePrefixes.java
index bc3213b9e9a5aa69ed8793b61a9f809bd2204daa..9e029757389873dbf36eec9889abe4cbf4fb8703 100644 (file)
@@ -7,38 +7,53 @@
  */
 package org.opendaylight.yangtools.yang.data.codec.xml;
 
-import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
+import java.util.Comparator;
+import java.util.List;
 import java.util.Map.Entry;
+import java.util.stream.Collectors;
 import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Mutable;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 
-class RandomPrefix {
+/**
+ * Interface to prefix assignment based on a {@link NamespaceContext} and advice from {@link PreferredPrefixes}.
+ */
+final class NamespacePrefixes implements Mutable {
     // 32 characters, carefully chosen
-    private static final String LOOKUP = "abcdefghiknoprstABCDEFGHIKNOPRST";
+    @VisibleForTesting
+    static final String LOOKUP = "abcdefghiknoprstABCDEFGHIKNOPRST";
+    @VisibleForTesting
+    static final int SHIFT = 5;
     private static final int MASK = 0x1f;
-    private static final int SHIFT = 5;
 
     private int counter = 0;
 
     // BiMap to make values lookup faster
-    private final BiMap<XMLNamespace, String> prefixes = HashBiMap.create();
+    private final BiMap<XMLNamespace, String> emittedPrefixes = HashBiMap.create();
+    private final PreferredPrefixes pref;
     private final NamespaceContext context;
 
-    RandomPrefix(final NamespaceContext context) {
+    NamespacePrefixes(final PreferredPrefixes pref, final NamespaceContext context) {
+        this.pref = requireNonNull(pref);
         this.context = context;
     }
 
-    Iterable<Entry<XMLNamespace, String>> getPrefixes() {
-        return prefixes.entrySet();
+    List<Entry<XMLNamespace, String>> emittedPrefixes() {
+        return emittedPrefixes.entrySet().stream()
+            // Order by prefix
+            .sorted(Comparator.comparing(Entry::getValue))
+            .collect(Collectors.toList());
     }
 
-    String encodePrefix(final XMLNamespace namespace) {
-        String prefix = prefixes.get(namespace);
+    @NonNull String encodePrefix(final XMLNamespace namespace) {
+        var prefix = emittedPrefixes.get(namespace);
         if (prefix != null) {
             return prefix;
         }
@@ -50,12 +65,16 @@ class RandomPrefix {
             }
         }
 
-        do {
-            prefix = encode(counter);
-            counter++;
-        } while (alreadyUsedPrefix(prefix));
+        prefix = pref.prefixForNamespace(namespace);
+        if (prefix == null) {
+            prefix = encode(counter++);
+        }
+
+        while (alreadyUsedPrefix(prefix)) {
+            prefix = encode(counter++);
+        }
 
-        prefixes.put(namespace, prefix);
+        emittedPrefixes.put(namespace, prefix);
         return prefix;
     }
 
@@ -66,25 +85,13 @@ class RandomPrefix {
 
         // It seems JDK8 is violating the API contract of NamespaceContext by returning null for unbound prefixes,
         // rather than specified NULL_NS_URI. Work this around by checking explicitly for null.
-        final String str = context.getNamespaceURI(prefix);
+        final var str = context.getNamespaceURI(prefix);
         return str != null && !XMLConstants.NULL_NS_URI.equals(str);
     }
 
     @VisibleForTesting
-    static int decode(final String str) {
-        int ret = 0;
-        for (char c : str.toCharArray()) {
-            int idx = LOOKUP.indexOf(c);
-            checkArgument(idx != -1, "Invalid string %s", str);
-            ret = (ret << SHIFT) + idx;
-        }
-
-        return ret;
-    }
-
-    @VisibleForTesting
-    static String encode(int num) {
-        final StringBuilder sb = new StringBuilder();
+    static @NonNull String encode(int num) {
+        final var sb = new StringBuilder();
 
         do {
             sb.append(LOOKUP.charAt(num & MASK));
diff --git a/codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/PreferredPrefixes.java b/codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/PreferredPrefixes.java
new file mode 100644 (file)
index 0000000..ddf3551
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2023 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.yangtools.yang.data.codec.xml;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+
+/**
+ * Prefixes preferred by an {@link EffectiveModelContext}. This acts as an advisory to {@link NamespacePrefixes} for
+ * picking namespace prefixes. This works with IETF guidelines, which prefer XML prefix names coming from {@code prefix}
+ * statement's argument. This, unfortunately, is not sufficient, as these are not guaranteed to be unique, but we deal
+ * with those ambiguities.
+ */
+abstract sealed class PreferredPrefixes {
+    private static final class Precomputed extends PreferredPrefixes {
+        static final @NonNull Precomputed EMPTY = new Precomputed(Map.of());
+
+        private final Map<XMLNamespace, String> mappings;
+
+        Precomputed(final Map<XMLNamespace, String> mappings) {
+            this.mappings = requireNonNull(mappings);
+        }
+
+        @Override
+        String prefixForNamespace(final XMLNamespace namespace) {
+            return mappings.get(namespace);
+        }
+
+        @Override
+        Map<XMLNamespace, ?> mappings() {
+            return mappings;
+        }
+    }
+
+    static final class Shared extends PreferredPrefixes {
+        private final ConcurrentMap<XMLNamespace, Optional<String>> mappings = new ConcurrentHashMap<>();
+        private final EffectiveModelContext modelContext;
+
+        Shared(final EffectiveModelContext modelContext) {
+            this.modelContext = requireNonNull(modelContext);
+        }
+
+        @Override
+        String prefixForNamespace(final XMLNamespace namespace) {
+            final var existing = mappings.get(namespace);
+            if (existing != null) {
+                return existing.orElse(null);
+            }
+
+            final var modules = modelContext.findModuleStatements(namespace).iterator();
+            // Note: we are not caching anything if we do not find the module
+            return modules.hasNext() ? loadPrefix(namespace, modules.next().prefix().argument()) : null;
+        }
+
+        /**
+         * Completely populate known mappings and return an optimized version equivalent of this object.
+         *
+         * @return A pre-computed {@link PreferredPrefixes} instance
+         */
+        @NonNull PreferredPrefixes toPrecomputed() {
+            for (var module : modelContext.getModuleStatements().values()) {
+                prefixForNamespace(module.namespace().argument());
+            }
+            return new Precomputed(Map.copyOf(
+                Maps.transformValues(Maps.filterValues(mappings, Optional::isPresent), Optional::orElseThrow)));
+        }
+
+        @Override
+        Map<XMLNamespace, ?> mappings() {
+            return mappings;
+        }
+
+        private @Nullable String loadPrefix(final XMLNamespace namespace, final String prefix) {
+            final var mapping = isValidMapping(namespace, prefix) ? Optional.of(prefix) : Optional.<String>empty();
+            final var raced = mappings.putIfAbsent(namespace, mapping);
+            return (raced != null ? raced : mapping).orElse(null);
+        }
+
+        // Validate that all modules which have the same prefix have also the name namespace
+        private boolean isValidMapping(final XMLNamespace namespace, final String prefix) {
+            if (startsWithXml(prefix)) {
+                return false;
+            }
+            for (var module : modelContext.getModuleStatements().values()) {
+                if (prefix.equals(module.prefix().argument()) && !namespace.equals(module.namespace().argument())) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // https://www.w3.org/TR/xml-names/#xmlReserved
+        private static boolean startsWithXml(final String prefix) {
+            if (prefix.length() < 3) {
+                return false;
+            }
+            final var first = prefix.charAt(0);
+            if (first != 'x' && first != 'X') {
+                return false;
+            }
+            final var second = prefix.charAt(1);
+            if (second != 'm' && second != 'M') {
+                return false;
+            }
+            final var third = prefix.charAt(2);
+            return third == 'l' || third == 'L';
+        }
+    }
+
+    private PreferredPrefixes() {
+        // Hidden on purpose
+    }
+
+    static @NonNull PreferredPrefixes empty() {
+        return Precomputed.EMPTY;
+    }
+
+    abstract @Nullable String prefixForNamespace(@NonNull XMLNamespace namespace);
+
+    @Override
+    public final String toString() {
+        return MoreObjects.toStringHelper(this).add("mappings", mappings()).toString();
+    }
+
+    abstract Map<XMLNamespace, ?> mappings();
+}
index ddb85228797325595d409b5e5c42d016a27cd0dc..fe285af1a07da522f6dedf54c3920e6670c322e0 100644 (file)
@@ -39,11 +39,16 @@ final class SchemaAwareXMLStreamNormalizedNodeStreamWriter
     private final NormalizedNodeStreamWriterStack tracker;
     private final SchemaAwareXMLStreamWriterUtils streamUtils;
 
-    SchemaAwareXMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer, final EffectiveModelContext context,
-            final NormalizedNodeStreamWriterStack tracker) {
-        super(writer);
+    private SchemaAwareXMLStreamNormalizedNodeStreamWriter(final PreferredPrefixes pref, final XMLStreamWriter writer,
+            final EffectiveModelContext modelContext, final NormalizedNodeStreamWriterStack tracker) {
+        super(pref, writer);
         this.tracker = requireNonNull(tracker);
-        streamUtils = new SchemaAwareXMLStreamWriterUtils(context);
+        streamUtils = new SchemaAwareXMLStreamWriterUtils(modelContext, pref);
+    }
+
+    SchemaAwareXMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer,
+            final EffectiveModelContext modelContext, final NormalizedNodeStreamWriterStack tracker) {
+        this(new PreferredPrefixes.Shared(modelContext), writer, modelContext, tracker);
     }
 
     @Override
index af4443f8d9fbbf7d99549466420f6fea93311a57..fd7f0e313ee09d4467b77b1ad506e50e3d8d94c7 100644 (file)
@@ -17,9 +17,11 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 final class SchemaAwareXMLStreamWriterUtils extends XMLStreamWriterUtils {
     private final @NonNull EffectiveModelContext modelContext;
+    private final @NonNull PreferredPrefixes pref;
 
-    SchemaAwareXMLStreamWriterUtils(final EffectiveModelContext modelContext) {
+    SchemaAwareXMLStreamWriterUtils(final EffectiveModelContext modelContext, final PreferredPrefixes pref) {
         this.modelContext = requireNonNull(modelContext);
+        this.pref = requireNonNull(pref);
     }
 
     @NonNull EffectiveModelContext modelContext() {
@@ -29,10 +31,10 @@ final class SchemaAwareXMLStreamWriterUtils extends XMLStreamWriterUtils {
     @Override
     String encodeInstanceIdentifier(final ValueWriter writer, final YangInstanceIdentifier value)
             throws XMLStreamException {
-        final var serializer = new InstanceIdentifierSerializer(DataSchemaContextTree.from(modelContext),
+        final var serializer = new InstanceIdentifierSerializer(DataSchemaContextTree.from(modelContext), pref,
             writer.getNamespaceContext());
         final var str = serializer.serialize(value);
-        for (var entry : serializer.getPrefixes()) {
+        for (var entry : serializer.emittedPrefixes()) {
             writer.writeNamespace(entry.getValue(), entry.getKey().toString());
         }
         return str;
index 9609dc31c878d280b51f63b889278c5eed5aa085..03ca5b853de03485f1393ef2239a32d0266cfd07 100644 (file)
@@ -36,7 +36,7 @@ final class SchemalessXMLStreamNormalizedNodeStreamWriter extends XMLStreamNorma
     private final Deque<NodeType> nodeTypeStack = new ArrayDeque<>();
 
     SchemalessXMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer) {
-        super(writer);
+        super(PreferredPrefixes.empty(), writer);
     }
 
     @Override
index 8f0b84451b7b24d5a57ed4468aeb7dbdc13671a8..9482e557e19fc6a5fdaff44cfe641db92a2ece48 100644 (file)
@@ -37,15 +37,15 @@ final class StreamWriterFacade extends ValueWriter {
     private static final Set<String> LEGACY_ATTRIBUTES = ConcurrentHashMap.newKeySet();
 
     private final XMLStreamWriter writer;
-    private final RandomPrefix prefixes;
+    private final NamespacePrefixes prefixes;
 
     // QName of an element we delayed emitting. This only happens if it is a naked element, without any attributes,
     // namespace declarations or value.
     private QName openElement;
 
-    StreamWriterFacade(final XMLStreamWriter writer) {
+    StreamWriterFacade(final PreferredPrefixes pref, final XMLStreamWriter writer) {
         this.writer = requireNonNull(writer);
-        prefixes = new RandomPrefix(writer.getNamespaceContext());
+        prefixes = new NamespacePrefixes(pref, writer.getNamespaceContext());
     }
 
     void writeCharacters(final String text) throws XMLStreamException {
index 5e899ef57bba5bc6d6bda7404cc63f1fe35dafd3..cd24490928593cb6d07d628a5e4b8418a9dd9562 100644 (file)
@@ -58,8 +58,8 @@ public abstract sealed class XMLStreamNormalizedNodeStreamWriter<T>
 
     private final @NonNull StreamWriterFacade facade;
 
-    XMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer) {
-        facade = new StreamWriterFacade(writer);
+    XMLStreamNormalizedNodeStreamWriter(final PreferredPrefixes pref, final XMLStreamWriter writer) {
+        facade = new StreamWriterFacade(pref, writer);
     }
 
     /**
index aec147d45fbe5c6e3ddba7db5be8820b2abf911a..bbb5e2686d2dc75ea4068725dcb9da358b9ed005 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.data.codec.xml;
 import static java.util.Objects.requireNonNull;
 
 import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
@@ -47,13 +48,15 @@ import org.opendaylight.yangtools.yang.model.api.type.UnknownTypeDefinition;
  * A thread-safe factory for instantiating {@link XmlCodec}s.
  */
 public final class XmlCodecFactory extends AbstractCodecFactory<XmlCodec<?>> {
-    private final MountPointContext mountCtx;
-    private final InstanceIdentifierXmlCodec instanceIdentifierCodec;
+    private final @NonNull MountPointContext mountCtx;
+    private final @NonNull PreferredPrefixes pref;
+    private final @NonNull InstanceIdentifierXmlCodec instanceIdentifierCodec;
 
     private XmlCodecFactory(final MountPointContext mountCtx) {
         super(mountCtx.getEffectiveModelContext(), new SharedCodecCache<>());
         this.mountCtx = requireNonNull(mountCtx);
-        instanceIdentifierCodec = new InstanceIdentifierXmlCodec(this);
+        pref = new PreferredPrefixes.Shared(getEffectiveModelContext());
+        instanceIdentifierCodec = new InstanceIdentifierXmlCodec(this, pref);
     }
 
     MountPointContext mountPointContext() {
@@ -107,7 +110,7 @@ public final class XmlCodecFactory extends AbstractCodecFactory<XmlCodec<?>> {
 
     @Override
     protected XmlCodec<?> identityRefCodec(final IdentityrefTypeDefinition type, final QNameModule module) {
-        return new IdentityrefXmlCodec(getEffectiveModelContext(), module);
+        return new IdentityrefXmlCodec(getEffectiveModelContext(), pref, module);
     }
 
     @Override
similarity index 66%
rename from codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/RandomPrefixTest.java
rename to codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/NamespacePrefixesTest.java
index 2564c3c4d6c743fa07973420fa4be02cbdb5ff98..534a8354d8ff20ba0bdb0213618cf33dd0e0e94e 100644 (file)
@@ -15,18 +15,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import org.junit.jupiter.api.Test;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 
-class RandomPrefixTest {
+class NamespacePrefixesTest {
     static final int MAX_COUNTER = 4000;
 
     @Test
     void testEncodeDecode() {
         final var allGenerated = new ArrayList<>(MAX_COUNTER);
         for (int i = 0; i < MAX_COUNTER; i++) {
-            final var encoded = RandomPrefix.encode(i);
-            assertEquals(RandomPrefix.decode(encoded), i);
+            final var encoded = NamespacePrefixes.encode(i);
+            assertEquals(decode(encoded), i);
             allGenerated.add(encoded);
         }
 
@@ -38,17 +40,17 @@ class RandomPrefixTest {
 
     @Test
     void testQNameWithPrefix() {
-        final var a = new RandomPrefix(null);
+        final var a = new NamespacePrefixes(PreferredPrefixes.empty(), null);
 
         final var allGenerated = new ArrayList<String>();
         for (int i = 0; i < MAX_COUNTER; i++) {
-            allGenerated.add(a.encodePrefix(XMLNamespace.of("localhost:" + RandomPrefix.encode(i))));
+            allGenerated.add(a.encodePrefix(XMLNamespace.of("localhost:" + NamespacePrefixes.encode(i))));
         }
 
         assertEquals(MAX_COUNTER, allGenerated.size());
         // We are generating MAX_COUNTER_VALUE + 27 prefixes total, so we should encounter a reset in prefix a start
         // from 0 at some point. At the end, there should be only 27 values in RandomPrefix cache
-        assertEquals(MAX_COUNTER, Iterables.size(a.getPrefixes()));
+        assertEquals(MAX_COUNTER, a.emittedPrefixes().size());
         assertThat(allGenerated, not(hasItem("xml")));
         assertThat(allGenerated, not(hasItem("xmla")));
         assertThat(allGenerated, not(hasItem("xmlz")));
@@ -58,20 +60,35 @@ class RandomPrefixTest {
 
     @Test
     void test2QNames1Namespace() {
-        final var a = new RandomPrefix(null);
+        final var a = new NamespacePrefixes(PreferredPrefixes.empty(), null);
 
         final var uri = XMLNamespace.of("localhost");
 
         assertEquals(a.encodePrefix(uri), a.encodePrefix(uri));
+        assertEquals(List.of(Map.entry(uri, "a")), a.emittedPrefixes());
     }
 
     @Test
     void testQNameNoPrefix() {
-        final var a = new RandomPrefix(null);
+        final var a = new NamespacePrefixes(PreferredPrefixes.empty(), null);
 
         final var uri = XMLNamespace.of("localhost");
+        final var second = XMLNamespace.of("second");
         assertEquals("a", a.encodePrefix(uri));
         assertEquals("a", a.encodePrefix(uri));
-        assertEquals("b", a.encodePrefix(XMLNamespace.of("second")));
+        assertEquals("b", a.encodePrefix(second));
+        assertEquals(List.of(Map.entry(uri, "a"), Map.entry(second, "b")), a.emittedPrefixes());
+    }
+
+    private static int decode(final String str) {
+        int ret = 0;
+        for (char c : str.toCharArray()) {
+            int idx = NamespacePrefixes.LOOKUP.indexOf(c);
+            if (idx == -1) {
+                throw new IllegalArgumentException("Invalid string " + str);
+            }
+            ret = (ret << NamespacePrefixes.SHIFT) + idx;
+        }
+        return ret;
     }
 }
diff --git a/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/PreferredPrefixesTest.java b/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/PreferredPrefixesTest.java
new file mode 100644 (file)
index 0000000..db4c9b7
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2023 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.yangtools.yang.data.codec.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.Map;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+class PreferredPrefixesTest {
+    private static final @NonNull XMLNamespace FOONS = XMLNamespace.of("foons");
+    private static final @NonNull XMLNamespace BARNS = XMLNamespace.of("barns");
+
+    @Test
+    void ignorePrefixWhenConflicting() {
+        final var context = YangParserTestUtils.parseYang("""
+            module foo {
+              namespace foons;
+              prefix conflict;
+            }""", """
+            module bar {
+              namespace barns;
+              prefix conflict;
+            }
+            """);
+        final var prefixes = new PreferredPrefixes.Shared(context);
+        assertNull(prefixes.prefixForNamespace(FOONS));
+        assertNull(prefixes.prefixForNamespace(BARNS));
+        assertEquals(Map.of(FOONS, Optional.empty(), BARNS, Optional.empty()), prefixes.mappings());
+        assertEquals("Precomputed{mappings={}}", prefixes.toPrecomputed().toString());
+    }
+
+    @Test
+    void bindPrefixAcrossRevisions() {
+        final var context = YangParserTestUtils.parseYang("""
+            module foo {
+              namespace foons;
+              prefix f;
+            }""", """
+            module foo2 {
+              namespace foons;
+              prefix f;
+              revision 2023-09-29;
+            }
+            """);
+        final var prefixes = new PreferredPrefixes.Shared(context);
+        assertEquals("f", prefixes.prefixForNamespace(FOONS));
+        assertEquals("Shared{mappings={foons=Optional[f]}}", prefixes.toString());
+        assertEquals("Precomputed{mappings={foons=f}}", prefixes.toPrecomputed().toString());
+    }
+
+    @Test
+    void ignorePrefixWhenStartsWithXml() {
+        final var context = YangParserTestUtils.parseYang("""
+            module foo {
+              namespace foons;
+              prefix XMl;
+            }""", """
+            module bar {
+              namespace barns;
+              prefix xmLa;
+            }""", """
+            module baz {
+              namespace bazns;
+              prefix xlx;
+            }""", """
+            module qux {
+              namespace quxns;
+              prefix xmx;
+            }""", """
+            module xyzzy {
+              namespace xyzzyns;
+              prefix xyz;
+            }""");
+        assertEquals(Map.of(
+            XMLNamespace.of("bazns"), "xlx",
+            XMLNamespace.of("quxns"), "xmx",
+            XMLNamespace.of("xyzzyns"), "xyz"), new PreferredPrefixes.Shared(context).toPrecomputed().mappings());
+    }
+}
index b0fd191b3ad7d783eeb8c61718fbf9b3a0d496a8..2c512635972ce50333f5e5ef5ae0028664ab29ab 100644 (file)
@@ -47,22 +47,24 @@ public class XmlStreamUtilsTest {
         void accept(XMLStreamWriter writer) throws XMLStreamException;
     }
 
-    private static EffectiveModelContext schemaContext;
+    private static EffectiveModelContext modelContext;
     private static Module leafRefModule;
+    private static PreferredPrefixes pref;
 
     @BeforeClass
     public static void initialize() {
-        schemaContext = YangParserTestUtils.parseYangResource("/leafref-test.yang");
-        assertNotNull(schemaContext);
-        assertEquals(1, schemaContext.getModules().size());
-        leafRefModule = schemaContext.getModules().iterator().next();
+        modelContext = YangParserTestUtils.parseYangResource("/leafref-test.yang");
+        assertNotNull(modelContext);
+        assertEquals(1, modelContext.getModules().size());
+        leafRefModule = modelContext.getModules().iterator().next();
         assertNotNull(leafRefModule);
+        pref = new PreferredPrefixes.Shared(modelContext);
     }
 
     @AfterClass
     public static void cleanup() {
         leafRefModule = null;
-        schemaContext = null;
+        modelContext = null;
     }
 
     @Test
@@ -71,7 +73,7 @@ public class XmlStreamUtilsTest {
 
         String xmlAsString = createXml(writer -> {
             writer.writeStartElement("element");
-            final StreamWriterFacade facade = new StreamWriterFacade(writer);
+            final var facade = new StreamWriterFacade(pref, writer);
             facade.writeCharacters(XMLStreamWriterUtils.encode(facade, QName.create(parent, "identity"), parent));
             facade.flush();
             writer.writeEndElement();
@@ -81,7 +83,7 @@ public class XmlStreamUtilsTest {
 
         xmlAsString = createXml(writer -> {
             writer.writeStartElement("elementDifferent");
-            final StreamWriterFacade facade = new StreamWriterFacade(writer);
+            final var facade = new StreamWriterFacade(pref, writer);
             facade.writeCharacters(XMLStreamWriterUtils.encode(facade, QName.create("different:namespace", "identity"),
                 parent));
             facade.flush();
@@ -140,7 +142,7 @@ public class XmlStreamUtilsTest {
     }
 
     private static TypeDefinition<?> getTargetNodeForLeafRef(final Class<?> clas, final String... names) {
-        final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
+        final SchemaInferenceStack stack = SchemaInferenceStack.of(modelContext);
         stack.enterDataTree(QName.create(leafRefModule.getQNameModule(), "cont2"));
         for (String name : names) {
             stack.enterDataTree(QName.create(leafRefModule.getQNameModule(), name));
index 62c77e0fd2e21f18d59b0978486a0688e319db48..5c1aa55c10ff5001eb8ff4dc572d2b12d6f24266 100644 (file)
@@ -24,8 +24,6 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.yangtools.yang.common.QName;
 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.YangInstanceIdentifier.NodeWithValue;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
@@ -59,7 +57,7 @@ class YT1473Test {
         final var modelContext = YangParserTestUtils.parseYang("""
             module bar {
               namespace barns;
-              prefix bar;
+              prefix b;
 
               import foo {
                 prefix foo;
@@ -93,7 +91,7 @@ class YT1473Test {
             }""", """
             module foo {
               namespace foons;
-              prefix foo;
+              prefix f;
               identity one;
 
               typedef bitz {
@@ -148,112 +146,120 @@ class YT1473Test {
     @Test
     void testSerializeSimple() throws Exception {
         // No escaping needed, use single quotes
-        assertBar("/a:str[.='str\"']", buildYangInstanceIdentifier(BAR_STR, "str\""));
-        assertBar("/a:str[.='str\\']", buildYangInstanceIdentifier(BAR_STR, "str\\"));
-        assertBar("/a:str[.='str\r']", buildYangInstanceIdentifier(BAR_STR, "str\r"));
-        assertBar("/a:str[.='str\n']", buildYangInstanceIdentifier(BAR_STR, "str\n"));
-        assertBar("/a:str[.='str\t']", buildYangInstanceIdentifier(BAR_STR, "str\t"));
-
-        assertFoo("/a:foo[a:str='str\"\\']", buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str\"\\"));
-        assertFoo("/a:foo[a:str='str\r\n\t']", buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str\r\n\t"));
+        assertBar("/b:str[.='str\"']", createIdentifier(BAR_STR, "str\""));
+        assertBar("/b:str[.='str\\']", createIdentifier(BAR_STR, "str\\"));
+        assertBar("/b:str[.='str\r']", createIdentifier(BAR_STR, "str\r"));
+        assertBar("/b:str[.='str\n']", createIdentifier(BAR_STR, "str\n"));
+        assertBar("/b:str[.='str\t']", createIdentifier(BAR_STR, "str\t"));
+
+        assertFoo("/f:foo[f:str='str\"\\']", createIdentifier(FOO_FOO, FOO_STR, "str\"\\"));
+        assertFoo("/f:foo[f:str='str\r\n\t']", createIdentifier(FOO_FOO, FOO_STR, "str\r\n\t"));
     }
 
     @Test
     void testSerializeEscaped() throws Exception {
         // Escaping is needed, use double quotes and escape
-        assertBar("/a:str[.=\"str'\\\"\"]", buildYangInstanceIdentifier(BAR_STR, "str'\""));
-        assertBar("/a:str[.=\"str'\\n\"]", buildYangInstanceIdentifier(BAR_STR, "str'\n"));
-        assertBar("/a:str[.=\"str'\\t\"]", buildYangInstanceIdentifier(BAR_STR, "str'\t"));
-        assertBar("/a:str[.=\"str'\r\"]", buildYangInstanceIdentifier(BAR_STR, "str'\r"));
+        assertBar("/b:str[.=\"str'\\\"\"]", createIdentifier(BAR_STR, "str'\""));
+        assertBar("/b:str[.=\"str'\\n\"]", createIdentifier(BAR_STR, "str'\n"));
+        assertBar("/b:str[.=\"str'\\t\"]", createIdentifier(BAR_STR, "str'\t"));
+        assertBar("/b:str[.=\"str'\r\"]", createIdentifier(BAR_STR, "str'\r"));
 
-        assertFoo("/a:foo[a:str=\"str'\\\"\\n\"]", buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str'\"\n"));
-        assertFoo("/a:foo[a:str=\"str'\\t\r\"]", buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str'\t\r"));
+        assertFoo("/f:foo[f:str=\"str'\\\"\\n\"]", createIdentifier(FOO_FOO, FOO_STR, "str'\"\n"));
+        assertFoo("/f:foo[f:str=\"str'\\t\r\"]", createIdentifier(FOO_FOO, FOO_STR, "str'\t\r"));
     }
 
     @Test
     void testSerializeIdentity() throws Exception {
-        assertFoo("/a:bar[a:qname='a:one']", buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE));
-        assertFooBar("/a:bar[a:qname='b:two']", buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO));
+        assertFoo("/f:bar[f:qname='f:one']", createIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE));
+        assertFooBar("/f:bar[f:qname='b:two']", createIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO));
     }
 
     @Test
     void testSerializeInstanceIdentifierRef() throws Exception {
-        assertFooBar("/a:baz[a:id=\"/a:bar[a:qname='b:two']\"]",
-            buildYangInstanceIdentifier(FOO_BAZ, FOO_ID, buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
+        assertFooBar("/f:baz[f:id=\"/f:bar[f:qname='b:two']\"]",
+            createIdentifier(FOO_BAZ, FOO_ID, createIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
     }
 
     @Test
     void testSerializeIdentityValue() throws Exception {
-        assertBarFoo("/a:foo[.='b:one']", buildYangInstanceIdentifier(BAR_FOO, FOO_ONE));
-        assertBar("/a:foo[.='a:two']", buildYangInstanceIdentifier(BAR_FOO, BAR_TWO));
+        assertFooBar("/b:foo[.='f:one']", createIdentifier(BAR_FOO, FOO_ONE));
+        assertBar("/b:foo[.='b:two']", createIdentifier(BAR_FOO, BAR_TWO));
     }
 
     @Test
     void testSerializeInstanceIdentifierValue() throws Exception {
-        assertBarFoo("/a:bar[.=\"/b:bar[b:qname='b:one']\"]",
-            buildYangInstanceIdentifier(BAR_BAR, buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE)));
-        assertBarFoo("/a:bar[.=\"/b:bar[b:qname='a:two']\"]",
-            buildYangInstanceIdentifier(BAR_BAR, buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
+        assertFooBar("/b:bar[.=\"/f:bar[f:qname='f:one']\"]",
+            createIdentifier(BAR_BAR, createIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE)));
+        assertFooBar("/b:bar[.=\"/f:bar[f:qname='b:two']\"]",
+            createIdentifier(BAR_BAR, createIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
     }
 
     @Test
     void testSerializeBits() throws Exception {
-        assertFoo("/a:bee[a:bts='']", buildYangInstanceIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of()));
-        assertFoo("/a:bee[a:bts='one']", buildYangInstanceIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of("one")));
-        assertFoo("/a:bee[a:bts='two three']",
-            buildYangInstanceIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of("two", "three")));
+        assertFoo("/f:bee[f:bts='']", createIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of()));
+        assertFoo("/f:bee[f:bts='one']", createIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of("one")));
+        assertFoo("/f:bee[f:bts='two three']", createIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of("two", "three")));
     }
 
     @Test
     void testSerializeBitsValue() throws Exception {
-        assertBar("/a:bee[.='']", buildYangInstanceIdentifier(BAR_BEE, ImmutableSet.of()));
-        assertBar("/a:bee[.='one']", buildYangInstanceIdentifier(BAR_BEE, ImmutableSet.of("one")));
-        assertBar("/a:bee[.='two three']", buildYangInstanceIdentifier(BAR_BEE, ImmutableSet.of("two", "three")));
+        assertBar("/b:bee[.='']", createIdentifier(BAR_BEE, ImmutableSet.of()));
+        assertBar("/b:bee[.='one']", createIdentifier(BAR_BEE, ImmutableSet.of("one")));
+        assertBar("/b:bee[.='two three']", createIdentifier(BAR_BEE, ImmutableSet.of("two", "three")));
+    }
+
+    private static void assertBar(final String expected, final YangInstanceIdentifier id) throws Exception {
+        final var writer = mockWriter();
+        doNothing().when(writer).writeNamespace("b", "barns");
+
+        final var reader = mock(NamespaceContext.class);
+        doReturn("barns").when(reader).getNamespaceURI("b");
+        assertSerdes(reader, writer, expected, id);
     }
 
     private static void assertFoo(final String expected, final YangInstanceIdentifier id) throws Exception {
-        final var context = mock(NamespaceContext.class);
-        doReturn("foons").when(context).getNamespaceURI("a");
-        assertSerdes(context, expected, id);
+        final var writer = mockWriter();
+        doNothing().when(writer).writeNamespace("f", "foons");
+
+        final var reader = mock(NamespaceContext.class);
+        doReturn("foons").when(reader).getNamespaceURI("f");
+
+        assertSerdes(reader, writer, expected, id);
     }
 
     private static void assertFooBar(final String expected, final YangInstanceIdentifier id) throws Exception {
-        final var context = mock(NamespaceContext.class);
-        doReturn("foons").when(context).getNamespaceURI("a");
-        doReturn("barns").when(context).getNamespaceURI("b");
-        assertSerdes(context, expected, id);
-    }
+        final var writer = mockWriter();
+        doNothing().when(writer).writeNamespace("f", "foons");
+        doNothing().when(writer).writeNamespace("b", "barns");
 
-    private static void assertBar(final String expected, final YangInstanceIdentifier id) throws Exception {
-        final var context = mock(NamespaceContext.class);
-        doReturn("barns").when(context).getNamespaceURI("a");
-        assertSerdes(context, expected, id);
-    }
+        final var reader = mock(NamespaceContext.class);
+        doReturn("foons").when(reader).getNamespaceURI("f");
+        doReturn("barns").when(reader).getNamespaceURI("b");
 
-    private static void assertBarFoo(final String expected, final YangInstanceIdentifier id) throws Exception {
-        final var context = mock(NamespaceContext.class);
-        doReturn("barns").when(context).getNamespaceURI("a");
-        doReturn("foons").when(context).getNamespaceURI("b");
-        assertSerdes(context, expected, id);
+        assertSerdes(reader, writer, expected, id);
     }
 
-    private static void assertSerdes(final NamespaceContext context, final String expected,
-            final YangInstanceIdentifier id) throws Exception {
-        final var writer = mock(XMLStreamWriter.class);
+    private static void assertSerdes(final NamespaceContext reader, final XMLStreamWriter writer,
+            final String expected, final YangInstanceIdentifier id) throws Exception {
+        assertEquals(id, CODEC.parseValue(reader, expected));
+
         final var captor = ArgumentCaptor.forClass(String.class);
         doNothing().when(writer).writeCharacters(captor.capture());
         CODEC.writeValue(writer, id);
         assertEquals(expected, captor.getValue());
+    }
 
-        assertEquals(id, CODEC.parseValue(context, expected));
+    private static XMLStreamWriter mockWriter() {
+        final var writer = mock(XMLStreamWriter.class);
+        doReturn(null).when(writer).getNamespaceContext();
+        return writer;
     }
 
-    private static YangInstanceIdentifier buildYangInstanceIdentifier(final QName node, final QName key,
-            final Object value) {
-        return YangInstanceIdentifier.of(new NodeIdentifier(node), NodeIdentifierWithPredicates.of(node, key, value));
+    private static YangInstanceIdentifier createIdentifier(final QName node, final QName key, final Object value) {
+        return YangInstanceIdentifier.builder().node(node).nodeWithKey(node, key, value).build();
     }
 
-    private static YangInstanceIdentifier buildYangInstanceIdentifier(final QName node, final Object value) {
-        return YangInstanceIdentifier.of(new NodeIdentifier(node), new NodeWithValue<>(node, value));
+    private static YangInstanceIdentifier createIdentifier(final QName node, final Object value) {
+        return YangInstanceIdentifier.builder().node(node).node(new NodeWithValue<>(node, value)).build();
     }
 }
index 0213590a69026856bfcd02f0495096530787f57d..4cd307d01e5ba797c34814143c19f8a04a794a15 100644 (file)
@@ -36,7 +36,7 @@ class YT1543Test {
         MODEL_CONTEXT = YangParserTestUtils.parseYang("""
             module foo {
               namespace foons;
-              prefix foop;
+              prefix fo;
 
               container foo {
                 leaf leaf {
@@ -46,7 +46,7 @@ class YT1543Test {
             }""", """
             module bar {
               namespace barns;
-              prefix barp;
+              prefix br;
 
               list bar {
                 key key;
@@ -57,7 +57,7 @@ class YT1543Test {
             }""", """
             module baz {
               namespace bazns;
-              prefix bazp;
+              prefix bz;
 
               container baz;
             }""");
@@ -79,7 +79,7 @@ class YT1543Test {
         }
 
         assertEquals("""
-            <foo xmlns="foons"><leaf xmlns:a="barns" xmlns:b="bazns">/a:bar[a:key='/b:baz']</leaf></foo>""",
+            <foo xmlns="foons"><leaf xmlns:br="barns" xmlns:bz="bazns">/br:bar[br:key='/bz:baz']</leaf></foo>""",
             stringWriter.toString());
     }
 
@@ -97,7 +97,7 @@ class YT1543Test {
         xmlWriter.close();
 
         assertEquals("""
-            <foo xmlns="foons"><leaf xmlns:a="barns" xmlns:b="bazns">/a:bar[a:key='/b:baz']</leaf></foo>""",
+            <foo xmlns="foons"><leaf xmlns:br="barns" xmlns:bz="bazns">/br:bar[br:key='/bz:baz']</leaf></foo>""",
             stringWriter.toString());
     }
 }