Fix checkstyle in yang-parser-spi
[yangtools.git] / yang / yang-parser-spi / src / main / java / org / opendaylight / yangtools / yang / parser / spi / meta / SubstatementValidator.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
9 package org.opendaylight.yangtools.yang.parser.spi.meta;
10
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.Maps;
14 import java.util.HashMap;
15 import java.util.Map;
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;
20
21 public final class SubstatementValidator {
22     /**
23      * Shorthand for unlimited number of occurances.
24      *
25      * @deprecated Deprecated since version 1.1.0. Use {@link Builder#addAny(StatementDefinition)},
26      *             {@link Builder#addAtLeast(StatementDefinition, int)},
27      *             {@link Builder#addMandatory(StatementDefinition)}, or
28      *             {@link Builder#addMultiple(StatementDefinition)} instead.
29      */
30     @Deprecated
31     public static final int MAX = Integer.MAX_VALUE;
32
33     private final Map<StatementDefinition, Cardinality> cardinalityMap;
34     private final Map<StatementDefinition, Cardinality> mandatoryStatements;
35     private final StatementDefinition currentStatement;
36
37     private SubstatementValidator(final Builder builder) {
38         this.cardinalityMap = builder.cardinalityMap.build();
39         this.currentStatement = builder.currentStatement;
40         this.mandatoryStatements = ImmutableMap.copyOf(Maps.filterValues(cardinalityMap, c -> c.getMin() > 0));
41     }
42
43     public static Builder builder(final StatementDefinition currentStatement) {
44         return new Builder(currentStatement);
45     }
46
47     public static class Builder {
48         private static final Cardinality ONE_MAX = new Cardinality(1, Integer.MAX_VALUE);
49         private static final Cardinality ONE_ONE = new Cardinality(1, 1);
50         private static final Cardinality ZERO_MAX = new Cardinality(0, Integer.MAX_VALUE);
51         private static final Cardinality ZERO_ONE = new Cardinality(0, 1);
52
53         private final ImmutableMap.Builder<StatementDefinition, Cardinality> cardinalityMap = ImmutableMap.builder();
54         private final StatementDefinition currentStatement;
55
56         private Builder(final StatementDefinition currentStatement) {
57             this.currentStatement = currentStatement;
58         }
59
60         private Builder add(final StatementDefinition def, final Cardinality card) {
61             cardinalityMap.put(def, card);
62             return this;
63         }
64
65         public Builder add(final StatementDefinition def, final int min, final int max) {
66             if (max == Integer.MAX_VALUE) {
67                 return addAtLeast(def, min);
68             } else if (min == 0) {
69                 return addAtMost(def, max);
70             } else {
71                 return add(def, new Cardinality(min, max));
72             }
73         }
74
75         // Equivalent to min .. Integer.MAX_VALUE
76         public Builder addAtLeast(final StatementDefinition def, final int min) {
77             switch (min) {
78                 case 0:
79                     return addAny(def);
80                 case 1:
81                     return addMultiple(def);
82                 default:
83                     return add(def, new Cardinality(min, Integer.MAX_VALUE));
84             }
85         }
86
87         // Equivalent to 0 .. max
88         public Builder addAtMost(final StatementDefinition def, final int max) {
89             return max == Integer.MAX_VALUE ? addAny(def) : add(def, new Cardinality(0, max));
90         }
91
92         // Equivalent to 0 .. Integer.MAX_VALUE
93         public Builder addAny(final StatementDefinition def) {
94             return add(def, ZERO_MAX);
95         }
96
97         // Equivalent to 1 .. 1
98         public Builder addMandatory(final StatementDefinition def) {
99             return add(def, ONE_ONE);
100         }
101
102         // Equivalent to 1 .. MAX
103         public Builder addMultiple(final StatementDefinition def) {
104             return add(def, ONE_MAX);
105         }
106
107         // Equivalent to 0 .. 1
108         public Builder addOptional(final StatementDefinition def) {
109             return add(def, ZERO_ONE);
110         }
111
112         public SubstatementValidator build() {
113             return new SubstatementValidator(this);
114         }
115     }
116
117     public void validate(final StmtContext<?, ?, ?> ctx) throws InvalidSubstatementException,
118             MissingSubstatementException {
119
120         final Map<StatementDefinition, Counter> stmtCounts = new HashMap<>();
121         for (StmtContext<?, ?, ?> stmtCtx : ctx.allSubstatements()) {
122             stmtCounts.computeIfAbsent(stmtCtx.getPublicDefinition(), key -> new Counter()).increment();
123         }
124
125         // Mark all mandatory statements as not present. We are using a Map instead of a Set, as it provides us with
126         // explicit value in case of failure (which is not important) and a more efficient instantiation performance
127         // (which is important).
128         final Map<StatementDefinition, Cardinality> missingMandatory = new HashMap<>(mandatoryStatements);
129
130         // Iterate over all statements
131         for (Entry<StatementDefinition, Counter> entry : stmtCounts.entrySet()) {
132             final StatementDefinition key = entry.getKey();
133             final Cardinality cardinality = cardinalityMap.get(key);
134             final int value = entry.getValue().getValue();
135
136             if (cardinality == null) {
137                 if (ctx.getFromNamespace(ExtensionNamespace.class, key.getStatementName()) == null) {
138                     throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
139                         "%s is not valid for %s. Error in module %s (%s)", key, currentStatement,
140                         ctx.getRoot().getStatementArgument(),
141                         ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
142                 }
143
144                 continue;
145             }
146
147             if (cardinality.getMin() > 0) {
148                 if (cardinality.getMin() > value) {
149                     throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
150                         "Minimal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
151                         cardinality.getMin(), value, ctx.getRoot().getStatementArgument(),
152                         ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
153                 }
154
155                 // Encountered a mandatory statement, hence we are not missing it
156                 missingMandatory.remove(key);
157             }
158             if (cardinality.getMax() < value) {
159                 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
160                     "Maximal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
161                     cardinality.getMax(), value, ctx.getRoot().getStatementArgument(),
162                     ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
163             }
164         }
165
166         // Check if there are any mandatory statements we have missed
167         if (!missingMandatory.isEmpty()) {
168             final Entry<StatementDefinition, Cardinality> e = missingMandatory.entrySet().iterator().next();
169             final StmtContext<?, ?, ?> root = ctx.getRoot();
170
171             throw new MissingSubstatementException(ctx.getStatementSourceReference(),
172                 "%s is missing %s. Minimal count is %s. Error in module %s (%s)", currentStatement, e.getKey(),
173                 e.getValue().getMin(), root.getStatementArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class,
174                     root));
175         }
176     }
177
178     private static final class Cardinality {
179         private final int min;
180         private final int max;
181
182         private Cardinality(final int min, final int max) {
183             Preconditions.checkArgument(min >= 0, "Min %s cannot be less than 0!");
184             Preconditions.checkArgument(min <= max, "Min %s can not be greater than max %s!", min, max);
185             this.min = min;
186             this.max = max;
187         }
188
189         private int getMax() {
190             return max;
191         }
192
193         private int getMin() {
194             return min;
195         }
196     }
197
198     private static final class Counter {
199         private int value;
200
201         void increment() {
202             value++;
203         }
204
205         int getValue() {
206             return value;
207         }
208     }
209 }