Replaced 'controller' with 'yangtools' in all package names.
[yangtools.git] / 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 java.net.URI;
11 import java.util.LinkedList;
12 import java.util.List;
13 import java.util.Queue;
14 import java.util.Set;
15
16 import org.opendaylight.yangtools.yang.common.QName;
17 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
18 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
19 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
20 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
21 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
22 import org.opendaylight.yangtools.yang.model.api.Module;
23 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
24 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
25 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
26 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
28 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
29
30 /**
31  * The Schema Context Util contains support methods for searching through Schema Context modules for specified schema
32  * nodes via Schema Path or Revision Aware XPath. The Schema Context Util is designed as mixin,
33  * so it is not instantiable.
34  *
35  * @author Lukas Sedlak <lsedlak@cisco.com>
36  */
37 public final class SchemaContextUtil {
38
39     private SchemaContextUtil() {
40     }
41
42     /**
43      * Method attempts to find DataSchemaNode in Schema Context via specified Schema Path. The returned
44      * DataSchemaNode from method will be the node at the end of the SchemaPath. If the DataSchemaNode is not present
45      * in the Schema Context the method will return <code>null</code>.
46      * <br>
47      * In case that Schema Context or Schema Path are not specified correctly (i.e. contains <code>null</code>
48      * values) the method will return IllegalArgumentException.
49      *
50      * @throws IllegalArgumentException
51      *
52      * @param context
53      *            Schema Context
54      * @param schemaPath
55      *            Schema Path to search for
56      * @return DataSchemaNode from the end of the Schema Path or
57      *         <code>null</code> if the Node is not present.
58      */
59     public static DataSchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
60         if (context == null) {
61             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
62         }
63         if (schemaPath == null) {
64             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
65         }
66
67         final Module module = resolveModuleFromSchemaPath(context, schemaPath);
68         final Queue<QName> prefixedPath = new LinkedList<>(schemaPath.getPath());
69
70         if ((module != null) && (prefixedPath != null)) {
71             return findSchemaNodeForGivenPath(context, module, prefixedPath);
72         }
73         return null;
74     }
75
76     /**
77      * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
78      * Non-conditional Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise the
79      * operation would fail and return <code>null</code>.
80      * <br>
81      * The Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in path,
82      * because in this state the Schema Context is completely unaware of data state and will be not able to properly
83      * resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
84      * <br>
85      * In case that Schema Context or Module or Revision Aware XPath contains <code>null</code> references the method
86      * will throw IllegalArgumentException
87      * <br>
88      * If the Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in depending
89      * module in Schema Context the method will return specified Data Schema Node, otherwise the operation will fail
90      * and method will return <code>null</code>.
91      *
92      * @throws IllegalArgumentException
93      *
94      * @param context Schema Context
95      * @param module Yang Module
96      * @param nonCondXPath Non Conditional Revision Aware XPath
97      * @return Returns Data Schema Node for specified Schema Context for given Non-conditional Revision Aware XPath,
98      * or <code>null</code> if the DataSchemaNode is not present in Schema Context.
99      */
100     public static DataSchemaNode findDataSchemaNode(final SchemaContext context, final Module module,
101             final RevisionAwareXPath nonCondXPath) {
102         if (context == null) {
103             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
104         }
105         if (module == null) {
106             throw new IllegalArgumentException("Module reference cannot be NULL!");
107         }
108         if (nonCondXPath == null) {
109             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
110         }
111
112         final String strXPath = nonCondXPath.toString();
113         if (strXPath != null) {
114             if (strXPath.contains("[")) {
115                 throw new IllegalArgumentException("Revision Aware XPath cannot contains condition!");
116             }
117             if (nonCondXPath.isAbsolute()) {
118                 final Queue<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
119                 if (qnamedPath != null) {
120                     final DataSchemaNode dataNode = findSchemaNodeForGivenPath(context, module, qnamedPath);
121                     return dataNode;
122                 }
123             }
124         }
125         return null;
126     }
127
128     /**
129      * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
130      * Non-conditional relative Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise
131      * the operation would fail and return <code>null</code>.
132      * <br>
133      * The relative Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in
134      * path, because in this state the Schema Context is completely unaware of data state and will be not able to
135      * properly resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
136      * <br>
137      * The Actual Schema Node MUST be specified correctly because from this Schema Node will search starts. If the
138      * Actual Schema Node is not correct the operation will simply fail, because it will be unable to find desired
139      * DataSchemaNode.
140      * <br>
141      * In case that Schema Context or Module or Actual Schema Node or relative Revision Aware XPath contains
142      * <code>null</code> references the method will throw IllegalArgumentException
143      * <br>
144      * If the Revision Aware XPath doesn't have flag <code>isAbsolute == false</code> the method will
145      * throw IllegalArgumentException.
146      * <br>
147      * If the relative Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in
148      * depending module in Schema Context the method will return specified Data Schema Node,
149      * otherwise the operation will fail
150      * and method will return <code>null</code>.
151      *
152      * @throws IllegalArgumentException
153      *
154      * @param context Schema Context
155      * @param module Yang Module
156      * @param actualSchemaNode Actual Schema Node
157      * @param relativeXPath Relative Non Conditional Revision Aware XPath
158      * @return DataSchemaNode if is present in specified Schema Context for given relative Revision Aware XPath,
159      * otherwise will return <code>null</code>.
160      */
161     public static DataSchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
162             final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
163         if (context == null) {
164             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
165         }
166         if (module == null) {
167             throw new IllegalArgumentException("Module reference cannot be NULL!");
168         }
169         if (actualSchemaNode == null) {
170             throw new IllegalArgumentException("Actual Schema Node reference cannot be NULL!");
171         }
172         if (relativeXPath == null) {
173             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
174         }
175         if (relativeXPath.isAbsolute()) {
176             throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
177                     + "for non relative Revision Aware XPath use findDataSchemaNode method!");
178         }
179
180         final SchemaPath actualNodePath = actualSchemaNode.getPath();
181         if (actualNodePath != null) {
182             final Queue<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualNodePath);
183
184             if (qnamePath != null) {
185                 final DataSchemaNode dataNode = findSchemaNodeForGivenPath(context, module, qnamePath);
186                 return dataNode;
187             }
188         }
189         return null;
190     }
191
192     /**
193      * Retrieve information from Schema Path and returns the module reference to which Schema Node belongs. The
194      * search for correct Module is based on namespace within the last item in Schema Path. If schema context
195      * contains module with namespace specified in last item of Schema Path, then operation will returns Module
196      * reference, otherwise returns <code>null</code>
197      * <br>
198      * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
199      *
200      * @throws IllegalArgumentException
201      *
202      * @param context Schema Context
203      * @param schemaPath Schema Path
204      * @return Module reference for given Schema Path if module is present in Schema Context,
205      * otherwise returns <code>null</code>
206      */
207     private static Module resolveModuleFromSchemaPath(final SchemaContext context, final SchemaPath schemaPath) {
208         if (context == null) {
209             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
210         }
211         if (schemaPath == null) {
212             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
213         }
214
215         final List<QName> path = schemaPath.getPath();
216         if (!path.isEmpty()) {
217             final QName qname = path.get(path.size() - 1);
218
219             if ((qname != null) && (qname.getNamespace() != null)) {
220                 return context.findModuleByNamespace(qname.getNamespace());
221             }
222         }
223
224         return null;
225     }
226
227     /**
228      * Returns the Yang Module from specified Schema Context in which the TypeDefinition is declared. If the
229      * TypeDefinition si not present in Schema Context then the method will return <code>null</code>
230      *
231      * If Schema Context or TypeDefinition contains <code>null</code> references the method will throw IllegalArgumentException
232      *
233      * @throws IllegalArgumentException
234      *
235      * @param context Schema Context
236      * @param type Type Definition
237      * @return Yang Module in which the TypeDefinition is declared, if is not present, returns <code>null</code>.
238      */
239     public static Module findParentModuleForTypeDefinition(final SchemaContext context, final TypeDefinition<?> type) {
240         final SchemaPath schemaPath = type.getPath();
241         if (schemaPath == null) {
242             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
243         }
244         final List<QName> qnamedPath = schemaPath.getPath();
245         if (qnamedPath == null || qnamedPath.isEmpty()) {
246             throw new IllegalStateException("Schema Path contains invalid state of path parts."
247                     + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
248                     + "of path.");
249         }
250
251         if (type instanceof ExtendedType) {
252             final QName qname = qnamedPath.get(qnamedPath.size() - 1);
253             if ((qname != null) && (qname.getNamespace() != null)) {
254                 return context.findModuleByNamespace(qname.getNamespace());
255             }
256         } else {
257             final QName qname = qnamedPath.get(qnamedPath.size() - 2);
258             if ((qname != null) && (qname.getNamespace() != null)) {
259                 return context.findModuleByNamespace(qname.getNamespace());
260             }
261         }
262         return null;
263     }
264
265     /**
266      * Returns parent Yang Module for specified Schema Context in which Schema Node is declared. If the Schema Node
267      * is not present in Schema Context the operation will return <code>null</code>.
268      * <br>
269      * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
270      *
271      * @throws IllegalArgumentException
272      *
273      * @param context Schema Context
274      * @param schemaNode Schema Node
275      * @return Yang Module for specified Schema Context and Schema Node, if Schema Node is NOT present,
276      * the method will returns <code>null</code>
277      */
278     public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
279         if (context == null) {
280             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
281         }
282         if (schemaNode == null) {
283             throw new IllegalArgumentException("Schema Node cannot be NULL!");
284         }
285
286         final SchemaPath schemaPath = schemaNode.getPath();
287         if (schemaPath == null) {
288             throw new IllegalStateException("Schema Path for Schema Node is not "
289                     + "set properly (Schema Path is NULL)");
290         }
291         final List<QName> qnamedPath = schemaPath.getPath();
292         if (qnamedPath == null || qnamedPath.isEmpty()) {
293             throw new IllegalStateException("Schema Path contains invalid state of path parts."
294                     + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
295                     + "of path.");
296         }
297         final QName qname = qnamedPath.get(qnamedPath.size() - 1);
298         return context.findModuleByNamespace(qname.getNamespace());
299     }
300
301     /**
302      * Method will attempt to find DataSchemaNode from specified Module and Queue of QNames through the Schema
303      * Context. The QNamed path could be defined across multiple modules in Schema Context so the method is called
304      * recursively. If the QNamed path contains QNames that are not part of any Module or Schema Context Path the
305      * operation will fail and returns <code>null</code>
306      * <br>
307      * If Schema Context, Module or Queue of QNames refers to <code>null</code> values,
308      * the method will throws IllegalArgumentException
309      *
310      * @throws IllegalArgumentException
311      *
312      * @param context Schema Context
313      * @param module Yang Module
314      * @param qnamedPath Queue of QNames
315      * @return DataSchemaNode if is present in Module(s) for specified Schema Context and given QNamed Path,
316      * otherwise will return <code>null</code>.
317      */
318     private static DataSchemaNode findSchemaNodeForGivenPath(final SchemaContext context, final Module module,
319             final Queue<QName> qnamedPath) {
320         if (context == null) {
321             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
322         }
323         if (module == null) {
324             throw new IllegalArgumentException("Module reference cannot be NULL!");
325         }
326         if (module.getNamespace() == null) {
327             throw new IllegalArgumentException("Namespace for Module cannot contains NULL reference!");
328         }
329         if (qnamedPath == null || qnamedPath.isEmpty()) {
330             throw new IllegalStateException("Schema Path contains invalid state of path parts."
331                     + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
332                     + "of path.");
333         }
334
335         DataNodeContainer nextNode = module;
336         final URI moduleNamespace = module.getNamespace();
337
338         QName childNodeQName;
339         DataSchemaNode schemaNode = null;
340         while ((nextNode != null) && !qnamedPath.isEmpty()) {
341             childNodeQName = qnamedPath.peek();
342             if (childNodeQName != null) {
343                 final URI childNodeNamespace = childNodeQName.getNamespace();
344
345                 schemaNode = nextNode.getDataChildByName(childNodeQName);
346                 if (schemaNode != null) {
347                     if (schemaNode instanceof ContainerSchemaNode) {
348                         nextNode = (ContainerSchemaNode) schemaNode;
349                     } else if (schemaNode instanceof ListSchemaNode) {
350                         nextNode = (ListSchemaNode) schemaNode;
351                     } else if (schemaNode instanceof ChoiceNode) {
352                         final ChoiceNode choice = (ChoiceNode) schemaNode;
353                         qnamedPath.poll();
354                         if (!qnamedPath.isEmpty()) {
355                             childNodeQName = qnamedPath.peek();
356                             nextNode = choice.getCaseNodeByName(childNodeQName);
357                             schemaNode = (DataSchemaNode) nextNode;
358                         }
359                     } else {
360                         nextNode = null;
361                     }
362                 } else if (!childNodeNamespace.equals(moduleNamespace)) {
363                     final Module nextModule = context.findModuleByNamespace(childNodeNamespace);
364                     schemaNode = findSchemaNodeForGivenPath(context, nextModule, qnamedPath);
365                     return schemaNode;
366                 }
367                 qnamedPath.poll();
368             }
369         }
370         return schemaNode;
371     }
372
373     /**
374      * Transforms string representation of XPath to Queue of QNames. The XPath is split by "/" and for each part of
375      * XPath is assigned correct module in Schema Path.
376      * <br>
377      * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
378      * the method will throws IllegalArgumentException
379      *
380      * @throws IllegalArgumentException
381      *
382      * @param context Schema Context
383      * @param parentModule Parent Module
384      * @param xpath XPath String
385      * @return
386      */
387     private static Queue<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule,
388             final String xpath) {
389         if (context == null) {
390             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
391         }
392         if (parentModule == null) {
393             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
394         }
395         if (xpath == null) {
396             throw new IllegalArgumentException("XPath string reference cannot be NULL!");
397         }
398
399         final Queue<QName> path = new LinkedList<>();
400         final String[] prefixedPath = xpath.split("/");
401         for (int i = 0; i < prefixedPath.length; ++i) {
402             if (!prefixedPath[i].isEmpty()) {
403                 path.add(stringPathPartToQName(context, parentModule, prefixedPath[i]));
404             }
405         }
406         return path;
407     }
408
409     /**
410      * Transforms part of Prefixed Path as java String to QName.
411      * <br>
412      * If the string contains module prefix separated by ":" (i.e. mod:container) this module is provided from from
413      * Parent Module list of imports. If the Prefixed module is present in Schema Context the QName can be
414      * constructed.
415      * <br>
416      * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
417      * QName.
418      * <br>
419      * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
420      * IllegalArgumentException
421      *
422      * @throws IllegalArgumentException
423      *
424      * @param context Schema Context
425      * @param parentModule Parent Module
426      * @param prefixedPathPart Prefixed Path Part string
427      * @return QName from prefixed Path Part String.
428      */
429     private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule,
430             final String prefixedPathPart) {
431         if (context == null) {
432             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
433         }
434         if (parentModule == null) {
435             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
436         }
437         if (prefixedPathPart == null) {
438             throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
439         }
440
441         if (prefixedPathPart.contains(":")) {
442             final String[] prefixedName = prefixedPathPart.split(":");
443             final Module module = resolveModuleForPrefix(context, parentModule, prefixedName[0]);
444             if (module != null) {
445                 return new QName(module.getNamespace(), module.getRevision(), prefixedName[1]);
446             }
447         } else {
448             return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
449         }
450         return null;
451     }
452
453     /**
454      * Method will attempt to resolve and provide Module reference for specified module prefix. Each Yang module
455      * could contains multiple imports which MUST be associated with corresponding module prefix. The method simply
456      * looks into module imports and returns the module that is bounded with specified prefix. If the prefix is not
457      * present in module or the prefixed module is not present in specified Schema Context,
458      * the method will return <code>null</code>.
459      * <br>
460      * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
461      * <br>
462      * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
463      * IllegalArgumentException
464      *
465      * @throws IllegalArgumentException
466      *
467      * @param context Schema Context
468      * @param module Yang Module
469      * @param prefix Module Prefix
470      * @return Module for given prefix in specified Schema Context if is present, otherwise returns <code>null</code>
471      */
472     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
473         if (context == null) {
474             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
475         }
476         if (module == null) {
477             throw new IllegalArgumentException("Module reference cannot be NULL!");
478         }
479         if (prefix == null) {
480             throw new IllegalArgumentException("Prefix string cannot be NULL!");
481         }
482
483         if (prefix.equals(module.getPrefix())) {
484             return module;
485         }
486
487         final Set<ModuleImport> imports = module.getImports();
488         for (final ModuleImport mi : imports) {
489             if (prefix.equals(mi.getPrefix())) {
490                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
491             }
492         }
493         return null;
494     }
495
496     /**
497      * @throws IllegalArgumentException
498      *
499      * @param context Schema Context
500      * @param module Yang Module
501      * @param relativeXPath Non conditional Revision Aware Relative XPath
502      * @param leafrefSchemaPath Schema Path for Leafref
503      * @return
504      */
505     private static Queue<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
506             final RevisionAwareXPath relativeXPath, final SchemaPath leafrefSchemaPath) {
507         final Queue<QName> absolutePath = new LinkedList<>();
508         if (context == null) {
509             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
510         }
511         if (module == null) {
512             throw new IllegalArgumentException("Module reference cannot be NULL!");
513         }
514         if (relativeXPath == null) {
515             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
516         }
517         if (relativeXPath.isAbsolute()) {
518             throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
519                     + "for non relative Revision Aware XPath use findDataSchemaNode method!");
520         }
521         if (leafrefSchemaPath == null) {
522             throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
523         }
524
525         final String strXPath = relativeXPath.toString();
526         if (strXPath != null) {
527             final String[] xpaths = strXPath.split("/");
528             if (xpaths != null) {
529                 int colCount = 0;
530                 while (xpaths[colCount].contains("..")) {
531                     ++colCount;
532                 }
533                 final List<QName> path = leafrefSchemaPath.getPath();
534                 if (path != null) {
535                     int lenght = path.size() - colCount - 1;
536                     for (int i = 0; i < lenght; ++i) {
537                         absolutePath.add(path.get(i));
538                     }
539                     for (int i = colCount; i < xpaths.length; ++i) {
540                         absolutePath.add(stringPathPartToQName(context, module, xpaths[i]));
541                     }
542                 }
543             }
544         }
545         return absolutePath;
546     }
547 }