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