*/
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.UnmodifiableIterator;
+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.NoSuchElementException;
-import java.util.Objects;
+import java.util.Collection;
+import java.util.List;
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
}
- @Override
- public boolean isAbsolute() {
- return true;
+ /**
+ * 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);
}
- @Override
- protected SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
- return new Absolute(parent, Preconditions.checkNotNull(qname));
+ /**
+ * 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));
}
- }
- /**
- * A relative schema node identifier.
- */
- public static final class Relative extends SchemaNodeIdentifier {
- private Relative(final SchemaNodeIdentifier parent, final QName qname) {
- super(parent, qname);
+ /**
+ * 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 false;
+ final SchemaPath implicitSchemaPathParent() {
+ return SchemaPath.ROOT;
}
@Override
- protected SchemaNodeIdentifier createInstance(final SchemaNodeIdentifier parent, final QName qname) {
- return new Relative(parent, Preconditions.checkNotNull(qname));
+ final String className() {
+ return "Absolute";
}
}
- @SuppressWarnings("rawtypes")
- private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, ImmutableList> LEGACYPATH_UPDATER =
- AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, ImmutableList.class, "legacyPath");
- private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, SchemaPath> SCHEMAPATH_UPDATER =
- AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, SchemaPath.class, "schemaPath");
/**
- * Shared instance of the conceptual root schema node.
+ * A descendant schema node identifier.
*/
- public static final SchemaNodeIdentifier ROOT = new Absolute(null, null);
+ public abstract static class Descendant extends SchemaNodeIdentifier {
+ Descendant() {
+ // Hidden on purpose
+ }
- /**
- * Shared instance of the "same" relative schema node.
- */
- public static final SchemaNodeIdentifier SAME = new Relative(null, null);
+ /**
+ * 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);
+ }
- /**
- * Parent path.
- */
- private final SchemaNodeIdentifier parent;
+ /**
+ * 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));
+ }
- /**
- * This component.
- */
- private final QName qname;
+ /**
+ * 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);
+ }
- /**
- * Cached hash code. We can use this since we are immutable.
- */
- private final int hash;
+ @Override
+ final SchemaPath implicitSchemaPathParent() {
+ return SchemaPath.SAME;
+ }
- /**
- * Cached legacy path, filled-in when {@link #getPath()} or {@link #getPathTowardsRoot()}
- * is invoked.
- */
- private volatile ImmutableList<QName> legacyPath;
+ @Override
+ final String className() {
+ return "Descendant";
+ }
+ }
- /**
- * Cached SchemaPath.
- */
- private volatile SchemaPath schemaPath;
+ private static final class AbsoluteSingle extends Absolute {
+ private final @NonNull QName qname;
- protected SchemaNodeIdentifier(final SchemaNodeIdentifier parent, final QName qname) {
- this.parent = parent;
- this.qname = qname;
+ AbsoluteSingle(final QName qname) {
+ this.qname = requireNonNull(qname);
+ }
- int h = Objects.hashCode(parent);
- if (qname != null) {
- h = h * 31 + qname.hashCode();
+ @Override
+ public ImmutableList<QName> getNodeIdentifiers() {
+ return ImmutableList.of(qname);
}
- hash = h;
- }
+ @Override
+ public QName firstNodeIdentifier() {
+ return qname;
+ }
- private ImmutableList<QName> getLegacyPath() {
- ImmutableList<QName> ret = legacyPath;
- if (ret == null) {
- ret = ImmutableList.copyOf(getPathTowardsRoot()).reverse();
- LEGACYPATH_UPDATER.lazySet(this, ret);
+ @Override
+ public QName lastNodeIdentifier() {
+ return qname;
}
- return ret;
+ @Override
+ Object pathObject() {
+ 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 SchemaNodeIdentifier instance.
- */
- public static SchemaNodeIdentifier create(final Iterable<QName> path, final boolean absolute) {
- final SchemaNodeIdentifier parent = absolute ? ROOT : SAME;
- return parent.createChild(path);
- }
+ private static final class AbsoluteMultiple extends Absolute {
+ private final @NonNull ImmutableList<QName> qnames;
- /**
- * 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);
+ AbsoluteMultiple(final ImmutableList<QName> qnames) {
+ this.qnames = requireNonNull(qnames);
+ }
+
+ @Override
+ public ImmutableList<QName> getNodeIdentifiers() {
+ return qnames;
+ }
+
+ @Override
+ Object pathObject() {
+ return qnames;
+ }
}
- /**
- * 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 DescendantSingle extends Descendant {
+ private final @NonNull QName qname;
- /**
- * 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;
+ DescendantSingle(final QName qname) {
+ this.qname = requireNonNull(qname);
}
- SchemaNodeIdentifier parentNode = this;
- for (QName qname : relative) {
- parentNode = parentNode.createInstance(parentNode, qname);
+ @Override
+ public ImmutableList<QName> getNodeIdentifiers() {
+ return ImmutableList.of(qname);
+ }
+
+ @Override
+ public QName firstNodeIdentifier() {
+ return qname;
}
- return parentNode;
+ @Override
+ public QName lastNodeIdentifier() {
+ return qname;
+ }
+
+ @Override
+ Object pathObject() {
+ return qname;
+ }
}
- /**
- * 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 class DescandantMultiple extends Descendant {
+ private final @NonNull ImmutableList<QName> qnames;
- SchemaNodeIdentifier parentNode = this;
- for (QName qname : relative.getPathFromRoot()) {
- parentNode = parentNode.createInstance(parentNode, qname);
+ DescandantMultiple(final ImmutableList<QName> qnames) {
+ this.qnames = requireNonNull(qnames);
}
- return parentNode;
- }
+ @Override
+ public ImmutableList<QName> getNodeIdentifiers() {
+ return qnames;
+ }
- /**
- * 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));
+ @Override
+ Object pathObject() {
+ return qnames;
+ }
}
- /**
- * 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 list of <code>qname</code> instances which represents
- * path from the root to the schema node.
- */
- public Iterable<QName> getPathFromRoot() {
- return getLegacyPath();
+ private static final AtomicReferenceFieldUpdater<SchemaNodeIdentifier, SchemaPath> SCHEMAPATH_UPDATER =
+ AtomicReferenceFieldUpdater.newUpdater(SchemaNodeIdentifier.class, SchemaPath.class, "schemaPath");
+
+ // Cached SchemaPath.
+ private volatile SchemaPath schemaPath;
+ // Cached hashCode
+ private volatile int hash;
+
+ SchemaNodeIdentifier() {
+ // Hidden on purpose
}
/**
- * 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 non-empty sequence of node identifiers which constitute this schema node identifier.
*
- * @return list of <code>qname</code> instances which represents
- * path from the schema node towards the root.
+ * @return Non-empty sequence of node identifiers
*/
- public Iterable<QName> getPathTowardsRoot() {
- return () -> new UnmodifiableIterator<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");
- }
- }
- };
- }
+ public abstract @NonNull List<QName> getNodeIdentifiers();
/**
- * Returns the immediate parent SchemaPath.
+ * Return the first node identifier. This method is equivalent to {@code getNodeIdentifiers().get(0)}, but is
+ * potentially more efficient.
*
- * @return Parent path, null if this SchemaPath is already toplevel.
+ * @return The first node identifier
*/
- public SchemaNodeIdentifier getParent() {
- return parent;
+ public @NonNull QName firstNodeIdentifier() {
+ return getNodeIdentifiers().get(0);
}
/**
- * Get the last component of this path.
+ * Return the last node identifier. This method is equivalent to {@code getNodeIdentifiers().get(size - 1)}, but
+ * is potentially more efficient.
*
- * @return The last component of this path.
+ * @return The last node identifier
*/
- public final QName getLastComponent() {
- return qname;
- }
-
- private SchemaPath createSchemaPath() {
- final SchemaPath newPath;
- if (parent == null) {
- final SchemaPath parentPath = isAbsolute() ? SchemaPath.ROOT : SchemaPath.SAME;
- newPath = qname == null ? parentPath : parentPath.createChild(qname);
- } else {
- newPath = parent.asSchemaPath().createChild(qname);
- }
-
- return SCHEMAPATH_UPDATER.compareAndSet(this, null, newPath) ? newPath : schemaPath;
+ public @NonNull QName lastNodeIdentifier() {
+ final List<QName> local = getNodeIdentifiers();
+ return local.get(local.size() - 1);
}
/**
*
* @return SchemaPath equivalent.
*/
- public final SchemaPath asSchemaPath() {
+ public final @NonNull SchemaPath asSchemaPath() {
final SchemaPath ret = schemaPath;
- return ret != null ? ret : createSchemaPath();
+ 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;
- return Objects.equals(qname, other.qname) && Objects.equals(parent, 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;
}
}