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
8 package org.opendaylight.yangtools.yang.parser.spi.meta;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.Maps;
14 import java.util.HashMap;
16 import java.util.Map.Entry;
17 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
18 import org.opendaylight.yangtools.yang.parser.spi.ExtensionNamespace;
19 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
21 public final class SubstatementValidator {
22 private final ImmutableMap<StatementDefinition, Cardinality> cardinalityMap;
23 private final ImmutableMap<StatementDefinition, Cardinality> mandatoryStatements;
24 private final StatementDefinition currentStatement;
26 private SubstatementValidator(final Builder builder) {
27 this.cardinalityMap = builder.cardinalityMap.build();
28 this.currentStatement = builder.currentStatement;
29 this.mandatoryStatements = ImmutableMap.copyOf(Maps.filterValues(cardinalityMap, c -> c.getMin() > 0));
32 public static Builder builder(final StatementDefinition currentStatement) {
33 return new Builder(currentStatement);
36 public static final class Builder {
37 private static final Cardinality ONE_MAX = new Cardinality(1, Integer.MAX_VALUE);
38 private static final Cardinality ONE_ONE = new Cardinality(1, 1);
39 private static final Cardinality ZERO_MAX = new Cardinality(0, Integer.MAX_VALUE);
40 private static final Cardinality ZERO_ONE = new Cardinality(0, 1);
42 private final ImmutableMap.Builder<StatementDefinition, Cardinality> cardinalityMap = ImmutableMap.builder();
43 private final StatementDefinition currentStatement;
45 Builder(final StatementDefinition currentStatement) {
46 this.currentStatement = currentStatement;
49 private Builder add(final StatementDefinition def, final Cardinality card) {
50 cardinalityMap.put(def, card);
54 public Builder add(final StatementDefinition def, final int min, final int max) {
55 if (max == Integer.MAX_VALUE) {
56 return addAtLeast(def, min);
57 } else if (min == 0) {
58 return addAtMost(def, max);
60 return add(def, new Cardinality(min, max));
64 // Equivalent to min .. Integer.MAX_VALUE
65 public Builder addAtLeast(final StatementDefinition def, final int min) {
70 return addMultiple(def);
72 return add(def, new Cardinality(min, Integer.MAX_VALUE));
76 // Equivalent to 0 .. max
77 public Builder addAtMost(final StatementDefinition def, final int max) {
78 return max == Integer.MAX_VALUE ? addAny(def) : add(def, new Cardinality(0, max));
81 // Equivalent to 0 .. Integer.MAX_VALUE
82 public Builder addAny(final StatementDefinition def) {
83 return add(def, ZERO_MAX);
86 // Equivalent to 1 .. 1
87 public Builder addMandatory(final StatementDefinition def) {
88 return add(def, ONE_ONE);
91 // Equivalent to 1 .. MAX
92 public Builder addMultiple(final StatementDefinition def) {
93 return add(def, ONE_MAX);
96 // Equivalent to 0 .. 1
97 public Builder addOptional(final StatementDefinition def) {
98 return add(def, ZERO_ONE);
101 public SubstatementValidator build() {
102 return new SubstatementValidator(this);
107 * Validate substatements inside a context.
109 * @param ctx Context to inspect
110 * @throws InvalidSubstatementException when there is a disallowed statement present.
111 * @throws MissingSubstatementException when a mandatory statement is missing.
113 public void validate(final StmtContext<?, ?, ?> ctx) {
115 final Map<StatementDefinition, Counter> stmtCounts = new HashMap<>();
116 for (StmtContext<?, ?, ?> stmtCtx : ctx.allSubstatements()) {
117 stmtCounts.computeIfAbsent(stmtCtx.publicDefinition(), key -> new Counter()).increment();
120 // Mark all mandatory statements as not present. We are using a Map instead of a Set, as it provides us with
121 // explicit value in case of failure (which is not important) and a more efficient instantiation performance
122 // (which is important).
123 final Map<StatementDefinition, Cardinality> missingMandatory = new HashMap<>(mandatoryStatements);
125 // Iterate over all statements
126 for (Entry<StatementDefinition, Counter> entry : stmtCounts.entrySet()) {
127 final StatementDefinition key = entry.getKey();
128 final Cardinality cardinality = cardinalityMap.get(key);
129 final int value = entry.getValue().getValue();
131 if (cardinality == null) {
132 if (ctx.getFromNamespace(ExtensionNamespace.class, key.getStatementName()) == null) {
133 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
134 "%s is not valid for %s. Error in module %s (%s)", key, currentStatement,
135 ctx.getRoot().rawStatementArgument(),
136 ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
142 if (cardinality.getMin() > 0) {
143 if (cardinality.getMin() > value) {
144 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
145 "Minimal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
146 cardinality.getMin(), value, ctx.getRoot().rawStatementArgument(),
147 ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
150 // Encountered a mandatory statement, hence we are not missing it
151 missingMandatory.remove(key);
153 if (cardinality.getMax() < value) {
154 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
155 "Maximal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
156 cardinality.getMax(), value, ctx.getRoot().rawStatementArgument(),
157 ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
161 // Check if there are any mandatory statements we have missed
162 if (!missingMandatory.isEmpty()) {
163 final Entry<StatementDefinition, Cardinality> e = missingMandatory.entrySet().iterator().next();
164 final StmtContext<?, ?, ?> root = ctx.getRoot();
166 throw new MissingSubstatementException(ctx.getStatementSourceReference(),
167 "%s is missing %s. Minimal count is %s. Error in module %s (%s)", currentStatement, e.getKey(),
168 e.getValue().getMin(), root.rawStatementArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class,
173 private static final class Cardinality {
174 private final int min;
175 private final int max;
177 Cardinality(final int min, final int max) {
178 checkArgument(min >= 0, "Min %s cannot be less than 0!", min);
179 checkArgument(min <= max, "Min %s can not be greater than max %s!", min, max);
193 private static final class Counter {