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