5f0b63f451d1185c09ff46699e3ca10ebecc15d6
[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.collect.ImmutableMap;
12 import com.google.common.collect.MapDifference;
13 import com.google.common.collect.Maps;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.Map;
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 import org.opendaylight.yangtools.yang.parser.util.YangParseException;
25
26 public final class SubstatementValidator {
27     private final Map<StatementDefinition, Cardinality> cardinalityMap;
28     private final StatementDefinition currentStatement;
29     private final SpecialCase specialCase;
30     public final static int MAX = Integer.MAX_VALUE;
31
32     private SubstatementValidator(Builder builder, SpecialCase specialCase) {
33         this.cardinalityMap = builder.cardinalityMap.build();
34         this.currentStatement = builder.currentStatement;
35         this.specialCase = specialCase;
36     }
37
38     public static Builder builder(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(StatementDefinition currentStatement) {
47             this.currentStatement = currentStatement;
48         }
49
50         public Builder add(StatementDefinition d, int min, 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(SpecialCase specialCase) {
60             return new SubstatementValidator(this, specialCase);
61         }
62     }
63
64     public void validate(StmtContext ctx) throws InvalidSubstatementException, MissingSubstatementException{
65         final Map<StatementDefinition, Integer> stmtDefMap = new HashMap<>();
66         final Map<StatementDefinition, Integer> validatedMap = new HashMap<>();
67         final Collection<StatementContextBase<?, ?, ?>> substatementsInit = new ArrayList<>();
68         substatementsInit.addAll(ctx.declaredSubstatements());
69         substatementsInit.addAll(ctx.effectiveSubstatements());
70
71         for (StatementContextBase<?, ?, ?> stmtCtx : substatementsInit) {
72             final StatementDefinition definition = stmtCtx.getPublicDefinition();
73             if (!stmtDefMap.containsKey(definition)) {
74                 stmtDefMap.put(definition, 1);
75             } else {
76                 stmtDefMap.put(definition, stmtDefMap.get(definition) + 1);
77             }
78         }
79
80         if (stmtDefMap.isEmpty() && specialCase == SpecialCase.NOTNULL) {
81             throw new InvalidSubstatementException(String.format("%s must contain atleast 1 element. Error in module " +
82                             "%s (%s)", currentStatement, ctx.getRoot().getStatementArgument(), ctx.getFromNamespace
83                             (ModuleCtxToModuleQName.class, ctx.getRoot())), ctx.getStatementSourceReference());
84         }
85
86         for (Map.Entry entry : stmtDefMap.entrySet()) {
87             final StatementDefinition key = (StatementDefinition) entry.getKey();
88             if (!cardinalityMap.containsKey(key)) {
89                 if (ctx.getFromNamespace(ExtensionNamespace.class, key.getArgumentName()) != null) {
90                     continue;
91                 }
92                 throw new InvalidSubstatementException(String.format("%s is not valid for %s. Error in module %s (%s)",
93                         key, currentStatement, ctx.getRoot().getStatementArgument(),
94                         ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot())),
95                         ctx.getStatementSourceReference());
96             }
97             if (cardinalityMap.get(key).getMin() > (Integer)entry.getValue()) {
98                 throw new InvalidSubstatementException(String.format("Minimal count of %s for %s is %s, detected %s. " +
99                                 "Error in module %s (%s)", key, currentStatement, cardinalityMap.get(key).getMin(),
100                         entry.getValue(), ctx.getRoot().getStatementArgument(),
101                         ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot())),
102                         ctx.getStatementSourceReference());
103             }
104             if (cardinalityMap.get(key).getMax() < (Integer)entry.getValue()) {
105                 throw new InvalidSubstatementException(String.format("Maximal count of %s for %s is %s, detected %s. " +
106                                 "Error in module %s (%s)", key, currentStatement, cardinalityMap.get(key).getMax(),
107                         entry.getValue(), ctx.getRoot().getStatementArgument(),
108                         ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot())),
109                         ctx.getStatementSourceReference());
110             }
111             validatedMap.put(key, 1);
112         }
113
114         final MapDifference<StatementDefinition, Object> diff = Maps.difference(validatedMap, cardinalityMap);
115
116         for (Map.Entry entry : diff.entriesOnlyOnRight().entrySet()) {
117             final int min = ((Cardinality)entry.getValue()).getMin();
118             if (min > 0) {
119                 throw new MissingSubstatementException(String.format("%s is missing %s. Minimal count is %s. Error in" +
120                         "  module %s (%s)", currentStatement, entry.getKey(), min, ctx.getRoot().getStatementArgument(),
121                         ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot())),
122                         ctx.getStatementSourceReference());
123             }
124         }
125     }
126
127     private static class Cardinality {
128         private final int min;
129         private final int max;
130
131         private Cardinality(int min, int max) throws YangParseException {
132             if (min > max) {
133                 throw new IllegalArgumentException("Min can not be greater than max!");
134             }
135             if (min < 0) {
136                 throw new IllegalArgumentException("Min can not be les than 0!");
137             }
138             this.min = min;
139             this.max = max;
140         }
141
142         private int getMax() {
143             return max;
144         }
145
146         private int getMin() {
147             return min;
148         }
149     }
150
151     public enum SpecialCase {
152         NOTNULL,
153         NULL
154     }
155 }