Promote Absolute.{last,first}NodeIdentifier
[yangtools.git] / yang / yang-model-api / src / main / java / org / opendaylight / yangtools / yang / model / api / stmt / SchemaNodeIdentifier.java
index f94b7fac71cac46931bcb276d6425875eb7afa8f..dd4c0cb624f3bb2f01c7c74c9a9b7e3fe9e9fb61 100644 (file)
  */
 package org.opendaylight.yangtools.yang.model.api.stmt;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.MoreObjects;
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Iterator;
+import java.util.Collection;
 import java.util.List;
-import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 
 /**
- * Represents unique path to the every schema node inside the schema node identifier
- * namespace.
- *
+ * Represents unique path to every schema node inside the schema node identifier namespace. This concept is defined
+ * in <a href="https://tools.ietf.org/html/rfc7950#section-6.5">RFC7950</a>.
  */
 public abstract class SchemaNodeIdentifier implements Immutable {
-
     /**
      * An absolute schema node identifier.
      */
-    public static final class Absolute extends SchemaNodeIdentifier {
-        private Absolute(final SchemaNodeIdentifier parent, final QName qname) {
-            super(parent, qname);
+    public abstract static class Absolute extends SchemaNodeIdentifier {
+        private static final Interner<Absolute> INTERNER = Interners.newWeakInterner();
+
+        Absolute() {
+            // Hidden on purpose
+        }
+
+        /**
+         * Create an absolute schema node identifier composed of a single node identifier.
+         *
+         * @param nodeIdentifier Single node identifier
+         * @return An absolute schema node identifier
+         * @throws NullPointerException if {@code nodeIdentifier} is null
+         */
+        public static @NonNull Absolute of(final QName nodeIdentifier) {
+            return new AbsoluteSingle(nodeIdentifier);
+        }
+
+        /**
+         * Create an absolute schema node identifier composed of multiple node identifiers.
+         *
+         * @param nodeIdentifiers Node identifiers
+         * @return An absolute schema node identifier
+         * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
+         * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
+         */
+        public static @NonNull Absolute of(final QName... nodeIdentifiers) {
+            return of(Arrays.asList(nodeIdentifiers));
+        }
+
+        /**
+         * Create an absolute schema node identifier composed of multiple node identifiers.
+         *
+         * @param nodeIdentifiers Node identifiers
+         * @return An absolute schema node identifier
+         * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
+         * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
+         */
+        public static @NonNull Absolute of(final Collection<QName> nodeIdentifiers) {
+            final ImmutableList<QName> qnames = checkQNames(nodeIdentifiers);
+            return qnames.size() == 1 ? of(qnames.get(0)) : new AbsoluteMultiple(qnames);
+        }
+
+        /**
+         * Return an interned reference to an equivalent object.
+         *
+         * @return An interned reference, or this object if it was previously interned.
+         */
+        public final @NonNull Absolute intern() {
+            return INTERNER.intern(this);
         }
 
         @Override
-        public boolean isAbsolute() {
-            return true;
+        final SchemaPath implicitSchemaPathParent() {
+            return SchemaPath.ROOT;
         }
 
         @Override
-        protected SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
-            return new Absolute(parent, qname);
+        final String className() {
+            return "Absolute";
         }
     }
 
     /**
-     * A relative schema node identifier.
+     * A descendant schema node identifier.
      */
-    public static class Relative extends SchemaNodeIdentifier {
-        private Relative(final SchemaNodeIdentifier parent, final QName qname) {
-            super(parent, qname);
+    public abstract static class Descendant extends SchemaNodeIdentifier {
+        Descendant() {
+            // Hidden on purpose
+        }
+
+        /**
+         * Create a descendant schema node identifier composed of a single node identifier.
+         *
+         * @param nodeIdentifier Single node identifier
+         * @return A descendant schema node identifier
+         * @throws NullPointerException if {@code nodeIdentifier} is null
+         */
+        public static @NonNull Descendant of(final QName nodeIdentifier) {
+            return new DescendantSingle(nodeIdentifier);
+        }
+
+        /**
+         * Create a descendant schema node identifier composed of multiple node identifiers.
+         *
+         * @param nodeIdentifiers Node identifiers
+         * @return A descendant schema node identifier
+         * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
+         * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
+         */
+        public static @NonNull Descendant of(final QName... nodeIdentifiers) {
+            return of(Arrays.asList(nodeIdentifiers));
+        }
+
+        /**
+         * Create a descendant schema node identifier composed of multiple node identifiers.
+         *
+         * @param nodeIdentifiers Node identifiers
+         * @return A descendant schema node identifier
+         * @throws NullPointerException if {@code nodeIdentifiers} or any of its members is null
+         * @throws IllegalArgumentException if {@code nodeIdentifiers} is empty
+         */
+        public static @NonNull Descendant of(final Collection<QName> nodeIdentifiers) {
+            final ImmutableList<QName> qnames = checkQNames(nodeIdentifiers);
+            return qnames.size() == 1 ? of(qnames.get(0)) : new DescandantMultiple(qnames);
         }
 
         @Override
-        public boolean isAbsolute() {
-            return false;
+        final SchemaPath implicitSchemaPathParent() {
+            return SchemaPath.SAME;
         }
 
         @Override
-        protected SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
-            return new Relative(parent, qname);
+        final String className() {
+            return "Descendant";
         }
     }
 
-    @SuppressWarnings("rawtypes")
-    private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, ImmutableList> LEGACYPATH_UPDATER =
-            AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, ImmutableList.class, "legacyPath");
+    private static final class AbsoluteSingle extends Absolute {
+        private final @NonNull QName qname;
 
-    /**
-     * Shared instance of the conceptual root schema node.
-     */
-    public static final SchemaNodeIdentifier ROOT = new Absolute(null, null);
+        AbsoluteSingle(final QName qname) {
+            this.qname = requireNonNull(qname);
+        }
 
-    /**
-     * Shared instance of the "same" relative schema node.
-     */
-    public static final SchemaNodeIdentifier SAME = new Relative(null, null);
+        @Override
+        public ImmutableList<QName> getNodeIdentifiers() {
+            return ImmutableList.of(qname);
+        }
 
-    /**
-     * Parent path.
-     */
-    private final SchemaNodeIdentifier parent;
+        @Override
+        public QName firstNodeIdentifier() {
+            return qname;
+        }
 
-    /**
-     * This component.
-     */
-    private final QName qname;
+        @Override
+        public QName lastNodeIdentifier() {
+            return qname;
+        }
 
-    /**
-     * Cached hash code. We can use this since we are immutable.
-     */
-    private final int hash;
+        @Override
+        Object pathObject() {
+            return qname;
+        }
+    }
 
-    /**
-     * Cached legacy path, filled-in when {@link #getPath()} or {@link #getPathTowardsRoot()}
-     * is invoked.
-     */
-    private volatile ImmutableList<QName> legacyPath;
+    private static final class AbsoluteMultiple extends Absolute {
+        private final @NonNull ImmutableList<QName> qnames;
 
-    protected SchemaNodeIdentifier(final SchemaNodeIdentifier parent, final QName qname) {
-        this.parent = parent;
-        this.qname = qname;
+        AbsoluteMultiple(final ImmutableList<QName> qnames) {
+            this.qnames = requireNonNull(qnames);
+        }
 
-        int h = parent == null ? 0 : parent.hashCode();
-        if (qname != null) {
-            h = h * 31 + qname.hashCode();
+        @Override
+        public ImmutableList<QName> getNodeIdentifiers() {
+            return qnames;
         }
 
-        hash = h;
+        @Override
+        Object pathObject() {
+            return qnames;
+        }
     }
 
-    private ImmutableList<QName> getLegacyPath() {
-        ImmutableList<QName> ret = legacyPath;
-        if (ret == null) {
-            ret = ImmutableList.copyOf(getPathTowardsRoot()).reverse();
-            LEGACYPATH_UPDATER.lazySet(this, ret);
+    private static final class DescendantSingle extends Descendant {
+        private final @NonNull QName qname;
+
+        DescendantSingle(final QName qname) {
+            this.qname = requireNonNull(qname);
         }
 
-        return ret;
-    }
+        @Override
+        public ImmutableList<QName> getNodeIdentifiers() {
+            return ImmutableList.of(qname);
+        }
 
-    /**
-     * Returns the complete path to schema node.
-     *
-     * @return list of <code>QName</code> instances which represents complete
-     *         path to schema node
-     *
-     * @deprecated Use {@link #getPathFromRoot()} instead.
-     */
-    @Deprecated
-    public List<QName> getPath() {
-        return getLegacyPath();
-    }
+        @Override
+        public QName firstNodeIdentifier() {
+            return qname;
+        }
 
-    /**
-     * Constructs new instance of this class with the concrete path.
-     *
-     * @param path
-     *            list of QName instances which specifies exact path to the
-     *            module node
-     * @param absolute
-     *            boolean value which specifies if the path is absolute or
-     *            relative
-     *
-     * @return A SchemaPath instance.
-     */
-    public static SchemaNodeIdentifier create(final Iterable<QName> path, final boolean absolute) {
-        final SchemaNodeIdentifier parent = absolute ? ROOT : SAME;
-        return parent.createChild(path);
-    }
+        @Override
+        public QName lastNodeIdentifier() {
+            return qname;
+        }
 
-    /**
-     * Constructs new instance of this class with the concrete path.
-     *
-     * @param absolute
-     *            boolean value which specifies if the path is absolute or
-     *            relative
-     * @param path
-     *            one or more QName instances which specifies exact path to the
-     *            module node
-     *
-     * @return A SchemaPath instance.
-     */
-    public static SchemaNodeIdentifier create(final boolean absolute, final QName... path) {
-        return create(Arrays.asList(path), absolute);
+        @Override
+        Object pathObject() {
+            return qname;
+        }
     }
 
-    /**
-     * Create a new instance.
-     *
-     * @param parent Parent schema node identifier
-     * @param qname next path element
-     * @return A new SchemaPath instance
-     */
-    protected abstract SchemaNodeIdentifier createInstance(SchemaNodeIdentifier parent, QName qname);
+    private static final class DescandantMultiple extends Descendant {
+        private final @NonNull ImmutableList<QName> qnames;
 
-    /**
-     * Create a child path based on concatenation of this path and a relative path.
-     *
-     * @param relative Relative path
-     * @return A new child path
-     */
-    public SchemaNodeIdentifier createChild(final Iterable<QName> relative) {
-        if (Iterables.isEmpty(relative)) {
-            return this;
+        DescandantMultiple(final ImmutableList<QName> qnames) {
+            this.qnames = requireNonNull(qnames);
         }
 
-        SchemaNodeIdentifier parentNode = this;
-        for (QName qname : relative) {
-            parentNode = parentNode.createInstance(parentNode, qname);
+        @Override
+        public ImmutableList<QName> getNodeIdentifiers() {
+            return qnames;
         }
 
-        return parentNode;
+        @Override
+        Object pathObject() {
+            return qnames;
+        }
     }
 
-    /**
-     * Create a child path based on concatenation of this path and a relative path.
-     *
-     * @param relative Relative SchemaPath
-     * @return A new child path
-     */
-    public SchemaNodeIdentifier createChild(final SchemaNodeIdentifier relative) {
-        Preconditions.checkArgument(!relative.isAbsolute(), "Child creation requires relative path");
+    private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, SchemaPath> SCHEMAPATH_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, SchemaPath.class, "schemaPath");
 
-        SchemaNodeIdentifier parentNode = this;
-        for (QName qname : relative.getPathFromRoot()) {
-            parentNode = parentNode.createInstance(parentNode, qname);
-        }
-
-        return parentNode;
-    }
+    // Cached SchemaPath.
+    private volatile SchemaPath schemaPath;
+    // Cached hashCode
+    private volatile int hash;
 
-    /**
-     * Create a child path based on concatenation of this path and additional
-     * path elements.
-     *
-     * @param elements Relative SchemaPath elements
-     * @return A new child path
-     */
-    public SchemaNodeIdentifier createChild(final QName... elements) {
-        return createChild(Arrays.asList(elements));
+    SchemaNodeIdentifier() {
+        // Hidden on purpose
     }
 
     /**
-     * Returns the list of nodes which need to be traversed to get from the
-     * starting point (root for absolute SchemaPaths) to the node represented
-     * by this object.
+     * Return the non-empty sequence of node identifiers which constitute this schema node identifier.
      *
-     * @return list of <code>qname</code> instances which represents
-     *         path from the root to the schema node.
+     * @return Non-empty sequence of node identifiers
      */
-    public Iterable<QName> getPathFromRoot() {
-        return getLegacyPath();
-    }
+    public abstract @NonNull List<QName> getNodeIdentifiers();
 
     /**
-     * Returns the list of nodes which need to be traversed to get from this
-     * node to the starting point (root for absolute SchemaPaths).
+     * Return the first node identifier. This method is equivalent to {@code getNodeIdentifiers().get(0)}, but is
+     * potentially more efficient.
      *
-     * @return list of <code>qname</code> instances which represents
-     *         path from the schema node towards the root.
+     * @return The first node identifier
      */
-    public Iterable<QName> getPathTowardsRoot() {
-        return new Iterable<QName>() {
-            @Override
-            public Iterator<QName> iterator() {
-                return new Iterator<QName>() {
-                    private SchemaNodeIdentifier current = SchemaNodeIdentifier.this;
-
-                    @Override
-                    public boolean hasNext() {
-                        return current.parent != null;
-                    }
-
-                    @Override
-                    public QName next() {
-                        if (current.parent != null) {
-                            final QName ret = current.qname;
-                            current = current.parent;
-                            return ret;
-                        } else {
-                            throw new NoSuchElementException("No more elements available");
-                        }
-                    }
-
-                    @Override
-                    public void remove() {
-                        throw new UnsupportedOperationException("Component removal not supported");
-                    }
-                };
-            }
-        };
+    public @NonNull QName firstNodeIdentifier() {
+        return getNodeIdentifiers().get(0);
     }
 
     /**
-     * Returns the immediate parent SchemaPath.
+     * Return the last node identifier. This method is equivalent to {@code getNodeIdentifiers().get(size - 1)}, but
+     * is potentially more efficient.
      *
-     * @return Parent path, null if this SchemaPath is already toplevel.
+     * @return The last node identifier
      */
-    public SchemaNodeIdentifier getParent() {
-        return parent;
+    public @NonNull QName lastNodeIdentifier() {
+        final List<QName> local = getNodeIdentifiers();
+        return local.get(local.size() - 1);
     }
 
     /**
-     * Get the last component of this path.
+     * Create the {@link SchemaPath} equivalent of this identifier.
      *
-     * @return The last component of this path.
+     * @return SchemaPath equivalent.
      */
-    public final QName getLastComponent() {
-        return qname;
+    public final @NonNull SchemaPath asSchemaPath() {
+        final SchemaPath ret = schemaPath;
+        return ret != null ? ret : loadSchemaPath();
     }
 
-    /**
-     * Describes whether schema node identifier is|isn't absolute.
-     *
-     * @return boolean value which is <code>true</code> if schema path is
-     *         absolute.
-     */
-    public abstract boolean isAbsolute();
-
     @Override
     public final int hashCode() {
-        return hash;
+        final int local;
+        return (local = hash) != 0 ? local : (hash = pathObject().hashCode());
     }
 
     @Override
-    public boolean equals(final Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final SchemaNodeIdentifier other = (SchemaNodeIdentifier) obj;
-
-        if (qname != null) {
-            if (!qname.equals(other.qname)) {
-                return false;
-            }
-        } else {
-            if (other.qname != null) {
-                return false;
-            }
-        }
-
-        if (parent == null) {
-            return other.parent == null;
-        }
-        return parent.equals(other.parent);
+    public final boolean equals(final Object obj) {
+        return this == obj || obj != null && getClass() == obj.getClass()
+                && pathObject().equals(((SchemaNodeIdentifier) obj).pathObject());
     }
 
     @Override
     public final String toString() {
-        return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
+        return MoreObjects.toStringHelper(className()).add("qnames", toStringQNames()).toString();
     }
 
-    protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
-        return toStringHelper.add("path", getPathFromRoot());
+    abstract @NonNull SchemaPath implicitSchemaPathParent();
+
+    abstract @NonNull Object pathObject();
+
+    abstract @NonNull String className();
+
+    private @NonNull SchemaPath loadSchemaPath() {
+        final SchemaPath newPath = implicitSchemaPathParent().createChild(getNodeIdentifiers());
+        return SCHEMAPATH_UPDATER.compareAndSet(this, null, newPath) ? newPath : schemaPath;
+    }
+
+    private List<?> toStringQNames() {
+        final List<QName> ids = getNodeIdentifiers();
+        return ids.size() < 2 ? ids : simplifyQNames(ids);
+    }
+
+    @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
+            justification = "https://github.com/spotbugs/spotbugs/issues/811")
+    private static ImmutableList<QName> checkQNames(final Collection<QName> qnames) {
+        final ImmutableList<QName> ret = ImmutableList.copyOf(qnames);
+        checkArgument(!ret.isEmpty(), "SchemaNodeIdentifier has to have at least one node identifier");
+        return ret;
+    }
+
+    private static List<?> simplifyQNames(final List<QName> qnames) {
+        final List<Object> ret = new ArrayList<>(qnames.size());
+
+        QNameModule prev = null;
+        for (QName qname : qnames) {
+            final QNameModule module = qname.getModule();
+            ret.add(module.equals(prev) ? qname.getLocalName() : qname);
+            prev = module;
+        }
+
+        return ret;
     }
 }