Fixup SchemaNodeIdentifier design
[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 com.google.common.collect.Iterables;
16 import java.util.Collection;
17 import java.util.Optional;
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.Revision;
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     private StmtContextUtils() {
49         // Hidden on purpose
50     }
51
52     @SuppressWarnings("unchecked")
53     public static <A, D extends DeclaredStatement<A>> A firstAttributeOf(
54             final Iterable<? extends StmtContext<?, ?, ?>> contexts, final Class<D> declaredType) {
55         for (final StmtContext<?, ?, ?> ctx : contexts) {
56             if (producesDeclared(ctx, declaredType)) {
57                 return (A) ctx.getStatementArgument();
58             }
59         }
60         return null;
61     }
62
63     @SuppressWarnings("unchecked")
64     public static <A, D extends DeclaredStatement<A>> A firstAttributeOf(final StmtContext<?, ?, ?> ctx,
65             final Class<D> declaredType) {
66         return producesDeclared(ctx, declaredType) ? (A) ctx.getStatementArgument() : null;
67     }
68
69     public static <A, D extends DeclaredStatement<A>> A firstSubstatementAttributeOf(
70             final StmtContext<?, ?, ?> ctx, final Class<D> declaredType) {
71         return firstAttributeOf(ctx.allSubstatements(), declaredType);
72     }
73
74     @SuppressWarnings("unchecked")
75     public static <A, D extends DeclaredStatement<A>> StmtContext<A, ?, ?> findFirstDeclaredSubstatement(
76             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
77         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
78             if (producesDeclared(subStmtContext, declaredType)) {
79                 return (StmtContext<A, ?, ?>) subStmtContext;
80             }
81         }
82         return null;
83     }
84
85     @SafeVarargs
86     public static StmtContext<?, ?, ?> findFirstDeclaredSubstatement(final StmtContext<?, ?, ?> stmtContext,
87             int startIndex, final Class<? extends DeclaredStatement<?>>... types) {
88         if (startIndex >= types.length) {
89             return null;
90         }
91
92         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
93             if (producesDeclared(subStmtContext, types[startIndex])) {
94                 return startIndex + 1 == types.length ? subStmtContext : findFirstDeclaredSubstatement(subStmtContext,
95                         ++startIndex, types);
96             }
97         }
98         return null;
99     }
100
101     @SuppressWarnings("unchecked")
102     public static <A, D extends DeclaredStatement<A>> Collection<StmtContext<A, D, ?>> findAllDeclaredSubstatements(
103             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
104         final ImmutableList.Builder<StmtContext<A, D, ?>> listBuilder = ImmutableList.builder();
105         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
106             if (producesDeclared(subStmtContext, declaredType)) {
107                 listBuilder.add((StmtContext<A, D, ?>) subStmtContext);
108             }
109         }
110         return listBuilder.build();
111     }
112
113     @SuppressWarnings("unchecked")
114     public static <A, D extends DeclaredStatement<A>> Collection<StmtContext<A, D, ?>> findAllEffectiveSubstatements(
115             final StmtContext<?, ?, ?> stmtContext, final Class<D> type) {
116         final ImmutableList.Builder<StmtContext<A, D, ?>> listBuilder = ImmutableList.builder();
117         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.effectiveSubstatements()) {
118             if (producesDeclared(subStmtContext, type)) {
119                 listBuilder.add((StmtContext<A, D, ?>) subStmtContext);
120             }
121         }
122         return listBuilder.build();
123     }
124
125     public static <A, D extends DeclaredStatement<A>> Collection<StmtContext<A, D, ?>> findAllSubstatements(
126             final StmtContext<?, ?, ?> stmtContext, final Class<D> type) {
127         final ImmutableList.Builder<StmtContext<A, D, ?>> listBuilder = ImmutableList.builder();
128         listBuilder.addAll(findAllDeclaredSubstatements(stmtContext, type));
129         listBuilder.addAll(findAllEffectiveSubstatements(stmtContext, type));
130         return listBuilder.build();
131     }
132
133     @SuppressWarnings("unchecked")
134     public static <A, D extends DeclaredStatement<A>> StmtContext<A, ?, ?> findFirstEffectiveSubstatement(
135             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
136         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.effectiveSubstatements()) {
137             if (producesDeclared(subStmtContext, declaredType)) {
138                 return (StmtContext<A, ?, ?>) subStmtContext;
139             }
140         }
141         return null;
142     }
143
144     /**
145      * Searches for the first substatement of the specified type in the specified statement context.
146      * First, it tries to find the substatement in the effective substatements of the statement context.
147      * If it was not found, then it proceeds to search in the declared substatements. If it still was not found,
148      * the method returns null.
149      *
150      * @param stmtContext statement context to search in
151      * @param declaredType substatement type to search for
152      * @param <A> statement argument type
153      * @param <D> declared statement type
154      * @return statement context that was searched for or null if was not found
155      */
156     public static <A, D extends DeclaredStatement<A>> StmtContext<A, ?, ?> findFirstSubstatement(
157             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
158         final StmtContext<A, ?, ?> effectiveSubstatement = findFirstEffectiveSubstatement(stmtContext, declaredType);
159         return effectiveSubstatement != null ? effectiveSubstatement : findFirstDeclaredSubstatement(stmtContext,
160                 declaredType);
161     }
162
163     public static <D extends DeclaredStatement<?>> StmtContext<?, ?, ?> findFirstDeclaredSubstatementOnSublevel(
164             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType, int sublevel) {
165         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
166             if (sublevel == 1 && producesDeclared(subStmtContext, declaredType)) {
167                 return subStmtContext;
168             }
169             if (sublevel > 1) {
170                 final StmtContext<?, ?, ?> result = findFirstDeclaredSubstatementOnSublevel(subStmtContext,
171                         declaredType, --sublevel);
172                 if (result != null) {
173                     return result;
174                 }
175             }
176         }
177
178         return null;
179     }
180
181     public static <D extends DeclaredStatement<?>> StmtContext<?, ?, ?> findDeepFirstDeclaredSubstatement(
182             final StmtContext<?, ?, ?> stmtContext, final Class<D> declaredType) {
183         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
184             if (producesDeclared(subStmtContext, declaredType)) {
185                 return subStmtContext;
186             }
187
188             final StmtContext<?, ?, ?> result = findDeepFirstDeclaredSubstatement(subStmtContext, declaredType);
189             if (result != null) {
190                 return result;
191             }
192         }
193
194         return null;
195     }
196
197     public static boolean producesDeclared(final StmtContext<?, ?, ?> ctx,
198             final Class<? extends DeclaredStatement<?>> type) {
199         return type.isAssignableFrom(ctx.getPublicDefinition().getDeclaredRepresentationClass());
200     }
201
202     public static boolean isInExtensionBody(final StmtContext<?, ?, ?> stmtCtx) {
203         StmtContext<?, ?, ?> current = stmtCtx;
204         while (current.coerceParentContext().getParentContext() != null) {
205             current = current.getParentContext();
206             if (isUnknownStatement(current)) {
207                 return true;
208             }
209         }
210
211         return false;
212     }
213
214     /**
215      * Returns true if supplied statement context represents unknown statement,
216      * otherwise returns false.
217      *
218      * @param stmtCtx
219      *            statement context to be checked
220      * @return true if supplied statement context represents unknown statement,
221      *         otherwise false
222      * @throws NullPointerException
223      *             if supplied statement context is null
224      */
225     public static boolean isUnknownStatement(final StmtContext<?, ?, ?> stmtCtx) {
226         return UnknownStatement.class
227                 .isAssignableFrom(stmtCtx.getPublicDefinition().getDeclaredRepresentationClass());
228     }
229
230     /**
231      * Returns true if supplied statement context represents unrecognized
232      * statement, otherwise returns false.
233      *
234      * @param stmtCtx
235      *            statement context to be checked
236      * @return true if supplied statement context represents unrecognized
237      *         statement, otherwise false
238      * @throws NullPointerException
239      *             if supplied statement context is null
240      */
241     public static boolean isUnrecognizedStatement(final StmtContext<?, ?, ?> stmtCtx) {
242         return UnrecognizedStatement.class
243                 .isAssignableFrom(stmtCtx.getPublicDefinition().getDeclaredRepresentationClass());
244     }
245
246     public static boolean checkFeatureSupport(final StmtContext<?, ?, ?> stmtContext,
247             final Set<QName> supportedFeatures) {
248         boolean isSupported = false;
249         boolean containsIfFeature = false;
250         for (final StmtContext<?, ?, ?> stmt : stmtContext.declaredSubstatements()) {
251             if (YangStmtMapping.IF_FEATURE.equals(stmt.getPublicDefinition())) {
252                 containsIfFeature = true;
253                 @SuppressWarnings("unchecked")
254                 final Predicate<Set<QName>> argument = (Predicate<Set<QName>>) stmt.coerceStatementArgument();
255                 if (argument.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.coerceParentContext();
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());
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 = findFirstDeclaredSubstatement(
427             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             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()) && hasParentOfType(ctx, YangStmtMapping.LIST)
444                 && findFirstDeclaredSubstatement(ctx.coerceParentContext(), KeyStatement.class) != null;
445     }
446
447     private static boolean isListKey(final StmtContext<?, ?, ?> leafStmtCtx,
448             final StmtContext<Collection<SchemaNodeIdentifier>, ?, ?> keyStmtCtx) {
449         for (final SchemaNodeIdentifier keyIdentifier : keyStmtCtx.coerceStatementArgument()) {
450             if (leafStmtCtx.getStatementArgument().equals(Iterables.getLast(keyIdentifier.getNodeIdentifiers()))) {
451                 return true;
452             }
453         }
454
455         return false;
456     }
457
458     private static void disallowIfFeatureAndWhenOnListKeys(final StmtContext<?, ?, ?> leafStmtCtx) {
459         leafStmtCtx.allSubstatements().forEach(leafSubstmtCtx -> {
460             final StatementDefinition statementDef = leafSubstmtCtx.getPublicDefinition();
461             SourceException.throwIf(YangStmtMapping.IF_FEATURE.equals(statementDef)
462                     || YangStmtMapping.WHEN.equals(statementDef), leafStmtCtx.getStatementSourceReference(),
463                     "%s statement is not allowed in %s leaf statement which is specified as a list key.",
464                     statementDef.getStatementName(), leafStmtCtx.getStatementArgument());
465         });
466     }
467
468     public static QName qnameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
469         if (Strings.isNullOrEmpty(value)) {
470             return ctx.getPublicDefinition().getStatementName();
471         }
472
473         String prefix;
474         QNameModule qnameModule = null;
475         String localName = null;
476
477         final String[] namesParts = value.split(":");
478         switch (namesParts.length) {
479             case 1:
480                 localName = namesParts[0];
481                 qnameModule = getRootModuleQName(ctx);
482                 break;
483             default:
484                 prefix = namesParts[0];
485                 localName = namesParts[1];
486                 qnameModule = getModuleQNameByPrefix(ctx, prefix);
487                 // in case of unknown statement argument, we're not going to parse it
488                 if (qnameModule == null && isUnknownStatement(ctx)) {
489                     localName = value;
490                     qnameModule = getRootModuleQName(ctx);
491                 }
492                 if (qnameModule == null && ctx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
493                     ctx = ctx.getOriginalCtx().orElse(null);
494                     qnameModule = getModuleQNameByPrefix(ctx, prefix);
495                 }
496         }
497
498         return internedQName(ctx,
499             InferenceException.throwIfNull(qnameModule, ctx.getStatementSourceReference(),
500             "Cannot resolve QNameModule for '%s'", value), localName);
501     }
502
503     /**
504      * Parse a YANG identifier string in context of a statement.
505      *
506      * @param ctx Statement context
507      * @param str String to be parsed
508      * @return An interned QName
509      * @throws NullPointerException if any of the arguments are null
510      * @throws SourceException if the string is not a valid YANG identifier
511      */
512     public static QName parseIdentifier(final StmtContext<?, ?, ?> ctx, final String str) {
513         SourceException.throwIf(str.isEmpty(), ctx.getStatementSourceReference(),
514                 "Identifier may not be an empty string");
515         return internedQName(ctx, str);
516     }
517
518     public static QName parseNodeIdentifier(StmtContext<?, ?, ?> ctx, final String prefix,
519             final String localName) {
520         final QNameModule module = getModuleQNameByPrefix(ctx, prefix);
521         if (module != null) {
522             return internedQName(ctx, module, localName);
523         }
524
525         if (ctx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
526             final Optional<? extends StmtContext<?, ?, ?>> optOrigCtx = ctx.getOriginalCtx();
527             if (optOrigCtx.isPresent()) {
528                 ctx = optOrigCtx.get();
529                 final QNameModule origModule = getModuleQNameByPrefix(ctx, prefix);
530                 if (origModule != null) {
531                     return internedQName(ctx, origModule, localName);
532                 }
533             }
534         }
535
536         throw new InferenceException(ctx.getStatementSourceReference(), "Cannot resolve QNameModule for '%s'", prefix);
537     }
538
539     /**
540      * Parse a YANG node identifier string in context of a statement.
541      *
542      * @param ctx Statement context
543      * @param str String to be parsed
544      * @return An interned QName
545      * @throws NullPointerException if any of the arguments are null
546      * @throws SourceException if the string is not a valid YANG node identifier
547      */
548     public static QName parseNodeIdentifier(final StmtContext<?, ?, ?> ctx, final String str) {
549         SourceException.throwIf(str.isEmpty(), ctx.getStatementSourceReference(),
550                 "Node identifier may not be an empty string");
551
552         final int colon = str.indexOf(':');
553         if (colon == -1) {
554             return internedQName(ctx, str);
555         }
556
557         final String prefix = str.substring(0, colon);
558         SourceException.throwIf(prefix.isEmpty(), ctx.getStatementSourceReference(),
559             "String '%s' has an empty prefix", str);
560         final String localName = str.substring(colon + 1);
561         SourceException.throwIf(localName.isEmpty(), ctx.getStatementSourceReference(),
562             "String '%s' has an empty identifier", str);
563
564         return parseNodeIdentifier(ctx, prefix, localName);
565     }
566
567     private static QName internedQName(final StmtContext<?, ?, ?> ctx, final String localName) {
568         return internedQName(ctx, getRootModuleQName(ctx), localName);
569     }
570
571     private static QName internedQName(final StmtContext<?, ?, ?> ctx, final QNameModule module,
572             final String localName) {
573         final QName template;
574         try {
575             template = QName.create(module, localName);
576         } catch (IllegalArgumentException e) {
577             throw new SourceException(ctx.getStatementSourceReference(), e, "Invalid identifier '%s'", localName);
578         }
579         return template.intern();
580     }
581
582     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
583         if (ctx == null) {
584             return null;
585         }
586
587         final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
588         final QNameModule qnameModule;
589
590         if (producesDeclared(rootCtx, ModuleStatement.class)) {
591             qnameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
592         } else if (producesDeclared(rootCtx, SubmoduleStatement.class)) {
593             final String belongsToModuleName = firstAttributeOf(rootCtx.declaredSubstatements(),
594                 BelongsToStatement.class);
595             qnameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
596         } else {
597             qnameModule = null;
598         }
599
600         checkArgument(qnameModule != null, "Failed to look up root QNameModule for %s", ctx);
601         return qnameModule;
602     }
603
604     public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
605         final StmtContext<?, ?, ?> importedModule = ctx.getRoot().getFromNamespace(ImportPrefixToModuleCtx.class,
606             prefix);
607         final QNameModule qnameModule = ctx.getFromNamespace(ModuleCtxToModuleQName.class, importedModule);
608         if (qnameModule != null) {
609             return qnameModule;
610         }
611
612         if (producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
613             final String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
614             return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
615         }
616
617         return null;
618     }
619
620     public static SourceIdentifier createSourceIdentifier(final StmtContext<?, ?, ?> root) {
621         final QNameModule qNameModule = root.getFromNamespace(ModuleCtxToModuleQName.class, root);
622         if (qNameModule != null) {
623             // creates SourceIdentifier for a module
624             return RevisionSourceIdentifier.create((String) root.getStatementArgument(), qNameModule.getRevision());
625         }
626
627         // creates SourceIdentifier for a submodule
628         final Optional<Revision> revision = getLatestRevision(root.declaredSubstatements());
629         return RevisionSourceIdentifier.create((String) root.getStatementArgument(), revision);
630     }
631
632     public static Optional<Revision> getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
633         Revision revision = null;
634         for (final StmtContext<?, ?, ?> subStmt : subStmts) {
635             if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(
636                     RevisionStatement.class)) {
637                 if (revision == null && subStmt.getStatementArgument() != null) {
638                     revision = (Revision) subStmt.getStatementArgument();
639                 } else {
640                     final Revision subArg = (Revision) subStmt.getStatementArgument();
641                     if (subArg != null && subArg.compareTo(revision) > 0) {
642                         revision = subArg;
643                     }
644                 }
645             }
646         }
647         return Optional.ofNullable(revision);
648     }
649 }