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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
21 package org.oran.smo.yangtools.parser.model.statements.yang;
23 import java.math.BigDecimal;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
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;
39 * Type-safe Yang core statement.
41 * @author Mark Hollmann
43 public class YRange extends AbstractStatement {
45 public YRange(final AbstractStatement parentStatement, final YangDomElement domNode) {
46 super(parentStatement, domNode);
50 public StatementArgumentType getArgumentType() {
51 return StatementArgumentType.VALUE;
55 public StatementModuleAndName getStatementModuleAndName() {
59 public String getRangeValues() {
60 return domElement.getTrimmedValueOrEmpty();
63 public YErrorAppTag getErrorAppTag() {
64 return getChild(CY.STMT_ERROR_APP_TAG);
67 public YErrorMessage getErrorMessage() {
68 return getChild(CY.STMT_ERROR_MESSAGE);
71 protected void validate(final ParserExecutionContext context) {
72 validateArgumentNotNullNotEmpty(context);
73 checkUsedUnderIntegerOrDecimal64(context);
74 validateBoundaries(context);
77 public void validateBoundaries(final ParserExecutionContext context) {
78 final List<BoundaryPair> boundaries = getBoundaries();
79 checkBoundaries(context, boundaries);
83 * Returns the boundaries for this range. "min" and "max" are resolved to the
84 * minimum / maximum values for the data type.
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.
91 public List<BoundaryPair> getBoundaries() {
93 * A range can only sit under type, so it's safe to do the typecast below.
95 return getBoundaries((YType) getParentStatement());
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() :
103 return getBoundaries(yangDataType, fractionDigits);
106 private List<BoundaryPair> getBoundaries(final YangDataType forDataType, final int fractionDigits) {
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."
125 * Official definition is:
127 * range-boundary = min-keyword / max-keyword / integer-value / decimal-value
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.
136 final BigDecimal minValueForRange = getMinValueForRange(forDataType, fractionDigits);
137 final BigDecimal maxValueForRange = getMaxValueForRange(forDataType, fractionDigits);
139 if (minValueForRange == null || maxValueForRange == null) {
140 return Collections.<BoundaryPair> emptyList();
144 * Now parse the range values string. This may very well throw somewhere if
145 * the syntax has not been adhered to.
149 final List<BoundaryPair> result = new ArrayList<>();
150 final String stringefiedRangeValues = getRangeValues().trim();
152 if (stringefiedRangeValues.isEmpty() || stringefiedRangeValues.startsWith("|") || stringefiedRangeValues
155 return Collections.<BoundaryPair> emptyList();
158 final String[] ranges = stringefiedRangeValues.contains("|") ?
159 stringefiedRangeValues.split("\\|") :
160 new String[] { stringefiedRangeValues };
161 for (final String oneRange : ranges) {
163 if (oneRange.trim().startsWith("..") || oneRange.trim().endsWith("..")) {
165 return Collections.<BoundaryPair> emptyList();
168 final String[] boundary = oneRange.contains("..") ?
169 oneRange.split("\\.\\.") :
170 new String[] { oneRange, oneRange };
172 final String lowerBoundary = boundary[0].trim();
173 final String upperBoundary = boundary[1].trim();
175 BigDecimal lowerValue = null;
176 if (lowerBoundary.equals("min")) {
177 lowerValue = minValueForRange;
178 } else if (lowerBoundary.equals("max")) {
179 lowerValue = maxValueForRange;
181 lowerValue = NumberHelper.extractYangIntegerValueOrDecimalValue(lowerBoundary); // Note: May throw
184 BigDecimal upperValue = null;
185 if (upperBoundary.equals("min")) {
186 upperValue = minValueForRange;
187 } else if (upperBoundary.equals("max")) {
188 upperValue = maxValueForRange;
190 upperValue = NumberHelper.extractYangIntegerValueOrDecimalValue(upperBoundary); // Note: May throw
193 result.add(new BoundaryPair(lowerValue, upperValue));
198 } catch (final Exception ex) {
203 * If we get here then an issue has occurred somewhere. We return empty boundaries
204 * as these cannot be reliably established.
206 return Collections.<BoundaryPair> emptyList();
210 * Returns the data type of the parent 'type' statement.
212 private YangDataType getParentTypeDataType() {
213 final AbstractStatement parentStatement = getParentStatement();
214 return DataTypeHelper.getYangDataType(((YType) parentStatement).getDataType());
217 public void checkBoundaries(final ParserExecutionContext context, final List<BoundaryPair> boundaries) {
219 * A range can only sit under type, so it's safe to do the typecast below.
221 checkBoundaries(context, boundaries, (YType) getParentStatement());
224 public void checkBoundaries(final ParserExecutionContext context, final List<BoundaryPair> boundaries,
225 final YType forType) {
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() :
231 checkBoundaries(context, boundaries, yangDataType, fractionDigits);
234 private void checkBoundaries(final ParserExecutionContext context, final List<BoundaryPair> boundaries,
235 final YangDataType forDataType, final int fractionDigits) {
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.
241 final BigDecimal minValueForRange = getMinValueForRange(forDataType, fractionDigits);
242 final BigDecimal maxValueForRange = getMaxValueForRange(forDataType, fractionDigits);
244 if (minValueForRange == null || maxValueForRange == null) {
249 * If we don't have boundaries that would be a finding.
251 if (boundaries.isEmpty()) {
252 context.addFinding(new Finding(this, ParserFindingType.P053_INVALID_VALUE,
253 "value '" + getRangeValues() + "' not valid for range."));
257 final boolean isIntegerType = DataTypeHelper.isYangIntegerType(getParentTypeDataType());
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.
265 for (int i = 0; i < boundaries.size(); ++i) {
266 boolean allOk = true;
268 if (isIntegerType && boundaries.get(i).lower.stripTrailingZeros().scale() > 0) {
271 if (isIntegerType && boundaries.get(i).upper.stripTrailingZeros().scale() > 0) {
275 if (boundaries.get(i).lower.compareTo(minValueForRange) < 0) {
278 if (boundaries.get(i).upper.compareTo(minValueForRange) < 0) {
281 if (boundaries.get(i).lower.compareTo(maxValueForRange) > 0) {
284 if (boundaries.get(i).upper.compareTo(maxValueForRange) > 0) {
289 if (boundaries.get(i).lower.compareTo(boundaries.get(i).upper) > 0) {
294 if (boundaries.get(i).lower.compareTo(boundaries.get(i - 1).upper) <= 0) {
300 context.addFinding(new Finding(this, ParserFindingType.P053_INVALID_VALUE,
301 "value '" + getRangeValues() + "' not valid for range."));
307 private void checkUsedUnderIntegerOrDecimal64(final ParserExecutionContext context) {
308 final YangDataType dataType = getParentTypeDataType();
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 + "'."));
316 private static boolean dataTypeIsIntegerOrDecimal64(final YangDataType dataType) {
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) {
343 * Returns the smallest possible value for the data type. May return null if this cannot be resolved.
345 private static BigDecimal getMinValueForRange(final YangDataType forDataType, final int fractionDigits) {
347 if (DataTypeHelper.isYangIntegerType(forDataType)) {
349 return (NumberHelper.getMinValueForYangIntegerDataType(forDataType));
351 } else if (DataTypeHelper.isYangDecimal64Type(forDataType)) {
355 return NumberHelper.getMinValueForYangDecimalDataType(fractionDigits);
362 * Returns the largest possible value for the data type. May return null if this cannot be resolved.
364 private static BigDecimal getMaxValueForRange(final YangDataType forDataType, final int fractionDigits) {
366 if (DataTypeHelper.isYangIntegerType(forDataType)) {
368 return (NumberHelper.getMaxValueForYangIntegerDataType(forDataType));
370 } else if (DataTypeHelper.isYangDecimal64Type(forDataType)) {
374 return NumberHelper.getMaxValueForYangDecimalDataType(fractionDigits);
380 public static class BoundaryPair {
381 public final BigDecimal lower;
382 public final BigDecimal upper;
384 public BoundaryPair(final BigDecimal lower, final BigDecimal upper) {