Bug 2366 - Effective statments impl merge, retest & bugfix
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / AugmentUtils.java
1 /**
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  * <p/>
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.stmt.rfc6020;
9
10 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
11
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14 import com.google.common.collect.ImmutableList.Builder;
15 import java.util.Collection;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Objects;
22 import java.util.Set;
23 import javax.annotation.Nullable;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.common.QNameModule;
26 import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
27 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
28 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
29 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
30 import org.opendaylight.yangtools.yang.model.api.stmt.DataDefinitionStatement;
31 import org.opendaylight.yangtools.yang.model.api.stmt.MandatoryStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
33 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
34 import org.opendaylight.yangtools.yang.model.api.stmt.WhenStatement;
35 import org.opendaylight.yangtools.yang.parser.spi.NamespaceToModule;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
37 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
38 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.TypeOfCopy;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
40 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
41 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
42 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
43 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
44
45 public final class AugmentUtils {
46
47     private static final Logger LOG = LoggerFactory.getLogger(AugmentUtils.class);
48
49     private static final String REGEX_PATH_REL1 = "\\.\\.?\\s*/(.+)";
50     private static final String REGEX_PATH_REL2 = "//.*";
51
52     private AugmentUtils() {
53     }
54
55     public static Iterable<QName> parseAugmentPath(StmtContext<?, ?, ?> ctx,
56             String path) {
57
58         if (path.matches(REGEX_PATH_REL1) || path.matches(REGEX_PATH_REL2)) {
59             throw new IllegalArgumentException(
60                     "An argument for augment can be only absolute path; or descendant if used in uses");
61         }
62
63         return Utils.parseXPath(ctx, path);
64     }
65
66     public static void copyFromSourceToTarget(
67             StatementContextBase<?, ?, ?> sourceCtx,
68             StatementContextBase<?, ?, ?> targetCtx) throws SourceException {
69
70         copyDeclaredStmts(sourceCtx, targetCtx);
71         copyEffectiveStmts(sourceCtx, targetCtx);
72     }
73
74     public static void copyDeclaredStmts(
75             StatementContextBase<?, ?, ?> sourceCtx,
76             StatementContextBase<?, ?, ?> targetCtx) throws SourceException {
77
78         Collection<? extends StatementContextBase<?, ?, ?>> declaredSubStatements = sourceCtx
79                 .declaredSubstatements();
80         final List<StatementContextBase> subStatements = new Builder<StatementContextBase>()
81                 .addAll(targetCtx.declaredSubstatements())
82                 .addAll(targetCtx.effectiveSubstatements()).build();
83         boolean sourceAndTargetInSameModule = Utils.getRootModuleQName(
84                 sourceCtx).equals(Utils.getRootModuleQName(targetCtx));
85
86         TypeOfCopy typeOfCopy = sourceCtx.getParentContext()
87                 .getPublicDefinition().getDeclaredRepresentationClass()
88                 .equals(UsesStatement.class) ? TypeOfCopy.ADDED_BY_USES_AUGMENTATION
89                 : TypeOfCopy.ADDED_BY_AUGMENTATION;
90
91         for (StatementContextBase<?, ?, ?> originalStmtCtx : declaredSubStatements) {
92             if (needToCopyByAugment(originalStmtCtx)) {
93                 validateNodeCanBeCopiedByAugment(originalStmtCtx,
94                         subStatements, sourceAndTargetInSameModule);
95
96                 StatementContextBase<?, ?, ?> copy = originalStmtCtx
97                         .createCopy(targetCtx, typeOfCopy);
98                 targetCtx.addEffectiveSubstatement(copy);
99             } else if (isReusedByAugment(originalStmtCtx)) {
100                 targetCtx.addEffectiveSubstatement(originalStmtCtx);
101             }
102         }
103     }
104
105     public static void copyEffectiveStmts(
106             StatementContextBase<?, ?, ?> sourceCtx,
107             StatementContextBase<?, ?, ?> targetCtx) throws SourceException {
108
109         Collection<? extends StatementContextBase<?, ?, ?>> effectiveSubstatements = sourceCtx
110                 .effectiveSubstatements();
111         final List<StatementContextBase> subStatements = new Builder<StatementContextBase>()
112                 .addAll(targetCtx.declaredSubstatements())
113                 .addAll(targetCtx.effectiveSubstatements()).build();
114         boolean sourceAndTargetInSameModule = Utils.getRootModuleQName(
115                 sourceCtx).equals(Utils.getRootModuleQName(targetCtx));
116
117         TypeOfCopy typeOfCopy = sourceCtx.getParentContext()
118                 .getPublicDefinition().getDeclaredRepresentationClass()
119                 .equals(UsesStatement.class) ? TypeOfCopy.ADDED_BY_USES_AUGMENTATION
120                 : TypeOfCopy.ADDED_BY_AUGMENTATION;
121
122         for (StatementContextBase<?, ?, ?> originalStmtCtx : effectiveSubstatements) {
123             if (needToCopyByAugment(originalStmtCtx)) {
124                 validateNodeCanBeCopiedByAugment(originalStmtCtx,
125                         subStatements, sourceAndTargetInSameModule);
126
127                 StatementContextBase<?, ?, ?> copy = originalStmtCtx
128                         .createCopy(targetCtx, typeOfCopy);
129                 targetCtx.addEffectiveSubstatement(copy);
130             } else if (isReusedByAugment(originalStmtCtx)) {
131                 targetCtx.addEffectiveSubstatement(originalStmtCtx);
132             }
133         }
134     }
135
136     private static void validateNodeCanBeCopiedByAugment(
137             final StatementContextBase<?, ?, ?> sourceCtx,
138             final List<StatementContextBase> targetSubStatements,
139             boolean sourceAndTargetInSameModule) {
140
141         if (sourceCtx.getPublicDefinition().getDeclaredRepresentationClass()
142                 .equals(WhenStatement.class)) {
143             return;
144         }
145
146         if (!sourceAndTargetInSameModule) {
147             final List<StatementContextBase> sourceSubStatements = new Builder<StatementContextBase>()
148                     .addAll(sourceCtx.declaredSubstatements())
149                     .addAll(sourceCtx.effectiveSubstatements()).build();
150
151             for (final StatementContextBase sourceSubStatement : sourceSubStatements) {
152                 if (sourceSubStatement.getPublicDefinition()
153                         .getDeclaredRepresentationClass()
154                         .equals(MandatoryStatement.class)) {
155                     throw new IllegalArgumentException(
156                             String.format(
157                                     "An augment cannot add node '%s' because it is mandatory and in module different from target",
158                                     sourceCtx.rawStatementArgument()));
159                 }
160             }
161         }
162
163         for (final StatementContextBase subStatement : targetSubStatements) {
164
165             final boolean sourceIsDataNode = DataDefinitionStatement.class
166                     .isAssignableFrom(sourceCtx.getPublicDefinition()
167                             .getDeclaredRepresentationClass());
168             final boolean targetIsDataNode = DataDefinitionStatement.class
169                     .isAssignableFrom(subStatement.getPublicDefinition()
170                             .getDeclaredRepresentationClass());
171             boolean qNamesEqual = sourceIsDataNode
172                     && targetIsDataNode
173                     && Objects.equals(sourceCtx.getStatementArgument(),
174                             subStatement.getStatementArgument());
175
176             if (qNamesEqual) {
177                 throw new IllegalStateException(
178                         String.format(
179                                 "An augment cannot add node named '%s' because this name is already used in target",
180                                 sourceCtx.rawStatementArgument()));
181             }
182         }
183     }
184
185     public static QNameModule getNewQNameModule(
186             StatementContextBase<?, ?, ?> targetCtx,
187             StatementContextBase<?, ?, ?> sourceCtx) {
188         Object targetStmtArgument = targetCtx.getStatementArgument();
189
190         final StatementContextBase<?, ?, ?> root = sourceCtx.getRoot();
191         final QNameModule sourceQNameModule = root.getFromNamespace(
192                 ModuleCtxToModuleQName.class, root);
193
194         if (targetStmtArgument instanceof QName) {
195             QName targetQName = (QName) targetStmtArgument;
196             QNameModule targetQNameModule = targetQName.getModule();
197
198             if (targetQNameModule.equals(sourceQNameModule)) {
199                 return null;
200             } else {
201                 return targetQNameModule;
202             }
203         } else {
204             return null;
205         }
206     }
207
208     public static boolean needToCopyByAugment(StmtContext<?, ?, ?> stmtContext) {
209
210         Set<StatementDefinition> noCopyDefSet = new HashSet<>();
211         noCopyDefSet.add(Rfc6020Mapping.USES);
212
213         StatementDefinition def = stmtContext.getPublicDefinition();
214         return !noCopyDefSet.contains(def);
215     }
216
217     public static boolean isReusedByAugment(StmtContext<?, ?, ?> stmtContext) {
218
219         Set<StatementDefinition> reusedDefSet = new HashSet<>();
220         reusedDefSet.add(Rfc6020Mapping.TYPEDEF);
221
222         StatementDefinition def = stmtContext.getPublicDefinition();
223
224         return reusedDefSet.contains(def);
225     }
226
227     public static StatementContextBase<?, ?, ?> getAugmentTargetCtx(
228             final Mutable<SchemaNodeIdentifier, AugmentStatement, EffectiveStatement<SchemaNodeIdentifier, AugmentStatement>> augmentNode) {
229
230         final SchemaNodeIdentifier augmentTargetNode = augmentNode
231                 .getStatementArgument();
232         if (augmentTargetNode == null) {
233             throw new IllegalArgumentException(
234                     "Augment argument null, something bad happened in some of previous parsing phases");
235         }
236
237         List<StatementContextBase<?, ?, ?>> rootStatementCtxList = new LinkedList<>();
238         if (augmentTargetNode.isAbsolute()) {
239
240             QNameModule module = augmentTargetNode.getPathFromRoot().iterator()
241                     .next().getModule();
242
243             StatementContextBase<?, ?, ?> rootStatementCtx = (StatementContextBase<?, ?, ?>) augmentNode
244                     .getFromNamespace(NamespaceToModule.class, module);
245             rootStatementCtxList.add(rootStatementCtx);
246
247             final Map<?, ?> subModules = rootStatementCtx
248                     .getAllFromNamespace(IncludedModuleContext.class);
249             if (subModules != null) {
250                 rootStatementCtxList
251                         .addAll((Collection<? extends StatementContextBase<?, ?, ?>>) subModules
252                                 .values());
253             }
254
255         } else {
256             StatementContextBase<?, ?, ?> parent = (StatementContextBase<?, ?, ?>) augmentNode
257                     .getParentContext();
258             if (StmtContextUtils.producesDeclared(parent, UsesStatement.class)) {
259                 rootStatementCtxList.add(parent.getParentContext());
260             } else {
261                 // error
262             }
263         }
264
265         StatementContextBase<?, ?, ?> augmentTargetCtx = null;
266         for (final StatementContextBase<?, ?, ?> rootStatementCtx : rootStatementCtxList) {
267             augmentTargetCtx = findCtxOfNodeInRoot(rootStatementCtx,
268                     augmentTargetNode);
269             if (augmentTargetCtx != null)
270                 break;
271         }
272
273         return augmentTargetCtx;
274     }
275
276     @Nullable
277     public static StatementContextBase<?, ?, ?> findCtxOfNodeInSubstatements(
278             StatementContextBase<?, ?, ?> rootStmtCtx,
279             final Iterable<QName> path) {
280
281         StatementContextBase<?, ?, ?> parent = rootStmtCtx;
282
283         Iterator<QName> pathIter = path.iterator();
284         while (pathIter.hasNext()) {
285             QName nextPathQName = pathIter.next();
286             StatementContextBase<?, ?, ?> foundSubstatement = getSubstatementByQName(
287                     parent, nextPathQName);
288
289             if (foundSubstatement == null) {
290                 return null;
291             }
292             if (!pathIter.hasNext()) {
293                 return foundSubstatement;
294             }
295
296             parent = foundSubstatement;
297         }
298
299         return null;
300     }
301
302     public static StatementContextBase<?, ?, ?> getSubstatementByQName(
303             StatementContextBase<?, ?, ?> parent, QName nextPathQName) {
304
305         Collection<StatementContextBase<?, ?, ?>> declaredSubstatement = parent
306                 .declaredSubstatements();
307         Collection<StatementContextBase<?, ?, ?>> effectiveSubstatement = parent
308                 .effectiveSubstatements();
309
310         Collection<StatementContextBase<?, ?, ?>> allSubstatements = new LinkedList<>();
311         allSubstatements.addAll(declaredSubstatement);
312         allSubstatements.addAll(effectiveSubstatement);
313
314         for (StatementContextBase<?, ?, ?> substatement : allSubstatements) {
315             Object substatementArgument = substatement.getStatementArgument();
316             QName substatementQName;
317             if (substatementArgument instanceof QName) {
318                 substatementQName = (QName) substatementArgument;
319
320                 if (isSupportedAugmentTarget(substatement)
321                         && nextPathQName.getLocalName().equals(
322                                 substatementQName.getLocalName())) {
323                     return substatement;
324                 }
325             } // augment to extension
326             else if (StmtContextUtils.producesDeclared(substatement,
327                     UnknownStatementImpl.class)
328                     && substatementArgument instanceof String) {
329                 if (nextPathQName.getLocalName().equals(substatementArgument)) {
330                     String message = "Module '"+substatement.getRoot().getStatementArgument()+"': augment into extension '"+substatementArgument+"'.";
331                     LOG.warn(message);
332                     return substatement;
333                 }
334             }
335         }
336
337         return null;
338     }
339
340     public static boolean isSupportedAugmentTarget(
341             StatementContextBase<?, ?, ?> substatementCtx) {
342
343         /*
344          * :TODO Substatement must be allowed augment target type e.g.
345          * Container, etc... and must be not for example grouping, identity etc.
346          * It is problem in case when more than one substatements have the same
347          * QName, for example Grouping and Container are siblings and they have
348          * the same QName. We must find the Container and the Grouping must be
349          * ignored as disallowed augment target.
350          */
351
352         Collection<?> allowedAugmentTargets = substatementCtx.getFromNamespace(
353                 ValidationBundlesNamespace.class,
354                 ValidationBundleType.SUPPORTED_AUGMENT_TARGETS);
355
356         // if no allowed target is returned we consider all targets allowed
357         return allowedAugmentTargets == null || allowedAugmentTargets.isEmpty()
358                 || allowedAugmentTargets.contains(substatementCtx.getPublicDefinition());
359     }
360
361     @Nullable
362     public static StatementContextBase<?, ?, ?> findCtxOfNodeInRoot(
363             StatementContextBase<?, ?, ?> rootStmtCtx,
364             final SchemaNodeIdentifier node) {
365         return findCtxOfNodeInSubstatements(rootStmtCtx, node.getPathFromRoot());
366     }
367 }