b0d3f65af32676f358ff58366bd6bc8e8d316877
[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.statements.yang;
22
23 import java.math.BigDecimal;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27
28 import org.oran.smo.yangtools.parser.ParserExecutionContext;
29 import org.oran.smo.yangtools.parser.findings.Finding;
30 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
31 import org.oran.smo.yangtools.parser.model.statements.AbstractStatement;
32 import org.oran.smo.yangtools.parser.model.statements.StatementModuleAndName;
33 import org.oran.smo.yangtools.parser.model.util.DataTypeHelper;
34 import org.oran.smo.yangtools.parser.model.util.DataTypeHelper.YangDataType;
35 import org.oran.smo.yangtools.parser.model.util.NumberHelper;
36 import org.oran.smo.yangtools.parser.model.yangdom.YangDomElement;
37
38 /**
39  * Type-safe Yang core statement.
40  *
41  * @author Mark Hollmann
42  */
43 public class YRange extends AbstractStatement {
44
45     public YRange(final AbstractStatement parentStatement, final YangDomElement domNode) {
46         super(parentStatement, domNode);
47     }
48
49     @Override
50     public StatementArgumentType getArgumentType() {
51         return StatementArgumentType.VALUE;
52     }
53
54     @Override
55     public StatementModuleAndName getStatementModuleAndName() {
56         return CY.STMT_RANGE;
57     }
58
59     public String getRangeValues() {
60         return domElement.getTrimmedValueOrEmpty();
61     }
62
63     public YErrorAppTag getErrorAppTag() {
64         return getChild(CY.STMT_ERROR_APP_TAG);
65     }
66
67     public YErrorMessage getErrorMessage() {
68         return getChild(CY.STMT_ERROR_MESSAGE);
69     }
70
71     protected void validate(final ParserExecutionContext context) {
72         validateArgumentNotNullNotEmpty(context);
73         checkUsedUnderIntegerOrDecimal64(context);
74         validateBoundaries(context);
75     }
76
77     public void validateBoundaries(final ParserExecutionContext context) {
78         final List<BoundaryPair> boundaries = getBoundaries();
79         checkBoundaries(context, boundaries);
80     }
81
82     /**
83      * Returns the boundaries for this range. "min" and "max" are resolved to the
84      * minimum / maximum values for the data type.
85      * <p>
86      * The returned list contains pairings for each boundary part of the range.
87      * Both upper and lower boundary are expressed as BigDecimal. Where the type
88      * owning this range statement is integer, a client should invoke toBigInteger
89      * to get the boundaries as integer values.
90      */
91     public List<BoundaryPair> getBoundaries() {
92         /*
93          * A range can only sit under type, so it's safe to do the typecast below.
94          */
95         return getBoundaries((YType) getParentStatement());
96     }
97
98     public List<BoundaryPair> getBoundaries(final YType forType) {
99         final YangDataType yangDataType = DataTypeHelper.getYangDataType(forType.getDataType());
100         final int fractionDigits = forType.hasAtLeastOneChildOf(CY.STMT_FRACTION_DIGITS) ?
101                 ((YFractionDigits) forType.getChild(CY.STMT_FRACTION_DIGITS)).getFractionDigits() :
102                 0;
103         return getBoundaries(yangDataType, fractionDigits);
104     }
105
106     private List<BoundaryPair> getBoundaries(final YangDataType forDataType, final int fractionDigits) {
107
108         /*
109          * From the RFC:
110          *
111          * "A range consists of an explicit value, or a lower-inclusive bound,
112          * two consecutive dots "..", and an upper-inclusive bound. Multiple
113          * values or ranges can be given, separated by "|". If multiple values
114          * or ranges are given, they all MUST be disjoint and MUST be in
115          * ascending order. If a range restriction is applied to a type that is
116          * already range-restricted, the new restriction MUST be equally
117          * limiting or more limiting, i.e., raising the lower bounds, reducing
118          * the upper bounds, removing explicit values or ranges, or splitting
119          * ranges into multiple ranges with intermediate gaps. Each explicit
120          * value and range boundary value given in the range expression MUST
121          * match the type being restricted or be one of the special values "min"
122          * or "max". "min" and "max" mean the minimum and maximum values
123          * accepted for the type being restricted, respectively."
124          *
125          * Official definition is:
126          *
127          * range-boundary = min-keyword / max-keyword / integer-value / decimal-value
128          */
129
130         /*
131          * The first problem that we are faced with is to know what actual values we
132          * should use for min and max. That solely depends on the data type of the
133          * parent 'type' statement. If this cannot be resolved for whatever reason we
134          * return empty boundaries so that a client doesn't have to worry about null values.
135          */
136         final BigDecimal minValueForRange = getMinValueForRange(forDataType, fractionDigits);
137         final BigDecimal maxValueForRange = getMaxValueForRange(forDataType, fractionDigits);
138
139         if (minValueForRange == null || maxValueForRange == null) {
140             return Collections.<BoundaryPair> emptyList();
141         }
142
143         /*
144          * Now parse the range values string. This may very well throw somewhere if
145          * the syntax has not been adhered to.
146          */
147         try {
148
149             final List<BoundaryPair> result = new ArrayList<>();
150             final String stringefiedRangeValues = getRangeValues().trim();
151
152             if (stringefiedRangeValues.isEmpty() || stringefiedRangeValues.startsWith("|") || stringefiedRangeValues
153                     .endsWith("|")) {
154                 // syntax error
155                 return Collections.<BoundaryPair> emptyList();
156             }
157
158             final String[] ranges = stringefiedRangeValues.contains("|") ?
159                     stringefiedRangeValues.split("\\|") :
160                     new String[] { stringefiedRangeValues };
161             for (final String oneRange : ranges) {
162
163                 if (oneRange.trim().startsWith("..") || oneRange.trim().endsWith("..")) {
164                     // syntax error
165                     return Collections.<BoundaryPair> emptyList();
166                 }
167
168                 final String[] boundary = oneRange.contains("..") ?
169                         oneRange.split("\\.\\.") :
170                         new String[] { oneRange, oneRange };
171
172                 final String lowerBoundary = boundary[0].trim();
173                 final String upperBoundary = boundary[1].trim();
174
175                 BigDecimal lowerValue = null;
176                 if (lowerBoundary.equals("min")) {
177                     lowerValue = minValueForRange;
178                 } else if (lowerBoundary.equals("max")) {
179                     lowerValue = maxValueForRange;
180                 } else {
181                     lowerValue = NumberHelper.extractYangIntegerValueOrDecimalValue(lowerBoundary);             // Note: May throw
182                 }
183
184                 BigDecimal upperValue = null;
185                 if (upperBoundary.equals("min")) {
186                     upperValue = minValueForRange;
187                 } else if (upperBoundary.equals("max")) {
188                     upperValue = maxValueForRange;
189                 } else {
190                     upperValue = NumberHelper.extractYangIntegerValueOrDecimalValue(upperBoundary);             // Note: May throw
191                 }
192
193                 result.add(new BoundaryPair(lowerValue, upperValue));
194             }
195
196             return result;
197
198         } catch (final Exception ex) {
199             /* no-op */
200         }
201
202         /*
203          * If we get here then an issue has occurred somewhere. We return empty boundaries
204          * as these cannot be reliably established.
205          */
206         return Collections.<BoundaryPair> emptyList();
207     }
208
209     /**
210      * Returns the data type of the parent 'type' statement.
211      */
212     private YangDataType getParentTypeDataType() {
213         final AbstractStatement parentStatement = getParentStatement();
214         return DataTypeHelper.getYangDataType(((YType) parentStatement).getDataType());
215     }
216
217     public void checkBoundaries(final ParserExecutionContext context, final List<BoundaryPair> boundaries) {
218         /*
219          * A range can only sit under type, so it's safe to do the typecast below.
220          */
221         checkBoundaries(context, boundaries, (YType) getParentStatement());
222     }
223
224     public void checkBoundaries(final ParserExecutionContext context, final List<BoundaryPair> boundaries,
225             final YType forType) {
226
227         final YangDataType yangDataType = DataTypeHelper.getYangDataType(forType.getDataType());
228         final int fractionDigits = forType.hasAtLeastOneChildOf(CY.STMT_FRACTION_DIGITS) ?
229                 ((YFractionDigits) forType.getChild(CY.STMT_FRACTION_DIGITS)).getFractionDigits() :
230                 0;
231         checkBoundaries(context, boundaries, yangDataType, fractionDigits);
232     }
233
234     private void checkBoundaries(final ParserExecutionContext context, final List<BoundaryPair> boundaries,
235             final YangDataType forDataType, final int fractionDigits) {
236
237         /*
238          * Establish min/max values for the data type. If this cannot be done for whatever reason
239          * (e.g. non-numeric data type) then the boundaries cannot be checked, and we return out.
240          */
241         final BigDecimal minValueForRange = getMinValueForRange(forDataType, fractionDigits);
242         final BigDecimal maxValueForRange = getMaxValueForRange(forDataType, fractionDigits);
243
244         if (minValueForRange == null || maxValueForRange == null) {
245             return;
246         }
247
248         /*
249          * If we don't have boundaries that would be a finding.
250          */
251         if (boundaries.isEmpty()) {
252             context.addFinding(new Finding(this, ParserFindingType.P053_INVALID_VALUE,
253                     "value '" + getRangeValues() + "' not valid for range."));
254             return;
255         }
256
257         final boolean isIntegerType = DataTypeHelper.isYangIntegerType(getParentTypeDataType());
258
259         /*
260          * Rules:
261          * - Each value must be within min/max values
262          * - In each pair, upper must be >= lower.
263          * - The lower of a pair must be >= the upper of the previous pair.
264          */
265         for (int i = 0; i < boundaries.size(); ++i) {
266             boolean allOk = true;
267
268             if (isIntegerType && boundaries.get(i).lower.stripTrailingZeros().scale() > 0) {
269                 allOk = false;
270             }
271             if (isIntegerType && boundaries.get(i).upper.stripTrailingZeros().scale() > 0) {
272                 allOk = false;
273             }
274
275             if (boundaries.get(i).lower.compareTo(minValueForRange) < 0) {
276                 allOk = false;
277             }
278             if (boundaries.get(i).upper.compareTo(minValueForRange) < 0) {
279                 allOk = false;
280             }
281             if (boundaries.get(i).lower.compareTo(maxValueForRange) > 0) {
282                 allOk = false;
283             }
284             if (boundaries.get(i).upper.compareTo(maxValueForRange) > 0) {
285                 allOk = false;
286             }
287
288             // lower <= upper ?
289             if (boundaries.get(i).lower.compareTo(boundaries.get(i).upper) > 0) {
290                 allOk = false;
291             }
292
293             if (i > 0) {
294                 if (boundaries.get(i).lower.compareTo(boundaries.get(i - 1).upper) <= 0) {
295                     allOk = false;
296                 }
297             }
298
299             if (!allOk) {
300                 context.addFinding(new Finding(this, ParserFindingType.P053_INVALID_VALUE,
301                         "value '" + getRangeValues() + "' not valid for range."));
302                 return;
303             }
304         }
305     }
306
307     private void checkUsedUnderIntegerOrDecimal64(final ParserExecutionContext context) {
308         final YangDataType dataType = getParentTypeDataType();
309
310         if (dataType != YangDataType.DERIVED____TYPE && !dataTypeIsIntegerOrDecimal64(dataType)) {
311             context.addFinding(new Finding(this, ParserFindingType.P018_ILLEGAL_CHILD_STATEMENT,
312                     "'range' statement not allowed under type '" + dataType + "'."));
313         }
314     }
315
316     private static boolean dataTypeIsIntegerOrDecimal64(final YangDataType dataType) {
317         switch (dataType) {
318             case INT8:
319             case INT16:
320             case INT32:
321             case INT64:
322             case UINT8:
323             case UINT16:
324             case UINT32:
325             case UINT64:
326             case DECIMAL64:
327                 return true;
328             default:
329                 return false;
330         }
331     }
332
333     public boolean isWithinRangeBoundaries(final BigDecimal toCheck) {
334         for (final BoundaryPair boundaryPair : getBoundaries()) {
335             if (toCheck.compareTo(boundaryPair.lower) >= 0 && toCheck.compareTo(boundaryPair.upper) <= 0) {
336                 return true;
337             }
338         }
339         return false;
340     }
341
342     /**
343      * Returns the smallest possible value for the data type. May return null if this cannot be resolved.
344      */
345     private static BigDecimal getMinValueForRange(final YangDataType forDataType, final int fractionDigits) {
346
347         if (DataTypeHelper.isYangIntegerType(forDataType)) {
348
349             return (NumberHelper.getMinValueForYangIntegerDataType(forDataType));
350
351         } else if (DataTypeHelper.isYangDecimal64Type(forDataType)) {
352             /*
353              * Type is decimal64.
354              */
355             return NumberHelper.getMinValueForYangDecimalDataType(fractionDigits);
356         }
357
358         return null;
359     }
360
361     /**
362      * Returns the largest possible value for the data type. May return null if this cannot be resolved.
363      */
364     private static BigDecimal getMaxValueForRange(final YangDataType forDataType, final int fractionDigits) {
365
366         if (DataTypeHelper.isYangIntegerType(forDataType)) {
367
368             return (NumberHelper.getMaxValueForYangIntegerDataType(forDataType));
369
370         } else if (DataTypeHelper.isYangDecimal64Type(forDataType)) {
371             /*
372              * Type is decimal64.
373              */
374             return NumberHelper.getMaxValueForYangDecimalDataType(fractionDigits);
375         }
376
377         return null;
378     }
379
380     public static class BoundaryPair {
381         public final BigDecimal lower;
382         public final BigDecimal upper;
383
384         public BoundaryPair(final BigDecimal lower, final BigDecimal upper) {
385             this.lower = lower;
386             this.upper = upper;
387         }
388     }
389 }