43b1e9a426719c506041dcb09b666eeb857d9532
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / spi / 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;
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.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;
24
25 public final class SubstatementValidator {
26     /**
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.
31      */
32     @Deprecated
33     public final static int MAX = Integer.MAX_VALUE;
34
35     private final Map<StatementDefinition, Cardinality> cardinalityMap;
36     private final Map<StatementDefinition, Cardinality> mandatoryStatements;
37     private final StatementDefinition currentStatement;
38
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));
43     }
44
45     public static Builder builder(final StatementDefinition currentStatement) {
46         return new Builder(currentStatement);
47     }
48
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);
54
55         private final ImmutableMap.Builder<StatementDefinition, Cardinality> cardinalityMap = ImmutableMap.builder();
56         private final StatementDefinition currentStatement;
57
58         private Builder(final StatementDefinition currentStatement) {
59             this.currentStatement = currentStatement;
60         }
61
62         private Builder add(final StatementDefinition d, final Cardinality c) {
63             cardinalityMap.put(d, c);
64             return this;
65         }
66
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);
72             } else {
73                 return add(d, new Cardinality(min, max));
74             }
75         }
76
77         // Equivalent to min .. Integer.MAX_VALUE
78         public Builder addAtLeast(final StatementDefinition d, final int min) {
79             switch (min) {
80                 case 0:
81                     return addAny(d);
82                 case 1:
83                     return addMultiple(d);
84                 default:
85                     return add(d, new Cardinality(min, Integer.MAX_VALUE));
86             }
87         }
88
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));
92         }
93
94         // Equivalent to 0 .. Integer.MAX_VALUE
95         public Builder addAny(final StatementDefinition d) {
96             return add(d, ZERO_MAX);
97         }
98
99         // Equivalent to 1 .. 1
100         public Builder addMandatory(final StatementDefinition d) {
101             return add(d, ONE_ONE);
102         }
103
104         // Equivalent to 1 .. MAX
105         public Builder addMultiple(final StatementDefinition d) {
106             return add(d, ONE_MAX);
107         }
108
109         // Equivalent to 0 .. 1
110         public Builder addOptional(final StatementDefinition d) {
111             return add(d, ZERO_ONE);
112         }
113
114         public SubstatementValidator build() {
115             return new SubstatementValidator(this);
116         }
117     }
118
119     public void validate(final StmtContext<?, ?, ?> ctx) throws InvalidSubstatementException,
120             MissingSubstatementException {
121
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();
125         }
126
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);
131
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();
137
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()));
144                 }
145
146                 continue;
147             }
148
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()));
155                 }
156
157                 // Encountered a mandatory statement, hence we are not missing it
158                 missingMandatory.remove(key);
159             }
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()));
165             }
166         }
167
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();
172
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,
176                     root));
177         }
178     }
179
180     private static final class Cardinality {
181         private final int min;
182         private final int max;
183
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);
187             this.min = min;
188             this.max = max;
189         }
190
191         private int getMax() {
192             return max;
193         }
194
195         private int getMin() {
196             return min;
197         }
198     }
199
200     private static final class Counter {
201         private int value;
202
203         void increment() {
204             value++;
205         }
206
207         int getValue() {
208             return value;
209         }
210     }
211 }