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