BUG-8043: correct RangeConstraint definition
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / TypeUtils.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.stmt.rfc6020;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Splitter;
12 import com.google.common.base.Strings;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.collect.Iterables;
16 import java.math.BigDecimal;
17 import java.math.BigInteger;
18 import java.util.ArrayList;
19 import java.util.HashSet;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.common.YangVersion;
26 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
27 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
28 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
29 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
30 import org.opendaylight.yangtools.yang.model.api.stmt.TypeEffectiveStatement;
31 import org.opendaylight.yangtools.yang.model.api.stmt.UnresolvedNumber;
32 import org.opendaylight.yangtools.yang.model.api.stmt.ValueRange;
33 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
34 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
35 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
37 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
38 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
39 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
40
41 /**
42 * Utility class for manipulating YANG base and extended types implementation.
43 */
44 public final class TypeUtils {
45
46     public static final String BINARY = "binary";
47     public static final String BITS = "bits";
48     public static final String BOOLEAN = "boolean";
49     public static final String DECIMAL64 = "decimal64";
50     public static final String EMPTY = "empty";
51     public static final String ENUMERATION = "enumeration";
52     public static final String IDENTITY_REF = "identityref";
53     public static final String INSTANCE_IDENTIFIER = "instance-identifier";
54     public static final String INT8 = "int8";
55     public static final String INT16 = "int16";
56     public static final String INT32 = "int32";
57     public static final String INT64 = "int64";
58     public static final String LEAF_REF = "leafref";
59     public static final String STRING = "string";
60     public static final String UINT8 = "uint8";
61     public static final String UINT16 = "uint16";
62     public static final String UINT32 = "uint32";
63     public static final String UINT64 = "uint64";
64     public static final String UNION = "union";
65
66     private static final Map<String, String> BUILT_IN_TYPES = ImmutableMap.<String, String>builder()
67         .put(BINARY, BINARY)
68         .put(BITS, BITS)
69         .put(BOOLEAN, BOOLEAN)
70         .put(DECIMAL64, DECIMAL64)
71         .put(EMPTY, EMPTY)
72         .put(ENUMERATION, ENUMERATION)
73         .put(IDENTITY_REF,IDENTITY_REF)
74         .put(INSTANCE_IDENTIFIER, INSTANCE_IDENTIFIER)
75         .put(INT8, INT8)
76         .put(INT16, INT16)
77         .put(INT32, INT32)
78         .put(INT64, INT64)
79         .put(LEAF_REF, LEAF_REF)
80         .put(STRING, STRING)
81         .put(UINT8, UINT8)
82         .put(UINT16, UINT16)
83         .put(UINT32, UINT32)
84         .put(UINT64, UINT64)
85         .put(UNION, UNION)
86         .build();
87
88     private static final Set<String> TYPE_BODY_STMTS = ImmutableSet.of(
89         DECIMAL64, ENUMERATION, LEAF_REF, IDENTITY_REF, BITS, UNION);
90     private static final Splitter PIPE_SPLITTER = Splitter.on('|').trimResults();
91     private static final Splitter TWO_DOTS_SPLITTER = Splitter.on("..").trimResults();
92
93     private TypeUtils() {
94     }
95
96     private static BigDecimal yangConstraintToBigDecimal(final Number number) {
97         if (UnresolvedNumber.max().equals(number)) {
98             return RangeStatementImpl.YANG_MAX_NUM;
99         }
100         if (UnresolvedNumber.min().equals(number)) {
101             return RangeStatementImpl.YANG_MIN_NUM;
102         }
103
104         return new BigDecimal(number.toString());
105     }
106
107     private static int compareNumbers(final Number n1, final Number n2) {
108
109         final BigDecimal num1 = yangConstraintToBigDecimal(n1);
110         final BigDecimal num2 = yangConstraintToBigDecimal(n2);
111
112         return new BigDecimal(num1.toString()).compareTo(new BigDecimal(num2.toString()));
113     }
114
115     private static Number parseIntegerConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
116         if ("max".equals(value)) {
117             return UnresolvedNumber.max();
118         }
119         if ("min".equals(value)) {
120             return UnresolvedNumber.min();
121         }
122
123         try {
124             return new BigInteger(value);
125         } catch (final NumberFormatException e) {
126             throw new SourceException(ctx.getStatementSourceReference(), e, "Value %s is not a valid integer", value);
127         }
128     }
129
130     private static Number parseDecimalConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
131         if ("max".equals(value)) {
132             return UnresolvedNumber.max();
133         }
134         if ("min".equals(value)) {
135             return UnresolvedNumber.min();
136         }
137
138         try {
139             return value.indexOf('.') != -1 ? new BigDecimal(value) : new BigInteger(value);
140         } catch (final NumberFormatException e) {
141             throw new SourceException(String.format("Value %s is not a valid decimal number", value),
142                     ctx.getStatementSourceReference(), e);
143         }
144     }
145
146     public static List<ValueRange> parseRangeListFromString(final StmtContext<?, ?, ?> ctx,
147             final String rangeArgument) {
148         final List<ValueRange> ranges = new ArrayList<>();
149
150         for (final String singleRange : PIPE_SPLITTER.split(rangeArgument)) {
151             final Iterator<String> boundaries = TWO_DOTS_SPLITTER.split(singleRange).iterator();
152             final Number min = parseDecimalConstraintValue(ctx, boundaries.next());
153
154             final Number max;
155             if (boundaries.hasNext()) {
156                 max = parseDecimalConstraintValue(ctx, boundaries.next());
157
158                 // if min larger than max then error
159                 SourceException.throwIf(compareNumbers(min, max) == 1, ctx.getStatementSourceReference(),
160                         "Range constraint %s has descending order of boundaries; should be ascending", singleRange);
161                 SourceException.throwIf(boundaries.hasNext(), ctx.getStatementSourceReference(),
162                     "Wrong number of boundaries in range constraint %s", singleRange);
163             } else {
164                 max = min;
165             }
166
167             // some of intervals overlapping
168             InferenceException.throwIf(ranges.size() > 1
169                 && compareNumbers(min, Iterables.getLast(ranges).upperBound()) != 1,
170                 ctx.getStatementSourceReference(),  "Some of the value ranges in %s are not disjoint",
171                 rangeArgument);
172             ranges.add(ValueRange.of(min, max));
173         }
174
175         return ranges;
176     }
177
178     public static List<ValueRange> parseLengthListFromString(final StmtContext<?, ?, ?> ctx,
179             final String lengthArgument) {
180         final List<ValueRange> ranges = new ArrayList<>();
181
182         for (final String singleRange : PIPE_SPLITTER.split(lengthArgument)) {
183             final Iterator<String> boundaries = TWO_DOTS_SPLITTER.split(singleRange).iterator();
184             final Number min = parseIntegerConstraintValue(ctx, boundaries.next());
185
186             final Number max;
187             if (boundaries.hasNext()) {
188                 max = parseIntegerConstraintValue(ctx, boundaries.next());
189
190                 // if min larger than max then error
191                 SourceException.throwIf(compareNumbers(min, max) == 1, ctx.getStatementSourceReference(),
192                         "Length constraint %s has descending order of boundaries; should be ascending.", singleRange);
193                 SourceException.throwIf(boundaries.hasNext(), ctx.getStatementSourceReference(),
194                         "Wrong number of boundaries in length constraint %s.", singleRange);
195             } else {
196                 max = min;
197             }
198
199             // some of intervals overlapping
200             InferenceException.throwIf(ranges.size() > 1
201                 && compareNumbers(min, Iterables.getLast(ranges).upperBound()) != 1,
202                         ctx.getStatementSourceReference(),  "Some of the length ranges in %s are not disjoint",
203                         lengthArgument);
204             ranges.add(ValueRange.of(min, max));
205         }
206
207         return ranges;
208     }
209
210     public static boolean isYangTypeBodyStmtString(final String typeName) {
211         return TYPE_BODY_STMTS.contains(typeName);
212     }
213
214     public static boolean isYangBuiltInTypeString(final String typeName) {
215         return BUILT_IN_TYPES.containsKey(typeName);
216     }
217
218
219     public static SchemaPath typeEffectiveSchemaPath(final StmtContext<?, ?, ?> stmtCtx) {
220         final SchemaPath path = stmtCtx.getSchemaPath().get();
221         final SchemaPath parent = path.getParent();
222         final QName parentQName = parent.getLastComponent();
223         Preconditions.checkArgument(parentQName != null, "Path %s has an empty parent", path);
224
225         final QName qname = stmtCtx.getFromNamespace(QNameCacheNamespace.class,
226             QName.create(parentQName, path.getLastComponent().getLocalName()));
227         return parent.createChild(qname);
228     }
229
230     /**
231      * Checks whether supplied type has any of specified default values marked
232      * with an if-feature. This method creates mutable copy of supplied set of
233      * default values.
234      *
235      * @param yangVersion
236      *            yang version
237      * @param typeStmt
238      *            type statement which should be checked
239      * @param defaultValues
240      *            set of default values which should be checked. The method
241      *            creates mutable copy of this set
242      *
243      * @return true if any of specified default values is marked with an
244      *         if-feature, otherwise false
245      */
246     public static boolean hasDefaultValueMarkedWithIfFeature(final YangVersion yangVersion,
247             final TypeEffectiveStatement<?> typeStmt, final Set<String> defaultValues) {
248         return !defaultValues.isEmpty() && yangVersion == YangVersion.VERSION_1_1
249                 && isRelevantForIfFeatureCheck(typeStmt)
250                 && isAnyDefaultValueMarkedWithIfFeature(typeStmt, new HashSet<>(defaultValues));
251     }
252
253     /**
254      * Checks whether supplied type has specified default value marked with an
255      * if-feature. This method creates mutable set of supplied default value.
256      *
257      * @param yangVersion
258      *            yang version
259      * @param typeStmt
260      *            type statement which should be checked
261      * @param defaultValue
262      *            default value to be checked
263      *
264      * @return true if specified default value is marked with an if-feature,
265      *         otherwise false
266      */
267     public static boolean hasDefaultValueMarkedWithIfFeature(final YangVersion yangVersion,
268             final TypeEffectiveStatement<?> typeStmt, final String defaultValue) {
269         final HashSet<String> defaultValues = new HashSet<>();
270         defaultValues.add(defaultValue);
271         return !Strings.isNullOrEmpty(defaultValue) && yangVersion == YangVersion.VERSION_1_1
272                 && isRelevantForIfFeatureCheck(typeStmt)
273                 && isAnyDefaultValueMarkedWithIfFeature(typeStmt, defaultValues);
274     }
275
276     static String findBuiltinString(final String rawArgument) {
277         return BUILT_IN_TYPES.get(rawArgument);
278     }
279
280     private static boolean isRelevantForIfFeatureCheck(final TypeEffectiveStatement<?> typeStmt) {
281         final TypeDefinition<?> typeDefinition = typeStmt.getTypeDefinition();
282         return typeDefinition instanceof EnumTypeDefinition || typeDefinition instanceof BitsTypeDefinition
283                 || typeDefinition instanceof UnionTypeDefinition;
284     }
285
286     private static boolean isAnyDefaultValueMarkedWithIfFeature(final TypeEffectiveStatement<?> typeStmt,
287             final Set<String> defaultValues) {
288         final Iterator<? extends EffectiveStatement<?, ?>> iter = typeStmt.effectiveSubstatements().iterator();
289         while (iter.hasNext() && !defaultValues.isEmpty()) {
290             final EffectiveStatement<?, ?> effectiveSubstatement = iter.next();
291             if (YangStmtMapping.BIT.equals(effectiveSubstatement.statementDefinition())) {
292                 final QName bitQName = (QName) effectiveSubstatement.argument();
293                 if (defaultValues.remove(bitQName.getLocalName()) && containsIfFeature(effectiveSubstatement)) {
294                     return true;
295                 }
296             } else if (YangStmtMapping.ENUM.equals(effectiveSubstatement.statementDefinition())
297                     && defaultValues.remove(effectiveSubstatement.argument())
298                     && containsIfFeature(effectiveSubstatement)) {
299                 return true;
300             } else if (effectiveSubstatement instanceof TypeEffectiveStatement && isAnyDefaultValueMarkedWithIfFeature(
301                     (TypeEffectiveStatement<?>) effectiveSubstatement, defaultValues)) {
302                 return true;
303             }
304         }
305
306         return false;
307     }
308
309     private static boolean containsIfFeature(final EffectiveStatement<?, ?> effectiveStatement) {
310         for (final EffectiveStatement<?, ?> effectiveSubstatement : effectiveStatement.effectiveSubstatements()) {
311             if (YangStmtMapping.IF_FEATURE.equals(effectiveSubstatement.statementDefinition())) {
312                 return true;
313             }
314         }
315         return false;
316     }
317 }