+ private static List<QName> createWalkablePath(final Iterable<QName> schemaNodePath, final SchemaContext context,
+ final int colCount) {
+ final List<Integer> indexToRemove = new ArrayList<>();
+ List<QName> schemaNodePathRet = Lists.newArrayList(schemaNodePath);
+ for (int j = 0, i = schemaNodePathRet.size() - 1; i >= 0 && j != colCount; i--, j++) {
+ final SchemaNode nodeIn = findTargetNode(context, schemaNodePathRet);
+ if (nodeIn instanceof CaseSchemaNode || nodeIn instanceof ChoiceSchemaNode) {
+ indexToRemove.add(i);
+ j--;
+ }
+ schemaNodePathRet.remove(i);
+ }
+ schemaNodePathRet = Lists.newArrayList(schemaNodePath);
+ for (int i : indexToRemove) {
+ schemaNodePathRet.remove(i);
+ }
+ return schemaNodePathRet;
+ }
+
+ private static SchemaNode resolveDerefPath(final SchemaContext context, final Module module,
+ final SchemaNode actualSchemaNode, final String xpath) {
+ final int paren = xpath.indexOf(')', 6);
+ checkArgument(paren != -1, "Cannot find matching parentheses in %s", xpath);
+
+ final String derefArg = xpath.substring(6, paren).strip();
+ // Look up the node which we need to reference
+ final SchemaNode derefTarget = findTargetNode(context, resolveRelativePath(context, module, actualSchemaNode,
+ doSplitXPath(derefArg)));
+ checkArgument(derefTarget != null, "Cannot find deref(%s) target node %s in context of %s", derefArg,
+ actualSchemaNode);
+ checkArgument(derefTarget instanceof TypedDataSchemaNode, "deref(%s) resolved to non-typed %s", derefArg,
+ derefTarget);
+
+ // We have a deref() target, decide what to do about it
+ final TypeDefinition<?> targetType = ((TypedDataSchemaNode) derefTarget).getType();
+ if (targetType instanceof InstanceIdentifierTypeDefinition) {
+ // Static inference breaks down, we cannot determine where this points to
+ // FIXME: dedicated exception, users can recover from it, derive from IAE
+ throw new UnsupportedOperationException("Cannot infer instance-identifier reference " + targetType);
+ }
+
+ // deref() is define only for instance-identifier and leafref types, handle the latter
+ checkArgument(targetType instanceof LeafrefTypeDefinition, "Illegal target type %s", targetType);
+
+ final PathExpression targetPath = ((LeafrefTypeDefinition) targetType).getPathStatement();
+ LOG.debug("Derefencing path {}", targetPath);
+
+ final SchemaNode deref = targetPath.isAbsolute()
+ ? findTargetNode(context, actualSchemaNode.getQName().getModule(),
+ ((LocationPathSteps) targetPath.getSteps()).getLocationPath())
+ : findDataSchemaNodeForRelativeXPath(context, module, actualSchemaNode, targetPath);
+ if (deref == null) {
+ LOG.debug("Path {} could not be derefenced", targetPath);
+ return null;
+ }
+
+ checkArgument(deref instanceof LeafSchemaNode, "Unexpected %s reference in %s", deref, targetPath);
+
+ final List<String> qnames = doSplitXPath(xpath.substring(paren + 1).stripLeading());
+ return findTargetNode(context, resolveRelativePath(context, module, deref, qnames));
+ }
+
+ private static @Nullable SchemaNode findTargetNode(final SchemaContext context, final QNameModule localNamespace,
+ final YangLocationPath path) {
+ final Deque<QName> ret = new ArrayDeque<>();
+ for (Step step : path.getSteps()) {
+ if (step instanceof AxisStep) {
+ // We only support parent axis steps
+ final YangXPathAxis axis = ((AxisStep) step).getAxis();
+ checkState(axis == YangXPathAxis.PARENT, "Unexpected axis %s", axis);
+ ret.removeLast();
+ continue;
+ }
+
+ // This has to be a QNameStep
+ checkState(step instanceof QNameStep, "Unhandled step %s in %s", step, path);
+ ret.addLast(resolve(((QNameStep) step).getQName(), localNamespace));
+ }
+
+ return findTargetNode(context, ret);
+ }
+
+ private static @Nullable SchemaNode findTargetNode(final SchemaContext context, final Iterable<QName> 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);
+ }
+
+ private static QName resolve(final AbstractQName toResolve, final QNameModule localNamespace) {
+ if (toResolve instanceof QName) {
+ return (QName) toResolve;
+ } else if (toResolve instanceof UnqualifiedQName) {
+ return ((UnqualifiedQName) toResolve).bindTo(localNamespace);
+ } else {
+ throw new IllegalStateException("Unhandled step " + toResolve);
+ }
+ }
+
+ @VisibleForTesting
+ static int normalizeXPath(final List<String> xpath) {
+ LOG.trace("Normalize {}", xpath);
+
+ // We need to make multiple passes here, as the leading XPaths as we can have "../abc/../../def", which really
+ // is "../../def"
+ while (true) {
+ // Next up: count leading ".." components
+ int leadingParents = 0;
+ while (true) {
+ if (leadingParents == xpath.size()) {
+ return leadingParents;
+ }
+ if (!"..".equals(xpath.get(leadingParents))) {
+ break;
+ }
+
+ ++leadingParents;
+ }