Populate parser/ hierarchy
[yangtools.git] / parser / 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 package org.opendaylight.yangtools.yang.parser.spi.meta;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
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     private final ImmutableMap<StatementDefinition, Cardinality> cardinalityMap;
23     private final ImmutableMap<StatementDefinition, Cardinality> mandatoryStatements;
24     private final StatementDefinition currentStatement;
25
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));
30     }
31
32     public static Builder builder(final StatementDefinition currentStatement) {
33         return new Builder(currentStatement);
34     }
35
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);
41
42         private final ImmutableMap.Builder<StatementDefinition, Cardinality> cardinalityMap = ImmutableMap.builder();
43         private final StatementDefinition currentStatement;
44
45         Builder(final StatementDefinition currentStatement) {
46             this.currentStatement = currentStatement;
47         }
48
49         private Builder add(final StatementDefinition def, final Cardinality card) {
50             cardinalityMap.put(def, card);
51             return this;
52         }
53
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);
59             } else {
60                 return add(def, new Cardinality(min, max));
61             }
62         }
63
64         // Equivalent to min .. Integer.MAX_VALUE
65         public Builder addAtLeast(final StatementDefinition def, final int min) {
66             switch (min) {
67                 case 0:
68                     return addAny(def);
69                 case 1:
70                     return addMultiple(def);
71                 default:
72                     return add(def, new Cardinality(min, Integer.MAX_VALUE));
73             }
74         }
75
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));
79         }
80
81         // Equivalent to 0 .. Integer.MAX_VALUE
82         public Builder addAny(final StatementDefinition def) {
83             return add(def, ZERO_MAX);
84         }
85
86         // Equivalent to 1 .. 1
87         public Builder addMandatory(final StatementDefinition def) {
88             return add(def, ONE_ONE);
89         }
90
91         // Equivalent to 1 .. MAX
92         public Builder addMultiple(final StatementDefinition def) {
93             return add(def, ONE_MAX);
94         }
95
96         // Equivalent to 0 .. 1
97         public Builder addOptional(final StatementDefinition def) {
98             return add(def, ZERO_ONE);
99         }
100
101         public SubstatementValidator build() {
102             return new SubstatementValidator(this);
103         }
104     }
105
106     /**
107      * Validate substatements inside a context.
108      *
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.
112      */
113     public void validate(final StmtContext<?, ?, ?> ctx) {
114
115         final Map<StatementDefinition, Counter> stmtCounts = new HashMap<>();
116         for (StmtContext<?, ?, ?> stmtCtx : ctx.allSubstatements()) {
117             stmtCounts.computeIfAbsent(stmtCtx.publicDefinition(), key -> new Counter()).increment();
118         }
119
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);
124
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();
130
131             if (cardinality == null) {
132                 if (ctx.getFromNamespace(ExtensionNamespace.class, key.getStatementName()) == null) {
133                     final StmtContext<?, ?, ?> root = ctx.getRoot();
134                     throw new InvalidSubstatementException(ctx, "%s is not valid for %s. Error in module %s (%s)", key,
135                         currentStatement, root.rawArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class, root));
136                 }
137
138                 continue;
139             }
140
141             if (cardinality.getMin() > 0) {
142                 if (cardinality.getMin() > value) {
143                     final StmtContext<?, ?, ?> root = ctx.getRoot();
144                     throw new InvalidSubstatementException(ctx,
145                         "Minimal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
146                         cardinality.getMin(), value, root.rawArgument(),
147                         ctx.getFromNamespace(ModuleCtxToModuleQName.class, root));
148                 }
149
150                 // Encountered a mandatory statement, hence we are not missing it
151                 missingMandatory.remove(key);
152             }
153             if (cardinality.getMax() < value) {
154                 final StmtContext<?, ?, ?> root = ctx.getRoot();
155                 throw new InvalidSubstatementException(ctx,
156                     "Maximal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
157                     cardinality.getMax(), value, root.rawArgument(),
158                     ctx.getFromNamespace(ModuleCtxToModuleQName.class, root));
159             }
160         }
161
162         // Check if there are any mandatory statements we have missed
163         if (!missingMandatory.isEmpty()) {
164             final Entry<StatementDefinition, Cardinality> e = missingMandatory.entrySet().iterator().next();
165             final StmtContext<?, ?, ?> root = ctx.getRoot();
166
167             throw new MissingSubstatementException(ctx,
168                 "%s is missing %s. Minimal count is %s. Error in module %s (%s)", currentStatement, e.getKey(),
169                 e.getValue().getMin(), root.rawArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class, root));
170         }
171     }
172
173     private static final class Cardinality {
174         private final int min;
175         private final int max;
176
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);
180             this.min = min;
181             this.max = max;
182         }
183
184         int getMax() {
185             return max;
186         }
187
188         int getMin() {
189             return min;
190         }
191     }
192
193     private static final class Counter {
194         private int value;
195
196         void increment() {
197             value++;
198         }
199
200         int getValue() {
201             return value;
202         }
203     }
204 }