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