9a590abc12d877c5e3bf76d6dc79cdff7bd06930
[smo/teiv.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2024 Ericsson
4  *  Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21 package org.oran.smo.yangtools.parser.model.util;
22
23 import java.math.BigDecimal;
24 import java.math.BigInteger;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.regex.Pattern;
31 import java.util.regex.PatternSyntaxException;
32 import java.util.stream.Collectors;
33
34 import org.oran.smo.yangtools.parser.findings.Finding;
35 import org.oran.smo.yangtools.parser.findings.FindingsManager;
36 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
37 import org.oran.smo.yangtools.parser.model.statements.yang.YBit;
38 import org.oran.smo.yangtools.parser.model.statements.yang.YEnum;
39 import org.oran.smo.yangtools.parser.model.statements.yang.YLength;
40 import org.oran.smo.yangtools.parser.model.statements.yang.YPattern;
41 import org.oran.smo.yangtools.parser.model.statements.yang.YPosition;
42 import org.oran.smo.yangtools.parser.model.statements.yang.YRange.BoundaryPair;
43 import org.oran.smo.yangtools.parser.model.statements.yang.YType;
44 import org.oran.smo.yangtools.parser.model.statements.yang.YValue;
45
46 /**
47  * Utility class handling data types.
48  *
49  * @author Mark Hollmann
50  */
51 public abstract class DataTypeHelper {
52
53     public static boolean isYangNumericType(final String dataType) {
54         return isYangNumericType(getYangDataType(dataType));
55     }
56
57     public static boolean isYangNumericType(final YangDataType yangDataType) {
58         return isYangIntegerType(yangDataType) || isYangDecimal64Type(yangDataType);
59     }
60
61     public static boolean isYangIntegerType(final String dataType) {
62         return isYangIntegerType(getYangDataType(dataType));
63     }
64
65     public static boolean isYangIntegerType(final YangDataType yangDataType) {
66         return isYangSignedIntegerType(yangDataType) || isYangUnsignedIntegerType(yangDataType);
67     }
68
69     public static boolean isYangSignedIntegerType(final String dataType) {
70         return isYangSignedIntegerType(getYangDataType(dataType));
71     }
72
73     public static boolean isYangSignedIntegerType(final YangDataType yangDataType) {
74         switch (yangDataType) {
75             case INT8:
76             case INT16:
77             case INT32:
78             case INT64:
79                 return true;
80             default:
81                 return false;
82         }
83     }
84
85     public static boolean isYangUnsignedIntegerType(final String dataType) {
86         return isYangUnsignedIntegerType(getYangDataType(dataType));
87     }
88
89     public static boolean isYangUnsignedIntegerType(final YangDataType yangDataType) {
90         switch (yangDataType) {
91             case UINT8:
92             case UINT16:
93             case UINT32:
94             case UINT64:
95                 return true;
96             default:
97                 return false;
98         }
99     }
100
101     public static boolean isYangDecimal64Type(final String dataType) {
102         return isYangDecimal64Type(getYangDataType(dataType));
103     }
104
105     public static boolean isYangDecimal64Type(final YangDataType yangDataType) {
106         return yangDataType == YangDataType.DECIMAL64;
107     }
108
109     /**
110      * Returns whether the datatype is one of the built-in YANG data types.
111      */
112     public static boolean isBuiltInType(final String dataType) {
113         return getYangDataType(dataType) != YangDataType.DERIVED____TYPE;
114     }
115
116     /**
117      * Returns whether the datatype is a derived type.
118      */
119     public static boolean isDerivedType(final String dataType) {
120         return getYangDataType(dataType) == YangDataType.DERIVED____TYPE;
121     }
122
123     public static boolean isUnionType(final String dataType) {
124         return isUnionType(getYangDataType(dataType));
125     }
126
127     public static boolean isUnionType(final YangDataType yangDataType) {
128         return yangDataType == YangDataType.UNION;
129     }
130
131     public static boolean isEmptyType(final String dataType) {
132         return isEmptyType(getYangDataType(dataType));
133     }
134
135     public static boolean isEmptyType(final YangDataType yangDataType) {
136         return yangDataType == YangDataType.EMPTY;
137     }
138
139     public static boolean isStringType(final String dataType) {
140         return isStringType(getYangDataType(dataType));
141     }
142
143     public static boolean isStringType(final YangDataType yangDataType) {
144         return yangDataType == YangDataType.STRING;
145     }
146
147     public static boolean isBinaryType(final String dataType) {
148         return isBinaryType(getYangDataType(dataType));
149     }
150
151     public static boolean isBinaryType(final YangDataType yangDataType) {
152         return yangDataType == YangDataType.BINARY;
153     }
154
155     /**
156      * Returns the YANG datatype, which can be one of the build-in data types, or a
157      * derived data type.
158      */
159     public static YangDataType getYangDataType(final String dataType) {
160
161         if (dataType.equals("instance-identifier")) {
162             return YangDataType.INSTANCE_IDENTIFIER;
163         }
164
165         if (dataType.contains(":")) {
166             return YangDataType.DERIVED____TYPE;
167         }
168
169         try {
170             return YangDataType.valueOf(dataType.toUpperCase());
171         } catch (final Exception ex) {
172             /* no-op */
173         }
174
175         /*
176          * Derived type being used without prefix. According to RFC this seems to be
177          * allowed...? Really?
178          */
179         return YangDataType.DERIVED____TYPE;
180     }
181
182     public enum YangDataType {
183         INT8,
184         INT16,
185         INT32,
186         INT64,
187         UINT8,
188         UINT16,
189         UINT32,
190         UINT64,
191         DECIMAL64,
192         STRING,
193         BOOLEAN,
194         ENUMERATION,
195         BITS,
196         BINARY,
197         LEAFREF,
198         IDENTITYREF,
199         EMPTY,
200         UNION,
201         INSTANCE_IDENTIFIER,
202         DERIVED____TYPE
203     }
204
205     /**
206      * Given a 'type' statement, computes the position for each of the bits within.
207      * The rules as outlined in chapter 9.7.4.2 of the RFC are applied.
208      * <p>
209      * This method allows a pre-defined mapping to be supplied. This may be used in
210      * a situation where a bits data type has been restricted, and the numeric
211      * position has to be used from the typedef where the bits have been defined.
212      * <p>
213      * The returned value maps bit names to their position value.
214      *
215      * @param predefinedMapping
216      *     may be null
217      */
218     public static Map<String, Long> calculatePositionOfBits(final FindingsManager findingsManager, final YType type,
219             final Map<String, Long> predefinedMapping) {
220
221         final Map<String, Long> result = new HashMap<>();
222
223         /*
224          * Calculate the numeric position of each bit in the type.
225          */
226         long nextImplicitBitPosition = 0;
227         for (final YBit bit : type.getBits()) {
228
229             final String bitName = bit.getBitName();
230
231             /*
232              * Position can be either explicit or implicit. If it is implicit, then it is
233              * "one higher than the previous value". If it is explicit, then there is a
234              * 'position' statement under the bit statement. If neither is the case, it will
235              * be taken from the predefined mapping, if possible.
236              */
237             Long positionOfThisBit = Long.valueOf(nextImplicitBitPosition);
238
239             if (bit.getPosition() != null) {
240                 final YPosition yPosition = bit.getPosition();
241                 positionOfThisBit = Long.valueOf(yPosition.getPosition());
242             } else if (predefinedMapping != null && predefinedMapping.containsKey(bitName)) {
243                 positionOfThisBit = predefinedMapping.get(bitName);
244             }
245
246             if (positionOfThisBit.longValue() > YPosition.MAX_POSITION_VALUE && findingsManager != null) {
247                 findingsManager.addFinding(new Finding(bit, ParserFindingType.P053_INVALID_VALUE,
248                         "bit position value larger than '" + YPosition.MAX_POSITION_VALUE + "'."));
249             }
250
251             if (result.containsValue(positionOfThisBit) && findingsManager != null) {
252                 findingsManager.addFinding(new Finding(bit, ParserFindingType.P053_INVALID_VALUE,
253                         "Duplicate bit position value '" + positionOfThisBit + "'."));
254             }
255
256             result.put(bitName, positionOfThisBit);
257
258             nextImplicitBitPosition = positionOfThisBit.longValue() + 1;
259         }
260
261         return result;
262     }
263
264     /**
265      * Does the exact same thing as the previous method, but for data type
266      * enumeration.
267      *
268      * @param predefinedMapping
269      *     may be null
270      */
271     public static Map<String, Long> calculateValuesOfEnums(final FindingsManager findingsManager, final YType type,
272             final Map<String, Long> predefinedMapping) {
273
274         final Map<String, Long> result = new HashMap<>();
275
276         /*
277          * Calculate the numeric value of each enum in the type.
278          */
279         long nextImplicitEnumValue = 0;
280         for (final YEnum oneEnum : type.getEnums()) {
281
282             final String enumName = oneEnum.getEnumName();
283
284             /*
285              * Value can be either explicit or implicit. If it is implicit, then it is "one
286              * higher than the previous value". If it is explicit, then there is a 'value'
287              * statement under the enum statement. If neither is the case, it will be taken
288              * from the predefined mapping, if possible.
289              */
290             Long valueOfThisEnum = Long.valueOf(nextImplicitEnumValue);
291
292             if (oneEnum.getValue() != null) {
293                 final YValue value = oneEnum.getValue();
294                 valueOfThisEnum = Long.valueOf(value.getEnumValue());
295             } else if (predefinedMapping != null && predefinedMapping.containsKey(enumName)) {
296                 valueOfThisEnum = predefinedMapping.get(enumName);
297             }
298
299             if ((valueOfThisEnum.longValue() < (long) Integer.MIN_VALUE || valueOfThisEnum
300                     .longValue() > (long) Integer.MAX_VALUE) && findingsManager != null) {
301                 findingsManager.addFinding(new Finding(oneEnum, ParserFindingType.P053_INVALID_VALUE,
302                         "enum value outside allowed range."));
303             }
304
305             if (result.containsValue(valueOfThisEnum) && findingsManager != null) {
306                 findingsManager.addFinding(new Finding(oneEnum, ParserFindingType.P053_INVALID_VALUE,
307                         "Duplicate enum value '" + valueOfThisEnum + "'."));
308             }
309
310             result.put(enumName, valueOfThisEnum);
311
312             nextImplicitEnumValue = valueOfThisEnum.longValue() + 1;
313         }
314
315         return result;
316     }
317
318     /**
319      * Given a value as string, is this value a valid value in respect of the
320      * non-union data type supplied, considering constraints?
321      */
322     public static boolean isStringefiedValueValid(final String stringefiedValue, final YType yType) {
323
324         final YangDataType yangDataType = DataTypeHelper.getYangDataType(yType.getDataType());
325
326         if (yangDataType == YangDataType.UNION) {
327             throw new RuntimeException("This method does not handle union.");
328         }
329
330         switch (yangDataType) {
331             case UINT8:
332             case UINT16:
333             case UINT32:
334             case UINT64:
335             case INT8:
336             case INT16:
337             case INT32:
338             case INT64:
339                 return isValidIntegerValue(stringefiedValue, yType);
340             case DECIMAL64:
341                 return isValidDecimalValue(stringefiedValue, yType);
342             case BOOLEAN:
343                 return isValidBooleanValue(stringefiedValue, yType);
344             case ENUMERATION:
345                 return isValidEnumerationValue(stringefiedValue, yType);
346             case BITS:
347                 return isValidBitsValue(stringefiedValue, yType);
348             case STRING:
349                 return isValidStringValue(stringefiedValue, yType);
350             case EMPTY:
351                 return isValidEmptyValue(stringefiedValue, yType);
352             default:
353                 break;
354         }
355
356         return true;
357     }
358
359     public static boolean isValidIntegerValue(final String stringefiedValue, final YType yType) {
360
361         if (stringefiedValue == null) {
362             return false;
363         }
364
365         final BigInteger integerValue = NumberHelper.getIntegerDefaultValue(stringefiedValue);
366         if (integerValue == null) {
367             return false;
368         }
369
370         if (!isIntegerValueCorrectInRespectOfSize(integerValue, yType)) {
371             return false;
372         }
373
374         return isIntegerValueCorrectInRespectOfConstrainedRange(integerValue, yType);
375     }
376
377     public static boolean isIntegerValueCorrectInRespectOfSize(final BigInteger integerValue, final YType yType) {
378
379         final YangDataType yangDataType = DataTypeHelper.getYangDataType(yType.getDataType());
380         final BigInteger integerMinValue = NumberHelper.getMinValueForYangIntegerDataType(yangDataType).toBigIntegerExact();
381         final BigInteger integerMaxValue = NumberHelper.getMaxValueForYangIntegerDataType(yangDataType).toBigIntegerExact();
382
383         return (integerValue.compareTo(integerMinValue) >= 0 && integerValue.compareTo(integerMaxValue) <= 0);
384     }
385
386     public static boolean isIntegerValueCorrectInRespectOfConstrainedRange(final BigInteger integerValue,
387             final YType yType) {
388
389         if (yType.getRange() == null) {
390             return true;
391         }
392
393         boolean withinOneOfTheBoundaries = false;
394         for (final BoundaryPair boundary : yType.getRange().getBoundaries()) {
395             if (integerValue.compareTo(boundary.lower.toBigIntegerExact()) >= 0 && integerValue.compareTo(boundary.upper
396                     .toBigIntegerExact()) <= 0) {
397                 withinOneOfTheBoundaries = true;
398                 break;
399             }
400         }
401         return withinOneOfTheBoundaries;
402     }
403
404     public static boolean isValidDecimalValue(final String stringefiedValue, final YType yType) {
405
406         if (stringefiedValue == null) {
407             return false;
408         }
409
410         if (yType.getFractionDigits() == null) {
411             /*
412              * We cannot ascertain if the default value is ok, as the fraction-digits
413              * statement is missing. That would have caused a separate finding anyway.
414              */
415             return true;
416         }
417
418         final BigDecimal decimalValue = NumberHelper.getDecimalValue(stringefiedValue);
419         if (decimalValue == null) {
420             return false;
421         }
422
423         if (!isDecimalValueCorrectInRespectOfSize(decimalValue, yType)) {
424             return false;
425         }
426
427         return isDecimalValueCorrectInRespectOfConstrainedRange(decimalValue, yType);
428     }
429
430     public static boolean isDecimalValueCorrectInRespectOfSize(final BigDecimal decimalValue, final YType yType) {
431
432         final int fractionDigits = yType.getFractionDigits().getFractionDigits();
433         final BigDecimal decimalMinValue = NumberHelper.getMinValueForYangDecimalDataType(fractionDigits);
434         final BigDecimal decimalMaxValue = NumberHelper.getMaxValueForYangDecimalDataType(fractionDigits);
435         if (decimalValue.compareTo(decimalMinValue) < 0 || decimalValue.compareTo(decimalMaxValue) > 0) {
436             return false;
437         }
438
439         return (decimalValue.stripTrailingZeros().scale() <= yType.getFractionDigits().getFractionDigits());
440     }
441
442     public static boolean isDecimalValueCorrectInRespectOfConstrainedRange(final BigDecimal decimalValue,
443             final YType yType) {
444
445         if (yType.getRange() == null) {
446             return true;
447         }
448
449         boolean withinOneOfTheBoundaries = false;
450         for (final BoundaryPair boundary : yType.getRange().getBoundaries()) {
451             if (decimalValue.compareTo(boundary.lower) >= 0 && decimalValue.compareTo(boundary.upper) <= 0) {
452                 withinOneOfTheBoundaries = true;
453                 break;
454             }
455         }
456
457         return withinOneOfTheBoundaries;
458     }
459
460     public static boolean isValidBooleanValue(final String stringefiedValue, final YType yType) {
461         return ("true".equals(stringefiedValue) || "false".equals(stringefiedValue));
462     }
463
464     public static boolean isValidEnumerationValue(final String stringefiedValue, final YType yType) {
465
466         if (stringefiedValue == null) {
467             return false;
468         }
469
470         return findEnum(stringefiedValue, yType) != null;
471     }
472
473     public static YEnum findEnum(final String stringefiedValue, final YType yType) {
474         for (final YEnum enumStatement : yType.getEnums()) {
475             if (enumStatement.getEnumName().equals(stringefiedValue)) {
476                 return enumStatement;
477             }
478         }
479         return null;
480     }
481
482     public static boolean isValidBitsValue(final String stringefiedValue, final YType yType) {
483
484         if (stringefiedValue == null) {
485             return false;
486         }
487
488         if (!isBitsValueCorrectInRespectOfUniqueness(stringefiedValue, yType)) {
489             return false;
490         }
491
492         return isBitsValueCorrectInRespectOfNames(stringefiedValue, yType);
493     }
494
495     public static boolean isBitsValueCorrectInRespectOfUniqueness(final String stringefiedValue, final YType yType) {
496         final List<String> stringList = GrammarHelper.parseToStringList(stringefiedValue);
497         return stringList.size() == new HashSet<>(stringList).size();
498     }
499
500     public static boolean isBitsValueCorrectInRespectOfNames(final String stringefiedValue, final YType yType) {
501
502         final Set<String> bitsValue = new HashSet<>(GrammarHelper.parseToStringList(stringefiedValue));
503         final Set<String> bitNames = yType.getBits().stream().map(bit -> bit.getBitName()).collect(Collectors.toSet());
504
505         bitsValue.removeAll(bitNames);
506         return bitsValue.isEmpty();
507     }
508
509     private static boolean isValidStringValue(final String stringefiedValue, final YType yType) {
510
511         if (stringefiedValue == null) {
512             return false;
513         }
514
515         if (!isStringValueCorrectInRespectOfConstrainedLength(stringefiedValue, yType)) {
516             return false;
517         }
518
519         return isStringValueCorrectInRespectOfPatterns(stringefiedValue, yType);
520     }
521
522     public static boolean isStringValueCorrectInRespectOfConstrainedLength(final String stringValue, final YType yType) {
523
524         if (yType.getLength() == null) {
525             return true;
526         }
527
528         boolean withinOneOfTheBoundaries = false;
529         for (final YLength.BoundaryPair boundary : yType.getLength().getBoundaries()) {
530             if (stringValue.length() >= boundary.lower && stringValue.length() <= boundary.upper) {
531                 withinOneOfTheBoundaries = true;
532                 break;
533             }
534         }
535
536         return withinOneOfTheBoundaries;
537     }
538
539     private static final String COMPILED_PATTERN = "COMPILED_PATTERN";
540
541     public static boolean isStringValueCorrectInRespectOfPatterns(final String stringValue, final YType yType) {
542
543         for (final YPattern yPattern : yType.getPatterns()) {
544
545             try {
546                 /*
547                  * Performance improvement to re-use a compiled pattern.
548                  */
549                 Pattern compiledPattern = yPattern.getCustomAppData(COMPILED_PATTERN);
550                 if (compiledPattern == null) {
551                     compiledPattern = Pattern.compile(yPattern.getPattern());
552                     yPattern.setCustomAppData(COMPILED_PATTERN, compiledPattern);
553                 }
554
555                 if (!compiledPattern.matcher(stringValue).matches()) {
556                     return false;
557                 }
558             } catch (final PatternSyntaxException ignored) {
559                 /* ignore - a finding would have been issued on this already */
560             }
561         }
562
563         return true;
564     }
565
566     public static boolean isValidEmptyValue(final String stringefiedValue, final YType yType) {
567         /*
568          * An empty value is represented as empty element in XML when transferred
569          * over NETCONF, so we need to handle it.
570          */
571         return stringefiedValue == null || stringefiedValue.isEmpty();
572     }
573 }