ac3550c530891a6b1b2298b576c2a9c4c5351761
[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.resolvers;
22
23 import java.math.BigDecimal;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.stream.Collectors;
28
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;
48
49 /**
50  * A class that can resolve usages of derived types to their base type.
51  * <p/>
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).
54  *
55  * @author Mark Hollmann
56  */
57 public abstract class TypeResolver {
58
59     /**
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.
62      */
63     public static void resolveUsagesOfDerivedTypes(final ParserExecutionContext context, final Schema schema) {
64
65         /*
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.
69          *
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.
72          */
73
74         int iterationCount = 10;
75         boolean atLeastOneResolved = true;
76
77         while (iterationCount > 0 && atLeastOneResolved) {
78
79             atLeastOneResolved = false;
80             iterationCount--;
81
82             /*
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.
86              */
87             final List<YType> types = findTypeStatementsToConsider(schema);
88             for (final YType type : types) {
89                 try {
90                     atLeastOneResolved |= resolveTypeToBaseType(context, schema, type);
91                 } catch (final Exception ex) {
92                     /* Swallow and move to next. Best effort here, keep trying other types. */
93                 }
94             }
95
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."));
101                     break;
102                 }
103             }
104         }
105
106         /*
107          * Done resolving. If some type statements are left with derived types they could not
108          * be resolved because of circular dependencies.
109          */
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.")));
114
115         /*
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).
118          */
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);
123             if (used == 0) {
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."));
129             }
130         }
131
132         /*
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.
135          */
136         atLeastOneResolved = true;
137
138         while (atLeastOneResolved) {
139             atLeastOneResolved = false;
140
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);
145             }
146         }
147     }
148
149     /**
150      * Resolves usage of a derived type to its underlying type.
151      *
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
156      * rules apply here.
157      * 4.) Replace the original "type" with the clone that has been updated.
158      *
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!
161      *
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.
164      */
165     private static boolean resolveTypeToBaseType(final ParserExecutionContext context, final Schema schema,
166             final YType typeUsingDerivedType) {
167
168         final AbstractStatement parentOfTypeStatement = typeUsingDerivedType.getParentStatement();
169
170         final String derivedDataType = typeUsingDerivedType.getDataType();
171         if (derivedDataType.isEmpty()) {
172             /*
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.
175              */
176             setDerivedTypeNotResolvable(typeUsingDerivedType);
177             return false;
178         }
179
180         /*
181          * We look for the typedef. This could sit further up the tree, at the top of the module, in an included submodule.
182          */
183         final YTypedef typedef = Helper.findStatement(context, schema, typeUsingDerivedType, CY.STMT_TYPEDEF,
184                 derivedDataType);
185         if (typedef == null) {
186             setDerivedTypeNotResolvable(typeUsingDerivedType);
187             context.addFinding(new Finding(typeUsingDerivedType, ParserFindingType.P113_UNRESOLVABLE_DERIVED_TYPE,
188                     "Cannot resolve typedef '" + typeUsingDerivedType.getDataType() + "'."));
189             return false;
190         }
191
192         /*
193          * Mark the typedef has been used.
194          */
195         increaseUsageCount(typedef);
196
197         /*
198          * Check for nested derived type usage, and if found resolve those first.
199          */
200         if (derivedTypeUsedWithinFoundTypedef(context, typeUsingDerivedType, typedef)) {
201             return false;
202         }
203
204         /*
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.
208          */
209         addOriginallyDefinedInYam(typedef.getType(), typedef.getYangModelRoot().getModuleOrSubModuleName());
210
211         /*
212          * The typedef may be used multiple times so we can't simply re-parent the YType statement tree - we must clone it.
213          */
214         final YType clonedTypeUnderTypedef = new YType(typedef, typedef.getType().getDomElement());
215         clonedTypeUnderTypedef.cloneFrom(typedef.getType());
216
217         /*
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.
221          */
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);
227
228         /*
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.
232          */
233         if (typedef.getDefault() != null) {
234             assignDefaultFromTypedefToWhereItIsUsed(typedef, typeUsingDerivedType);
235         }
236
237         /*
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.
243          */
244         parentOfTypeStatement.replaceChildInPlace(typeUsingDerivedType, clonedTypeUnderTypedef);
245
246         /*
247          * Keep a record of the reference for later analysis.
248          */
249         addTypedefReference(clonedTypeUnderTypedef, typedef);
250
251         /*
252          * If there is a 'range' statement under the type now we need to re-validate. Same with
253          * 'length' statement.
254          */
255         if (clonedTypeUnderTypedef.getRange() != null) {
256             clonedTypeUnderTypedef.getRange().validateBoundaries(context);
257         }
258         if (clonedTypeUnderTypedef.getLength() != null) {
259             clonedTypeUnderTypedef.getLength().validateBoundaries(context);
260         }
261
262         return true;
263     }
264
265     private static boolean derivedTypeUsedWithinFoundTypedef(final ParserExecutionContext context,
266             final YType typeUsingDerivedType, final YTypedef typedef) {
267
268         final List<YType> typesUnderFoundTypedef = getTypesFromUnderTypedef(typedef);
269
270         boolean hasTypeThatIsNotResolveable = false;
271         boolean hasTypeThatIsDerived = false;
272
273         for (final YType typeInTypedef : typesUnderFoundTypedef) {
274
275             if (isDerivedTypeNotResolvable(typeInTypedef)) {
276
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() + "."));
283             }
284
285             if (DataTypeHelper.isDerivedType(typeInTypedef.getDataType())) {
286                 hasTypeThatIsDerived = true;
287             }
288         }
289
290         if (hasTypeThatIsNotResolveable) {
291             /*
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.
294              */
295             setDerivedTypeNotResolvable(typeUsingDerivedType);
296         }
297
298         /*
299          * The typedef itself references another typedef, so resolve the other
300          * typedef first before attempting this one here.
301          */
302         return hasTypeThatIsDerived;
303     }
304
305     private static List<YType> getTypesFromUnderTypedef(final YTypedef typedef) {
306         final List<YType> result = new ArrayList<>();
307
308         result.add(typedef.getType());
309
310         for (final YType unionMember : typedef.getType().getTypes()) {
311             result.add(unionMember);
312         }
313
314         return result;
315     }
316
317     /**
318      * A typedef can have a default value. If the derived type does not have a default, such a default must be copied over.
319      */
320     private static void assignDefaultFromTypedefToWhereItIsUsed(final YTypedef typedef, final YType originalTypeStatement) {
321
322         AbstractStatement parent = originalTypeStatement.getParentStatement();
323
324         if (parent.is(CY.STMT_TYPE)) {
325             /*
326              * type-under-type, i.e. union.
327              */
328             parent = parent.getParentStatement();
329         }
330
331         /*
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.
334          */
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)) {
337                 /*
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.
340                  */
341             } else {
342                 /*
343                  * We simple clone the default value from the found typedef under where the type is
344                  * being used.
345                  */
346                 final YDefault defaultUnderFoundTypedef = typedef.getDefault();
347                 final YDefault clonedDefault = new YDefault(parent, defaultUnderFoundTypedef.getDomElement());
348                 clonedDefault.cloneFrom(defaultUnderFoundTypedef);
349             }
350         }
351     }
352
353     private static void checkAndRestrictPatterns(final ParserExecutionContext context, final YType typeUsingTypedef,
354             final YType clonedTypeUnderTypedef) {
355
356         if (typeUsingTypedef.getPatterns().isEmpty()) {
357             /*
358              * There is no 'pattern' under the type that uses the derived type; nothing to do.
359              */
360             return;
361         }
362
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."));
367             return;
368         }
369
370         /*
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.
375          */
376         clonedTypeUnderTypedef.addChildren(typeUsingTypedef.getPatterns());
377     }
378
379     private static void checkAndRestrictLength(final ParserExecutionContext context, final YType typeUsingTypedef,
380             final YType clonedTypeUnderTypedef) {
381
382         if (typeUsingTypedef.getLength() == null) {
383             /*
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.
386              */
387             return;
388         }
389
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."));
395             return;
396         }
397
398         if (clonedTypeUnderTypedef.getLength() != null) {
399             /*
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.
402              *
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:
405              *
406              * Derived type: [0-10],[30-35],[80-99]
407              * Using type:   [0-5],[30-31],[33-35]
408              *
409              * Conversely, this here is not OK:
410              *
411              * Derived type: [0-10],[30-35],[80-99]
412              * Using type:   [0-20],[30-90
413              */
414             final List<YLength.BoundaryPair> derivedTypeBoundaries = clonedTypeUnderTypedef.getLength().getBoundaries();
415             final List<YLength.BoundaryPair> usingTypeBoundaries = typeUsingTypedef.getLength().getBoundaries();
416
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."));
422                 }
423             }
424         }
425
426         /*
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.
430          */
431         clonedTypeUnderTypedef.replaceChildrenWith(typeUsingTypedef.getLength());
432     }
433
434     private static boolean fitsIntoLengthBoundary(final YLength.BoundaryPair boundaryToCheck,
435             final List<YLength.BoundaryPair> allowedBoundaries) {
436
437         final long lowerToCheck = boundaryToCheck.lower;
438         final long upperToCheck = boundaryToCheck.upper;
439
440         for (final YLength.BoundaryPair oneAllowedBoundary : allowedBoundaries) {
441             if (oneAllowedBoundary.lower <= lowerToCheck && oneAllowedBoundary.upper >= upperToCheck) {
442                 return true;
443             }
444         }
445
446         return false;
447     }
448
449     private static void checkAndRestrictRange(final ParserExecutionContext context, final YType typeUsingTypedef,
450             final YType clonedTypeUnderTypedef) {
451
452         if (typeUsingTypedef.getRange() == null) {
453             /*
454              * There is no 'range' under the type that uses the typedef, hence
455              * there is no restriction, hence nothing to check or do.
456              */
457             return;
458         }
459
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."));
464             return;
465         }
466
467         if (clonedTypeUnderTypedef.getRange() != null) {
468             /*
469              * Logic is very similar to how 'length' is handled, so see comments further above...
470              */
471             final List<YRange.BoundaryPair> derivedTypeBoundaries = clonedTypeUnderTypedef.getRange().getBoundaries();
472             final List<YRange.BoundaryPair> usingTypeBoundaries = typeUsingTypedef.getRange().getBoundaries(
473                     clonedTypeUnderTypedef);
474
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."));
480                 }
481             }
482         }
483
484         /*
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.
488          */
489         clonedTypeUnderTypedef.replaceChildrenWith(typeUsingTypedef.getRange());
490     }
491
492     private static boolean fitsIntoRangeBoundary(final YRange.BoundaryPair boundaryToCheck,
493             final List<YRange.BoundaryPair> allowedBoundaries) {
494
495         final BigDecimal lowerToCheck = boundaryToCheck.lower;
496         final BigDecimal upperToCheck = boundaryToCheck.upper;
497
498         for (final YRange.BoundaryPair oneAllowedBoundary : allowedBoundaries) {
499             if (oneAllowedBoundary.lower.compareTo(lowerToCheck) <= 0 && oneAllowedBoundary.upper.compareTo(
500                     upperToCheck) >= 0) {
501                 return true;
502             }
503         }
504
505         return false;
506     }
507
508     /**
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.
511      */
512     private static void checkAndRestrictBits(final ParserExecutionContext context, final YType typeUsingTypedef,
513             final YType clonedTypeUnderTypedef) {
514
515         if (typeUsingTypedef.getBits().isEmpty()) {
516             return;
517         }
518
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."));
523             return;
524         }
525
526         /*
527          * We first establish what the positions are (implicit or explicit) for all bits.
528          */
529         final Map<String, Long> positionOfBitsInDerivedType = DataTypeHelper.calculatePositionOfBits(context
530                 .getFindingsManager(), clonedTypeUnderTypedef, null);
531
532         /*
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.
537          */
538         typeUsingTypedef.getBits().forEach(yBit -> {
539
540             if (yBit.getPosition() == null) {
541
542                 final Long positionOfBit = positionOfBitsInDerivedType.get(yBit.getBitName());
543                 /*
544                  * We create a new DOM node and 'position' statement on-the-fly under the 'bit' element.
545                  */
546                 final YangDomElement artificialPositionDomElement = new YangDomElement(CY.POSITION, positionOfBit == null ?
547                         "0" :
548                         positionOfBit.toString(), yBit.getDomElement(), yBit.getDomElement().getLineNumber());
549                 new YPosition(yBit, artificialPositionDomElement);
550             }
551         });
552
553         /*
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.
557          */
558         final Map<String, Long> positionOfBitsInTypeUsingTypedef = DataTypeHelper.calculatePositionOfBits(context
559                 .getFindingsManager(), typeUsingTypedef, null);
560
561         for (final YBit bit : typeUsingTypedef.getBits()) {
562             final String bitName = bit.getBitName();
563
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."));
567             } else {
568
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 + "'."));
574                 }
575             }
576         }
577
578         /*
579          * And eventually replace the bits statements (due to this replace, we had
580          * to create artificial 'position' statements further above).
581          */
582         clonedTypeUnderTypedef.replaceChildrenWith(typeUsingTypedef.getBits());
583     }
584
585     /**
586      * Does the exact same as the previous method, only for enumerations.
587      */
588     private static void checkAndRestrictEnumeration(final ParserExecutionContext context, final YType typeUsingTypedef,
589             final YType clonedDerivedType) {
590
591         if (typeUsingTypedef.getEnums().isEmpty()) {
592             return;
593         }
594
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."));
599             return;
600         }
601
602         final Map<String, Long> valueOfEnumsInDerivedType = DataTypeHelper.calculateValuesOfEnums(context
603                 .getFindingsManager(), clonedDerivedType, null);
604
605         /*
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.
610          */
611         typeUsingTypedef.getEnums().forEach(yEnum -> {
612
613             if (yEnum.getValue() == null) {
614
615                 final Long valueOfEnum = valueOfEnumsInDerivedType.get(yEnum.getEnumName());
616                 /*
617                  * We create a new DOM node and 'value' statement on-the-fly under the 'enum' element.
618                  */
619                 final YangDomElement artificialPositionDomElement = new YangDomElement(CY.VALUE, valueOfEnum == null ?
620                         "0" :
621                         valueOfEnum.toString(), yEnum.getDomElement(), yEnum.getDomElement().getLineNumber());
622                 new YValue(yEnum, artificialPositionDomElement);
623             }
624         });
625
626         final Map<String, Long> valueOfEnumsInTypeUsingTypedef = DataTypeHelper.calculateValuesOfEnums(context
627                 .getFindingsManager(), typeUsingTypedef, null);
628
629         /*
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).
631          */
632         for (final YEnum oneEnum : typeUsingTypedef.getEnums()) {
633
634             final String enumName = oneEnum.getEnumName();
635
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."));
639             } else {
640
641                 final long enumValueInTypeUsingTypedef = valueOfEnumsInTypeUsingTypedef.get(enumName).longValue();
642
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 + "'."));
647                 }
648             }
649         }
650
651         /*
652          * And eventually replace the enum statements.
653          */
654         clonedDerivedType.replaceChildrenWith(typeUsingTypedef.getEnums());
655     }
656
657     /**
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.
660      */
661     @SuppressWarnings("unchecked")
662     private static List<YType> findTypeStatementsToConsider(final Schema schema) {
663
664         final List<YType> yTypes = (List<YType>) Helper.findStatementsInSchema(CY.STMT_TYPE, schema);
665
666         return yTypes.stream().filter(yType -> !DataTypeHelper.isBuiltInType(yType.getDataType())).filter(
667                 yType -> !isDerivedTypeNotResolvable(yType)).collect(Collectors.toList());
668     }
669
670     /**
671      * Resolves any usages of union-in-union
672      */
673     private static boolean resolveUnionInUnion(final ParserExecutionContext context, final YType yType) {
674
675         /*
676          * This type has to be a union, and it must have sub-types also of union, for this to fire.
677          */
678         if (!DataTypeHelper.isUnionType(yType.getDataType())) {
679             return false;
680         }
681
682         final boolean hasChildUnion = yType.getTypes().stream().filter(childType -> DataTypeHelper.isUnionType(childType
683                 .getDataType())).findAny().isPresent();
684         if (!hasChildUnion) {
685             return false;
686         }
687
688         /*
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:
691          *
692          * type union {
693          *      type int16;
694          *      type union {
695          *           type string;
696          *           type enumeration;
697          *      }
698          *      type uint64;
699          * }
700          *
701          * ... then the resolved union should look like this:
702          *
703          * type union {
704          *      type int16;
705          *      type string;
706          *      type enumeration;
707          *      type uint64;
708          * }
709          *
710          * This means we have to be careful with the order of the type
711          * statements when re-arranging these.
712          */
713
714         final List<YType> collectedTypeStatements = new ArrayList<>();
715         yType.getTypes().forEach(childType -> {
716             if (DataTypeHelper.isUnionType(childType.getDataType())) {
717                 /*
718                  * A union, so we need to add the children type statements of the union type.
719                  */
720                 collectedTypeStatements.addAll(childType.getTypes());
721             } else {
722                 /*
723                  * Not a union, so simple add to the collected types
724                  */
725                 collectedTypeStatements.add(childType);
726             }
727         });
728
729         /*
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.
732          */
733         yType.replaceChildrenWith(collectedTypeStatements);
734
735         return true;
736     }
737
738     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
739
740     private static final String DERIVED_TYPE_NOT_RESOLVABLE = "DERIVED_TYPE_NOT_RESOLVABLE";
741
742     private static void setDerivedTypeNotResolvable(final YType yType) {
743         yType.setCustomAppData(DERIVED_TYPE_NOT_RESOLVABLE);
744     }
745
746     private static boolean isDerivedTypeNotResolvable(final YType yType) {
747         return yType.hasCustomAppData(DERIVED_TYPE_NOT_RESOLVABLE);
748     }
749
750     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
751
752     private static final String TYPEDEF_USAGE_COUNT = "TYPEDEF_USAGE_COUNT";
753
754     private static int getTypedefUsageCount(final YTypedef typedef) {
755         final Integer usageCount = typedef.getCustomAppData(TYPEDEF_USAGE_COUNT);
756         return usageCount == null ? 0 : usageCount.intValue();
757     }
758
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));
763         } else {
764             yTypedef.setCustomAppData(TYPEDEF_USAGE_COUNT, Integer.valueOf(usageCount.intValue() + 1));
765         }
766     }
767
768     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
769
770     private static final String TYPEDEF_REFERENCE = "TYPEDEF_REFERENCE";
771     private static final String TYPEDEF_STACK = "TYPEDEF_STACK";
772
773     private static void addTypedefReference(final YType clonedType, final YTypedef origTypedef) {
774         Helper.addAppDataListInfo(clonedType, TYPEDEF_REFERENCE, origTypedef);
775
776         /*
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.
779          */
780         final AbstractStatement parentOfOrigTypedef = origTypedef.getParentStatement();
781
782         if (parentOfOrigTypedef.is(CY.STMT_MODULE) || parentOfOrigTypedef.is(CY.STMT_SUBMODULE)) {
783             final String moduleName = parentOfOrigTypedef.getYangModelRoot().getOwningYangModelRoot().getModule()
784                     .getModuleName();
785             final String namespace = parentOfOrigTypedef.getYangModelRoot().getNamespace();
786             final NamespaceModuleIdentifier nsmi = new NamespaceModuleIdentifier(namespace, moduleName, origTypedef
787                     .getTypedefName());
788
789             Helper.addAppDataListInfo(clonedType, TYPEDEF_STACK, nsmi);
790         }
791     }
792
793     public static List<YTypedef> getTypedefReference(final YType yType) {
794         return Helper.getAppDataListInfo(yType, TYPEDEF_REFERENCE);
795     }
796
797     /**
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.
800      *
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
803      * particular module.
804      */
805     public static List<NamespaceModuleIdentifier> getTypedefStack(final YType yType) {
806         return Helper.getAppDataListInfo(yType, TYPEDEF_STACK);
807     }
808
809     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
810
811     private static final String ORIGINALLY_DEFINED_IN_YAM = "ORIGINALLY_DEFINED_IN_YAM";
812
813     private static void addOriginallyDefinedInYam(final YType yType, final String yamName) {
814         yType.setCustomAppData(ORIGINALLY_DEFINED_IN_YAM, yamName);
815     }
816
817     /**
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.
820      */
821     public static String getOriginallyDefinedInYamName(final YType yType) {
822         return yType.getCustomAppData(ORIGINALLY_DEFINED_IN_YAM);
823     }
824 }