da704e5df417057aae3d038f29ae419e5fe7594f
[yangtools.git] / yang / yang-parser-impl / 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 com.google.common.base.Preconditions;
11 import com.google.common.base.Splitter;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableSet;
14 import com.google.common.collect.ImmutableSet.Builder;
15 import com.google.common.collect.Iterables;
16 import java.util.Collection;
17 import java.util.Set;
18 import java.util.function.Predicate;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.common.QNameModule;
21 import org.opendaylight.yangtools.yang.common.YangVersion;
22 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
23 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
24 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
25 import org.opendaylight.yangtools.yang.model.api.stmt.KeyStatement;
26 import org.opendaylight.yangtools.yang.model.api.stmt.LeafStatement;
27 import org.opendaylight.yangtools.yang.model.api.stmt.MandatoryStatement;
28 import org.opendaylight.yangtools.yang.model.api.stmt.MinElementsStatement;
29 import org.opendaylight.yangtools.yang.model.api.stmt.PresenceStatement;
30 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
31 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
32 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace;
33 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace.SupportedFeatures;
34 import org.opendaylight.yangtools.yang.parser.stmt.reactor.RootStatementContext;
35 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
36 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.UnknownStatementImpl;
37 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangDataStatementImpl;
38
39 public final class StmtContextUtils {
40     public static final Splitter LIST_KEY_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
41
42     private StmtContextUtils() {
43         throw new UnsupportedOperationException("Utility class");
44     }
45
46     @SuppressWarnings("unchecked")
47     public static <AT, DT extends DeclaredStatement<AT>> AT firstAttributeOf(
48             final Iterable<? extends StmtContext<?, ?, ?>> contexts, final Class<DT> declaredType) {
49         for (final StmtContext<?, ?, ?> ctx : contexts) {
50             if (producesDeclared(ctx, declaredType)) {
51                 return (AT) ctx.getStatementArgument();
52             }
53         }
54         return null;
55     }
56
57     @SuppressWarnings("unchecked")
58     public static <AT, DT extends DeclaredStatement<AT>> AT firstAttributeOf(final StmtContext<?, ?, ?> ctx,
59             final Class<DT> declaredType) {
60         return producesDeclared(ctx, declaredType) ? (AT) ctx.getStatementArgument() : null;
61     }
62
63     public static <AT, DT extends DeclaredStatement<AT>> AT firstSubstatementAttributeOf(
64             final StmtContext<?, ?, ?> ctx, final Class<DT> declaredType) {
65         final AT firstAttribute = firstAttributeOf(ctx.effectiveSubstatements(), declaredType);
66         return firstAttribute != null ? firstAttribute : firstAttributeOf(ctx.declaredSubstatements(), declaredType);
67     }
68
69     @SuppressWarnings("unchecked")
70     public static <AT, DT extends DeclaredStatement<AT>> StmtContext<AT, ?, ?> findFirstDeclaredSubstatement(
71             final StmtContext<?, ?, ?> stmtContext, final Class<DT> declaredType) {
72         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
73             if (producesDeclared(subStmtContext, declaredType)) {
74                 return (StmtContext<AT, ?, ?>) subStmtContext;
75             }
76         }
77         return null;
78     }
79
80     @SuppressWarnings("unchecked")
81     public static <AT, DT extends DeclaredStatement<AT>> Collection<StmtContext<AT, DT, ?>> findAllDeclaredSubstatements(
82             final StmtContext<?, ?, ?> stmtContext, final Class<DT> declaredType) {
83         final ImmutableList.Builder<StmtContext<AT, DT, ?>> listBuilder = ImmutableList.builder();
84         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
85             if (producesDeclared(subStmtContext, declaredType)) {
86                 listBuilder.add((StmtContext<AT, DT, ?>) subStmtContext);
87             }
88         }
89         return listBuilder.build();
90     }
91
92     @SuppressWarnings("unchecked")
93     public static <AT, DT extends DeclaredStatement<AT>> Collection<StmtContext<AT, DT, ?>> findAllEffectiveSubstatements(
94             final StmtContext<?, ?, ?> stmtContext, final Class<DT> type) {
95         final ImmutableList.Builder<StmtContext<AT, DT, ?>> listBuilder = ImmutableList.builder();
96         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.effectiveSubstatements()) {
97             if (producesDeclared(subStmtContext, type)) {
98                 listBuilder.add((StmtContext<AT, DT, ?>) subStmtContext);
99             }
100         }
101         return listBuilder.build();
102     }
103
104     public static <AT, DT extends DeclaredStatement<AT>> Collection<StmtContext<AT, DT, ?>> findAllSubstatements(
105             final StmtContext<?, ?, ?> stmtContext, final Class<DT> type) {
106         final ImmutableList.Builder<StmtContext<AT, DT, ?>> listBuilder = ImmutableList.builder();
107         listBuilder.addAll(findAllDeclaredSubstatements(stmtContext, type));
108         listBuilder.addAll(findAllEffectiveSubstatements(stmtContext, type));
109         return listBuilder.build();
110     }
111
112     @SuppressWarnings("unchecked")
113     public static <AT, DT extends DeclaredStatement<AT>> StmtContext<AT, ?, ?> findFirstEffectiveSubstatement(
114             final StmtContext<?, ?, ?> stmtContext, final Class<DT> declaredType) {
115         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.effectiveSubstatements()) {
116             if (producesDeclared(subStmtContext, declaredType)) {
117                 return (StmtContext<AT, ?, ?>) subStmtContext;
118             }
119         }
120         return null;
121     }
122
123     /**
124      * Searches for the first substatement of the specified type in the specified statement context.
125      * First, it tries to find the substatement in the effective substatements of the statement context.
126      * If it was not found, then it proceeds to search in the declared substatements. If it still was not found,
127      * the method returns null.
128      *
129      * @param stmtContext statement context to search in
130      * @param declaredType substatement type to search for
131      * @param <AT> statement argument type
132      * @param <DT> declared statement type
133      * @return statement context that was searched for or null if was not found
134      */
135     public static <AT, DT extends DeclaredStatement<AT>> StmtContext<AT, ?, ?> findFirstSubstatement(
136             final StmtContext<?, ?, ?> stmtContext, final Class<DT> declaredType) {
137         final StmtContext<AT, ?, ?> effectiveSubstatement = findFirstEffectiveSubstatement(stmtContext, declaredType);
138         return effectiveSubstatement != null ? effectiveSubstatement : findFirstDeclaredSubstatement(stmtContext,
139                 declaredType);
140     }
141
142     @SafeVarargs
143     public static StmtContext<?, ?, ?> findFirstDeclaredSubstatement(final StmtContext<?, ?, ?> stmtContext,
144             int startIndex, final Class<? extends DeclaredStatement<?>>... types) {
145         if (startIndex >= types.length) {
146             return null;
147         }
148
149         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
150             if (producesDeclared(subStmtContext, types[startIndex])) {
151                 return startIndex + 1 == types.length ? subStmtContext : findFirstDeclaredSubstatement(subStmtContext,
152                         ++startIndex, types);
153             }
154         }
155         return null;
156     }
157
158     public static <DT extends DeclaredStatement<?>> StmtContext<?, ?, ?> findFirstDeclaredSubstatementOnSublevel(
159             final StmtContext<?, ?, ?> stmtContext, final Class<DT> declaredType, int sublevel) {
160         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
161             if (sublevel == 1 && producesDeclared(subStmtContext, declaredType)) {
162                 return subStmtContext;
163             }
164             if (sublevel > 1) {
165                 final StmtContext<?, ?, ?> result = findFirstDeclaredSubstatementOnSublevel(subStmtContext,
166                         declaredType, --sublevel);
167                 if (result != null) {
168                     return result;
169                 }
170             }
171         }
172
173         return null;
174     }
175
176     public static <DT extends DeclaredStatement<?>> StmtContext<?, ?, ?> findDeepFirstDeclaredSubstatement(
177             final StmtContext<?, ?, ?> stmtContext, final Class<DT> declaredType) {
178         for (final StmtContext<?, ?, ?> subStmtContext : stmtContext.declaredSubstatements()) {
179             if (producesDeclared(subStmtContext, declaredType)) {
180                 return subStmtContext;
181             }
182
183             final StmtContext<?, ?, ?> result = findDeepFirstDeclaredSubstatement(subStmtContext, declaredType);
184             if (result != null) {
185                 return result;
186             }
187         }
188
189         return null;
190     }
191
192     public static boolean producesDeclared(final StmtContext<?, ?, ?> ctx,
193             final Class<? extends DeclaredStatement<?>> type) {
194         return type.isAssignableFrom(ctx.getPublicDefinition().getDeclaredRepresentationClass());
195     }
196
197     public static boolean isInExtensionBody(final StmtContext<?, ?, ?> stmtCtx) {
198         StmtContext<?, ?, ?> current = stmtCtx;
199         while (!current.getParentContext().isRootContext()) {
200             current = current.getParentContext();
201             if (producesDeclared(current, UnknownStatementImpl.class)) {
202                 return true;
203             }
204         }
205
206         return false;
207     }
208
209     /**
210      * Checks if the statement context has a 'yang-data' extension node as its parent.
211      *
212      * @param stmtCtx statement context to be checked
213      * @return true if the parent node is a 'yang-data' node, otherwise false
214      */
215     public static boolean hasYangDataExtensionParent(final StmtContext<?, ?, ?> stmtCtx) {
216         return producesDeclared(stmtCtx.getParentContext(), YangDataStatementImpl.class);
217     }
218
219     public static boolean isUnknownStatement(final StmtContext<?, ?, ?> stmtCtx) {
220         return producesDeclared(stmtCtx, UnknownStatementImpl.class);
221     }
222
223     public static Collection<SchemaNodeIdentifier> replaceModuleQNameForKey(
224             final StmtContext<Collection<SchemaNodeIdentifier>, KeyStatement, ?> keyStmtCtx,
225             final QNameModule newQNameModule) {
226
227         final Builder<SchemaNodeIdentifier> builder = ImmutableSet.builder();
228         boolean replaced = false;
229         for (final SchemaNodeIdentifier arg : keyStmtCtx.getStatementArgument()) {
230             final QName qname = arg.getLastComponent();
231             if (!newQNameModule.equals(qname)) {
232                 final QName newQname = keyStmtCtx.getFromNamespace(QNameCacheNamespace.class,
233                         QName.create(newQNameModule, qname.getLocalName()));
234                 builder.add(SchemaNodeIdentifier.create(false, newQname));
235                 replaced = true;
236             } else {
237                 builder.add(arg);
238             }
239         }
240
241         // This makes sure we reuse the collection when a grouping is
242         // instantiated in the same module
243         return replaced ? builder.build() : keyStmtCtx.getStatementArgument();
244     }
245
246     public static boolean areFeaturesSupported(final StmtContext.Mutable<?, ?, ?> stmtContext) {
247         switch (stmtContext.getSupportedByFeatures()) {
248         case SUPPORTED:
249             return true;
250         case NOT_SUPPORTED:
251             return false;
252         default:
253             break;
254         }
255
256         final Set<QName> supportedFeatures = stmtContext.getFromNamespace(SupportedFeaturesNamespace.class,
257                 SupportedFeatures.SUPPORTED_FEATURES);
258         /*
259          * If set of supported features has not been provided, all features are
260          * supported by default.
261          */
262         if (supportedFeatures == null) {
263             stmtContext.setSupportedByFeatures(true);
264             return true;
265         }
266
267         final boolean result = checkFeatureSupport(stmtContext, supportedFeatures);
268         stmtContext.setSupportedByFeatures(result);
269         return result;
270     }
271
272     private static boolean checkFeatureSupport(final StmtContext.Mutable<?, ?, ?> stmtContext,
273             final Set<QName> supportedFeatures) {
274         boolean isSupported = false;
275         boolean containsIfFeature = false;
276         for (final StatementContextBase<?, ?, ?> stmt : stmtContext.declaredSubstatements()) {
277             if (YangStmtMapping.IF_FEATURE.equals(stmt.getPublicDefinition())) {
278                 if (stmtContext.isInYangDataExtensionBody()) {
279                     break;
280                 }
281
282                 containsIfFeature = true;
283                 if (((Predicate<Set<QName>>) stmt.getStatementArgument()).test(supportedFeatures)) {
284                     isSupported = true;
285                 } else {
286                     isSupported = false;
287                     break;
288                 }
289             }
290         }
291
292         return !containsIfFeature || isSupported;
293     }
294
295     /**
296      * Checks whether statement context is a presence container or not.
297      *
298      * @param stmtCtx
299      *            statement context
300      * @return true if it is a presence container
301      */
302     public static boolean isPresenceContainer(final StatementContextBase<?, ?, ?> stmtCtx) {
303         return stmtCtx.getPublicDefinition() == YangStmtMapping.CONTAINER && containsPresenceSubStmt(stmtCtx);
304     }
305
306     /**
307      * Checks whether statement context is a non-presence container or not.
308      *
309      * @param stmtCtx
310      *            statement context
311      * @return true if it is a non-presence container
312      */
313     public static boolean isNonPresenceContainer(final StatementContextBase<?, ?, ?> stmtCtx) {
314         return stmtCtx.getPublicDefinition() == YangStmtMapping.CONTAINER && !containsPresenceSubStmt(stmtCtx);
315     }
316
317     private static boolean containsPresenceSubStmt(final StatementContextBase<?, ?, ?> stmtCtx) {
318         return findFirstSubstatement(stmtCtx, PresenceStatement.class) != null;
319     }
320
321     /**
322      * Checks whether statement context is a mandatory leaf, choice, anyxml,
323      * list or leaf-list according to RFC6020 or not.
324      *
325      * @param stmtCtx
326      *            statement context
327      * @return true if it is a mandatory leaf, choice, anyxml, list or leaf-list
328      *         according to RFC6020.
329      */
330     public static boolean isMandatoryNode(final StatementContextBase<?, ?, ?> stmtCtx) {
331         if (!(stmtCtx.getPublicDefinition() instanceof YangStmtMapping)) {
332             return false;
333         }
334         switch ((YangStmtMapping) stmtCtx.getPublicDefinition()) {
335         case LEAF:
336         case CHOICE:
337         case ANYXML:
338             return Boolean.TRUE.equals(firstSubstatementAttributeOf(stmtCtx, MandatoryStatement.class));
339         case LIST:
340         case LEAF_LIST:
341             final Integer minElements = firstSubstatementAttributeOf(stmtCtx, MinElementsStatement.class);
342             return minElements != null && minElements > 0;
343         default:
344             return false;
345         }
346     }
347
348     /**
349      * Checks whether a statement context is a statement of supplied statement
350      * definition and whether it is not mandatory leaf, choice, anyxml, list or
351      * leaf-list according to RFC6020.
352      *
353      * @param stmtCtx
354      *            statement context
355      * @param stmtDef
356      *            statement definition
357      * @return true if supplied statement context is a statement of supplied
358      *         statement definition and if it is not mandatory leaf, choice,
359      *         anyxml, list or leaf-list according to RFC6020
360      */
361     public static boolean isNotMandatoryNodeOfType(final StatementContextBase<?, ?, ?> stmtCtx,
362             final StatementDefinition stmtDef) {
363         return stmtCtx.getPublicDefinition().equals(stmtDef) && !isMandatoryNode(stmtCtx);
364     }
365
366     /**
367      * Checks whether at least one ancestor of a StatementContext matches one
368      * from collection of statement definitions.
369      *
370      * @param ctx
371      *            StatementContext to be checked
372      * @param ancestorTypes
373      *            collection of statement definitions
374      *
375      * @return true if at least one ancestor of a StatementContext matches one
376      *         from collection of statement definitions, otherwise false.
377      */
378     public static boolean hasAncestorOfType(final StmtContext<?, ?, ?> ctx,
379             final Collection<StatementDefinition> ancestorTypes) {
380         Preconditions.checkNotNull(ctx);
381         Preconditions.checkNotNull(ancestorTypes);
382         StmtContext<?, ?, ?> current = ctx.getParentContext();
383         while (current != null) {
384             if (ancestorTypes.contains(current.getPublicDefinition())) {
385                 return true;
386             }
387             current = current.getParentContext();
388         }
389         return false;
390     }
391
392     /**
393      * Checks whether all of StmtContext's ancestors of specified type have a child of specified type
394      *
395      * @param ctx StmtContext to be checked
396      * @param ancestorType type of ancestor to search for
397      * @param ancestorChildType type of child to search for in the specified ancestor type
398      *
399      * @return true if all of StmtContext's ancestors of specified type have a child of specified type, otherwise false
400      */
401     public static <AT, DT extends DeclaredStatement<AT>> boolean hasAncestorOfTypeWithChildOfType(final StmtContext<?, ?, ?> ctx,
402             final StatementDefinition ancestorType, final StatementDefinition ancestorChildType) {
403         Preconditions.checkNotNull(ctx);
404         Preconditions.checkNotNull(ancestorType);
405         Preconditions.checkNotNull(ancestorChildType);
406         StmtContext<?, ?, ?> current = ctx.getParentContext();
407         while (!(current instanceof RootStatementContext)) {
408             if (ancestorType.equals(current.getPublicDefinition())) {
409                 @SuppressWarnings("unchecked")
410                 final Class<DT> ancestorChildTypeClass = (Class<DT>) ancestorChildType.getDeclaredRepresentationClass();
411                 if (findFirstSubstatement(current, ancestorChildTypeClass) == null) {
412                     return false;
413                 }
414             }
415             current = current.getParentContext();
416         }
417
418         return true;
419     }
420
421     /**
422      * Checks whether the parent of StmtContext is of specified type
423      *
424      * @param ctx
425      *            StmtContext to be checked
426      * @param parentType
427      *            type of parent to check
428      *
429      * @return true if the parent of StmtContext is of specified type, otherwise
430      *         false
431      */
432     public static boolean hasParentOfType(final StmtContext<?, ?, ?> ctx, final StatementDefinition parentType) {
433         Preconditions.checkNotNull(ctx);
434         Preconditions.checkNotNull(parentType);
435         final StmtContext<?, ?, ?> parentContext = ctx.getParentContext();
436         return parentContext != null ? parentType.equals(parentContext.getPublicDefinition()) : false;
437     }
438
439     /**
440      * Validates the specified statement context with regards to if-feature and when statement on list keys.
441      * The context can either be a leaf which is defined directly in the substatements of a keyed list or a uses
442      * statement defined in a keyed list (a uses statement may add leaves into the list).
443      *
444      * If one of the list keys contains an if-feature or a when statement in YANG 1.1 model, an exception is thrown.
445      *
446      * @param ctx statement context to be validated
447      */
448     public static void validateIfFeatureAndWhenOnListKeys(final StmtContext<?, ?, ?> ctx) {
449         Preconditions.checkNotNull(ctx);
450
451         if (!isRelevantForIfFeatureAndWhenOnListKeysCheck(ctx)) {
452             return;
453         }
454
455         final StmtContext<?, ?, ?> listStmtCtx = ctx.getParentContext();
456         final StmtContext<Collection<SchemaNodeIdentifier>, ?, ?> keyStmtCtx =
457                 StmtContextUtils.findFirstDeclaredSubstatement(listStmtCtx, KeyStatement.class);
458
459         if (YangStmtMapping.LEAF.equals(ctx.getPublicDefinition())) {
460             if (isListKey(ctx, keyStmtCtx)) {
461                 disallowIfFeatureAndWhenOnListKeys(ctx);
462             }
463         } else if (YangStmtMapping.USES.equals(ctx.getPublicDefinition())) {
464             StmtContextUtils.findAllEffectiveSubstatements(listStmtCtx, LeafStatement.class).forEach(leafStmtCtx -> {
465                 if (isListKey(leafStmtCtx, keyStmtCtx)) {
466                     disallowIfFeatureAndWhenOnListKeys(leafStmtCtx);
467                 }
468             });
469         }
470     }
471
472     private static boolean isRelevantForIfFeatureAndWhenOnListKeysCheck(final StmtContext<?, ?, ?> ctx) {
473         return YangVersion.VERSION_1_1.equals(ctx.getRootVersion())
474                 && StmtContextUtils.hasParentOfType(ctx, YangStmtMapping.LIST)
475                 && StmtContextUtils.findFirstDeclaredSubstatement(ctx.getParentContext(), KeyStatement.class) != null;
476     }
477
478     private static boolean isListKey(final StmtContext<?, ?, ?> leafStmtCtx,
479             final StmtContext<Collection<SchemaNodeIdentifier>, ?, ?> keyStmtCtx) {
480         for (final SchemaNodeIdentifier keyIdentifier : keyStmtCtx.getStatementArgument()) {
481             if (leafStmtCtx.getStatementArgument().equals(keyIdentifier.getLastComponent())) {
482                 return true;
483             }
484         }
485
486         return false;
487     }
488
489     private static void disallowIfFeatureAndWhenOnListKeys(final StmtContext<?, ?, ?> leafStmtCtx) {
490         Iterables.concat(leafStmtCtx.declaredSubstatements(), leafStmtCtx.effectiveSubstatements()).forEach(
491                 leafSubstmtCtx -> {
492             final StatementDefinition statementDef = leafSubstmtCtx.getPublicDefinition();
493             SourceException.throwIf(YangStmtMapping.IF_FEATURE.equals(statementDef)
494                     || YangStmtMapping.WHEN.equals(statementDef), leafStmtCtx.getStatementSourceReference(),
495                     "%s statement is not allowed in %s leaf statement which is specified as a list key.",
496                     statementDef.getStatementName(), leafStmtCtx.getStatementArgument());
497         });
498     }
499 }