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);
}
@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);
*/
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;
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
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());
}
@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 {
} 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);
*/
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;
}
}
}
- 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;
}
// 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));
--- /dev/null
+/*
+ * 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();
+}
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
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() {
@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;
private final Deque<NodeType> nodeTypeStack = new ArrayDeque<>();
SchemalessXMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer) {
- super(writer);
+ super(PreferredPrefixes.empty(), writer);
}
@Override
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 {
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);
}
/**
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;
* 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() {
@Override
protected XmlCodec<?> identityRefCodec(final IdentityrefTypeDefinition type, final QNameModule module) {
- return new IdentityrefXmlCodec(getEffectiveModelContext(), module);
+ return new IdentityrefXmlCodec(getEffectiveModelContext(), pref, module);
}
@Override
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);
}
@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")));
@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;
}
}
--- /dev/null
+/*
+ * 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());
+ }
+}
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
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();
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();
}
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));
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;
final var modelContext = YangParserTestUtils.parseYang("""
module bar {
namespace barns;
- prefix bar;
+ prefix b;
import foo {
prefix foo;
}""", """
module foo {
namespace foons;
- prefix foo;
+ prefix f;
identity one;
typedef bitz {
@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();
}
}
MODEL_CONTEXT = YangParserTestUtils.parseYang("""
module foo {
namespace foons;
- prefix foop;
+ prefix fo;
container foo {
leaf leaf {
}""", """
module bar {
namespace barns;
- prefix barp;
+ prefix br;
list bar {
key key;
}""", """
module baz {
namespace bazns;
- prefix bazp;
+ prefix bz;
container baz;
}""");
}
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());
}
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());
}
}