Reimplement SchemaContextUtil.getBaseTypeForLeafRef()
[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 static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.collect.Iterables;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.Optional;
18 import java.util.Set;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
23 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
24 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
26 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
27 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
29 import org.opendaylight.yangtools.yang.model.api.Module;
30 import org.opendaylight.yangtools.yang.model.api.ModuleLike;
31 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
32 import org.opendaylight.yangtools.yang.model.api.NotificationNodeContainer;
33 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
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 import org.opendaylight.yangtools.yang.model.api.Submodule;
39 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
40 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * The Schema Context Util contains support methods for searching through Schema Context modules for specified schema
46  * nodes via Schema Path or Revision Aware XPath. The Schema Context Util is designed as mixin, so it is not
47  * instantiable.
48  */
49 public final class SchemaContextUtil {
50     private static final Logger LOG = LoggerFactory.getLogger(SchemaContextUtil.class);
51
52
53     private SchemaContextUtil() {
54         // Hidden on purpose
55     }
56
57     /**
58      * Method attempts to find DataSchemaNode in Schema Context via specified Schema Path. The returned DataSchemaNode
59      * from method will be the node at the end of the SchemaPath. If the DataSchemaNode is not present in the Schema
60      * Context the method will return {@code null}.
61      *
62      * <p>
63      * In case that Schema Context or Schema Path are not specified correctly (i.e. contains {@code null} values) the
64      * method will throw IllegalArgumentException.
65      *
66      * @param context Schema Context
67      * @param schemaPath Schema Path to search for
68      * @return SchemaNode from the end of the Schema Path or {@code null} if the Node is not present.
69      * @throws NullPointerException if context or schemaPath is null
70      */
71     public static SchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
72         final Iterable<QName> prefixedPath = schemaPath.getPathFromRoot();
73         if (prefixedPath == null) {
74             LOG.debug("Schema path {} has null path", schemaPath);
75             return null;
76         }
77
78         LOG.trace("Looking for path {} in context {}", schemaPath, context);
79         return findNodeInSchemaContext(context, prefixedPath);
80     }
81
82     /**
83      * Returns parent Yang Module for specified Schema Context in which Schema
84      * Node is declared. If the Schema Node is not present in Schema Context the
85      * operation will return <code>null</code>.
86      *
87      * @param context Schema Context
88      * @param schemaNode Schema Node
89      * @return Yang Module for specified Schema Context and Schema Node, if Schema Node is NOT present, the method will
90      *         return <code>null</code>
91      * @throws NullPointerException if any of the arguments is null
92      */
93     public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
94         return context.findModule(schemaNode.getQName().getModule()).orElse(null);
95     }
96
97     public static SchemaNode findNodeInSchemaContext(final SchemaContext context, final Iterable<QName> path) {
98         final QName current = path.iterator().next();
99
100         LOG.trace("Looking up module {} in context {}", current, path);
101         final Optional<Module> module = context.findModule(current.getModule());
102         if (module.isEmpty()) {
103             LOG.debug("Module {} not found", current);
104             return null;
105         }
106
107         return findNodeInModule(module.get(), path);
108     }
109
110     /**
111      * Returns NotificationDefinition from Schema Context.
112      *
113      * @param schema SchemaContext in which lookup should be performed.
114      * @param path Schema Path of notification
115      * @return Notification schema or null, if notification is not present in schema context.
116      */
117     @Beta
118     public static @Nullable NotificationDefinition getNotificationSchema(final @NonNull SchemaContext schema,
119             final @NonNull SchemaPath path) {
120         requireNonNull(schema, "Schema context must not be null.");
121         requireNonNull(path, "Schema path must not be null.");
122         for (final NotificationDefinition potential : schema.getNotifications()) {
123             if (path.equals(potential.getPath())) {
124                 return potential;
125             }
126         }
127         return null;
128     }
129
130     /**
131      * Returns RPC Input or Output Data container from RPC definition.
132      *
133      * @param schema SchemaContext in which lookup should be performed.
134      * @param path Schema path of RPC input/output data container
135      * @return Notification schema or null, if notification is not present in schema context.
136      */
137     @Beta
138     public static @Nullable ContainerLike getRpcDataSchema(final @NonNull SchemaContext schema,
139             final @NonNull SchemaPath path) {
140         requireNonNull(schema, "Schema context must not be null.");
141         requireNonNull(path, "Schema path must not be null.");
142         final Iterator<QName> it = path.getPathFromRoot().iterator();
143         checkArgument(it.hasNext(), "Rpc must have QName.");
144         final QName rpcName = it.next();
145         checkArgument(it.hasNext(), "input or output must be part of path.");
146         final QName inOrOut = it.next();
147         for (final RpcDefinition potential : schema.getOperations()) {
148             if (rpcName.equals(potential.getQName())) {
149                 return SchemaNodeUtils.getRpcDataSchema(potential, inOrOut);
150             }
151         }
152         return null;
153     }
154
155     /**
156      * Extract the identifiers of all modules and submodules which were used to create a particular SchemaContext.
157      *
158      * @param context SchemaContext to be examined
159      * @return Set of ModuleIdentifiers.
160      */
161     public static Set<SourceIdentifier> getConstituentModuleIdentifiers(final SchemaContext context) {
162         final Set<SourceIdentifier> ret = new HashSet<>();
163
164         for (Module module : context.getModules()) {
165             ret.add(moduleToIdentifier(module));
166
167             for (Submodule submodule : module.getSubmodules()) {
168                 ret.add(moduleToIdentifier(submodule));
169             }
170         }
171
172         return ret;
173     }
174
175     private static SourceIdentifier moduleToIdentifier(final ModuleLike module) {
176         return RevisionSourceIdentifier.create(module.getName(), module.getRevision());
177     }
178
179     private static SchemaNode findNodeInModule(final Module module, final Iterable<QName> path) {
180         if (!path.iterator().hasNext()) {
181             LOG.debug("No node matching {} found in node {}", path, module);
182             return null;
183         }
184
185         final QName current = path.iterator().next();
186         LOG.trace("Looking for node {} in module {}", current, module);
187
188         SchemaNode foundNode = null;
189         final Iterable<QName> nextPath = nextLevel(path);
190
191         foundNode = module.dataChildByName(current);
192         if (foundNode != null && nextPath.iterator().hasNext()) {
193             foundNode = findNodeIn(foundNode, nextPath);
194         }
195
196         if (foundNode == null) {
197             foundNode = getGroupingByName(module, current);
198             if (foundNode != null && nextPath.iterator().hasNext()) {
199                 foundNode = findNodeIn(foundNode, nextPath);
200             }
201         }
202
203         if (foundNode == null) {
204             foundNode = getRpcByName(module, current);
205             if (foundNode != null && nextPath.iterator().hasNext()) {
206                 foundNode = findNodeIn(foundNode, nextPath);
207             }
208         }
209
210         if (foundNode == null) {
211             foundNode = getNotificationByName(module, current);
212             if (foundNode != null && nextPath.iterator().hasNext()) {
213                 foundNode = findNodeIn(foundNode, nextPath);
214             }
215         }
216
217         if (foundNode == null) {
218             LOG.debug("No node matching {} found in node {}", path, module);
219         }
220
221         return foundNode;
222     }
223
224     private static SchemaNode findNodeIn(final SchemaNode parent, final Iterable<QName> path) {
225         if (!path.iterator().hasNext()) {
226             LOG.debug("No node matching {} found in node {}", path, parent);
227             return null;
228         }
229
230         final QName current = path.iterator().next();
231         LOG.trace("Looking for node {} in node {}", current, parent);
232
233         SchemaNode foundNode = null;
234         final Iterable<QName> nextPath = nextLevel(path);
235
236         if (parent instanceof DataNodeContainer) {
237             final DataNodeContainer parentDataNodeContainer = (DataNodeContainer) parent;
238
239             foundNode = parentDataNodeContainer.dataChildByName(current);
240             if (foundNode != null && nextPath.iterator().hasNext()) {
241                 foundNode = findNodeIn(foundNode, nextPath);
242             }
243
244             if (foundNode == null) {
245                 foundNode = getGroupingByName(parentDataNodeContainer, current);
246                 if (foundNode != null && nextPath.iterator().hasNext()) {
247                     foundNode = findNodeIn(foundNode, nextPath);
248                 }
249             }
250         }
251
252         if (foundNode == null && parent instanceof ActionNodeContainer) {
253             final Optional<? extends SchemaNode> next = ((ActionNodeContainer) parent).getActions().stream()
254                 .filter(act -> current.equals(act.getQName())).findFirst();
255             if (next.isPresent() && nextPath.iterator().hasNext()) {
256                 foundNode = findNodeIn(next.orElseThrow(), nextPath);
257             }
258         }
259
260         if (foundNode == null && parent instanceof NotificationNodeContainer) {
261             foundNode = ((NotificationNodeContainer) parent).getNotifications().stream()
262                     .filter(notif -> current.equals(notif.getQName())).findFirst().orElse(null);
263             if (foundNode != null && nextPath.iterator().hasNext()) {
264                 foundNode = findNodeIn(foundNode, nextPath);
265             }
266         }
267
268         if (foundNode == null && parent instanceof OperationDefinition) {
269             final OperationDefinition parentRpcDefinition = (OperationDefinition) parent;
270
271             if (current.getLocalName().equals("input")) {
272                 foundNode = parentRpcDefinition.getInput();
273                 if (foundNode != null && nextPath.iterator().hasNext()) {
274                     foundNode = findNodeIn(foundNode, nextPath);
275                 }
276             }
277
278             if (current.getLocalName().equals("output")) {
279                 foundNode = parentRpcDefinition.getOutput();
280                 if (foundNode != null && nextPath.iterator().hasNext()) {
281                     foundNode = findNodeIn(foundNode, nextPath);
282                 }
283             }
284
285             if (foundNode == null) {
286                 foundNode = getGroupingByName(parentRpcDefinition, current);
287                 if (foundNode != null && nextPath.iterator().hasNext()) {
288                     foundNode = findNodeIn(foundNode, nextPath);
289                 }
290             }
291         }
292
293         if (foundNode == null && parent instanceof ChoiceSchemaNode) {
294             foundNode = ((ChoiceSchemaNode) parent).findCase(current).orElse(null);
295
296             if (foundNode != null && nextPath.iterator().hasNext()) {
297                 foundNode = findNodeIn(foundNode, nextPath);
298             }
299
300             if (foundNode == null) {
301                 // fallback that tries to map into one of the child cases
302                 for (final CaseSchemaNode caseNode : ((ChoiceSchemaNode) parent).getCases()) {
303                     final DataSchemaNode maybeChild = caseNode.dataChildByName(current);
304                     if (maybeChild != null) {
305                         foundNode = findNodeIn(maybeChild, nextPath);
306                         break;
307                     }
308                 }
309             }
310         }
311
312         if (foundNode == null) {
313             LOG.debug("No node matching {} found in node {}", path, parent);
314         }
315
316         return foundNode;
317
318     }
319
320     private static Iterable<QName> nextLevel(final Iterable<QName> path) {
321         return Iterables.skip(path, 1);
322     }
323
324     private static RpcDefinition getRpcByName(final Module module, final QName name) {
325         for (final RpcDefinition rpc : module.getRpcs()) {
326             if (rpc.getQName().equals(name)) {
327                 return rpc;
328             }
329         }
330         return null;
331     }
332
333     private static NotificationDefinition getNotificationByName(final Module module, final QName name) {
334         for (final NotificationDefinition notification : module.getNotifications()) {
335             if (notification.getQName().equals(name)) {
336                 return notification;
337             }
338         }
339         return null;
340     }
341
342     private static GroupingDefinition getGroupingByName(final DataNodeContainer dataNodeContainer, final QName name) {
343         for (final GroupingDefinition grouping : dataNodeContainer.getGroupings()) {
344             if (grouping.getQName().equals(name)) {
345                 return grouping;
346             }
347         }
348         return null;
349     }
350
351     private static GroupingDefinition getGroupingByName(final OperationDefinition rpc, final QName name) {
352         for (final GroupingDefinition grouping : rpc.getGroupings()) {
353             if (grouping.getQName().equals(name)) {
354                 return grouping;
355             }
356         }
357         return null;
358     }
359
360 }