Un-deprecate CopyableNode, AddedByUsesAware
[yangtools.git] / yang / yang-parser-spi / src / main / java / org / opendaylight / yangtools / yang / parser / spi / meta / StmtContextUtils.java
1 /*
2  * Copyright (c) 2015 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.parser.spi.meta;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.Strings;
14 import com.google.common.base.VerifyException;
15 import com.google.common.collect.ImmutableList;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.Set;
21 import java.util.function.Predicate;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.common.QNameModule;
25 import org.opendaylight.yangtools.yang.common.Revision;
26 import org.opendaylight.yangtools.yang.common.YangVersion;
27 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
28 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
29 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
30 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
31 import org.opendaylight.yangtools.yang.model.api.stmt.KeyEffectiveStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.KeyStatement;
33 import org.opendaylight.yangtools.yang.model.api.stmt.LeafStatement;
34 import org.opendaylight.yangtools.yang.model.api.stmt.MandatoryStatement;
35 import org.opendaylight.yangtools.yang.model.api.stmt.MinElementsStatement;
36 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
37 import org.opendaylight.yangtools.yang.model.api.stmt.PresenceEffectiveStatement;
38 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
39 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
40 import org.opendaylight.yangtools.yang.model.api.stmt.UnknownStatement;
41 import org.opendaylight.yangtools.yang.model.api.stmt.UnrecognizedStatement;
42 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceAction;
43 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceContext;
44 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.Prerequisite;
45 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
46 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
47 import org.opendaylight.yangtools.yang.parser.spi.source.ImportPrefixToModuleCtx;
48 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
49 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
50 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
51
52 public final class StmtContextUtils {
53     private StmtContextUtils() {
54         // Hidden on purpose
55     }
56
57     @SuppressWarnings("unchecked")
58     public static <A, D extends DeclaredStatement<A>> A firstAttributeOf(
59             final Iterable<? extends StmtContext<?, ?, ?>> contexts, final Class<D> declaredType) {
60         for (final StmtContext<?, ?, ?> ctx : contexts) {
61             if (ctx.producesDeclared(declaredType)) {
62                 return (A) ctx.argument();
63             }
64         }
65         return null;
66     }
67
68     @SuppressWarnings("unchecked")
69     public static <A, D extends DeclaredStatement<A>> A firstAttributeOf(final StmtContext<?, ?, ?> ctx,
70             final Class<D> declaredType) {
71         return ctx.producesDeclared(declaredType) ? (A) ctx.argument() : null;
72     }
73
74     public static <A, D extends DeclaredStatement<A>> A firstSubstatementAttributeOf(
75             final StmtContext<?, ?, ?> ctx, final Class<D> declaredType) {
76         return firstAttributeOf(ctx.allSubstatements(), declaredType);
77     }
78
79     @SuppressWarnings("unchecked")
80     public static <A, D extends DeclaredStatement<A>> StmtContext<A, ?, ?> findFirstDeclaredSubstatement(
81             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
82         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
83             if (subStmtContext.producesDeclared(declaredType)) {
84                 return (StmtContext<A, ?, ?>) subStmtContext;
85             }
86         }
87         return null;
88     }
89
90     @SafeVarargs
91     @SuppressWarnings({ "rawtypes", "unchecked" })
92     public static StmtContext<?, ?, ?> findFirstDeclaredSubstatement(final StmtContext<?, ?, ?> stmtContext,
93             int startIndex, final Class<? extends DeclaredStatement<?>>... types) {
94         if (startIndex >= types.length) {
95             return null;
96         }
97
98         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
99             if (subStmtContext.producesDeclared((Class) types[startIndex])) {
100                 return startIndex + 1 == types.length ? subStmtContext : findFirstDeclaredSubstatement(subStmtContext,
101                         ++startIndex, types);
102             }
103         }
104         return null;
105     }
106
107     @SuppressWarnings("unchecked")
108     public static <A, D extends DeclaredStatement<A>> Collection<StmtContext<A, D, ?>> findAllDeclaredSubstatements(
109             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
110         final ImmutableList.Builder<StmtContext<A, D, ?>> listBuilder = ImmutableList.builder();
111         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
112             if (subStmtContext.producesDeclared(declaredType)) {
113                 listBuilder.add((StmtContext<A, D, ?>) subStmtContext);
114             }
115         }
116         return listBuilder.build();
117     }
118
119     @SuppressWarnings("unchecked")
120     public static <A, D extends DeclaredStatement<A>> Collection<StmtContext<A, D, ?>> findAllEffectiveSubstatements(
121             final StmtContext<?, ?, ?> stmtContext, final Class<D> type) {
122         final ImmutableList.Builder<StmtContext<A, D, ?>> listBuilder = ImmutableList.builder();
123         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.effectiveSubstatements()) {
124             if (subStmtContext.producesDeclared(type)) {
125                 listBuilder.add((StmtContext<A, D, ?>) subStmtContext);
126             }
127         }
128         return listBuilder.build();
129     }
130
131     public static <A, D extends DeclaredStatement<A>> Collection<StmtContext<A, D, ?>> findAllSubstatements(
132             final StmtContext<?, ?, ?> stmtContext, final Class<D> type) {
133         final ImmutableList.Builder<StmtContext<A, D, ?>> listBuilder = ImmutableList.builder();
134         listBuilder.addAll(findAllDeclaredSubstatements(stmtContext, type));
135         listBuilder.addAll(findAllEffectiveSubstatements(stmtContext, type));
136         return listBuilder.build();
137     }
138
139     @SuppressWarnings("unchecked")
140     public static <A, D extends DeclaredStatement<A>> StmtContext<A, ?, ?> findFirstEffectiveSubstatement(
141             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
142         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.effectiveSubstatements()) {
143             if (subStmtContext.producesDeclared(declaredType)) {
144                 return (StmtContext<A, ?, ?>) subStmtContext;
145             }
146         }
147         return null;
148     }
149
150     /**
151      * Searches for the first substatement of the specified type in the specified statement context.
152      * First, it tries to find the substatement in the effective substatements of the statement context.
153      * If it was not found, then it proceeds to search in the declared substatements. If it still was not found,
154      * the method returns null.
155      *
156      * @param stmtContext statement context to search in
157      * @param declaredType substatement type to search for
158      * @param <A> statement argument type
159      * @param <D> declared statement type
160      * @return statement context that was searched for or null if was not found
161      * @deprecated Use {@link BoundStmtCtx#findSubstatementArgument(Class)} instead.
162      */
163     @Deprecated(forRemoval = true)
164     public static <A, D extends DeclaredStatement<A>> StmtContext<A, ?, ?> findFirstSubstatement(
165             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
166         final StmtContext<A, ?, ?> effectiveSubstatement = findFirstEffectiveSubstatement(stmtContext, declaredType);
167         return effectiveSubstatement != null ? effectiveSubstatement : findFirstDeclaredSubstatement(stmtContext,
168                 declaredType);
169     }
170
171     public static <D extends DeclaredStatement<?>> StmtContext<?, ?, ?> findFirstDeclaredSubstatementOnSublevel(
172             final StmtContext<?, ?, ?> stmtContext, final Class<? super D> declaredType, int sublevel) {
173         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
174             if (sublevel == 1 && subStmtContext.producesDeclared(declaredType)) {
175                 return subStmtContext;
176             }
177             if (sublevel > 1) {
178                 final StmtContext<?, ?, ?> result = findFirstDeclaredSubstatementOnSublevel(subStmtContext,
179                         declaredType, --sublevel);
180                 if (result != null) {
181                     return result;
182                 }
183             }
184         }
185
186         return null;
187     }
188
189     public static <D extends DeclaredStatement<?>> StmtContext<?, ?, ?> findDeepFirstDeclaredSubstatement(
190             final StmtContext<?, ?, ?> stmtContext, final Class<? super D> declaredType) {
191         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
192             if (subStmtContext.producesDeclared(declaredType)) {
193                 return subStmtContext;
194             }
195
196             final StmtContext<?, ?, ?> result = findDeepFirstDeclaredSubstatement(subStmtContext, declaredType);
197             if (result != null) {
198                 return result;
199             }
200         }
201
202         return null;
203     }
204
205     public static boolean isInExtensionBody(final StmtContext<?, ?, ?> stmtCtx) {
206         StmtContext<?, ?, ?> current = stmtCtx;
207
208         while (true) {
209             final StmtContext<?, ?, ?> parent = current.coerceParentContext();
210             if (parent.getParentContext() == null) {
211                 return false;
212             }
213             if (isUnknownStatement(parent)) {
214                 return true;
215             }
216             current = parent;
217         }
218     }
219
220     /**
221      * Returns true if supplied statement context represents unknown statement,
222      * otherwise returns false.
223      *
224      * @param stmtCtx
225      *            statement context to be checked
226      * @return true if supplied statement context represents unknown statement,
227      *         otherwise false
228      * @throws NullPointerException
229      *             if supplied statement context is null
230      */
231     public static boolean isUnknownStatement(final StmtContext<?, ?, ?> stmtCtx) {
232         return UnknownStatement.class.isAssignableFrom(stmtCtx.publicDefinition().getDeclaredRepresentationClass());
233     }
234
235     /**
236      * Returns true if supplied statement context represents unrecognized
237      * statement, otherwise returns false.
238      *
239      * @param stmtCtx
240      *            statement context to be checked
241      * @return true if supplied statement context represents unrecognized
242      *         statement, otherwise false
243      * @throws NullPointerException
244      *             if supplied statement context is null
245      */
246     public static boolean isUnrecognizedStatement(final StmtContext<?, ?, ?> stmtCtx) {
247         return stmtCtx.producesDeclared(UnrecognizedStatement.class);
248     }
249
250     public static boolean checkFeatureSupport(final StmtContext<?, ?, ?> stmtContext,
251             final Set<QName> supportedFeatures) {
252         boolean isSupported = false;
253         boolean containsIfFeature = false;
254         for (final StmtContext<?, ?, ?> stmt : stmtContext.declaredSubstatements()) {
255             if (YangStmtMapping.IF_FEATURE.equals(stmt.publicDefinition())) {
256                 containsIfFeature = true;
257                 @SuppressWarnings("unchecked")
258                 final Predicate<Set<QName>> argument = (Predicate<Set<QName>>) stmt.getArgument();
259                 if (argument.test(supportedFeatures)) {
260                     isSupported = true;
261                 } else {
262                     isSupported = false;
263                     break;
264                 }
265             }
266         }
267
268         return !containsIfFeature || isSupported;
269     }
270
271     /**
272      * Checks whether statement context is a presence container or not.
273      *
274      * @param stmtCtx
275      *            statement context
276      * @return true if it is a presence container
277      */
278     public static boolean isPresenceContainer(final StmtContext<?, ?, ?> stmtCtx) {
279         return stmtCtx.publicDefinition() == YangStmtMapping.CONTAINER && containsPresenceSubStmt(stmtCtx);
280     }
281
282     /**
283      * Checks whether statement context is a non-presence container or not.
284      *
285      * @param stmtCtx
286      *            statement context
287      * @return true if it is a non-presence container
288      */
289     public static boolean isNonPresenceContainer(final StmtContext<?, ?, ?> stmtCtx) {
290         return stmtCtx.publicDefinition() == YangStmtMapping.CONTAINER && !containsPresenceSubStmt(stmtCtx);
291     }
292
293     private static boolean containsPresenceSubStmt(final StmtContext<?, ?, ?> stmtCtx) {
294         return stmtCtx.hasSubstatement(PresenceEffectiveStatement.class);
295     }
296
297     /**
298      * Checks whether statement context is a mandatory leaf, choice, anyxml,
299      * list or leaf-list according to RFC6020 or not.
300      *
301      * @param stmtCtx
302      *            statement context
303      * @return true if it is a mandatory leaf, choice, anyxml, list or leaf-list
304      *         according to RFC6020.
305      */
306     public static boolean isMandatoryNode(final StmtContext<?, ?, ?> stmtCtx) {
307         if (!(stmtCtx.publicDefinition() instanceof YangStmtMapping)) {
308             return false;
309         }
310         switch ((YangStmtMapping) stmtCtx.publicDefinition()) {
311             case LEAF:
312             case CHOICE:
313             case ANYXML:
314                 return Boolean.TRUE.equals(firstSubstatementAttributeOf(stmtCtx, MandatoryStatement.class));
315             case LIST:
316             case LEAF_LIST:
317                 final Integer minElements = firstSubstatementAttributeOf(stmtCtx, MinElementsStatement.class);
318                 return minElements != null && minElements > 0;
319             default:
320                 return false;
321         }
322     }
323
324     /**
325      * Checks whether a statement context is a statement of supplied statement
326      * definition and whether it is not mandatory leaf, choice, anyxml, list or
327      * leaf-list according to RFC6020.
328      *
329      * @param stmtCtx
330      *            statement context
331      * @param stmtDef
332      *            statement definition
333      * @return true if supplied statement context is a statement of supplied
334      *         statement definition and if it is not mandatory leaf, choice,
335      *         anyxml, list or leaf-list according to RFC6020
336      */
337     public static boolean isNotMandatoryNodeOfType(final StmtContext<?, ?, ?> stmtCtx,
338             final StatementDefinition stmtDef) {
339         return stmtCtx.publicDefinition().equals(stmtDef) && !isMandatoryNode(stmtCtx);
340     }
341
342     /**
343      * Checks whether at least one ancestor of a StatementContext matches one from a collection of statement
344      * definitions.
345      *
346      * @param stmt Statement context to be checked
347      * @param ancestorTypes collection of statement definitions
348      * @return true if at least one ancestor of a StatementContext matches one
349      *         from collection of statement definitions, otherwise false.
350      */
351     public static boolean hasAncestorOfType(final StmtContext<?, ?, ?> stmt,
352             final Collection<StatementDefinition> ancestorTypes) {
353         requireNonNull(ancestorTypes);
354         StmtContext<?, ?, ?> current = stmt.getParentContext();
355         while (current != null) {
356             if (ancestorTypes.contains(current.publicDefinition())) {
357                 return true;
358             }
359             current = current.getParentContext();
360         }
361         return false;
362     }
363
364     /**
365      * Check whether all of StmtContext's {@code list} ancestors have a {@code key}.
366      *
367      * @param stmt EffectiveStmtCtx to be checked
368      * @param name Human-friendly statement name
369      * @throws SourceException if there is any keyless list ancestor
370      */
371     public static void validateNoKeylessListAncestorOf(final Mutable<?, ?, ?> stmt, final String name) {
372         requireNonNull(stmt);
373
374         // We do not expect this to by typically populated
375         final List<Mutable<?, ?, ?>> incomplete = new ArrayList<>(0);
376
377         Mutable<?, ?, ?> current = stmt.coerceParentContext();
378         Mutable<?, ?, ?> parent = current.getParentContext();
379         while (parent != null) {
380             if (YangStmtMapping.LIST == current.publicDefinition()
381                     && !current.hasSubstatement(KeyEffectiveStatement.class)) {
382                 if (ModelProcessingPhase.FULL_DECLARATION.isCompletedBy(current.getCompletedPhase())) {
383                     throw new SourceException(stmt, "%s %s is defined within a list that has no key statement", name,
384                         stmt.argument());
385                 }
386
387                 // Ancestor has not completed full declaration yet missing 'key' statement may materialize later
388                 incomplete.add(current);
389             }
390
391             current = parent;
392             parent = current.getParentContext();
393         }
394
395         // Deal with whatever incomplete ancestors we encountered
396         for (Mutable<?, ?, ?> ancestor : incomplete) {
397             // This check must complete during the ancestor's FULL_DECLARATION phase, i.e. the ancestor must not reach
398             // EFFECTIVE_MODEL until it is done.
399             final ModelActionBuilder action = ancestor.newInferenceAction(ModelProcessingPhase.FULL_DECLARATION);
400             action.apply(new InferenceAction() {
401                 @Override
402                 public void apply(final InferenceContext ctx) {
403                     if (!ancestor.hasSubstatement(KeyEffectiveStatement.class)) {
404                         throw new SourceException(stmt, "%s %s is defined within a list that has no key statement",
405                             name, stmt.argument());
406                     }
407                 }
408
409                 @Override
410                 public void prerequisiteFailed(final Collection<? extends Prerequisite<?>> failed) {
411                     throw new VerifyException("Should never happen");
412                 }
413             });
414         }
415     }
416
417     /**
418      * Checks whether the parent of StmtContext is of specified type.
419      *
420      * @param ctx StmtContext to be checked
421      * @param parentType type of parent to check
422      * @return true if the parent of StmtContext is of specified type, otherwise false
423      */
424     public static boolean hasParentOfType(final StmtContext<?, ?, ?> ctx, final StatementDefinition parentType) {
425         requireNonNull(parentType);
426         final StmtContext<?, ?, ?> parentContext = ctx.getParentContext();
427         return parentContext != null && parentType.equals(parentContext.publicDefinition());
428     }
429
430     /**
431      * Validates the specified statement context with regards to if-feature and when statement on list keys.
432      * The context can either be a leaf which is defined directly in the substatements of a keyed list or a uses
433      * statement defined in a keyed list (a uses statement may add leaves into the list).
434      *
435      * <p>
436      * If one of the list keys contains an if-feature or a when statement in YANG 1.1 model, an exception is thrown.
437      *
438      * @param ctx statement context to be validated
439      */
440     public static void validateIfFeatureAndWhenOnListKeys(final StmtContext<?, ?, ?> ctx) {
441         if (!isRelevantForIfFeatureAndWhenOnListKeysCheck(ctx)) {
442             return;
443         }
444
445         final StmtContext<?, ?, ?> listStmtCtx = ctx.coerceParentContext();
446         final StmtContext<Set<QName>, ?, ?> keyStmtCtx = findFirstDeclaredSubstatement(listStmtCtx, KeyStatement.class);
447
448         if (YangStmtMapping.LEAF.equals(ctx.publicDefinition())) {
449             if (isListKey(ctx, keyStmtCtx)) {
450                 disallowIfFeatureAndWhenOnListKeys(ctx);
451             }
452         } else if (YangStmtMapping.USES.equals(ctx.publicDefinition())) {
453             findAllEffectiveSubstatements(listStmtCtx, LeafStatement.class).forEach(leafStmtCtx -> {
454                 if (isListKey(leafStmtCtx, keyStmtCtx)) {
455                     disallowIfFeatureAndWhenOnListKeys(leafStmtCtx);
456                 }
457             });
458         }
459     }
460
461     private static boolean isRelevantForIfFeatureAndWhenOnListKeysCheck(final StmtContext<?, ?, ?> ctx) {
462         return YangVersion.VERSION_1_1.equals(ctx.yangVersion()) && hasParentOfType(ctx, YangStmtMapping.LIST)
463                 && findFirstDeclaredSubstatement(ctx.coerceParentContext(), KeyStatement.class) != null;
464     }
465
466     private static boolean isListKey(final StmtContext<?, ?, ?> leafStmtCtx,
467             final StmtContext<Set<QName>, ?, ?> keyStmtCtx) {
468         return keyStmtCtx.getArgument().contains(leafStmtCtx.argument());
469     }
470
471     private static void disallowIfFeatureAndWhenOnListKeys(final StmtContext<?, ?, ?> leafStmtCtx) {
472         leafStmtCtx.allSubstatements().forEach(leafSubstmtCtx -> {
473             final StatementDefinition statementDef = leafSubstmtCtx.publicDefinition();
474             SourceException.throwIf(YangStmtMapping.IF_FEATURE.equals(statementDef)
475                     || YangStmtMapping.WHEN.equals(statementDef), leafStmtCtx,
476                     "%s statement is not allowed in %s leaf statement which is specified as a list key.",
477                     statementDef.getStatementName(), leafStmtCtx.argument());
478         });
479     }
480
481     public static @NonNull QName qnameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
482         if (Strings.isNullOrEmpty(value)) {
483             return ctx.publicDefinition().getStatementName();
484         }
485
486         String prefix;
487         QNameModule qnameModule = null;
488         String localName = null;
489
490         final String[] namesParts = value.split(":");
491         switch (namesParts.length) {
492             case 1:
493                 localName = namesParts[0];
494                 qnameModule = getRootModuleQName(ctx);
495                 break;
496             default:
497                 prefix = namesParts[0];
498                 localName = namesParts[1];
499                 qnameModule = getModuleQNameByPrefix(ctx, prefix);
500                 // in case of unknown statement argument, we're not going to parse it
501                 if (qnameModule == null && isUnknownStatement(ctx)) {
502                     localName = value;
503                     qnameModule = getRootModuleQName(ctx);
504                 }
505                 if (qnameModule == null && ctx.history().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
506                     ctx = ctx.getOriginalCtx().orElse(null);
507                     qnameModule = getModuleQNameByPrefix(ctx, prefix);
508                 }
509         }
510
511         return internedQName(ctx, InferenceException.throwIfNull(qnameModule, ctx,
512             "Cannot resolve QNameModule for '%s'", value), localName);
513     }
514
515     /**
516      * Parse a YANG identifier string in context of a statement.
517      *
518      * @param ctx Statement context
519      * @param str String to be parsed
520      * @return An interned QName
521      * @throws NullPointerException if any of the arguments are null
522      * @throws SourceException if the string is not a valid YANG identifier
523      */
524     public static @NonNull QName parseIdentifier(final StmtContext<?, ?, ?> ctx, final String str) {
525         SourceException.throwIf(str.isEmpty(), ctx, "Identifier may not be an empty string");
526         return internedQName(ctx, str);
527     }
528
529     public static @NonNull QName parseNodeIdentifier(final StmtContext<?, ?, ?> ctx, final String prefix,
530             final String localName) {
531         return internedQName(ctx,
532             InferenceException.throwIfNull(getModuleQNameByPrefix(ctx, prefix), ctx,
533                 "Cannot resolve QNameModule for '%s'", prefix),
534             localName);
535     }
536
537     /**
538      * Parse a YANG node identifier string in context of a statement.
539      *
540      * @param ctx Statement context
541      * @param str String to be parsed
542      * @return An interned QName
543      * @throws NullPointerException if any of the arguments are null
544      * @throws SourceException if the string is not a valid YANG node identifier
545      */
546     public static @NonNull QName parseNodeIdentifier(final StmtContext<?, ?, ?> ctx, final String str) {
547         SourceException.throwIf(str.isEmpty(), ctx, "Node identifier may not be an empty string");
548
549         final int colon = str.indexOf(':');
550         if (colon == -1) {
551             return internedQName(ctx, str);
552         }
553
554         final String prefix = str.substring(0, colon);
555         SourceException.throwIf(prefix.isEmpty(), ctx, "String '%s' has an empty prefix", str);
556         final String localName = str.substring(colon + 1);
557         SourceException.throwIf(localName.isEmpty(), ctx, "String '%s' has an empty identifier", str);
558
559         return parseNodeIdentifier(ctx, prefix, localName);
560     }
561
562     private static @NonNull QName internedQName(final StmtContext<?, ?, ?> ctx, final String localName) {
563         return internedQName(ctx, getRootModuleQName(ctx), localName);
564     }
565
566     private static @NonNull QName internedQName(final CommonStmtCtx ctx, final QNameModule module,
567             final String localName) {
568         final QName template;
569         try {
570             template = QName.create(module, localName);
571         } catch (IllegalArgumentException e) {
572             throw new SourceException(ctx, e, "Invalid identifier '%s'", localName);
573         }
574         return template.intern();
575     }
576
577     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
578         if (ctx == null) {
579             return null;
580         }
581
582         final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
583         final QNameModule qnameModule;
584
585         if (rootCtx.producesDeclared(ModuleStatement.class)) {
586             qnameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
587         } else if (rootCtx.producesDeclared(SubmoduleStatement.class)) {
588             final String belongsToModuleName = firstAttributeOf(rootCtx.declaredSubstatements(),
589                 BelongsToStatement.class);
590             qnameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
591         } else {
592             qnameModule = null;
593         }
594
595         checkArgument(qnameModule != null, "Failed to look up root QNameModule for %s", ctx);
596         return qnameModule;
597     }
598
599     public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
600         final StmtContext<?, ?, ?> root = ctx.getRoot();
601         final StmtContext<?, ?, ?> importedModule = root.getFromNamespace(ImportPrefixToModuleCtx.class, prefix);
602         final QNameModule qnameModule = ctx.getFromNamespace(ModuleCtxToModuleQName.class, importedModule);
603         if (qnameModule != null) {
604             return qnameModule;
605         }
606
607         if (root.producesDeclared(SubmoduleStatement.class)) {
608             final String moduleName = root.getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
609             return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
610         }
611
612         return null;
613     }
614
615     public static Optional<Revision> getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
616         Revision revision = null;
617         for (final StmtContext<?, ?, ?> subStmt : subStmts) {
618             if (subStmt.producesDeclared(RevisionStatement.class)) {
619                 if (revision == null && subStmt.argument() != null) {
620                     revision = (Revision) subStmt.argument();
621                 } else {
622                     final Revision subArg = (Revision) subStmt.argument();
623                     if (subArg != null && subArg.compareTo(revision) > 0) {
624                         revision = subArg;
625                     }
626                 }
627             }
628         }
629         return Optional.ofNullable(revision);
630     }
631 }