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