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