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