--- /dev/null
+/*
+ * Copyright (c) 2019 Pantheon Technologies, s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableBiMap.Builder;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import org.opendaylight.yangtools.concepts.WritableObject;
+
+/**
+ * A BiMap-based implementation of {@link YangNamespaceContext}. This implementation requires the default namespace
+ * to be present.
+ *
+ */
+@Beta
+public final class BiMapYangNamespaceContext implements YangNamespaceContext, WritableObject {
+ private static final long serialVersionUID = 1L;
+
+ private final ImmutableBiMap<String, QNameModule> mapping;
+ private final QNameModule defaultNamespace;
+
+ public BiMapYangNamespaceContext(final QNameModule defaultNamespace, final BiMap<String, QNameModule> mapping) {
+ this.defaultNamespace = requireNonNull(defaultNamespace);
+ checkArgument(mapping.containsValue(defaultNamespace), "Mapping %s does not contain default namespace %s",
+ mapping, defaultNamespace);
+ this.mapping = ImmutableBiMap.copyOf(mapping);
+ }
+
+ @Override
+ public Optional<QNameModule> getDefaultNamespace() {
+ return Optional.of(defaultNamespace);
+ }
+
+ @Override
+ public Optional<QNameModule> findNamespaceForPrefix(final String prefix) {
+ return Optional.ofNullable(mapping.get(requireNonNull(prefix)));
+ }
+
+ @Override
+ public Optional<String> findPrefixForNamespace(final QNameModule namespace) {
+ return Optional.ofNullable(mapping.inverse().get(requireNonNull(namespace)));
+ }
+
+ @Override
+ public void writeTo(final DataOutput out) throws IOException {
+ defaultNamespace.writeTo(out);
+ out.writeInt(mapping.size());
+ for (Entry<String, QNameModule> entry : mapping.entrySet()) {
+ out.writeUTF(entry.getKey());
+ entry.getValue().writeTo(out);
+ }
+ }
+
+ public static BiMapYangNamespaceContext readFrom(final DataInput in) throws IOException {
+ final QNameModule defaultNamespace = QNameModule.readFrom(in);
+ final int size = in.readInt();
+ final Builder<String, QNameModule> builder = ImmutableBiMap.builder();
+ for (int i = 0; i < size; ++i) {
+ final String prefix = in.readUTF();
+ final QNameModule namespace = QNameModule.readFrom(in);
+ builder.put(prefix, namespace);
+ }
+
+ return new BiMapYangNamespaceContext(defaultNamespace, builder.build());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(defaultNamespace, mapping);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BiMapYangNamespaceContext)) {
+ return false;
+ }
+ final BiMapYangNamespaceContext other = (BiMapYangNamespaceContext) obj;
+ return defaultNamespace.equals(other.defaultNamespace) && mapping.equals(other.mapping);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("defaultNamespace", defaultNamespace).add("mapping", mapping)
+ .toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2019 Pantheon Technologies, s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import java.io.Serializable;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Interface for mapping between {@link String} prefixes and {@link QNameModule} namespaces. The conceptual model
+ * matches prefix mapping inside a YANG {@code module} as defined through the use of {@code prefix} and {@code import}
+ * statements and detailed in <a href="https://tools.ietf.org/html/rfc7950#section-7.1.4">RFC7950 Section 7.1.4</a>.
+ *
+ * <p>
+ * Each namespace context can have a default namespace and a set of prefix/namespace mappings. A namespace can be bound
+ * to multiple prefixes at the same time. The default namespace must also have a prefix assigned.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public interface YangNamespaceContext extends Immutable, Serializable {
+ /**
+ * Return the default namespace in this context.
+ *
+ * @return Default namespace, if supported.
+ */
+ @NonNull Optional<QNameModule> getDefaultNamespace();
+
+ /**
+ * Return QNameModule to which a particular prefix is bound.
+ *
+ * @param prefix Prefix to look up
+ * @return QNameModule bound to specified prefix
+ * @throws NullPointerException if {@code prefix} is null
+ */
+ @NonNull Optional<QNameModule> findNamespaceForPrefix(String prefix);
+
+ /**
+ * Return a prefix to which a particular QNameModule is bound. If a namespace is bound to multiple prefixes, it is
+ * left unspecified which of those prefixes is returned.
+ *
+ * @param namespace QNameModule to look up
+ * @return Prefix to which the QNameModule is bound
+ * @throws NullPointerException if {@code module} is null
+ */
+ @NonNull Optional<String> findPrefixForNamespace(QNameModule namespace);
+
+ /**
+ * Create a {@link QName} in the default namespace.
+ *
+ * @param localName QName local name
+ * @return A QName.
+ * @throws NullPointerException if {@code localName} is null
+ * @throws IllegalArgumentException if {@code localName} does not conform to local name requirements
+ * @throws IllegalStateException if this context does not have default namespace
+ */
+ default @NonNull QName createQName(final String localName) {
+ final Optional<QNameModule> namespace = getDefaultNamespace();
+ checkState(namespace.isPresent(), "%s does not have a default namespace", this);
+ return QName.create(namespace.get(), requireNonNull(localName));
+ }
+
+ /**
+ * Create a {@link QName} by resolving a prefix against currently-bound prefixes and combining it with specified
+ * local name.
+ *
+ * @param prefix Namespace prefix
+ * @param localName QName local name
+ * @return A QName.
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if {@code localName} does not conform to local name requirements or if the
+ * prefix is not bound in this context.
+ */
+ default @NonNull QName createQName(final String prefix, final String localName) {
+ final Optional<QNameModule> namespace = findNamespaceForPrefix(prefix);
+ checkArgument(namespace.isPresent(), "Prefix %s is not bound", prefix);
+ return QName.create(namespace.get(), localName);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2019 Pantheon Technologies, s.r.o. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableBiMap;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Optional;
+import org.junit.Test;
+
+public class BiMapYangNamespaceContextTest {
+ private static final QNameModule FOO = QNameModule.create(URI.create("foo"));
+ private static final QNameModule BAR = QNameModule.create(URI.create("bar"));
+ private static final QNameModule BAZ = QNameModule.create(URI.create("baz"));
+
+ private final BiMapYangNamespaceContext context = new BiMapYangNamespaceContext(FOO,
+ ImmutableBiMap.of("foo", FOO, "bar", BAR));
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnmappedDefault() {
+ new BiMapYangNamespaceContext(FOO, ImmutableBiMap.of());
+ }
+
+ @Test
+ public void testDefaultNamespace() {
+ assertEquals(Optional.of(FOO), context.getDefaultNamespace());
+ }
+
+ @Test
+ public void testEquals() {
+ assertTrue(context.equals(context));
+ assertTrue(context.equals(new BiMapYangNamespaceContext(FOO, ImmutableBiMap.of("foo", FOO, "bar", BAR))));
+ assertFalse(context.equals(null));
+ assertFalse(context.equals(new BiMapYangNamespaceContext(FOO, ImmutableBiMap.of("foo", FOO))));
+ assertFalse(context.equals(new BiMapYangNamespaceContext(BAR, ImmutableBiMap.of("bar", BAR))));
+ }
+
+ @Test
+ public void testPrefixForNamespace() {
+ assertEquals(Optional.of("foo"), context.findPrefixForNamespace(FOO));
+ assertEquals(Optional.of("bar"), context.findPrefixForNamespace(BAR));
+ assertEquals(Optional.empty(), context.findPrefixForNamespace(BAZ));
+ }
+
+ @Test
+ public void testNamespaceForPrefix() {
+ assertEquals(Optional.of(FOO), context.findNamespaceForPrefix("foo"));
+ assertEquals(Optional.of(BAR), context.findNamespaceForPrefix("bar"));
+ assertEquals(Optional.empty(), context.findNamespaceForPrefix("baz"));
+ }
+
+ @Test
+ public void testReadWrite() throws IOException {
+ final byte[] bytes;
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+ try (DataOutputStream dos = new DataOutputStream(bos)) {
+ context.writeTo(dos);
+ }
+ bytes = bos.toByteArray();
+ }
+
+ final BiMapYangNamespaceContext other;
+ try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes))) {
+ other = BiMapYangNamespaceContext.readFrom(dis);
+ }
+
+ assertEquals(context, other);
+ }
+
+ @Test
+ public void testCreateQName() {
+ assertEquals(QName.create(FOO, "foo"), context.createQName("foo"));
+ assertEquals(QName.create(FOO, "some"), context.createQName("foo", "some"));
+ }
+}