*/
package org.opendaylight.yangtools.yang.model.api;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
* Node which can contains other nodes.
*/
public interface DataNodeContainer {
-
/**
* Returns set of all newly defined types within this DataNodeContainer.
*
Set<TypeDefinition<?>> getTypeDefinitions();
/**
- * Returns set of all child nodes defined within this DataNodeContainer.
- * Although the return type is a collection, each node is guaranteed to
- * be present at most once.
+ * Returns set of all child nodes defined within this DataNodeContainer. Although the return type is a collection,
+ * each node is guaranteed to be present at most once.
+ *
+ * <p>
+ * Note that the nodes returned are <strong>NOT</strong> {@code data nodes}, but rather {@link DataSchemaNode}s,
+ * hence {@link ChoiceSchemaNode} and {@link CaseSchemaNode} are present instead of their children. This
+ * is consistent with {@code schema tree}.
*
* @return child nodes in lexicographical order
*/
/**
* Returns the child node corresponding to the specified name.
*
- * @param name
- * QName of child
- * @return child node of this DataNodeContainer if child with given name is present, null otherwise
+ * <p>
+ * Note that the nodes searched are <strong>NOT</strong> {@code data nodes}, but rather {@link DataSchemaNode}s,
+ * hence {@link ChoiceSchemaNode} and {@link CaseSchemaNode} are returned instead of their matching children. This
+ * is consistent with {@code schema tree}.
*
+ * @param name QName of child
+ * @return child node of this DataNodeContainer if child with given name is present, null otherwise
* @deprecated Use {@link #findDataChildByName(QName)} instead.
+ * @throws NullPointerException if {@code name} is null
*/
@Deprecated
@Nullable default DataSchemaNode getDataChildByName(final QName name) {
/**
* Returns the child node corresponding to the specified name.
*
- * @param name
- * QName of child
+ * <p>
+ * Note that the nodes searched are <strong>NOT</strong> {@code data nodes}, but rather {@link DataSchemaNode}s,
+ * hence {@link ChoiceSchemaNode} and {@link CaseSchemaNode} are returned instead of their matching children.
+ *
+ * @param name QName of child
* @return child node of this DataNodeContainer if child with given name is present, empty otherwise
- * @throws NullPointerException if name is null
+ * @throws NullPointerException if {@code name} is null
*/
Optional<DataSchemaNode> findDataChildByName(QName name);
* @return Set of all uses nodes defined within this DataNodeContainer
*/
Set<UsesNode> getUses();
+
+ /**
+ * Returns a {@code data node} identified by a QName. This method is distinct from
+ * {@link #findDataChildByName(QName)} in that it skips over {@link ChoiceSchemaNode}s and {@link CaseSchemaNode}s,
+ * hence mirroring layout of the {@code data tree}, not {@code schema tree}.
+ *
+ * @param name QName identifier of the data node
+ * @return Direct or indirect child of this DataNodeContainer which is a {@code data node}, empty otherwise
+ * @throws NullPointerException if {@code name} is null
+ */
+ @Beta
+ default Optional<DataSchemaNode> findDataTreeChild(final QName name) {
+ // First we try to find a direct child and check if it is a data node (as per RFC7950)
+ final Optional<DataSchemaNode> optDataChild = findDataChildByName(name);
+ if (HelperMethods.isDataNode(optDataChild)) {
+ return optDataChild;
+ }
+
+ // There either is no such node present, or there are Choice/CaseSchemaNodes with the same name involved,
+ // hence we have to resort to a full search.
+ for (DataSchemaNode child : getChildNodes()) {
+ if (child instanceof ChoiceSchemaNode) {
+ for (CaseSchemaNode choiceCase : ((ChoiceSchemaNode) child).getCases().values()) {
+ final Optional<DataSchemaNode> caseChild = choiceCase.findDataTreeChild(name);
+ if (caseChild.isPresent()) {
+ return caseChild;
+ }
+ }
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Returns a {@code data node} identified by a series of QNames. This is equivalent to incrementally calling
+ * {@link #findDataTreeChild(QName)}.
+ *
+ * @param path Series of QNames towards identifying the requested data node
+ * @return Direct or indirect child of this DataNodeContainer which is a {@code data node}, empty otherwise
+ * @throws IllegalArgumentException if {@code path} is determined to go beyond a not-container-nor-list node.
+ * @throws NoSuchElementException if {@code path} is empty
+ * @throws NullPointerException if {@code path} is null or contains a null
+ */
+ @Beta
+ default Optional<DataSchemaNode> findDataTreeChild(final QName... path) {
+ return findDataTreeChild(Arrays.asList(path));
+ }
+
+ /**
+ * Returns a {@code data node} identified by a series of QNames. This is equivalent to incrementally calling
+ * {@link #findDataTreeChild(QName)}.
+ *
+ * @param path Series of QNames towards identifying the requested data node
+ * @return Direct or indirect child of this DataNodeContainer which is a {@code data node}, empty otherwise
+ * @throws IllegalArgumentException if {@code path} is determined to go beyond a not-container-nor-list node.
+ * @throws NoSuchElementException if {@code path} is empty
+ * @throws NullPointerException if {@code path} is null or contains a null
+ */
+ @Beta
+ default Optional<DataSchemaNode> findDataTreeChild(final Iterable<QName> path) {
+ final Iterator<QName> it = path.iterator();
+ DataNodeContainer parent = this;
+ do {
+ final Optional<DataSchemaNode> optChild = parent.findDataTreeChild(requireNonNull(it.next()));
+ if (!optChild.isPresent() || !it.hasNext()) {
+ return optChild;
+ }
+
+ final DataSchemaNode child = optChild.get();
+ checkArgument(child instanceof DataNodeContainer, "Path %s extends beyond terminal child %s", path, child);
+ parent = (DataNodeContainer) child;
+ } while (true);
+ }
}
* Non-conditional Revision Aware XPath, or <code>null</code> if the
* DataSchemaNode is not present in Schema Context.
*/
+ // FIXME: This entire method is ill-defined, as the resolution process depends on where the XPath is defined --
+ // notably RPCs, actions and notifications modify the data tree temporarily. See sections 6.4.1 and 9.9.2
+ // of RFC7950.
+ //
+ // Most notably we need to understand whether the XPath is being resolved in the data tree, or as part of
+ // a notification/action/RPC, as then the SchemaContext grows tentative nodes ... which could be addressed
+ // via a derived SchemaContext (i.e. this class would have to have a
+ //
+ // SchemaContext notificationSchemaContext(SchemaContext delegate, NotificationDefinition notif)
+ //
+ // which would then be passed in to a method similar to this one. In static contexts, like MD-SAL codegen,
+ // that feels like an overkill.
public static SchemaNode findDataSchemaNode(final SchemaContext context, final Module module,
final RevisionAwareXPath nonCondXPath) {
Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
Preconditions.checkArgument(strXPath.indexOf('[') == -1,
"Revision Aware XPath may not contain a condition");
if (nonCondXPath.isAbsolute()) {
- final List<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
- if (qnamedPath != null) {
- return findNodeInSchemaContext(context, qnamedPath);
- }
+ final List<QName> path = xpathToQNamePath(context, module, strXPath);
+
+ // We do not have enough information about resolution context, hence cannot account for actions, RPCs
+ // and notifications. We therefore attempt to make a best estimate, but this can still fail.
+ final Optional<DataSchemaNode> pureData = context.findDataTreeChild(path);
+ return pureData.isPresent() ? pureData.get() : findNodeInSchemaContext(context, path);
}
}
return null;
* given relative Revision Aware XPath, otherwise will return
* <code>null</code>.
*/
+ // FIXME: This entire method is ill-defined, as the resolution process depends on where the XPath is defined --
+ // notably RPCs, actions and notifications modify the data tree temporarily. See sections 6.4.1 and 9.9.2
+ // of RFC7950.
+ //
+ // Most notably we need to understand whether the XPath is being resolved in the data tree, or as part of
+ // a notification/action/RPC, as then the SchemaContext grows tentative nodes ... which could be addressed
+ // via a derived SchemaContext (i.e. this class would have to have a
+ //
+ // SchemaContext notificationSchemaContext(SchemaContext delegate, NotificationDefinition notif)
+ //
+ // which would then be passed in to a method similar to this one. In static contexts, like MD-SAL codegen,
+ // that feels like an overkill.
public static SchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
if (actualNodePath != null) {
final Iterable<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
- if (qnamePath != null) {
- return findNodeInSchemaContext(context, qnamePath);
- }
+ // We do not have enough information about resolution context, hence cannot account for actions, RPCs
+ // and notifications. We therefore attempt to make a best estimate, but this can still fail.
+ final Optional<DataSchemaNode> pureData = context.findDataTreeChild(qnamePath);
+ return pureData.isPresent() ? pureData.get() : findNodeInSchemaContext(context, qnamePath);
}
return null;
}