Add YangNamespaceContext 18/80618/6
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 27 Feb 2019 08:35:18 +0000 (09:35 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 27 Feb 2019 10:14:58 +0000 (11:14 +0100)
We require prefix/QNameModule mapping in multiple contexts, some
of which are bound to a SchemaContext, some of which are bound to
a particular Module.

This patch adds a generalized YangNamespaceContext to serve in these
situations and provides a simple BiMap-based implementation.

Change-Id: I3d44a55ea3569532395b3acb9dc7dedba70c1f4e
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/BiMapYangNamespaceContext.java [new file with mode: 0644]
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/YangNamespaceContext.java [new file with mode: 0644]
yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/BiMapYangNamespaceContextTest.java [new file with mode: 0644]

diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/BiMapYangNamespaceContext.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/BiMapYangNamespaceContext.java
new file mode 100644 (file)
index 0000000..96edd48
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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();
+    }
+}
diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/YangNamespaceContext.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/YangNamespaceContext.java
new file mode 100644 (file)
index 0000000..7481579
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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);
+    }
+}
diff --git a/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/BiMapYangNamespaceContextTest.java b/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/BiMapYangNamespaceContextTest.java
new file mode 100644 (file)
index 0000000..a4a697d
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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"));
+    }
+}