5a92e31c6eb1cc645a049a9c17829d9df65342b3
[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(StmtContext<?, ?, ?> ctx, 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(StatementContextBase<?, ?, ?> sourceCtx,
65             StatementContextBase<?, ?, ?> targetCtx) throws SourceException {
66
67         copyDeclaredStmts(sourceCtx, targetCtx);
68         copyEffectiveStmts(sourceCtx, targetCtx);
69     }
70
71     public static void copyDeclaredStmts(StatementContextBase<?, ?, ?> sourceCtx,
72             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(StatementContextBase<?, ?, ?> sourceCtx,
96             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, 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(StatementContextBase<?, ?, ?> targetCtx,
159             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(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(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         return augmentTargetCtx;
238     }
239
240     @Nullable
241     public static StatementContextBase<?, ?, ?> findCtxOfNodeInSubstatements(StatementContextBase<?, ?, ?> rootStmtCtx,
242             final Iterable<QName> path) {
243
244         StatementContextBase<?, ?, ?> parent = rootStmtCtx;
245
246         Iterator<QName> pathIter = path.iterator();
247         while (pathIter.hasNext()) {
248             QName nextPathQName = pathIter.next();
249             StatementContextBase<?, ?, ?> foundSubstatement = getSubstatementByQName(parent, nextPathQName);
250
251             if (foundSubstatement == null) {
252                 return null;
253             }
254             if (!pathIter.hasNext()) {
255                 return foundSubstatement;
256             }
257
258             parent = foundSubstatement;
259         }
260
261         return null;
262     }
263
264     public static StatementContextBase<?, ?, ?> getSubstatementByQName(StatementContextBase<?, ?, ?> parent,
265             QName nextPathQName) {
266
267         Collection<StatementContextBase<?, ?, ?>> declaredSubstatement = parent.declaredSubstatements();
268         Collection<StatementContextBase<?, ?, ?>> effectiveSubstatement = parent.effectiveSubstatements();
269
270         Collection<StatementContextBase<?, ?, ?>> allSubstatements = new LinkedList<>();
271         allSubstatements.addAll(declaredSubstatement);
272         allSubstatements.addAll(effectiveSubstatement);
273
274         for (StatementContextBase<?, ?, ?> substatement : allSubstatements) {
275             Object substatementArgument = substatement.getStatementArgument();
276             QName substatementQName;
277             if (substatementArgument instanceof QName) {
278                 substatementQName = (QName) substatementArgument;
279
280                 if (isSupportedAugmentTarget(substatement)
281                         && nextPathQName.getLocalName().equals(substatementQName.getLocalName())) {
282                     return substatement;
283                 }
284             } // augment to extension
285             else if (StmtContextUtils.producesDeclared(substatement, UnknownStatementImpl.class)
286                     && substatementArgument instanceof String) {
287
288                 final String nextPathName = nextPathQName.getLocalName();
289
290                 if (nextPathName.equals(substatementArgument)
291                         || nextPathName.equals(substatement.getPublicDefinition().getStatementName().getLocalName())) {
292                     String message = "Module '" + substatement.getRoot().getStatementArgument()
293                             + "': augment into extension '" + substatementArgument + "'.";
294                     LOG.warn(message);
295                     return substatement;
296                 }
297             }
298         }
299
300         return null;
301     }
302
303     public static boolean isSupportedAugmentTarget(StatementContextBase<?, ?, ?> substatementCtx) {
304
305         /*
306          * :TODO Substatement must be allowed augment target type e.g. Container, etc... and must be not for example
307          * grouping, identity etc. It is problem in case when more than one substatements have the same QName, for
308          * example Grouping and Container are siblings and they have the same QName. We must find the Container and the
309          * Grouping must be ignored as disallowed augment target.
310          */
311
312         Collection<?> allowedAugmentTargets = substatementCtx.getFromNamespace(ValidationBundlesNamespace.class,
313                 ValidationBundleType.SUPPORTED_AUGMENT_TARGETS);
314
315         // if no allowed target is returned we consider all targets allowed
316         return allowedAugmentTargets == null || allowedAugmentTargets.isEmpty()
317                 || allowedAugmentTargets.contains(substatementCtx.getPublicDefinition());
318     }
319
320     @Nullable
321     public static StatementContextBase<?, ?, ?> findCtxOfNodeInRoot(StatementContextBase<?, ?, ?> rootStmtCtx,
322             final SchemaNodeIdentifier node) {
323         return findCtxOfNodeInSubstatements(rootStmtCtx, node.getPathFromRoot());
324     }
325 }