Fix unique statement argument propagation
[yangtools.git] / parser / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / stmt / meta / UniqueStatementSupport.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.meta;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.CharMatcher;
13 import com.google.common.base.Splitter;
14 import com.google.common.collect.ImmutableBiMap;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.collect.Lists;
18 import com.google.common.collect.Maps;
19 import java.util.Collection;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Objects;
23 import java.util.Set;
24 import java.util.regex.Pattern;
25 import java.util.stream.Stream;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.common.QNameModule;
28 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
29 import org.opendaylight.yangtools.yang.model.api.meta.DeclarationReference;
30 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
31 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.LeafEffectiveStatement;
33 import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
34 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
35 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Descendant;
36 import org.opendaylight.yangtools.yang.model.api.stmt.UniqueEffectiveStatement;
37 import org.opendaylight.yangtools.yang.model.api.stmt.UniqueStatement;
38 import org.opendaylight.yangtools.yang.model.ri.stmt.DeclaredStatementDecorators;
39 import org.opendaylight.yangtools.yang.model.ri.stmt.DeclaredStatements;
40 import org.opendaylight.yangtools.yang.model.ri.stmt.EffectiveStatements;
41 import org.opendaylight.yangtools.yang.parser.api.YangParserConfiguration;
42 import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.ArgumentUtils;
43 import org.opendaylight.yangtools.yang.parser.spi.ParserNamespaces;
44 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
45 import org.opendaylight.yangtools.yang.parser.spi.meta.BoundStmtCtx;
46 import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
47 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
48 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceAction;
49 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.InferenceContext;
50 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder.Prerequisite;
51 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
52 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
53 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
54 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
55 import org.opendaylight.yangtools.yang.parser.spi.meta.SubstatementValidator;
56 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
57
58 public final class UniqueStatementSupport
59         extends AbstractStatementSupport<Set<Descendant>, UniqueStatement, UniqueEffectiveStatement> {
60     /**
61      * Support 'sep' ABNF rule in RFC7950 section 14. CRLF pattern is used to squash line-break from CRLF to LF form
62      * and then we use SEP_SPLITTER, which can operate on single characters.
63      */
64     private static final Pattern CRLF_PATTERN = Pattern.compile("\r\n", Pattern.LITERAL);
65     private static final Splitter SEP_SPLITTER = Splitter.on(CharMatcher.anyOf(" \t\n").precomputed())
66             .omitEmptyStrings();
67
68     private static final SubstatementValidator SUBSTATEMENT_VALIDATOR =
69         SubstatementValidator.builder(YangStmtMapping.UNIQUE).build();
70
71     public UniqueStatementSupport(final YangParserConfiguration config) {
72         super(YangStmtMapping.UNIQUE,
73             StatementPolicy.copyDeclared(
74                 (copy, current, substatements) -> copy.getArgument().equals(current.getArgument())),
75             config, SUBSTATEMENT_VALIDATOR);
76     }
77
78     @Override
79     public Set<Descendant> adaptArgumentValue(
80             final StmtContext<Set<Descendant>, UniqueStatement, UniqueEffectiveStatement> ctx,
81             final QNameModule targetModule) {
82         // Copy operation to a targetNamespace -- this implies rehosting node-identifiers to target namespace. Check
83         // if that is needed first, though, so as not to copy things unnecessarily.
84         final var origArg = ctx.getArgument();
85         if (allMatch(origArg.stream().flatMap(desc -> desc.getNodeIdentifiers().stream()), targetModule)) {
86             return origArg;
87         }
88
89         return origArg.stream()
90             .map(descendant -> {
91                 final var nodeIds = descendant.getNodeIdentifiers();
92                 // Only update descendants that need updating
93                 return allMatch(nodeIds.stream(), targetModule) ? descendant
94                     : Descendant.of(Lists.transform(nodeIds, nodeId -> nodeId.bindTo(targetModule).intern()));
95             })
96             .collect(ImmutableSet.toImmutableSet());
97     }
98
99     @Override
100     public ImmutableSet<Descendant> parseArgumentValue(final StmtContext<?, ?, ?> ctx, final String value) {
101         final var uniqueConstraints = parseUniqueConstraintArgument(ctx, value);
102         SourceException.throwIf(uniqueConstraints.isEmpty(), ctx,
103             "Invalid argument value '%s' of unique statement. The value must contains at least one descendant schema "
104                 + "node identifier.", value);
105         return uniqueConstraints;
106     }
107
108     @Override
109     public void onStatementAdded(final Mutable<Set<Descendant>, UniqueStatement, UniqueEffectiveStatement> stmt) {
110         // Check whether this statement is in a list statement and if so ...
111         final var list = stmt.coerceParentContext();
112         if (list.producesEffective(ListEffectiveStatement.class)) {
113             final var listParent = list.coerceParentContext();
114             // ... do not allow parent to complete until we have resolved ...
115             final var action = listParent.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL);
116             // ... we require the list to be completely resolve ...
117             action.requiresCtx(list, ModelProcessingPhase.EFFECTIVE_MODEL);
118             // ... after which we will continue
119             action.apply(new RequireEffectiveList(stmt, list, listParent));
120         }
121     }
122
123     @Override
124     protected UniqueStatement createDeclared(final BoundStmtCtx<Set<Descendant>> ctx,
125             final ImmutableList<DeclaredStatement<?>> substatements) {
126         return DeclaredStatements.createUnique(ctx.getRawArgument(), ctx.getArgument(), substatements);
127     }
128
129     @Override
130     protected UniqueStatement attachDeclarationReference(final UniqueStatement stmt,
131             final DeclarationReference reference) {
132         return DeclaredStatementDecorators.decorateUnique(stmt, reference);
133     }
134
135     @Override
136     protected UniqueEffectiveStatement createEffective(final Current<Set<Descendant>, UniqueStatement> stmt,
137             final ImmutableList<? extends EffectiveStatement<?, ?>> substatements) {
138         return EffectiveStatements.createUnique(stmt.declared(), substatements);
139     }
140
141     private static boolean allMatch(final Stream<QName> qnames, final QNameModule module) {
142         return qnames.allMatch(qname -> module.equals(qname.getModule()));
143     }
144
145     private static ImmutableSet<Descendant> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
146             final String argumentValue) {
147         // deal with 'line-break' rule, which is either "\n" or "\r\n", but not "\r"
148         final String nocrlf = CRLF_PATTERN.matcher(argumentValue).replaceAll("\n");
149
150         final var uniqueConstraintNodes = new HashSet<Descendant>();
151         for (var uniqueArgToken : SEP_SPLITTER.split(nocrlf)) {
152             final var nodeIdentifier = ArgumentUtils.nodeIdentifierFromPath(ctx, uniqueArgToken);
153             SourceException.throwIf(nodeIdentifier instanceof Absolute, ctx,
154                 "Unique statement argument '%s' contains schema node identifier '%s' which is not in the descendant "
155                     + "node identifier form.", argumentValue, uniqueArgToken);
156             uniqueConstraintNodes.add((Descendant) nodeIdentifier);
157         }
158         return ImmutableSet.copyOf(uniqueConstraintNodes);
159     }
160
161     /**
162      * Inference action to process parent list reaching effective model, i.e. we can tell it is now complete.
163      */
164     private static final class RequireEffectiveList implements InferenceAction {
165         private final StmtContext<Set<Descendant>, ?, ?> unique;
166         private final StmtContext<?, ?, ?> list;
167         private final Mutable<?, ?, ?> parent;
168
169         RequireEffectiveList(final StmtContext<Set<Descendant>, ?, ?> unique, final StmtContext<?, ?, ?> list,
170                 final Mutable<?, ?, ?> parent) {
171             this.unique = requireNonNull(unique);
172             this.list = requireNonNull(list);
173             this.parent = requireNonNull(parent);
174         }
175
176         @Override
177         public void apply(final InferenceContext ctx) {
178             if (isApplicable()) {
179                 // So now, we have the effective list, we again block its parent from resolving ...
180                 final var action = parent.newInferenceAction(ModelProcessingPhase.EFFECTIVE_MODEL);
181                 // ... and before going further ...
182                 action.apply(new RequireLeafDescendants(unique,
183                     // ... require that each schema node identifier resolves against the schema tree
184                     Maps.uniqueIndex(unique.getArgument(),
185                         desc -> action.requiresCtxPath(list,
186                             // FIXME: why do we need this cast?
187                             (ParserNamespace) ParserNamespaces.schemaTree(), desc.getNodeIdentifiers(),
188                             ModelProcessingPhase.EFFECTIVE_MODEL))));
189             }
190         }
191
192         @Override
193         public void prerequisiteFailed(final Collection<? extends Prerequisite<?>> failed) {
194             InferenceException.throwIf(isApplicable(), unique, "Parent list failed to reach effective model");
195         }
196
197         private boolean isApplicable() {
198             return list.isSupportedToBuildEffective() && unique.isSupportedToBuildEffective();
199         }
200     }
201
202     private static final class RequireLeafDescendants implements InferenceAction {
203         private final Map<Prerequisite<StmtContext<?, ?, ?>>, Descendant> prereqs;
204         private final StmtContext<Set<Descendant>, ?, ?> unique;
205
206         RequireLeafDescendants(final StmtContext<Set<Descendant>, ?, ?> unique,
207                 final Map<Prerequisite<StmtContext<?, ?, ?>>, Descendant> prereqs) {
208             this.unique = requireNonNull(unique);
209             this.prereqs = requireNonNull(prereqs);
210
211         }
212
213         @Override
214         public void apply(final InferenceContext ctx) {
215             // All prerequisites have resolved, so now check each ...
216             for (var entry : prereqs.entrySet()) {
217                 final var stmt = entry.getKey().resolve(ctx);
218                 // ... and if it is not a leaf, report an error
219                 SourceException.throwIf(!stmt.producesEffective(LeafEffectiveStatement.class),
220                     unique, "Path %s resolved to non-leaf %s", stmt.publicDefinition().getStatementName());
221             }
222         }
223
224         @Override
225         public void prerequisiteFailed(final Collection<? extends Prerequisite<?>> failed) {
226             // Report failed descandants
227             final var inv = ImmutableBiMap.copyOf(prereqs);
228             throw new SourceException(unique,
229                 "Following components of unique statement argument refer to non-existent nodes: %s",
230                 failed.stream().map(inv::get).filter(Objects::nonNull).collect(ImmutableSet.toImmutableSet()));
231         }
232     }
233 }