071e32db8752d28947571ce23a3cae1d87c4b57c
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / SchemaContextUtil.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.model.util;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Function;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Splitter;
14 import com.google.common.collect.Iterables;
15 import java.util.Arrays;
16 import java.util.Iterator;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.Set;
20 import javax.annotation.Nonnull;
21 import javax.annotation.Nullable;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.common.QNameModule;
24 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
27 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
29 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.Module;
32 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
33 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
34 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
35 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
36 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
37 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
39 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * The Schema Context Util contains support methods for searching through Schema
46  * Context modules for specified schema nodes via Schema Path or Revision Aware
47  * XPath. The Schema Context Util is designed as mixin, so it is not
48  * instantiable.
49  *
50  */
51 public final class SchemaContextUtil {
52     private static final Logger LOG = LoggerFactory.getLogger(SchemaContextUtil.class);
53     private static final Splitter COLON_SPLITTER = Splitter.on(':');
54     private static final Splitter SLASH_SPLITTER = Splitter.on('/');
55
56     private SchemaContextUtil() {
57     }
58
59     /**
60      * Method attempts to find DataSchemaNode in Schema Context via specified
61      * Schema Path. The returned DataSchemaNode from method will be the node at
62      * the end of the SchemaPath. If the DataSchemaNode is not present in the
63      * Schema Context the method will return <code>null</code>. <br>
64      * In case that Schema Context or Schema Path are not specified correctly
65      * (i.e. contains <code>null</code> values) the method will return
66      * IllegalArgumentException.
67      *
68      * @throws IllegalArgumentException
69      *
70      * @param context
71      *            Schema Context
72      * @param schemaPath
73      *            Schema Path to search for
74      * @return SchemaNode from the end of the Schema Path or <code>null</code>
75      *         if the Node is not present.
76      */
77     public static SchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
78         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
79         Preconditions.checkArgument(schemaPath != null, "Schema Path reference cannot be NULL");
80
81         final Iterable<QName> prefixedPath = schemaPath.getPathFromRoot();
82         if (prefixedPath == null) {
83             LOG.debug("Schema path {} has null path", schemaPath);
84             return null;
85         }
86
87         LOG.trace("Looking for path {} in context {}", schemaPath, context);
88         return findNodeInSchemaContext(context, prefixedPath);
89     }
90
91     /**
92      * Method attempts to find DataSchemaNode inside of provided Schema Context
93      * and Yang Module accordingly to Non-conditional Revision Aware XPath. The
94      * specified Module MUST be present in Schema Context otherwise the
95      * operation would fail and return <code>null</code>. <br>
96      * The Revision Aware XPath MUST be specified WITHOUT the conditional
97      * statement (i.e. without [cond]) in path, because in this state the Schema
98      * Context is completely unaware of data state and will be not able to
99      * properly resolve XPath. If the XPath contains condition the method will
100      * return IllegalArgumentException. <br>
101      * In case that Schema Context or Module or Revision Aware XPath contains
102      * <code>null</code> references the method will throw
103      * IllegalArgumentException <br>
104      * If the Revision Aware XPath is correct and desired Data Schema Node is
105      * present in Yang module or in depending module in Schema Context the
106      * method will return specified Data Schema Node, otherwise the operation
107      * will fail and method will return <code>null</code>.
108      *
109      * @throws IllegalArgumentException
110      *
111      * @param context
112      *            Schema Context
113      * @param module
114      *            Yang Module
115      * @param nonCondXPath
116      *            Non Conditional Revision Aware XPath
117      * @return Returns Data Schema Node for specified Schema Context for given
118      *         Non-conditional Revision Aware XPath, or <code>null</code> if the
119      *         DataSchemaNode is not present in Schema Context.
120      */
121     public static SchemaNode findDataSchemaNode(final SchemaContext context, final Module module, final RevisionAwareXPath nonCondXPath) {
122         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
123         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
124         Preconditions.checkArgument(nonCondXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
125
126         final String strXPath = nonCondXPath.toString();
127         if (strXPath != null) {
128             Preconditions.checkArgument(strXPath.indexOf('[') == -1, "Revision Aware XPath may not contain a condition");
129             if (nonCondXPath.isAbsolute()) {
130                 final List<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
131                 if (qnamedPath != null) {
132                     return findNodeInSchemaContext(context, qnamedPath);
133                 }
134             }
135         }
136         return null;
137     }
138
139     /**
140      * Method attempts to find DataSchemaNode inside of provided Schema Context
141      * and Yang Module accordingly to Non-conditional relative Revision Aware
142      * XPath. The specified Module MUST be present in Schema Context otherwise
143      * the operation would fail and return <code>null</code>. <br>
144      * The relative Revision Aware XPath MUST be specified WITHOUT the
145      * conditional statement (i.e. without [cond]) in path, because in this
146      * state the Schema Context is completely unaware of data state and will be
147      * not able to properly resolve XPath. If the XPath contains condition the
148      * method will return IllegalArgumentException. <br>
149      * The Actual Schema Node MUST be specified correctly because from this
150      * Schema Node will search starts. If the Actual Schema Node is not correct
151      * the operation will simply fail, because it will be unable to find desired
152      * DataSchemaNode. <br>
153      * In case that Schema Context or Module or Actual Schema Node or relative
154      * Revision Aware XPath contains <code>null</code> references the method
155      * will throw IllegalArgumentException <br>
156      * If the Revision Aware XPath doesn't have flag
157      * <code>isAbsolute == false</code> the method will throw
158      * IllegalArgumentException. <br>
159      * If the relative Revision Aware XPath is correct and desired Data Schema
160      * Node is present in Yang module or in depending module in Schema Context
161      * the method will return specified Data Schema Node, otherwise the
162      * operation will fail and method will return <code>null</code>.
163      *
164      * @throws IllegalArgumentException
165      *
166      * @param context
167      *            Schema Context
168      * @param module
169      *            Yang Module
170      * @param actualSchemaNode
171      *            Actual Schema Node
172      * @param relativeXPath
173      *            Relative Non Conditional Revision Aware XPath
174      * @return DataSchemaNode if is present in specified Schema Context for
175      *         given relative Revision Aware XPath, otherwise will return
176      *         <code>null</code>.
177      */
178     public static SchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
179             final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
180         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
181         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
182         Preconditions.checkArgument(actualSchemaNode != null, "Actual Schema Node reference cannot be NULL");
183         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
184         Preconditions.checkState(!relativeXPath.isAbsolute(),
185                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
186                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
187
188         final SchemaPath actualNodePath = actualSchemaNode.getPath();
189         if (actualNodePath != null) {
190             final Iterable<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
191
192             if (qnamePath != null) {
193                 return findNodeInSchemaContext(context, qnamePath);
194             }
195         }
196         return null;
197     }
198
199     /**
200      * Returns parent Yang Module for specified Schema Context in which Schema
201      * Node is declared. If the Schema Node is not present in Schema Context the
202      * operation will return <code>null</code>. <br>
203      * If Schema Context or Schema Node contains <code>null</code> references
204      * the method will throw IllegalArgumentException
205      *
206      * @throws IllegalArgumentException
207      *
208      * @param context
209      *            Schema Context
210      * @param schemaNode
211      *            Schema Node
212      * @return Yang Module for specified Schema Context and Schema Node, if
213      *         Schema Node is NOT present, the method will returns
214      *         <code>null</code>
215      */
216     public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
217         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL!");
218         Preconditions.checkArgument(schemaNode != null, "Schema Node cannot be NULL!");
219         Preconditions.checkState(schemaNode.getPath() != null, "Schema Path for Schema Node is not "
220                 + "set properly (Schema Path is NULL)");
221
222         final QName qname = Iterables.getFirst(schemaNode.getPath().getPathTowardsRoot(), null);
223         Preconditions.checkState(qname != null,
224                 "Schema Path contains invalid state of path parts. " +
225                         "The Schema Path MUST contain at least ONE QName which defines namespace and Local name of path.");
226         return context.findModuleByNamespaceAndRevision(qname.getNamespace(), qname.getRevision());
227     }
228
229     public static SchemaNode findNodeInSchemaContext(final SchemaContext context, final Iterable<QName> path) {
230         final QName current = path.iterator().next();
231
232         LOG.trace("Looking up module {} in context {}", current, path);
233         final Module module = context.findModuleByNamespaceAndRevision(current.getNamespace(), current.getRevision());
234         if (module == null) {
235             LOG.debug("Module {} not found", current);
236             return null;
237         }
238
239         return findNodeInModule(module, path);
240     }
241
242     /**
243      * Returns NotificationDefinition from Schema Context
244      *
245      * @param schema SchemaContext in which lookup should be performed.
246      * @param path Schema Path of notification
247      * @return Notification schema or null, if notification is not present in schema context.
248      */
249     @Beta
250     public static @Nullable NotificationDefinition getNotificationSchema(@Nonnull final SchemaContext schema,@Nonnull  final SchemaPath path) {
251         Preconditions.checkNotNull(schema, "Schema context must not be null.");
252         Preconditions.checkNotNull(path, "Schema path must not be null.");
253         for (final NotificationDefinition potential : schema.getNotifications()) {
254             if (path.equals(potential.getPath())) {
255                return potential;
256             }
257         }
258         return null;
259     }
260
261     /**
262      * Returns RPC Input or Output Data container from RPC definition.
263      *
264      * @param schema SchemaContext in which lookup should be performed.
265      * @param path Schema path of RPC input/output data container
266      * @return Notification schema or null, if notification is not present in schema context.
267      */
268     @Beta
269     public static @Nullable ContainerSchemaNode getRpcDataSchema(@Nonnull final SchemaContext schema,@Nonnull  final SchemaPath path) {
270         Preconditions.checkNotNull(schema, "Schema context must not be null.");
271         Preconditions.checkNotNull(path, "Schema path must not be null.");
272         final Iterator<QName> it = path.getPathFromRoot().iterator();
273         Preconditions.checkArgument(it.hasNext(), "Rpc must have QName.");
274         final QName rpcName = it.next();
275         Preconditions.checkArgument(it.hasNext(), "input or output must be part of path.");
276         final QName inOrOut = it.next();
277         for (final RpcDefinition potential : schema.getOperations()) {
278             if (rpcName.equals(potential.getQName())) {
279                return SchemaNodeUtils.getRpcDataSchema(potential, inOrOut);
280             }
281         }
282         return null;
283     }
284
285     private static SchemaNode findNodeInModule(final Module module, final Iterable<QName> path) {
286
287         Preconditions.checkArgument(module != null, "Parent reference cannot be NULL");
288         Preconditions.checkArgument(path != null, "Path reference cannot be NULL");
289
290         if (!path.iterator().hasNext()) {
291             LOG.debug("No node matching {} found in node {}", path, module);
292             return null;
293         }
294
295         final QName current = path.iterator().next();
296         LOG.trace("Looking for node {} in module {}", current, module);
297
298         SchemaNode foundNode = null;
299         final Iterable<QName> nextPath = nextLevel(path);
300
301         foundNode = module.getDataChildByName(current);
302         if (foundNode != null && nextPath.iterator().hasNext()) {
303             foundNode = findNodeIn(foundNode, nextPath);
304         }
305
306         if (foundNode == null) {
307             foundNode = getGroupingByName(module, current);
308             if (foundNode != null && nextPath.iterator().hasNext()) {
309                 foundNode = findNodeIn(foundNode, nextPath);
310             }
311         }
312
313         if (foundNode == null) {
314             foundNode = getRpcByName(module, current);
315             if (foundNode != null && nextPath.iterator().hasNext()) {
316                 foundNode = findNodeIn(foundNode, nextPath);
317             }
318         }
319
320         if (foundNode == null) {
321             foundNode = getNotificationByName(module, current);
322             if (foundNode != null && nextPath.iterator().hasNext()) {
323                 foundNode = findNodeIn(foundNode, nextPath);
324             }
325         }
326
327         if (foundNode == null) {
328             LOG.debug("No node matching {} found in node {}", path, module);
329         }
330
331         return foundNode;
332
333     }
334
335     private static SchemaNode findNodeIn(final SchemaNode parent, final Iterable<QName> path) {
336
337         Preconditions.checkArgument(parent != null, "Parent reference cannot be NULL");
338         Preconditions.checkArgument(path != null, "Path reference cannot be NULL");
339
340         if (!path.iterator().hasNext()) {
341             LOG.debug("No node matching {} found in node {}", path, parent);
342             return null;
343         }
344
345         final QName current = path.iterator().next();
346         LOG.trace("Looking for node {} in node {}", current, parent);
347
348         SchemaNode foundNode = null;
349         final Iterable<QName> nextPath = nextLevel(path);
350
351         if (parent instanceof DataNodeContainer) {
352             final DataNodeContainer parentDataNodeContainer = (DataNodeContainer) parent;
353
354             foundNode = parentDataNodeContainer.getDataChildByName(current);
355             if (foundNode != null && nextPath.iterator().hasNext()) {
356                 foundNode = findNodeIn(foundNode, nextPath);
357             }
358
359             if (foundNode == null) {
360                 foundNode = getGroupingByName(parentDataNodeContainer, current);
361                 if (foundNode != null && nextPath.iterator().hasNext()) {
362                     foundNode = findNodeIn(foundNode, nextPath);
363                 }
364             }
365         }
366
367         if (foundNode == null && parent instanceof RpcDefinition) {
368             final RpcDefinition parentRpcDefinition = (RpcDefinition) parent;
369
370             if (current.getLocalName().equals("input")) {
371                 foundNode = parentRpcDefinition.getInput();
372                 if (foundNode != null && nextPath.iterator().hasNext()) {
373                     foundNode = findNodeIn(foundNode, nextPath);
374                 }
375             }
376
377             if (current.getLocalName().equals("output")) {
378                 foundNode = parentRpcDefinition.getOutput();
379                 if (foundNode != null && nextPath.iterator().hasNext()) {
380                     foundNode = findNodeIn(foundNode, nextPath);
381                 }
382             }
383
384             if (foundNode == null) {
385                 foundNode = getGroupingByName(parentRpcDefinition, current);
386                 if (foundNode != null && nextPath.iterator().hasNext()) {
387                     foundNode = findNodeIn(foundNode, nextPath);
388                 }
389             }
390         }
391
392         if (foundNode == null && parent instanceof ChoiceSchemaNode) {
393             foundNode = ((ChoiceSchemaNode) parent).getCaseNodeByName(current);
394             if (foundNode != null && nextPath.iterator().hasNext()) {
395                 foundNode = findNodeIn(foundNode, nextPath);
396             }
397         }
398
399         if (foundNode == null) {
400             LOG.debug("No node matching {} found in node {}", path, parent);
401         }
402
403         return foundNode;
404
405     }
406
407     private static Iterable<QName> nextLevel(final Iterable<QName> path) {
408         return Iterables.skip(path, 1);
409     }
410
411     private static RpcDefinition getRpcByName(final Module module, final QName name) {
412         for (final RpcDefinition rpc : module.getRpcs()) {
413             if (rpc.getQName().equals(name)) {
414                 return rpc;
415             }
416         }
417         return null;
418     }
419
420     private static NotificationDefinition getNotificationByName(final Module module, final QName name) {
421         for (final NotificationDefinition notification : module.getNotifications()) {
422             if (notification.getQName().equals(name)) {
423                 return notification;
424             }
425         }
426         return null;
427     }
428
429     private static GroupingDefinition getGroupingByName(final DataNodeContainer dataNodeContainer, final QName name) {
430         for (final GroupingDefinition grouping : dataNodeContainer.getGroupings()) {
431             if (grouping.getQName().equals(name)) {
432                 return grouping;
433             }
434         }
435         return null;
436     }
437
438     private static GroupingDefinition getGroupingByName(final RpcDefinition rpc, final QName name) {
439         for (final GroupingDefinition grouping : rpc.getGroupings()) {
440             if (grouping.getQName().equals(name)) {
441                 return grouping;
442             }
443         }
444         return null;
445     }
446
447     /**
448      * Transforms string representation of XPath to Queue of QNames. The XPath
449      * is split by "/" and for each part of XPath is assigned correct module in
450      * Schema Path. <br>
451      * If Schema Context, Parent Module or XPath string contains
452      * <code>null</code> values, the method will throws IllegalArgumentException
453      *
454      * @throws IllegalArgumentException
455      *
456      * @param context
457      *            Schema Context
458      * @param parentModule
459      *            Parent Module
460      * @param xpath
461      *            XPath String
462      * @return return a list of QName
463      */
464     private static List<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule, final String xpath) {
465         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
466         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
467         Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
468
469         final List<QName> path = new LinkedList<QName>();
470         for (final String pathComponent : SLASH_SPLITTER.split(xpath)) {
471             if (!pathComponent.isEmpty()) {
472                 path.add(stringPathPartToQName(context, parentModule, pathComponent));
473             }
474         }
475         return path;
476     }
477
478     /**
479      * Transforms part of Prefixed Path as java String to QName. <br>
480      * If the string contains module prefix separated by ":" (i.e.
481      * mod:container) this module is provided from from Parent Module list of
482      * imports. If the Prefixed module is present in Schema Context the QName
483      * can be constructed. <br>
484      * If the Prefixed Path Part does not contains prefix the Parent's Module
485      * namespace is taken for construction of QName. <br>
486      * If Schema Context, Parent Module or Prefixed Path Part refers to
487      * <code>null</code> the method will throw IllegalArgumentException
488      *
489      * @throws IllegalArgumentException
490      *
491      * @param context
492      *            Schema Context
493      * @param parentModule
494      *            Parent Module
495      * @param prefixedPathPart
496      *            Prefixed Path Part string
497      * @return QName from prefixed Path Part String.
498      */
499     private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule, final String prefixedPathPart) {
500         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
501         Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
502         Preconditions.checkArgument(prefixedPathPart != null, "Prefixed Path Part cannot be NULL!");
503
504         if (prefixedPathPart.indexOf(':') != -1) {
505             final Iterator<String> prefixedName = COLON_SPLITTER.split(prefixedPathPart).iterator();
506             final String modulePrefix = prefixedName.next();
507
508             final Module module = resolveModuleForPrefix(context, parentModule, modulePrefix);
509             Preconditions.checkArgument(module != null, "Failed to resolve xpath: no module found for prefix %s in module %s",
510                     modulePrefix, parentModule.getName());
511
512             return QName.create(module.getQNameModule(), prefixedName.next());
513         } else {
514             return QName.create(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
515         }
516     }
517
518     /**
519      * Method will attempt to resolve and provide Module reference for specified
520      * module prefix. Each Yang module could contains multiple imports which
521      * MUST be associated with corresponding module prefix. The method simply
522      * looks into module imports and returns the module that is bounded with
523      * specified prefix. If the prefix is not present in module or the prefixed
524      * module is not present in specified Schema Context, the method will return
525      * <code>null</code>. <br>
526      * If String prefix is the same as prefix of the specified Module the
527      * reference to this module is returned. <br>
528      * If Schema Context, Module or Prefix are referring to <code>null</code>
529      * the method will return IllegalArgumentException
530      *
531      * @throws IllegalArgumentException
532      *
533      * @param context
534      *            Schema Context
535      * @param module
536      *            Yang Module
537      * @param prefix
538      *            Module Prefix
539      * @return Module for given prefix in specified Schema Context if is
540      *         present, otherwise returns <code>null</code>
541      */
542     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
543         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
544         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
545         Preconditions.checkArgument(prefix != null, "Prefix string cannot be NULL");
546
547         if (prefix.equals(module.getPrefix())) {
548             return module;
549         }
550
551         final Set<ModuleImport> imports = module.getImports();
552         for (final ModuleImport mi : imports) {
553             if (prefix.equals(mi.getPrefix())) {
554                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
555             }
556         }
557         return null;
558     }
559
560     /**
561      * @throws IllegalArgumentException
562      *
563      * @param context
564      *            Schema Context
565      * @param module
566      *            Yang Module
567      * @param relativeXPath
568      *            Non conditional Revision Aware Relative XPath
569      * @param actualSchemaNode
570      *            actual schema node
571      * @return list of QName
572      */
573     private static Iterable<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
574             final RevisionAwareXPath relativeXPath, final SchemaNode actualSchemaNode) {
575         Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL");
576         Preconditions.checkArgument(module != null, "Module reference cannot be NULL");
577         Preconditions.checkArgument(relativeXPath != null, "Non Conditional Revision Aware XPath cannot be NULL");
578         Preconditions.checkState(!relativeXPath.isAbsolute(),
579                 "Revision Aware XPath MUST be relative i.e. MUST contains ../, "
580                         + "for non relative Revision Aware XPath use findDataSchemaNode method");
581         Preconditions.checkState(actualSchemaNode.getPath() != null,
582                 "Schema Path reference for Leafref cannot be NULL");
583
584         final Iterable<String> xpaths = SLASH_SPLITTER.split(relativeXPath.toString());
585
586         // Find out how many "parent" components there are
587         // FIXME: is .contains() the right check here?
588         // FIXME: case ../../node1/node2/../node3/../node4
589         int colCount = 0;
590         for (final Iterator<String> it = xpaths.iterator(); it.hasNext() && it.next().contains(".."); ) {
591             ++colCount;
592         }
593
594         final Iterable<QName> schemaNodePath = actualSchemaNode.getPath().getPathFromRoot();
595
596         if (Iterables.size(schemaNodePath) - colCount >= 0) {
597             return Iterables.concat(Iterables.limit(schemaNodePath, Iterables.size(schemaNodePath) - colCount),
598                     Iterables.transform(Iterables.skip(xpaths, colCount), new Function<String, QName>() {
599                         @Override
600                         public QName apply(final String input) {
601                             return stringPathPartToQName(context, module, input);
602                         }
603                     }));
604         }
605         return Iterables.concat(schemaNodePath,
606                 Iterables.transform(Iterables.skip(xpaths, colCount), new Function<String, QName>() {
607                     @Override
608                     public QName apply(final String input) {
609                         return stringPathPartToQName(context, module, input);
610                     }
611                 }));
612     }
613
614     /**
615      * Extracts the base type of node on which schema node points to. If target node is again of type LeafrefTypeDefinition, methods will be call recursively until it reach concrete
616      * type definition.
617      *
618      * @param typeDefinition
619      *            type of node which will be extracted
620      * @param schemaContext
621      *            Schema Context
622      * @param schema
623      *            Schema Node
624      * @return recursively found type definition this leafref is pointing to or null if the xpath is incorrect (null is there to preserve backwards compatibility)
625      */
626     public static TypeDefinition<?> getBaseTypeForLeafRef(final LeafrefTypeDefinition typeDefinition, final SchemaContext schemaContext, final SchemaNode schema) {
627         RevisionAwareXPath pathStatement = typeDefinition.getPathStatement();
628         pathStatement = new RevisionAwareXPathImpl(stripConditionsFromXPathString(pathStatement), pathStatement.isAbsolute());
629
630         Module parentModule = findParentModuleByType(schemaContext, schema);
631
632         final DataSchemaNode dataSchemaNode;
633         if(pathStatement.isAbsolute()) {
634             dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(schemaContext, parentModule, pathStatement);
635         } else {
636             dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemaContext, parentModule, schema, pathStatement);
637         }
638
639         // FIXME this is just to preserve backwards compatibility since yangtools do not mind wrong leafref xpaths
640         // and current expected behaviour for such cases is to just use pure string
641         // This should throw an exception about incorrect XPath in leafref
642         if(dataSchemaNode == null) {
643             return null;
644         }
645
646         final TypeDefinition<?> targetTypeDefinition = typeDefinition(dataSchemaNode);
647
648         if(targetTypeDefinition instanceof LeafrefTypeDefinition) {
649             return getBaseTypeForLeafRef(((LeafrefTypeDefinition) targetTypeDefinition), schemaContext, dataSchemaNode);
650         } else {
651             return targetTypeDefinition;
652         }
653     }
654
655     /**
656      * Returns parent Yang Module for specified Schema Context in which Schema
657      * Node is declared. If Schema Node is of type 'ExtendedType' it tries to find parent module
658      * in which the type was originally declared (needed for correct leafref path resolution). <br>
659      * If the Schema Node is not present in Schema Context the
660      * operation will return <code>null</code>. <br>
661      * If Schema Context or Schema Node contains <code>null</code> references
662      * the method will throw IllegalArgumentException
663      *
664      * @throws IllegalArgumentException
665      *
666      * @param schemaContext
667      *            Schema Context
668      * @param schemaNode
669      *            Schema Node
670      * @return Yang Module for specified Schema Context and Schema Node, if
671      *         Schema Node is NOT present, the method will returns
672      *         <code>null</code>
673      */
674     public static Module findParentModuleByType(final SchemaContext schemaContext, final SchemaNode schemaNode) {
675         Preconditions.checkArgument(schemaContext != null, "Schema Context reference cannot be NULL!");
676         Preconditions.checkArgument(schemaNode != null, "Schema Node cannot be NULL!");
677         TypeDefinition<?> nodeType = null;
678
679         if (schemaNode instanceof LeafSchemaNode) {
680             nodeType = ((LeafSchemaNode) schemaNode).getType();
681         } else if (schemaNode instanceof LeafListSchemaNode) {
682             nodeType = ((LeafListSchemaNode) schemaNode).getType();
683         }
684
685         if (nodeType instanceof ExtendedType) {
686             while (nodeType.getBaseType() instanceof ExtendedType) {
687                 nodeType = nodeType.getBaseType();
688             }
689
690             QNameModule typeDefModuleQname = nodeType.getQName().getModule();
691
692             return schemaContext.findModuleByNamespaceAndRevision(typeDefModuleQname.getNamespace(),
693                     typeDefModuleQname.getRevision());
694         }
695
696         return SchemaContextUtil.findParentModule(schemaContext, schemaNode);
697     }
698
699     /**
700      * Returns base type for {@code typeDefinition} which belongs to module specified via {@code qName}. This handle case
701      * when leafref type isn't specified as type substatement of leaf or leaf-list but is defined in other module as typedef
702      * which is then imported to referenced module.
703      *
704      * Because {@code typeDefinition} is definied via typedef statement, only absolute path is meaningful.
705      *
706      * @param typeDefinition
707      * @param schemaContext
708      * @param qName
709      * @return
710      */
711     public static TypeDefinition<?> getBaseTypeForLeafRef(final LeafrefTypeDefinition typeDefinition,
712             final SchemaContext schemaContext, final QName qName) {
713         final RevisionAwareXPath pathStatement = typeDefinition.getPathStatement();
714         final RevisionAwareXPath strippedPathStatement = new RevisionAwareXPathImpl(stripConditionsFromXPathString(pathStatement), pathStatement.isAbsolute());
715         if (!strippedPathStatement.isAbsolute()) {
716             return null;
717         }
718
719         final Module parentModule = schemaContext.findModuleByNamespaceAndRevision(qName.getNamespace(),qName.getRevision());
720         final DataSchemaNode dataSchemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(schemaContext, parentModule, strippedPathStatement);
721         final TypeDefinition<?> targetTypeDefinition = typeDefinition(dataSchemaNode);
722         if (targetTypeDefinition instanceof LeafrefTypeDefinition) {
723             return getBaseTypeForLeafRef(((LeafrefTypeDefinition) targetTypeDefinition), schemaContext, dataSchemaNode);
724         } else {
725             return targetTypeDefinition;
726         }
727     }
728
729     /**
730      * Removes conditions from xPath pointed to target node.
731      *
732      * @param pathStatement
733      *            xPath to target node
734      * @return string representation of xPath without conditions
735      *
736      */
737     private static String stripConditionsFromXPathString(final RevisionAwareXPath pathStatement) {
738         return pathStatement.toString().replaceAll("\\[.*\\]", "");
739     }
740
741     /**
742      * Extracts the base type of leaf schema node until it reach concrete type of TypeDefinition.
743      *
744      * @param node
745      *            a node representing LeafSchemaNode
746      * @return concrete type definition of node value
747      */
748     private static TypeDefinition<? extends Object> typeDefinition(final LeafSchemaNode node) {
749         TypeDefinition<?> baseType = node.getType();
750         while (baseType.getBaseType() != null) {
751             baseType = baseType.getBaseType();
752         }
753         return baseType;
754     }
755
756     /**
757      * Extracts the base type of leaf schema node until it reach concrete type of TypeDefinition.
758      *
759      * @param node
760      *            a node representing LeafListSchemaNode
761      * @return concrete type definition of node value
762      */
763     private static TypeDefinition<? extends Object> typeDefinition(final LeafListSchemaNode node) {
764         TypeDefinition<?> baseType = node.getType();
765         while (baseType.getBaseType() != null) {
766             baseType = baseType.getBaseType();
767         }
768         return baseType;
769     }
770
771     /**
772      * Gets the base type of DataSchemaNode value.
773      *
774      * @param node
775      *            a node representing DataSchemaNode
776      * @return concrete type definition of node value
777      */
778     private static TypeDefinition<? extends Object> typeDefinition(final DataSchemaNode node) {
779         if (node instanceof LeafListSchemaNode) {
780             return typeDefinition((LeafListSchemaNode) node);
781         } else if (node instanceof LeafSchemaNode) {
782             return typeDefinition((LeafSchemaNode) node);
783         } else {
784             throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.<Object> asList(node).toString());
785         }
786     }
787 }