+ Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input)));
+ }
+
+ 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 defaultModule,
+ 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);
+ final QName qname;
+ if (step instanceof ResolvedQNameStep) {
+ qname = ((ResolvedQNameStep) step).getQName();
+ } else if (step instanceof UnresolvedQNameStep) {
+ final AbstractQName toResolve = ((UnresolvedQNameStep) step).getQName();
+ // TODO: should handle qualified QNames, too? parser should have resolved them when we get here...
+ checkState(toResolve instanceof UnqualifiedQName, "Unhandled qname %s in %s", toResolve, path);
+ qname = QName.create(defaultModule, toResolve.getLocalName());
+ } else {
+ throw new IllegalStateException("Unhandled step " + step);
+ }
+
+ ret.addLast(qname);
+ }
+
+ 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);
+ }
+
+ @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;
+ }
+
+ // Now let's see if there there is a '..' in the rest
+ final int dots = findDots(xpath, leadingParents + 1);
+ if (dots == -1) {
+ return leadingParents;
+ }
+
+ xpath.remove(dots - 1);
+ xpath.remove(dots - 1);
+ LOG.trace("Next iteration {}", xpath);
+ }
+ }
+
+ private static int findDots(final List<String> xpath, final int startIndex) {
+ for (int i = startIndex; i < xpath.size(); ++i) {
+ if ("..".equals(xpath.get(i))) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private static List<String> doSplitXPath(final String xpath) {
+ return SLASH_SPLITTER.splitToList(xpath);