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