2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.yangtools.yang.parser.spi;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.Iterables;
14 import com.google.common.collect.Maps;
15 import java.util.HashMap;
17 import java.util.Map.Entry;
18 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
19 import org.opendaylight.yangtools.yang.parser.spi.meta.InvalidSubstatementException;
20 import org.opendaylight.yangtools.yang.parser.spi.meta.MissingSubstatementException;
21 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
22 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
23 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
25 public final class SubstatementValidator {
27 * @deprecated Deprecated since version 1.1.0. Use {@link Builder#addAny(StatementDefinition)},
28 * {@link Builder#addAtLeast(StatementDefinition, int)},
29 * {@link Builder#addMandatory(StatementDefinition)}, or
30 * {@link Builder#addMultiple(StatementDefinition)} instead.
33 public final static int MAX = Integer.MAX_VALUE;
35 private final Map<StatementDefinition, Cardinality> cardinalityMap;
36 private final Map<StatementDefinition, Cardinality> mandatoryStatements;
37 private final StatementDefinition currentStatement;
39 private SubstatementValidator(final Builder builder) {
40 this.cardinalityMap = builder.cardinalityMap.build();
41 this.currentStatement = builder.currentStatement;
42 this.mandatoryStatements = ImmutableMap.copyOf(Maps.filterValues(cardinalityMap, c -> c.getMin() > 0));
45 public static Builder builder(final StatementDefinition currentStatement) {
46 return new Builder(currentStatement);
49 public static class Builder {
50 private static final Cardinality ONE_MAX = new Cardinality(1, Integer.MAX_VALUE);
51 private static final Cardinality ONE_ONE = new Cardinality(1, 1);
52 private static final Cardinality ZERO_MAX = new Cardinality(0, Integer.MAX_VALUE);
53 private static final Cardinality ZERO_ONE = new Cardinality(0, 1);
55 private final ImmutableMap.Builder<StatementDefinition, Cardinality> cardinalityMap = ImmutableMap.builder();
56 private final StatementDefinition currentStatement;
58 private Builder(final StatementDefinition currentStatement) {
59 this.currentStatement = currentStatement;
62 private Builder add(final StatementDefinition d, final Cardinality c) {
63 cardinalityMap.put(d, c);
67 public Builder add(final StatementDefinition d, final int min, final int max) {
68 if (max == Integer.MAX_VALUE) {
69 return addAtLeast(d, min);
70 } else if (min == 0) {
71 return addAtMost(d, max);
73 return add(d, new Cardinality(min, max));
77 // Equivalent to min .. Integer.MAX_VALUE
78 public Builder addAtLeast(final StatementDefinition d, final int min) {
83 return addMultiple(d);
85 return add(d, new Cardinality(min, Integer.MAX_VALUE));
89 // Equivalent to 0 .. max
90 public Builder addAtMost(final StatementDefinition d, final int max) {
91 return max == Integer.MAX_VALUE ? addAny(d) : add(d, new Cardinality(0, max));
94 // Equivalent to 0 .. Integer.MAX_VALUE
95 public Builder addAny(final StatementDefinition d) {
96 return add(d, ZERO_MAX);
99 // Equivalent to 1 .. 1
100 public Builder addMandatory(final StatementDefinition d) {
101 return add(d, ONE_ONE);
104 // Equivalent to 1 .. MAX
105 public Builder addMultiple(final StatementDefinition d) {
106 return add(d, ONE_MAX);
109 // Equivalent to 0 .. 1
110 public Builder addOptional(final StatementDefinition d) {
111 return add(d, ZERO_ONE);
114 public SubstatementValidator build() {
115 return new SubstatementValidator(this);
119 public void validate(final StmtContext<?, ?, ?> ctx) throws InvalidSubstatementException,
120 MissingSubstatementException {
122 final Map<StatementDefinition, Counter> stmtCounts = new HashMap<>();
123 for (StatementContextBase<?, ?, ?> stmtCtx : Iterables.concat(ctx.declaredSubstatements(), ctx.effectiveSubstatements())) {
124 stmtCounts.computeIfAbsent(stmtCtx.getPublicDefinition(), key -> new Counter()).increment();
127 // Mark all mandatory statements as not present. We are using a Map instead of a Set, as it provides us with
128 // explicit value in case of failure (which is not important) and a more efficient instantiation performance
129 // (which is important).
130 final Map<StatementDefinition, Cardinality> missingMandatory = new HashMap<>(mandatoryStatements);
132 // Iterate over all statements
133 for (Entry<StatementDefinition, Counter> entry : stmtCounts.entrySet()) {
134 final StatementDefinition key = entry.getKey();
135 final Cardinality cardinality = cardinalityMap.get(key);
136 final int value = entry.getValue().getValue();
138 if (cardinality == null) {
139 if (ctx.getFromNamespace(ExtensionNamespace.class, key.getStatementName()) == null) {
140 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
141 "%s is not valid for %s. Error in module %s (%s)", key, currentStatement,
142 ctx.getRoot().getStatementArgument(),
143 ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
149 if (cardinality.getMin() > 0) {
150 if (cardinality.getMin() > value) {
151 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
152 "Minimal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
153 cardinality.getMin(), value, ctx.getRoot().getStatementArgument(),
154 ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
157 // Encountered a mandatory statement, hence we are not missing it
158 missingMandatory.remove(key);
160 if (cardinality.getMax() < value) {
161 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
162 "Maximal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
163 cardinality.getMax(), value, ctx.getRoot().getStatementArgument(),
164 ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
168 // Check if there are any mandatory statements we have missed
169 if (!missingMandatory.isEmpty()) {
170 final Entry<StatementDefinition, Cardinality> e = missingMandatory.entrySet().iterator().next();
171 final StmtContext<?, ?, ?> root = ctx.getRoot();
173 throw new MissingSubstatementException(ctx.getStatementSourceReference(),
174 "%s is missing %s. Minimal count is %s. Error in module %s (%s)", currentStatement, e.getKey(),
175 e.getValue().getMin(), root.getStatementArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class,
180 private static final class Cardinality {
181 private final int min;
182 private final int max;
184 private Cardinality(final int min, final int max) {
185 Preconditions.checkArgument(min >= 0, "Min %s cannot be less than 0!");
186 Preconditions.checkArgument(min <= max, "Min %s can not be greater than max %s!", min, max);
191 private int getMax() {
195 private int getMin() {
200 private static final class Counter {