4c9eb686c6adeb1b4b130ce04a3c8b792bc3bc6a
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / SchemaUtils.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.data.impl.schema;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Predicate;
13 import com.google.common.collect.Collections2;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.collect.Iterables;
16 import com.google.common.collect.Maps;
17 import com.google.common.collect.Sets;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Set;
23 import javax.annotation.Nullable;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
28 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
29 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
30 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
31 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
35 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
36 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
38
39 public final class SchemaUtils {
40     private SchemaUtils() {
41         throw new UnsupportedOperationException();
42     }
43
44     /**
45      * @param qname - schema node to find
46      * @param dataSchemaNode - iterable of schemaNodes to look through
47      * @return - schema node with newest revision or absent if no schema node with matching qname is found
48      */
49     public static Optional<DataSchemaNode> findFirstSchema(final QName qname, final Iterable<DataSchemaNode> dataSchemaNode) {
50         DataSchemaNode sNode = null;
51         if (dataSchemaNode != null && qname != null) {
52             for (DataSchemaNode dsn : dataSchemaNode) {
53                 if (qname.isEqualWithoutRevision(dsn.getQName())) {
54                     if (sNode == null || sNode.getQName().getRevision().compareTo(dsn.getQName().getRevision()) < 0) {
55                         sNode = dsn;
56                     }
57                 } else if (dsn instanceof ChoiceSchemaNode) {
58                     for (ChoiceCaseNode choiceCase : ((ChoiceSchemaNode) dsn).getCases()) {
59
60                         final DataSchemaNode dataChildByName = choiceCase.getDataChildByName(qname);
61                         if (dataChildByName != null) {
62                             return Optional.of(dataChildByName);
63                         }
64                         Optional<DataSchemaNode> foundDsn = findFirstSchema(qname, choiceCase.getChildNodes());
65                         if (foundDsn.isPresent()) {
66                             return foundDsn;
67                         }
68                     }
69                 }
70             }
71         }
72         return Optional.fromNullable(sNode);
73     }
74
75     /**
76      *
77      * Find child schema node identified by its QName within a provided schema node
78      *
79      * @param schema schema for parent node - search root
80      * @param qname qname(with or without a revision) of a child node to be found in the parent schema
81      * @return found schema node
82      * @throws java.lang.IllegalStateException if the child was not found in parent schema node
83      */
84     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname) {
85         // Try to find child schema node directly, but use a fallback that compares QNames without revisions and auto-expands choices
86         final DataSchemaNode dataChildByName = schema.getDataChildByName(qname);
87         return dataChildByName == null ? findSchemaForChild(schema, qname, schema.getChildNodes()) : dataChildByName;
88     }
89
90     @Nullable
91     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname, final boolean strictMode) {
92         if (strictMode) {
93             return findSchemaForChild(schema, qname);
94         }
95
96         Optional<DataSchemaNode> childSchemaOptional = findFirstSchema(qname, schema.getChildNodes());
97         if (!childSchemaOptional.isPresent()) {
98             return null;
99         }
100         return childSchemaOptional.get();
101     }
102
103     public static DataSchemaNode findSchemaForChild(final DataNodeContainer schema, final QName qname, final Iterable<DataSchemaNode> childNodes) {
104         Optional<DataSchemaNode> childSchema = findFirstSchema(qname, childNodes);
105         Preconditions.checkState(childSchema.isPresent(),
106                 "Unknown child(ren) node(s) detected, identified by: %s, in: %s", qname, schema);
107         return childSchema.get();
108     }
109
110     public static AugmentationSchema findSchemaForAugment(final AugmentationTarget schema, final Set<QName> qNames) {
111         Optional<AugmentationSchema> schemaForAugment = findAugment(schema, qNames);
112         Preconditions.checkState(schemaForAugment.isPresent(), "Unknown augmentation node detected, identified by: %s, in: %s",
113                 qNames, schema);
114         return schemaForAugment.get();
115     }
116
117     public static AugmentationSchema findSchemaForAugment(final ChoiceSchemaNode schema, final Set<QName> qNames) {
118         Optional<AugmentationSchema> schemaForAugment = Optional.absent();
119
120         for (ChoiceCaseNode choiceCaseNode : schema.getCases()) {
121             schemaForAugment = findAugment(choiceCaseNode, qNames);
122             if (schemaForAugment.isPresent()) {
123                 break;
124             }
125         }
126
127         Preconditions.checkState(schemaForAugment.isPresent(), "Unknown augmentation node detected, identified by: %s, in: %s",
128                 qNames, schema);
129         return schemaForAugment.get();
130     }
131
132     private static Optional<AugmentationSchema> findAugment(final AugmentationTarget schema, final Set<QName> qNames) {
133         for (AugmentationSchema augment : schema.getAvailableAugmentations()) {
134             HashSet<QName> qNamesFromAugment = Sets.newHashSet(Collections2.transform(augment.getChildNodes(),
135                 DataSchemaNode::getQName));
136
137             if (qNamesFromAugment.equals(qNames)) {
138                 return Optional.of(augment);
139             }
140         }
141
142         return Optional.absent();
143     }
144
145     public static DataSchemaNode findSchemaForChild(final ChoiceSchemaNode schema, final QName childPartialQName) {
146         for (ChoiceCaseNode choiceCaseNode : schema.getCases()) {
147             Optional<DataSchemaNode> childSchema = findFirstSchema(childPartialQName, choiceCaseNode.getChildNodes());
148             if (childSchema.isPresent()) {
149                 return childSchema.get();
150             }
151         }
152
153
154         throw new IllegalStateException(String.format("Unknown child(ren) node(s) detected, identified by: %s, in: %s",
155                 childPartialQName, schema));
156     }
157
158     /**
159      * Recursively find all child nodes that come from choices.
160      *
161      * @param schema schema
162      * @return Map with all child nodes, to their most top augmentation
163      */
164     public static Map<QName, ChoiceSchemaNode> mapChildElementsFromChoices(final DataNodeContainer schema) {
165         return mapChildElementsFromChoices(schema, schema.getChildNodes());
166     }
167
168     private static Map<QName, ChoiceSchemaNode> mapChildElementsFromChoices(final DataNodeContainer schema, final Iterable<DataSchemaNode> childNodes) {
169         Map<QName, ChoiceSchemaNode> mappedChoices = Maps.newLinkedHashMap();
170
171         for (final DataSchemaNode childSchema : childNodes) {
172             if (childSchema instanceof ChoiceSchemaNode) {
173
174                 if (isFromAugment(schema, childSchema)) {
175                     continue;
176                 }
177
178                 for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) {
179
180                     for (QName qName : getChildNodesRecursive(choiceCaseNode)) {
181                         mappedChoices.put(qName, (ChoiceSchemaNode) childSchema);
182                     }
183                 }
184             }
185         }
186
187         return mappedChoices;
188     }
189
190     private static boolean isFromAugment(final DataNodeContainer schema, final DataSchemaNode childSchema) {
191         if (!(schema instanceof AugmentationTarget)) {
192             return false;
193         }
194
195         for (AugmentationSchema augmentationSchema : ((AugmentationTarget) schema).getAvailableAugmentations()) {
196             if (augmentationSchema.getDataChildByName(childSchema.getQName()) != null) {
197                 return true;
198             }
199         }
200
201         return false;
202     }
203
204     /**
205      * Recursively find all child nodes that come from augmentations.
206      *
207      * @param schema schema
208      * @return Map with all child nodes, to their most top augmentation
209      */
210     public static Map<QName, AugmentationSchema> mapChildElementsFromAugments(final AugmentationTarget schema) {
211
212         Map<QName, AugmentationSchema> childNodesToAugmentation = Maps.newLinkedHashMap();
213
214         // Find QNames of augmented child nodes
215         Map<QName, AugmentationSchema> augments = Maps.newHashMap();
216         for (final AugmentationSchema augmentationSchema : schema.getAvailableAugmentations()) {
217             for (DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) {
218                 augments.put(dataSchemaNode.getQName(), augmentationSchema);
219             }
220         }
221
222         // Augmented nodes have to be looked up directly in augmentationTarget
223         // because nodes from augment do not contain nodes from other augmentations
224         if (schema instanceof DataNodeContainer) {
225
226             for (DataSchemaNode child : ((DataNodeContainer) schema).getChildNodes()) {
227                 // If is not augmented child, continue
228                 if (!(augments.containsKey(child.getQName()))) {
229                     continue;
230                 }
231
232                 AugmentationSchema mostTopAugmentation = augments.get(child.getQName());
233
234                 // recursively add all child nodes in case of augment, case and choice
235                 if (child instanceof AugmentationSchema || child instanceof ChoiceCaseNode) {
236                     for (QName qName : getChildNodesRecursive((DataNodeContainer) child)) {
237                         childNodesToAugmentation.put(qName, mostTopAugmentation);
238                     }
239                 } else if (child instanceof ChoiceSchemaNode) {
240                     for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) child).getCases()) {
241                         for (QName qName : getChildNodesRecursive(choiceCaseNode)) {
242                             childNodesToAugmentation.put(qName, mostTopAugmentation);
243                         }
244                     }
245                 } else {
246                     childNodesToAugmentation.put(child.getQName(), mostTopAugmentation);
247                 }
248             }
249         }
250
251         // Choice Node has to map child nodes from all its cases
252         if (schema instanceof ChoiceSchemaNode) {
253             for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) schema).getCases()) {
254                 if (!(augments.containsKey(choiceCaseNode.getQName()))) {
255                     continue;
256                 }
257
258                 for (QName qName : getChildNodesRecursive(choiceCaseNode)) {
259                     childNodesToAugmentation.put(qName, augments.get(choiceCaseNode.getQName()));
260                 }
261             }
262         }
263
264         return childNodesToAugmentation;
265     }
266
267     /**
268      * Recursively list all child nodes.
269      *
270      * In case of choice, augment and cases, step in.
271      *
272      * @param nodeContainer node container
273      * @return set of QNames
274      */
275     public static Set<QName> getChildNodesRecursive(final DataNodeContainer nodeContainer) {
276         Set<QName> allChildNodes = Sets.newHashSet();
277
278         for (DataSchemaNode childSchema : nodeContainer.getChildNodes()) {
279             if (childSchema instanceof ChoiceSchemaNode) {
280                 for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) childSchema).getCases()) {
281                     allChildNodes.addAll(getChildNodesRecursive(choiceCaseNode));
282                 }
283             } else if (childSchema instanceof AugmentationSchema || childSchema instanceof ChoiceCaseNode) {
284                 allChildNodes.addAll(getChildNodesRecursive((DataNodeContainer) childSchema));
285             }
286             else {
287                 allChildNodes.add(childSchema.getQName());
288             }
289         }
290
291         return allChildNodes;
292     }
293
294     /**
295      * Retrieves real schemas for augmented child node.
296      *
297      * Schema of the same child node from augment, and directly from target is not the same.
298      * Schema of child node from augment is incomplete, therefore its useless for XML/NormalizedNode translation.
299      *
300      * @param targetSchema target schema
301      * @param augmentSchema augment schema
302      * @return set of nodes
303      */
304     public static Set<DataSchemaNode> getRealSchemasForAugment(final AugmentationTarget targetSchema, final AugmentationSchema augmentSchema) {
305         if (!(targetSchema.getAvailableAugmentations().contains(augmentSchema))) {
306             return Collections.emptySet();
307         }
308
309         Set<DataSchemaNode> realChildNodes = Sets.newHashSet();
310
311         if (targetSchema instanceof DataNodeContainer) {
312             realChildNodes = getRealSchemasForAugment((DataNodeContainer)targetSchema, augmentSchema);
313         } else if (targetSchema instanceof ChoiceSchemaNode) {
314             for (DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) {
315                 for (ChoiceCaseNode choiceCaseNode : ((ChoiceSchemaNode) targetSchema).getCases()) {
316                     if (getChildNodesRecursive(choiceCaseNode).contains(dataSchemaNode.getQName())) {
317                         realChildNodes.add(choiceCaseNode.getDataChildByName(dataSchemaNode.getQName()));
318                     }
319                 }
320             }
321         }
322
323         return realChildNodes;
324     }
325
326     public static Set<DataSchemaNode> getRealSchemasForAugment(final DataNodeContainer targetSchema,
327             final AugmentationSchema augmentSchema) {
328         Set<DataSchemaNode> realChildNodes = Sets.newHashSet();
329         for (DataSchemaNode dataSchemaNode : augmentSchema.getChildNodes()) {
330             DataSchemaNode realChild = targetSchema.getDataChildByName(dataSchemaNode.getQName());
331             realChildNodes.add(realChild);
332         }
333         return realChildNodes;
334     }
335
336     public static Optional<ChoiceCaseNode> detectCase(final ChoiceSchemaNode schema, final DataContainerChild<?, ?> child) {
337         for (ChoiceCaseNode choiceCaseNode : schema.getCases()) {
338             if (child instanceof AugmentationNode
339                     && belongsToCaseAugment(choiceCaseNode, (AugmentationIdentifier) child.getIdentifier())) {
340                 return Optional.of(choiceCaseNode);
341             } else if (choiceCaseNode.getDataChildByName(child.getNodeType()) != null) {
342                 return Optional.of(choiceCaseNode);
343             }
344         }
345
346         return Optional.absent();
347     }
348
349     public static boolean belongsToCaseAugment(final ChoiceCaseNode caseNode, final AugmentationIdentifier childToProcess) {
350         for (AugmentationSchema augmentationSchema : caseNode.getAvailableAugmentations()) {
351
352             Set<QName> currentAugmentChildNodes = Sets.newHashSet();
353             for (DataSchemaNode dataSchemaNode : augmentationSchema.getChildNodes()) {
354                 currentAugmentChildNodes.add(dataSchemaNode.getQName());
355             }
356
357             if (childToProcess.getPossibleChildNames().equals(currentAugmentChildNodes)){
358                 return true;
359             }
360         }
361
362         return false;
363     }
364
365     /**
366      * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such
367      * node is found then it is returned, else null.
368      *
369      * @param parent parent node
370      * @param child child node
371      * @return augmentation schema
372      */
373     public static AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) {
374         if (parent instanceof AugmentationTarget && !(parent instanceof ChoiceSchemaNode)) {
375             for (AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
376                 DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
377                 if (childInAugmentation != null) {
378                     return augmentation;
379                 }
380             }
381         }
382         return null;
383     }
384
385     public static AugmentationIdentifier getNodeIdentifierForAugmentation(final AugmentationSchema schema) {
386         final Collection<QName> qnames = Collections2.transform(schema.getChildNodes(), DataSchemaNode::getQName);
387         return new AugmentationIdentifier(ImmutableSet.copyOf(qnames));
388     }
389
390     /**
391      * Finds schema node for given path in schema context.
392      * @param schemaContext schema context
393      * @param path path
394      * @return schema node on path
395      */
396     public static SchemaNode findParentSchemaOnPath(final SchemaContext schemaContext, final SchemaPath path) {
397         SchemaNode current = Preconditions.checkNotNull(schemaContext);
398         for (final QName qname : path.getPathFromRoot()) {
399             current = findChildSchemaByQName(current, qname);
400         }
401         return current;
402     }
403
404     /**
405      * Find child schema node identified by its QName within a provided schema node.
406      * @param node schema node
407      * @param qname QName
408      * @return child schema node
409      * @throws java.lang.IllegalArgumentException if the schema node does not allow children
410      */
411     public static SchemaNode findChildSchemaByQName(final SchemaNode node, final QName qname) {
412         SchemaNode child = null;
413
414         if (node instanceof DataNodeContainer) {
415             child = ((DataNodeContainer) node).getDataChildByName(qname);
416
417             if (child == null && node instanceof SchemaContext) {
418                 child = tryFindGroupings((SchemaContext) node, qname).orNull();
419             }
420
421             if (child == null && node instanceof SchemaContext) {
422                 child = tryFindNotification((SchemaContext) node, qname)
423                         .or(tryFindRpc(((SchemaContext) node), qname)).orNull();
424             }
425         } else if (node instanceof ChoiceSchemaNode) {
426             child = ((ChoiceSchemaNode) node).getCaseNodeByName(qname);
427         } else if (node instanceof RpcDefinition) {
428             switch (qname.getLocalName()) {
429                 case "input":
430                     child = ((RpcDefinition) node).getInput();
431                     break;
432                 case "output":
433                     child = ((RpcDefinition) node).getOutput();
434                     break;
435                 default:
436                     child = null;
437                     break;
438             }
439         } else {
440             throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", node));
441         }
442
443         return child;
444     }
445
446     private static Optional<SchemaNode> tryFindGroupings(final SchemaContext ctx, final QName qname) {
447         return Optional.fromNullable(Iterables.find(ctx.getGroupings(), new SchemaNodePredicate(qname), null));
448     }
449
450     private static Optional<SchemaNode> tryFindRpc(final SchemaContext ctx, final QName qname) {
451         return Optional.fromNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null));
452     }
453
454     private static Optional<SchemaNode> tryFindNotification(final SchemaContext ctx, final QName qname) {
455         return Optional.fromNullable(Iterables.find(ctx.getNotifications(), new SchemaNodePredicate(qname), null));
456     }
457
458     private static final class SchemaNodePredicate implements Predicate<SchemaNode> {
459         private final QName qname;
460
461         public SchemaNodePredicate(final QName qname) {
462             this.qname = qname;
463         }
464
465         @Override
466         public boolean apply(final SchemaNode input) {
467             return input.getQName().equals(qname);
468         }
469     }
470
471 }