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