YANGTOOLS-706: reorganize statement definitions
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / stmt / augment / AbstractAugmentStatementSupport.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies, s.r.o. 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.rfc7950.stmt.augment;
9
10 import com.google.common.base.Verify;
11 import com.google.common.collect.ImmutableSet;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.Objects;
15 import java.util.Set;
16 import java.util.regex.Pattern;
17 import org.opendaylight.yangtools.yang.common.QName;
18 import org.opendaylight.yangtools.yang.common.YangVersion;
19 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
20 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
21 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
22 import org.opendaylight.yangtools.yang.model.api.stmt.DataDefinitionStatement;
23 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
24 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
25 import org.opendaylight.yangtools.yang.model.api.stmt.WhenStatement;
26 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
27 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
28 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
29 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
30 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.Prerequisite;
31 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
32 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
33 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
34 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
35 import org.opendaylight.yangtools.yang.parser.spi.source.AugmentToChoiceNamespace;
36 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
37 import org.opendaylight.yangtools.yang.parser.spi.source.StmtOrderingNamespace;
38 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
39 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
40 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
41 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.SchemaNodeIdentifierBuildNamespace;
42 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 abstract class AbstractAugmentStatementSupport extends AbstractStatementSupport<SchemaNodeIdentifier, AugmentStatement,
47         EffectiveStatement<SchemaNodeIdentifier, AugmentStatement>> {
48     private static final Logger LOG = LoggerFactory.getLogger(AugmentStatementImpl.class);
49     private static final Pattern PATH_REL_PATTERN1 = Pattern.compile("\\.\\.?\\s*/(.+)");
50     private static final Pattern PATH_REL_PATTERN2 = Pattern.compile("//.*");
51
52     AbstractAugmentStatementSupport() {
53         super(YangStmtMapping.AUGMENT);
54     }
55
56     @Override
57     public final SchemaNodeIdentifier parseArgumentValue(final StmtContext<?, ?, ?> ctx, final String value) {
58         SourceException.throwIf(PATH_REL_PATTERN1.matcher(value).matches()
59             || PATH_REL_PATTERN2.matcher(value).matches(), ctx.getStatementSourceReference(),
60             "Augment argument \'%s\' is not valid, it can be only absolute path; or descendant if used in uses",
61             value);
62
63         return Utils.nodeIdentifierFromPath(ctx, value);
64     }
65
66     @Override
67     public final AugmentStatement createDeclared(final StmtContext<SchemaNodeIdentifier, AugmentStatement, ?> ctx) {
68         return new AugmentStatementImpl(ctx);
69     }
70
71     @Override
72     public final EffectiveStatement<SchemaNodeIdentifier, AugmentStatement> createEffective(
73             final StmtContext<SchemaNodeIdentifier, AugmentStatement,
74             EffectiveStatement<SchemaNodeIdentifier, AugmentStatement>> ctx) {
75         return new AugmentEffectiveStatementImpl(ctx);
76     }
77
78     @Override
79     public final void onFullDefinitionDeclared(final Mutable<SchemaNodeIdentifier, AugmentStatement,
80             EffectiveStatement<SchemaNodeIdentifier, AugmentStatement>> augmentNode) {
81         if (!augmentNode.isSupportedByFeatures()) {
82             return;
83         }
84
85         super.onFullDefinitionDeclared(augmentNode);
86
87         if (StmtContextUtils.isInExtensionBody(augmentNode)) {
88             return;
89         }
90
91         final ModelActionBuilder augmentAction = augmentNode.newInferenceAction(
92             ModelProcessingPhase.EFFECTIVE_MODEL);
93         final Prerequisite<StmtContext<SchemaNodeIdentifier, AugmentStatement,
94             EffectiveStatement<SchemaNodeIdentifier, AugmentStatement>>> sourceCtxPrereq =
95                 augmentAction.requiresCtx(augmentNode, ModelProcessingPhase.EFFECTIVE_MODEL);
96         final Prerequisite<Mutable<?, ?, EffectiveStatement<?, ?>>> target =
97                 augmentAction.mutatesEffectiveCtx(getSearchRoot(augmentNode),
98                     SchemaNodeIdentifierBuildNamespace.class, augmentNode.getStatementArgument());
99
100         augmentAction.apply(new ModelActionBuilder.InferenceAction() {
101             @Override
102             public void apply(final ModelActionBuilder.InferenceContext ctx) {
103                 final StatementContextBase<?, ?, ?> augmentTargetCtx =
104                         (StatementContextBase<?, ?, ?>) target.resolve(ctx);
105                 if (!isSupportedAugmentTarget(augmentTargetCtx)
106                         || StmtContextUtils.isInExtensionBody(augmentTargetCtx)) {
107                     augmentNode.setIsSupportedToBuildEffective(false);
108                     return;
109                 }
110                 /**
111                  * Marks case short hand in augment
112                  */
113                 if (augmentTargetCtx.getPublicDefinition() == YangStmtMapping.CHOICE) {
114                     augmentNode.addToNs(AugmentToChoiceNamespace.class, augmentNode, Boolean.TRUE);
115                 }
116
117                 // FIXME: this is a workaround for models which augment a node which is added via an extension
118                 //        which we do not handle. This needs to be reworked in terms of unknown schema nodes.
119                 final StatementContextBase<?, ?, ?> augmentSourceCtx = (StatementContextBase<?, ?, ?>) augmentNode;
120                 try {
121                     copyFromSourceToTarget(augmentSourceCtx, augmentTargetCtx);
122                     augmentTargetCtx.addEffectiveSubstatement(augmentSourceCtx);
123                     updateAugmentOrder(augmentSourceCtx);
124                 } catch (final SourceException e) {
125                     LOG.warn("Failed to add augmentation {} defined at {}",
126                         augmentTargetCtx.getStatementSourceReference(),
127                             augmentSourceCtx.getStatementSourceReference(), e);
128                 }
129             }
130
131             private void updateAugmentOrder(final StatementContextBase<?, ?, ?> augmentSourceCtx) {
132                 Integer currentOrder = augmentSourceCtx.getFromNamespace(StmtOrderingNamespace.class,
133                     YangStmtMapping.AUGMENT);
134                 if (currentOrder == null) {
135                     currentOrder = 1;
136                 } else {
137                     currentOrder++;
138                 }
139
140                 augmentSourceCtx.addToNs(StmtOrderingNamespace.class, YangStmtMapping.AUGMENT, currentOrder);
141             }
142
143             @Override
144             public void prerequisiteFailed(final Collection<? extends ModelActionBuilder.Prerequisite<?>> failed) {
145                 /*
146                  * Do not fail, if it is an uses-augment to an unknown node.
147                  */
148                 if (YangStmtMapping.USES == augmentNode.getParentContext().getPublicDefinition()) {
149                     final StatementContextBase<?, ?, ?> targetNode = Utils.findNode(getSearchRoot(augmentNode),
150                             augmentNode.getStatementArgument());
151                     if (targetNode != null && StmtContextUtils.isUnknownStatement(targetNode)) {
152                         augmentNode.setIsSupportedToBuildEffective(false);
153                         LOG.warn(
154                                 "Uses-augment to unknown node {}. Augmentation has not been performed. At line: {}",
155                                 augmentNode.getStatementArgument(), augmentNode.getStatementSourceReference());
156                         return;
157                     }
158                 }
159
160                 throw new InferenceException(augmentNode.getStatementSourceReference(),
161                         "Augment target '%s' not found", augmentNode.getStatementArgument());
162             }
163         });
164     }
165
166     private static Mutable<?, ?, ?> getSearchRoot(final Mutable<?, ?, ?> augmentContext) {
167         final Mutable<?, ?, ?> parent = augmentContext.getParentContext();
168         // Augment is in uses - we need to augment instantiated nodes in parent.
169         if (YangStmtMapping.USES == parent.getPublicDefinition()) {
170             return parent.getParentContext();
171         }
172         return parent;
173     }
174
175     static void copyFromSourceToTarget(final StatementContextBase<?, ?, ?> sourceCtx,
176             final StatementContextBase<?, ?, ?> targetCtx) {
177         final CopyType typeOfCopy = UsesStatement.class.equals(sourceCtx.getParentContext().getPublicDefinition()
178                 .getDeclaredRepresentationClass()) ? CopyType.ADDED_BY_USES_AUGMENTATION
179                 : CopyType.ADDED_BY_AUGMENTATION;
180         /*
181          * Since Yang 1.1, if an augmentation is made conditional with a
182          * "when" statement, it is allowed to add mandatory nodes.
183          */
184         final boolean skipCheckOfMandatoryNodes = YangVersion.VERSION_1_1.equals(sourceCtx.getRootVersion())
185                 && isConditionalAugmentStmt(sourceCtx);
186
187         final Collection<? extends Mutable<?, ?, ?>> declared = sourceCtx.mutableDeclaredSubstatements();
188         final Collection<? extends Mutable<?, ?, ?>> effective = sourceCtx.mutableEffectiveSubstatements();
189         final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(declared.size() + effective.size());
190
191         for (final Mutable<?, ?, ?> originalStmtCtx : declared) {
192             if (originalStmtCtx.isSupportedByFeatures()) {
193                 copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes);
194             }
195         }
196         for (final Mutable<?, ?, ?> originalStmtCtx : effective) {
197             copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes);
198         }
199
200         targetCtx.addEffectiveSubstatements(buffer);
201     }
202
203     /**
204      * Checks whether supplied statement context is conditional augment
205      * statement.
206      *
207      * @param ctx
208      *            statement context to be checked
209      *
210      * @return true if supplied statement context is conditional augment
211      *         statement, otherwise false
212      */
213     private static boolean isConditionalAugmentStmt(final StmtContext<?, ?, ?> ctx) {
214         return ctx.getPublicDefinition() == YangStmtMapping.AUGMENT
215                 && StmtContextUtils.findFirstSubstatement(ctx, WhenStatement.class) != null;
216     }
217
218     private static void copyStatement(final Mutable<?, ?, ?> original, final StatementContextBase<?, ?, ?> target,
219             final CopyType typeOfCopy, final Collection<Mutable<?, ?, ?>> buffer,
220             final boolean skipCheckOfMandatoryNodes) {
221         if (needToCopyByAugment(original)) {
222             validateNodeCanBeCopiedByAugment(original, target, typeOfCopy, skipCheckOfMandatoryNodes);
223
224             buffer.add(target.childCopyOf(original, typeOfCopy));
225         } else if (isReusedByAugment(original)) {
226             buffer.add(original);
227         }
228     }
229
230     private static void validateNodeCanBeCopiedByAugment(final StmtContext<?, ?, ?> sourceCtx,
231             final StatementContextBase<?, ?, ?> targetCtx, final CopyType typeOfCopy,
232             final boolean skipCheckOfMandatoryNodes) {
233
234         if (WhenStatement.class.equals(sourceCtx.getPublicDefinition().getDeclaredRepresentationClass())) {
235             return;
236         }
237
238         if (!skipCheckOfMandatoryNodes && typeOfCopy == CopyType.ADDED_BY_AUGMENTATION
239                 && reguiredCheckOfMandatoryNodes(sourceCtx, targetCtx)) {
240             checkForMandatoryNodes(sourceCtx);
241         }
242
243         for (final StmtContext<?, ?, ?> subStatement : targetCtx.allSubstatements()) {
244             final boolean sourceIsDataNode = DataDefinitionStatement.class.isAssignableFrom(sourceCtx
245                     .getPublicDefinition().getDeclaredRepresentationClass());
246             final boolean targetIsDataNode = DataDefinitionStatement.class.isAssignableFrom(subStatement
247                     .getPublicDefinition().getDeclaredRepresentationClass());
248             final boolean qNamesEqual = sourceIsDataNode && targetIsDataNode
249                     && Objects.equals(sourceCtx.getStatementArgument(), subStatement.getStatementArgument());
250
251             InferenceException.throwIf(qNamesEqual, sourceCtx.getStatementSourceReference(),
252                     "An augment cannot add node named '%s' because this name is already used in target",
253                     sourceCtx.rawStatementArgument());
254         }
255     }
256
257     private static void checkForMandatoryNodes(final StmtContext<?, ?, ?> sourceCtx) {
258         if (StmtContextUtils.isNonPresenceContainer(sourceCtx)) {
259             /*
260              * We need to iterate over both declared and effective sub-statements,
261              * because a mandatory node can be:
262              * a) declared in augment body
263              * b) added to augment body also via uses of a grouping and
264              * such sub-statements are stored in effective sub-statements collection.
265              */
266             sourceCtx.allSubstatementsStream().forEach(AbstractAugmentStatementSupport::checkForMandatoryNodes);
267         }
268
269         InferenceException.throwIf(StmtContextUtils.isMandatoryNode(sourceCtx),
270                 sourceCtx.getStatementSourceReference(),
271                 "An augment cannot add node '%s' because it is mandatory and in module different than target",
272                 sourceCtx.rawStatementArgument());
273     }
274
275     private static boolean reguiredCheckOfMandatoryNodes(final StmtContext<?, ?, ?> sourceCtx,
276             Mutable<?, ?, ?> targetCtx) {
277         /*
278          * If the statement argument is not QName, it cannot be mandatory
279          * statement, therefore return false and skip mandatory nodes validation
280          */
281         if (!(sourceCtx.getStatementArgument() instanceof QName)) {
282             return false;
283         }
284         final QName sourceStmtQName = (QName) sourceCtx.getStatementArgument();
285
286         // RootStatementContext, for example
287         final Mutable<?, ?, ?> root = targetCtx.getRoot();
288         do {
289             Verify.verify(targetCtx.getStatementArgument() instanceof QName,
290                     "Argument of augment target statement must be QName.");
291             final QName targetStmtQName = (QName) targetCtx.getStatementArgument();
292             /*
293              * If target is from another module, return true and perform
294              * mandatory nodes validation
295              */
296             if (!Utils.belongsToTheSameModule(targetStmtQName, sourceStmtQName)) {
297                 return true;
298             }
299
300             /*
301              * If target or one of the target's ancestors from the same namespace
302              * is a presence container
303              * or is non-mandatory choice
304              * or is non-mandatory list
305              * return false and skip mandatory nodes validation, because these nodes
306              * are not mandatory node containers according to RFC 6020 section 3.1.
307              */
308             if (StmtContextUtils.isPresenceContainer(targetCtx)
309                     || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.CHOICE)
310                     || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.LIST)) {
311                 return false;
312             }
313         } while ((targetCtx = targetCtx.getParentContext()) != root);
314
315         /*
316          * All target node's parents belong to the same module as source node,
317          * therefore return false and skip mandatory nodes validation.
318          */
319         return false;
320     }
321
322     private static final Set<YangStmtMapping> NOCOPY_DEF_SET = ImmutableSet.of(YangStmtMapping.USES,
323         YangStmtMapping.WHEN, YangStmtMapping.DESCRIPTION, YangStmtMapping.REFERENCE, YangStmtMapping.STATUS);
324
325     private static boolean needToCopyByAugment(final StmtContext<?, ?, ?> stmtContext) {
326         return !NOCOPY_DEF_SET.contains(stmtContext.getPublicDefinition());
327     }
328
329     private static final Set<YangStmtMapping> REUSED_DEF_SET = ImmutableSet.of(YangStmtMapping.TYPEDEF);
330
331     private static boolean isReusedByAugment(final StmtContext<?, ?, ?> stmtContext) {
332         return REUSED_DEF_SET.contains(stmtContext.getPublicDefinition());
333     }
334
335     static boolean isSupportedAugmentTarget(final StmtContext<?, ?, ?> substatementCtx) {
336         /*
337          * :TODO Substatement must be allowed augment target type e.g.
338          * Container, etc... and must not be for example grouping, identity etc.
339          * It is problem in case when more than one substatements have the same
340          * QName, for example Grouping and Container are siblings and they have
341          * the same QName. We must find the Container and the Grouping must be
342          * ignored as disallowed augment target.
343          */
344         final Collection<?> allowedAugmentTargets = substatementCtx.getFromNamespace(
345             ValidationBundlesNamespace.class, ValidationBundleType.SUPPORTED_AUGMENT_TARGETS);
346
347         // if no allowed target is returned we consider all targets allowed
348         return allowedAugmentTargets == null || allowedAugmentTargets.isEmpty()
349                 || allowedAugmentTargets.contains(substatementCtx.getPublicDefinition());
350     }
351 }