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.resolvers;
23 import java.math.BigDecimal;
24 import java.util.ArrayList;
25 import java.util.List;
27 import java.util.stream.Collectors;
29 import org.oran.smo.yangtools.parser.ParserExecutionContext;
30 import org.oran.smo.yangtools.parser.findings.Finding;
31 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
32 import org.oran.smo.yangtools.parser.model.schema.Schema;
33 import org.oran.smo.yangtools.parser.model.statements.AbstractStatement;
34 import org.oran.smo.yangtools.parser.model.statements.yang.CY;
35 import org.oran.smo.yangtools.parser.model.statements.yang.YBit;
36 import org.oran.smo.yangtools.parser.model.statements.yang.YDefault;
37 import org.oran.smo.yangtools.parser.model.statements.yang.YEnum;
38 import org.oran.smo.yangtools.parser.model.statements.yang.YLength;
39 import org.oran.smo.yangtools.parser.model.statements.yang.YPosition;
40 import org.oran.smo.yangtools.parser.model.statements.yang.YRange;
41 import org.oran.smo.yangtools.parser.model.statements.yang.YType;
42 import org.oran.smo.yangtools.parser.model.statements.yang.YTypedef;
43 import org.oran.smo.yangtools.parser.model.statements.yang.YValue;
44 import org.oran.smo.yangtools.parser.model.util.DataTypeHelper;
45 import org.oran.smo.yangtools.parser.model.util.DataTypeHelper.YangDataType;
46 import org.oran.smo.yangtools.parser.model.yangdom.YangDomElement;
47 import org.oran.smo.yangtools.parser.util.NamespaceModuleIdentifier;
50 * A class that can resolve usages of derived types to their base type.
52 * This class will correctly handle nested type resolution - that means, can handle a typedef
53 * referring to another typedef (which can and does happen in reality).
55 * @author Mark Hollmann
57 public abstract class TypeResolver {
60 * Resolving usages of derived types means that all places where a "type" statement is used, and the type
61 * referred-to is not one of the build-in types, that usage is replaced with the actual base type.
63 public static void resolveUsagesOfDerivedTypes(final ParserExecutionContext context, final Schema schema) {
66 * Get all occurrences of the "type" statement. For each statement, check whether the type refers
67 * to an in-build type - if not, it refers to a type defined within a "typedef". The "typedef"
68 * itself will contain a "type" statement, that can likewise be an in-built type, or a derived type.
70 * The easiest way of doing this is to iterate a number of times over all the "type" statements, and
71 * only to resolve them if the underlying typedef refers to a base type. We guard against infinite loops.
74 int iterationCount = 10;
75 boolean atLeastOneResolved = true;
77 while (iterationCount > 0 && atLeastOneResolved) {
79 atLeastOneResolved = false;
83 * It is correct that the list of "type" statements is fetched every time here, and not once outside
84 * the while-loop. The reason is that otherwise we would simply keep doing the same merge/replace 10 times,
85 * and also replaced "type" statements are detached from the tree, so no need to do these again.
87 final List<YType> types = findTypeStatementsToConsider(schema);
88 for (final YType type : types) {
90 atLeastOneResolved |= resolveTypeToBaseType(context, schema, type);
91 } catch (final Exception ex) {
92 /* Swallow and move to next. Best effort here, keep trying other types. */
96 if (iterationCount == 7) {
97 final List<YType> typesWithExcessiveTypedefDepth = findTypeStatementsToConsider(schema);
98 for (final YType type : typesWithExcessiveTypedefDepth) {
99 context.addFinding(new Finding(type, ParserFindingType.P112_EXCESSIVE_TYPEDEF_DEPTH,
100 "Statement refers to 'typedef' with nesting depth > 3."));
107 * Done resolving. If some type statements are left with derived types they could not
108 * be resolved because of circular dependencies.
110 final List<YType> stillUnresolvedTypeStatements = findTypeStatementsToConsider(schema);
111 stillUnresolvedTypeStatements.forEach(yType -> context.addFinding(new Finding(yType,
112 ParserFindingType.P111_CIRCULAR_TYPEDEF_REFERENCES,
113 "Likely circular references between 'type' and 'typedef'. Use the quoted file and line number as starting point for investigation.")));
116 * Perform a check to see which typedef statements have not been used or only used
117 * once and create findings for these (possibly poor modeling).
119 @SuppressWarnings("unchecked") final List<YTypedef> allTypedefs = (List<YTypedef>) Helper.findStatementsInSchema(
120 CY.STMT_TYPEDEF, schema);
121 for (final YTypedef oneTypedef : allTypedefs) {
122 final int used = getTypedefUsageCount(oneTypedef);
124 context.addFinding(new Finding(oneTypedef, ParserFindingType.P114_TYPEDEF_NOT_USED,
125 "typedef statement '" + oneTypedef.getTypedefName() + "' not used."));
126 } else if (used == 1) {
127 context.addFinding(new Finding(oneTypedef, ParserFindingType.P115_TYPEDEF_USED_ONCE_ONLY,
128 "typedef statement '" + oneTypedef.getTypedefName() + "' used only once; consider inlining."));
133 * It is possible that the replacement of typedefs has resulted in union-within-union.
134 * This will be resolved so that there is only a single union.
136 atLeastOneResolved = true;
138 while (atLeastOneResolved) {
139 atLeastOneResolved = false;
141 @SuppressWarnings("unchecked") final List<YType> allTypes = (List<YType>) Helper.findStatementsInSchema(
142 CY.STMT_TYPE, schema);
143 for (final YType type : allTypes) {
144 atLeastOneResolved |= resolveUnionInUnion(context, type);
150 * Resolves usage of a derived type to its underlying type.
152 * 1.) Find the actual base type (basically the sought typedef)
153 * 2.) Clone the complete tree that hangs under the "type", under the found "typedef". The clone is necessary as
154 * it may be used again by some other type or typedef, so we can't just shift the tree but must clone it.
155 * 3.) Merge together the statements that hang under the original "type", and the cloned "type". Certain override
157 * 4.) Replace the original "type" with the clone that has been updated.
159 * Note that the cloned tree keeps its reference to its original prefix-resolver. This is really important as the
160 * prefixes between the original YAML and the YAML in which the typedef is can be different!
162 * There is a small issue, however: We have to ensure that the resolution is done backwards, i.e. always only
163 * resolve if the typedef itself points to a base type. Otherwise we run into problems with modules and prefixes.
165 private static boolean resolveTypeToBaseType(final ParserExecutionContext context, final Schema schema,
166 final YType typeUsingDerivedType) {
168 final AbstractStatement parentOfTypeStatement = typeUsingDerivedType.getParentStatement();
170 final String derivedDataType = typeUsingDerivedType.getDataType();
171 if (derivedDataType.isEmpty()) {
173 * Pointless trying to resolve the data type. No point issuing a finding either, a
174 * P015_INVALID_SYNTAX_IN_DOCUMENT would have been issued already.
176 setDerivedTypeNotResolvable(typeUsingDerivedType);
181 * We look for the typedef. This could sit further up the tree, at the top of the module, in an included submodule.
183 final YTypedef typedef = Helper.findStatement(context, schema, typeUsingDerivedType, CY.STMT_TYPEDEF,
185 if (typedef == null) {
186 setDerivedTypeNotResolvable(typeUsingDerivedType);
187 context.addFinding(new Finding(typeUsingDerivedType, ParserFindingType.P113_UNRESOLVABLE_DERIVED_TYPE,
188 "Cannot resolve typedef '" + typeUsingDerivedType.getDataType() + "'."));
193 * Mark the typedef has been used.
195 increaseUsageCount(typedef);
198 * Check for nested derived type usage, and if found resolve those first.
200 if (derivedTypeUsedWithinFoundTypedef(context, typeUsingDerivedType, typedef)) {
205 * We have found the typedef, and the type within it is a build-in type. We attach some meta data to it first
206 * to keep track of where it came from. Downstream tooling may find this information useful, as quite often
207 * derived types have special semantics, and the resolving of these to their base type loses these semantics.
209 addOriginallyDefinedInYam(typedef.getType(), typedef.getYangModelRoot().getModuleOrSubModuleName());
212 * The typedef may be used multiple times so we can't simply re-parent the YType statement tree - we must clone it.
214 final YType clonedTypeUnderTypedef = new YType(typedef, typedef.getType().getDomElement());
215 clonedTypeUnderTypedef.cloneFrom(typedef.getType());
218 * Type restrictions can be applied to various sub-statements of the type, which always must make
219 * the allowable value set more restrictive (or be the same). We check for these here. See section
220 * 9.4 in the RFC, and the paragraphs in the various sub-statement that discuss restrictions.
222 checkAndRestrictPatterns(context, typeUsingDerivedType, clonedTypeUnderTypedef);
223 checkAndRestrictLength(context, typeUsingDerivedType, clonedTypeUnderTypedef);
224 checkAndRestrictRange(context, typeUsingDerivedType, clonedTypeUnderTypedef);
225 checkAndRestrictBits(context, typeUsingDerivedType, clonedTypeUnderTypedef);
226 checkAndRestrictEnumeration(context, typeUsingDerivedType, clonedTypeUnderTypedef);
229 * A possible default value under the typedef will be copied over to the leaf /
230 * leaf-list / typedef, but only if that does not always have a default value. See
231 * section 7.3.4 and 7.6.1 in the RFC.
233 if (typedef.getDefault() != null) {
234 assignDefaultFromTypedefToWhereItIsUsed(typedef, typeUsingDerivedType);
238 * We are now removing the original 'type' statement (that referred to the derived type) and are
239 * replacing it with the cloned and updated 'type' statement of the 'typedef'. That will then
240 * also clean up the tree underneath the typedef where temporarily we had two 'type' statements.
241 * Note that the replacement must be done such to keep the order of statements in the statement tree.
242 * This is required to handle nested unions correctly.
244 parentOfTypeStatement.replaceChildInPlace(typeUsingDerivedType, clonedTypeUnderTypedef);
247 * Keep a record of the reference for later analysis.
249 addTypedefReference(clonedTypeUnderTypedef, typedef);
252 * If there is a 'range' statement under the type now we need to re-validate. Same with
253 * 'length' statement.
255 if (clonedTypeUnderTypedef.getRange() != null) {
256 clonedTypeUnderTypedef.getRange().validateBoundaries(context);
258 if (clonedTypeUnderTypedef.getLength() != null) {
259 clonedTypeUnderTypedef.getLength().validateBoundaries(context);
265 private static boolean derivedTypeUsedWithinFoundTypedef(final ParserExecutionContext context,
266 final YType typeUsingDerivedType, final YTypedef typedef) {
268 final List<YType> typesUnderFoundTypedef = getTypesFromUnderTypedef(typedef);
270 boolean hasTypeThatIsNotResolveable = false;
271 boolean hasTypeThatIsDerived = false;
273 for (final YType typeInTypedef : typesUnderFoundTypedef) {
275 if (isDerivedTypeNotResolvable(typeInTypedef)) {
277 hasTypeThatIsNotResolveable = true;
278 context.addFinding(new Finding(typeUsingDerivedType,
279 ParserFindingType.P116_NESTED_DERIVED_TYPE_NOT_RESOLVABLE,
280 "Referenced typedef '" + typeUsingDerivedType
281 .getDataType() + "' has nested unresolvable 'type' statement " + typeInTypedef
282 .getDomElement().getNameValue() + "."));
285 if (DataTypeHelper.isDerivedType(typeInTypedef.getDataType())) {
286 hasTypeThatIsDerived = true;
290 if (hasTypeThatIsNotResolveable) {
292 * If the typedef that this type refers to has a 'type' that cannot be resolved,
293 * then this 'type' here likewise cannot be resolved.
295 setDerivedTypeNotResolvable(typeUsingDerivedType);
299 * The typedef itself references another typedef, so resolve the other
300 * typedef first before attempting this one here.
302 return hasTypeThatIsDerived;
305 private static List<YType> getTypesFromUnderTypedef(final YTypedef typedef) {
306 final List<YType> result = new ArrayList<>();
308 result.add(typedef.getType());
310 for (final YType unionMember : typedef.getType().getTypes()) {
311 result.add(unionMember);
318 * A typedef can have a default value. If the derived type does not have a default, such a default must be copied over.
320 private static void assignDefaultFromTypedefToWhereItIsUsed(final YTypedef typedef, final YType originalTypeStatement) {
322 AbstractStatement parent = originalTypeStatement.getParentStatement();
324 if (parent.is(CY.STMT_TYPE)) {
326 * type-under-type, i.e. union.
328 parent = parent.getParentStatement();
332 * Safety check so we don't end up adding a default value to a statement that
333 * doesn't support default as child statement.
335 if (parent.is(CY.STMT_LEAF) || parent.is(CY.STMT_LEAF_LIST) || parent.is(CY.STMT_TYPEDEF)) {
336 if (parent.hasAtLeastOneChildOf(CY.STMT_DEFAULT)) {
338 * The parent statement of where the derived type is used has already a default value, so
339 * the default value that hangs under the found typedef should be ignored.
343 * We simple clone the default value from the found typedef under where the type is
346 final YDefault defaultUnderFoundTypedef = typedef.getDefault();
347 final YDefault clonedDefault = new YDefault(parent, defaultUnderFoundTypedef.getDomElement());
348 clonedDefault.cloneFrom(defaultUnderFoundTypedef);
353 private static void checkAndRestrictPatterns(final ParserExecutionContext context, final YType typeUsingTypedef,
354 final YType clonedTypeUnderTypedef) {
356 if (typeUsingTypedef.getPatterns().isEmpty()) {
358 * There is no 'pattern' under the type that uses the derived type; nothing to do.
363 if (!DataTypeHelper.isStringType(clonedTypeUnderTypedef.getDataType())) {
364 context.addFinding(new Finding(typeUsingTypedef.getParentStatement(),
365 ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
366 "Base type of derived type is not 'string'; hence cannot use 'pattern' as substatement to restrict the type."));
371 * Patterns work slightly different compared to length or range. It is possible to specify multiple
372 * patterns as part of a type, and they must logically ALL (not ANY) be true. This means that the
373 * patterns from the derived type and the using-type are simply added together. So all we have to do
374 * is move any pattern from the using-type into the cloned derived type.
376 clonedTypeUnderTypedef.addChildren(typeUsingTypedef.getPatterns());
379 private static void checkAndRestrictLength(final ParserExecutionContext context, final YType typeUsingTypedef,
380 final YType clonedTypeUnderTypedef) {
382 if (typeUsingTypedef.getLength() == null) {
384 * There is no 'length' under the type that uses the derived type, hence there is no restriction
385 * currently, hence no need to check if any length is more restrictive or the same.
390 if (!DataTypeHelper.isStringType(clonedTypeUnderTypedef.getDataType()) && !DataTypeHelper.isBinaryType(
391 clonedTypeUnderTypedef.getDataType())) {
392 context.addFinding(new Finding(typeUsingTypedef.getParentStatement(),
393 ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
394 "Base type of derived type is not 'string' or 'binary'; hence cannot use 'length' as substatement to restrict the type."));
398 if (clonedTypeUnderTypedef.getLength() != null) {
400 * A 'length' has been defined as part of the derived type. We must check that the length defined
401 * on the using type is more restrictive, or at least equal.
403 * To do this, we make sure that all boundaries defined by the using type fit into any (single)
404 * boundary defined by the derived type. For example, this is here is fine:
406 * Derived type: [0-10],[30-35],[80-99]
407 * Using type: [0-5],[30-31],[33-35]
409 * Conversely, this here is not OK:
411 * Derived type: [0-10],[30-35],[80-99]
412 * Using type: [0-20],[30-90
414 final List<YLength.BoundaryPair> derivedTypeBoundaries = clonedTypeUnderTypedef.getLength().getBoundaries();
415 final List<YLength.BoundaryPair> usingTypeBoundaries = typeUsingTypedef.getLength().getBoundaries();
417 for (final YLength.BoundaryPair oneUsingTypeBoundary : usingTypeBoundaries) {
418 if (!fitsIntoLengthBoundary(oneUsingTypeBoundary, derivedTypeBoundaries)) {
419 context.addFinding(new Finding(typeUsingTypedef.getLength(),
420 ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
421 "When using a derived type and specifying 'length', the allowed length can only become more restrictive, not wider."));
427 * We replace any 'length' from under the cloned type, and replace it with the length under the
428 * type that uses the typedef. That's fine, as the cloned type will in a moment replace the
429 * type that uses the typedef.
431 clonedTypeUnderTypedef.replaceChildrenWith(typeUsingTypedef.getLength());
434 private static boolean fitsIntoLengthBoundary(final YLength.BoundaryPair boundaryToCheck,
435 final List<YLength.BoundaryPair> allowedBoundaries) {
437 final long lowerToCheck = boundaryToCheck.lower;
438 final long upperToCheck = boundaryToCheck.upper;
440 for (final YLength.BoundaryPair oneAllowedBoundary : allowedBoundaries) {
441 if (oneAllowedBoundary.lower <= lowerToCheck && oneAllowedBoundary.upper >= upperToCheck) {
449 private static void checkAndRestrictRange(final ParserExecutionContext context, final YType typeUsingTypedef,
450 final YType clonedTypeUnderTypedef) {
452 if (typeUsingTypedef.getRange() == null) {
454 * There is no 'range' under the type that uses the typedef, hence
455 * there is no restriction, hence nothing to check or do.
460 if (!DataTypeHelper.isYangNumericType(clonedTypeUnderTypedef.getDataType())) {
461 context.addFinding(new Finding(typeUsingTypedef.getParentStatement(),
462 ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
463 "Base type of derived type is not a numeric type; hence cannot use 'range' as substatement to restrict the type."));
467 if (clonedTypeUnderTypedef.getRange() != null) {
469 * Logic is very similar to how 'length' is handled, so see comments further above...
471 final List<YRange.BoundaryPair> derivedTypeBoundaries = clonedTypeUnderTypedef.getRange().getBoundaries();
472 final List<YRange.BoundaryPair> usingTypeBoundaries = typeUsingTypedef.getRange().getBoundaries(
473 clonedTypeUnderTypedef);
475 for (final YRange.BoundaryPair oneUsingTypeBoundary : usingTypeBoundaries) {
476 if (!fitsIntoRangeBoundary(oneUsingTypeBoundary, derivedTypeBoundaries)) {
477 context.addFinding(new Finding(typeUsingTypedef.getRange(),
478 ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
479 "When using a derived type and specifying 'range', the allowed range can only become more restrictive, not wider."));
485 * We replace any 'range' from under the cloned type, and replace it with the range under the
486 * type that uses the typedef. That's fine, as the cloned type will in a moment replace the
487 * type that uses the typedef.
489 clonedTypeUnderTypedef.replaceChildrenWith(typeUsingTypedef.getRange());
492 private static boolean fitsIntoRangeBoundary(final YRange.BoundaryPair boundaryToCheck,
493 final List<YRange.BoundaryPair> allowedBoundaries) {
495 final BigDecimal lowerToCheck = boundaryToCheck.lower;
496 final BigDecimal upperToCheck = boundaryToCheck.upper;
498 for (final YRange.BoundaryPair oneAllowedBoundary : allowedBoundaries) {
499 if (oneAllowedBoundary.lower.compareTo(lowerToCheck) <= 0 && oneAllowedBoundary.upper.compareTo(
500 upperToCheck) >= 0) {
509 * A 'bits' data type has been restricted. The restricted bits must be a subset of the original
510 * type, and the position values must match up. See chapter 9.7 in the RFC.
512 private static void checkAndRestrictBits(final ParserExecutionContext context, final YType typeUsingTypedef,
513 final YType clonedTypeUnderTypedef) {
515 if (typeUsingTypedef.getBits().isEmpty()) {
519 if (DataTypeHelper.getYangDataType(clonedTypeUnderTypedef.getDataType()) != YangDataType.BITS) {
520 context.addFinding(new Finding(typeUsingTypedef.getParentStatement(),
521 ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
522 "Base type of derived type is not 'bits'; hence cannot use 'bit' as substatement to restrict the type."));
527 * We first establish what the positions are (implicit or explicit) for all bits.
529 final Map<String, Long> positionOfBitsInDerivedType = DataTypeHelper.calculatePositionOfBits(context
530 .getFindingsManager(), clonedTypeUnderTypedef, null);
533 * If there is a bit-restriction, the RFC states that the bit under the type-using-typdef will
534 * retain all position values if the bit is listed. In other words, the position does not have
535 * to be explicitly supplied. This will cause significant issues in a moment when we perform the
536 * restriction. We there fore explicitly create position statements to overcome this.
538 typeUsingTypedef.getBits().forEach(yBit -> {
540 if (yBit.getPosition() == null) {
542 final Long positionOfBit = positionOfBitsInDerivedType.get(yBit.getBitName());
544 * We create a new DOM node and 'position' statement on-the-fly under the 'bit' element.
546 final YangDomElement artificialPositionDomElement = new YangDomElement(CY.POSITION, positionOfBit == null ?
548 positionOfBit.toString(), yBit.getDomElement(), yBit.getDomElement().getLineNumber());
549 new YPosition(yBit, artificialPositionDomElement);
554 * Now that the 'bit' statements under the type-using-typedef all have a 'position', we can compare
555 * these. According to RFC, the restriction must be a sub-set of the bits defined in the typedef
556 * and the position values must match up.
558 final Map<String, Long> positionOfBitsInTypeUsingTypedef = DataTypeHelper.calculatePositionOfBits(context
559 .getFindingsManager(), typeUsingTypedef, null);
561 for (final YBit bit : typeUsingTypedef.getBits()) {
562 final String bitName = bit.getBitName();
564 if (!positionOfBitsInDerivedType.containsKey(bitName)) {
565 context.addFinding(new Finding(bit, ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
566 "Bit '" + bitName + "' does not exist in derived type."));
569 final long bitPositionInTypeUsingTypedef = positionOfBitsInTypeUsingTypedef.get(bitName).longValue();
570 if (positionOfBitsInDerivedType.get(bitName).longValue() != bitPositionInTypeUsingTypedef) {
571 context.addFinding(new Finding(bit, ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
572 "'position' mismatch for bit '" + bitName + "'. In derived type: '" + positionOfBitsInDerivedType
573 .get(bitName) + "'; in type using the derived type: '" + bitPositionInTypeUsingTypedef + "'."));
579 * And eventually replace the bits statements (due to this replace, we had
580 * to create artificial 'position' statements further above).
582 clonedTypeUnderTypedef.replaceChildrenWith(typeUsingTypedef.getBits());
586 * Does the exact same as the previous method, only for enumerations.
588 private static void checkAndRestrictEnumeration(final ParserExecutionContext context, final YType typeUsingTypedef,
589 final YType clonedDerivedType) {
591 if (typeUsingTypedef.getEnums().isEmpty()) {
595 if (DataTypeHelper.getYangDataType(clonedDerivedType.getDataType()) != YangDataType.ENUMERATION) {
596 context.addFinding(new Finding(typeUsingTypedef.getParentStatement(),
597 ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
598 "Base type of derived type is not 'enumeration'; hence cannot use 'enum' as substatement to restrict the type."));
602 final Map<String, Long> valueOfEnumsInDerivedType = DataTypeHelper.calculateValuesOfEnums(context
603 .getFindingsManager(), clonedDerivedType, null);
606 * If there is a enum-restriction, the RFC states that the enum under the type-using-typdef will
607 * retain all 'value' values if the enum is listed. In other words, the value does not have
608 * to be explicitly supplied. This will cause significant issues in a moment when we perform the
609 * restriction. We there fore explicitly create value statements to overcome this.
611 typeUsingTypedef.getEnums().forEach(yEnum -> {
613 if (yEnum.getValue() == null) {
615 final Long valueOfEnum = valueOfEnumsInDerivedType.get(yEnum.getEnumName());
617 * We create a new DOM node and 'value' statement on-the-fly under the 'enum' element.
619 final YangDomElement artificialPositionDomElement = new YangDomElement(CY.VALUE, valueOfEnum == null ?
621 valueOfEnum.toString(), yEnum.getDomElement(), yEnum.getDomElement().getLineNumber());
622 new YValue(yEnum, artificialPositionDomElement);
626 final Map<String, Long> valueOfEnumsInTypeUsingTypedef = DataTypeHelper.calculateValuesOfEnums(context
627 .getFindingsManager(), typeUsingTypedef, null);
630 * According to RFC, the restriction must be a sub-set of the enums defined in the typedef - and the values must match up (or be omitted).
632 for (final YEnum oneEnum : typeUsingTypedef.getEnums()) {
634 final String enumName = oneEnum.getEnumName();
636 if (!valueOfEnumsInDerivedType.containsKey(enumName)) {
637 context.addFinding(new Finding(oneEnum, ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
638 "Enum '" + enumName + "' does not exist in derived type."));
641 final long enumValueInTypeUsingTypedef = valueOfEnumsInTypeUsingTypedef.get(enumName).longValue();
643 if (valueOfEnumsInDerivedType.get(enumName).longValue() != enumValueInTypeUsingTypedef) {
644 context.addFinding(new Finding(oneEnum, ParserFindingType.P117_ILLEGAL_DATA_TYPE_RESTRICTION,
645 "'value' mismatch for enum '" + enumName + "'. In derived type: '" + valueOfEnumsInDerivedType
646 .get(enumName) + "'; in type using the derived type: '" + enumValueInTypeUsingTypedef + "'."));
652 * And eventually replace the enum statements.
654 clonedDerivedType.replaceChildrenWith(typeUsingTypedef.getEnums());
658 * Returns all 'type' statements that should be considered. In effect, all 'type' statements that
659 * are not build-in YANG types and which have not been ruled out to be un-resolvable.
661 @SuppressWarnings("unchecked")
662 private static List<YType> findTypeStatementsToConsider(final Schema schema) {
664 final List<YType> yTypes = (List<YType>) Helper.findStatementsInSchema(CY.STMT_TYPE, schema);
666 return yTypes.stream().filter(yType -> !DataTypeHelper.isBuiltInType(yType.getDataType())).filter(
667 yType -> !isDerivedTypeNotResolvable(yType)).collect(Collectors.toList());
671 * Resolves any usages of union-in-union
673 private static boolean resolveUnionInUnion(final ParserExecutionContext context, final YType yType) {
676 * This type has to be a union, and it must have sub-types also of union, for this to fire.
678 if (!DataTypeHelper.isUnionType(yType.getDataType())) {
682 final boolean hasChildUnion = yType.getTypes().stream().filter(childType -> DataTypeHelper.isUnionType(childType
683 .getDataType())).findAny().isPresent();
684 if (!hasChildUnion) {
689 * The sub-unions need to be resolved. There is a complication here in respect of the order of
690 * union statements. Basically, the general order must be maintained. Consider this input:
701 * ... then the resolved union should look like this:
710 * This means we have to be careful with the order of the type
711 * statements when re-arranging these.
714 final List<YType> collectedTypeStatements = new ArrayList<>();
715 yType.getTypes().forEach(childType -> {
716 if (DataTypeHelper.isUnionType(childType.getDataType())) {
718 * A union, so we need to add the children type statements of the union type.
720 collectedTypeStatements.addAll(childType.getTypes());
723 * Not a union, so simple add to the collected types
725 collectedTypeStatements.add(childType);
730 * And now simply replace the original children with the collected types. That will effectively
731 * remove the (now resolved) child-union from the statement tree.
733 yType.replaceChildrenWith(collectedTypeStatements);
738 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
740 private static final String DERIVED_TYPE_NOT_RESOLVABLE = "DERIVED_TYPE_NOT_RESOLVABLE";
742 private static void setDerivedTypeNotResolvable(final YType yType) {
743 yType.setCustomAppData(DERIVED_TYPE_NOT_RESOLVABLE);
746 private static boolean isDerivedTypeNotResolvable(final YType yType) {
747 return yType.hasCustomAppData(DERIVED_TYPE_NOT_RESOLVABLE);
750 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
752 private static final String TYPEDEF_USAGE_COUNT = "TYPEDEF_USAGE_COUNT";
754 private static int getTypedefUsageCount(final YTypedef typedef) {
755 final Integer usageCount = typedef.getCustomAppData(TYPEDEF_USAGE_COUNT);
756 return usageCount == null ? 0 : usageCount.intValue();
759 private static void increaseUsageCount(final YTypedef yTypedef) {
760 final Integer usageCount = yTypedef.getCustomAppData(TYPEDEF_USAGE_COUNT);
761 if (usageCount == null) {
762 yTypedef.setCustomAppData(TYPEDEF_USAGE_COUNT, Integer.valueOf(1));
764 yTypedef.setCustomAppData(TYPEDEF_USAGE_COUNT, Integer.valueOf(usageCount.intValue() + 1));
768 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
770 private static final String TYPEDEF_REFERENCE = "TYPEDEF_REFERENCE";
771 private static final String TYPEDEF_STACK = "TYPEDEF_STACK";
773 private static void addTypedefReference(final YType clonedType, final YTypedef origTypedef) {
774 Helper.addAppDataListInfo(clonedType, TYPEDEF_REFERENCE, origTypedef);
777 * We keep track of the stack of typedefs. We only do this if the typedef is at the root
778 * of the YAM, otherwise it is "private" and can't be used outside of the module.
780 final AbstractStatement parentOfOrigTypedef = origTypedef.getParentStatement();
782 if (parentOfOrigTypedef.is(CY.STMT_MODULE) || parentOfOrigTypedef.is(CY.STMT_SUBMODULE)) {
783 final String moduleName = parentOfOrigTypedef.getYangModelRoot().getOwningYangModelRoot().getModule()
785 final String namespace = parentOfOrigTypedef.getYangModelRoot().getNamespace();
786 final NamespaceModuleIdentifier nsmi = new NamespaceModuleIdentifier(namespace, moduleName, origTypedef
789 Helper.addAppDataListInfo(clonedType, TYPEDEF_STACK, nsmi);
793 public static List<YTypedef> getTypedefReference(final YType yType) {
794 return Helper.getAppDataListInfo(yType, TYPEDEF_REFERENCE);
798 * Returns 0..n entries denoting the chain of typedefs that were resolved for the supplied type.
799 * Index [0] denotes the typedef that defines the original type.
801 * Note that if the typedef is declared on a submodule, the name of its owning module will be
802 * returned. To avoid confusion, it is recommended to use the namespace when looking for a
805 public static List<NamespaceModuleIdentifier> getTypedefStack(final YType yType) {
806 return Helper.getAppDataListInfo(yType, TYPEDEF_STACK);
809 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
811 private static final String ORIGINALLY_DEFINED_IN_YAM = "ORIGINALLY_DEFINED_IN_YAM";
813 private static void addOriginallyDefinedInYam(final YType yType, final String yamName) {
814 yType.setCustomAppData(ORIGINALLY_DEFINED_IN_YAM, yamName);
818 * Returns the name of the YAM where this type was originally defined. May return null in
819 * which case the type did not refer to a derived type.
821 public static String getOriginallyDefinedInYamName(final YType yType) {
822 return yType.getCustomAppData(ORIGINALLY_DEFINED_IN_YAM);