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