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