Introduce formatting methods for SourceException
[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     public final static int MAX = Integer.MAX_VALUE;
28     private final Map<StatementDefinition, Cardinality> cardinalityMap;
29     private final StatementDefinition currentStatement;
30     private final SpecialCase specialCase;
31
32     private SubstatementValidator(final Builder builder, final SpecialCase specialCase) {
33         this.cardinalityMap = builder.cardinalityMap.build();
34         this.currentStatement = builder.currentStatement;
35         this.specialCase = specialCase;
36     }
37
38     public static Builder builder(final StatementDefinition currentStatement) {
39         return new Builder(currentStatement);
40     }
41
42     public static class Builder {
43         private final ImmutableMap.Builder<StatementDefinition, Cardinality> cardinalityMap = ImmutableMap.builder();
44         private final StatementDefinition currentStatement;
45
46         private Builder(final StatementDefinition currentStatement) {
47             this.currentStatement = currentStatement;
48         }
49
50         public Builder add(final StatementDefinition d, final int min, final int max) {
51             this.cardinalityMap.put(d, new Cardinality(min, max));
52             return this;
53         }
54
55         public SubstatementValidator build() {
56             return new SubstatementValidator(this, SpecialCase.NULL);
57         }
58
59         public SubstatementValidator build(final SpecialCase specialCase) {
60             return new SubstatementValidator(this, specialCase);
61         }
62     }
63
64     public void validate(final StmtContext<?, ?, ?> ctx) throws InvalidSubstatementException,
65             MissingSubstatementException {
66         final Iterable<StatementContextBase<?, ?, ?>> substatementsInit = Iterables.concat(
67             ctx.declaredSubstatements(), ctx.effectiveSubstatements());
68
69         final Map<StatementDefinition, Integer> stmtDefMap = new HashMap<>();
70         for (StatementContextBase<?, ?, ?> stmtCtx : substatementsInit) {
71             final StatementDefinition definition = stmtCtx.getPublicDefinition();
72             if (!stmtDefMap.containsKey(definition)) {
73                 stmtDefMap.put(definition, 1);
74             } else {
75                 stmtDefMap.put(definition, stmtDefMap.get(definition) + 1);
76             }
77         }
78
79         if (stmtDefMap.isEmpty() && specialCase == SpecialCase.NOTNULL) {
80             throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
81                 "%s must contain atleast 1 element. Error in module %s (%s)", currentStatement,
82                 ctx.getRoot().getStatementArgument(),
83                 ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
84         }
85
86         final Map<StatementDefinition, Integer> validatedMap = new HashMap<>();
87         for (Entry<?, ?> entry : stmtDefMap.entrySet()) {
88             final StatementDefinition key = (StatementDefinition) entry.getKey();
89             if (!cardinalityMap.containsKey(key)) {
90                 if (ctx.getFromNamespace(ExtensionNamespace.class, key.getStatementName()) != null) {
91                     continue;
92                 }
93                 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
94                     "%s is not valid for %s. Error in module %s (%s)", key, currentStatement,
95                     ctx.getRoot().getStatementArgument(),
96                     ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
97             }
98             if (cardinalityMap.get(key).getMin() > (Integer) entry.getValue()) {
99                 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
100                     "Minimal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
101                     cardinalityMap.get(key).getMin(), entry.getValue(), ctx.getRoot().getStatementArgument(),
102                     ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
103             }
104             if (cardinalityMap.get(key).getMax() < (Integer) entry.getValue()) {
105                 throw new InvalidSubstatementException(ctx.getStatementSourceReference(),
106                     "Maximal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement,
107                     cardinalityMap.get(key).getMax(), entry.getValue(), ctx.getRoot().getStatementArgument(),
108                     ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
109             }
110             validatedMap.put(key, 1);
111         }
112
113         final MapDifference<StatementDefinition, Object> diff = Maps.difference(validatedMap, cardinalityMap);
114         for (Entry<?, ?> entry : diff.entriesOnlyOnRight().entrySet()) {
115             final int min = ((Cardinality) entry.getValue()).getMin();
116             if (min > 0) {
117                 throw new MissingSubstatementException(ctx.getStatementSourceReference(),
118                     "%s is missing %s. Minimal count is %s. Error in module %s (%s)", currentStatement, entry.getKey(),
119                     min, ctx.getRoot().getStatementArgument(),
120                     ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot()));
121             }
122         }
123     }
124
125     private static class Cardinality {
126         private final int min;
127         private final int max;
128
129         private Cardinality(final int min, final int max) {
130             Preconditions.checkArgument(min >= 0, "Min %s cannot be less than 0!");
131             Preconditions.checkArgument(min <= max, "Min %s can not be greater than max %s!", min, max);
132             this.min = min;
133             this.max = max;
134         }
135
136         private int getMax() {
137             return max;
138         }
139
140         private int getMin() {
141             return min;
142         }
143     }
144
145     public enum SpecialCase {
146         NOTNULL,
147         NULL
148     }
149 }