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