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