e233659d87bb7c2f39fe58648ee93b0cf4e78e08
[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.schema;
22
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26 import java.util.stream.Collectors;
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.ConformanceType;
32 import org.oran.smo.yangtools.parser.model.YangModel;
33 import org.oran.smo.yangtools.parser.model.statements.yang.YBelongsTo;
34 import org.oran.smo.yangtools.parser.model.statements.yang.YImport;
35 import org.oran.smo.yangtools.parser.model.statements.yang.YInclude;
36 import org.oran.smo.yangtools.parser.model.statements.yang.YPrefix;
37 import org.oran.smo.yangtools.parser.model.statements.yang.YRevision;
38 import org.oran.smo.yangtools.parser.model.util.StringHelper;
39
40 /**
41  * Checks for prefixes, imports, revisions, includes and belongs-to in order to catch
42  * basic issues before further processing is done.
43  *
44  * @author Mark Hollmann
45  */
46 public abstract class SchemaCheckModuleRelationships {
47
48     public static void performChecks(final ParserExecutionContext context, final ModuleRegistry moduleRegistry) {
49
50         checkPrefixesUnique(context, moduleRegistry);
51         checkImportsUnique(context, moduleRegistry);
52         checkImportsSatisfied(context, moduleRegistry);
53         checkIncludesSatisfied(context, moduleRegistry);
54         checkBelongsTosSatisfied(context, moduleRegistry);
55         checkForDuplicateModules(context, moduleRegistry);
56         checkForDuplicateRevisions(context, moduleRegistry);
57         checkImplementsAndImports(context, moduleRegistry);
58     }
59
60     // ==============================================================================================================================================
61
62     /**
63      * For each YAM check that all prefixes declared within the YAM are unique. If this is not the case
64      * then there will be ambiguity later on when prefixes are resolved.
65      */
66     private static void checkPrefixesUnique(final ParserExecutionContext context, final ModuleRegistry moduleRegistry) {
67
68         for (final YangModel yangModel : moduleRegistry.getAllYangModels()) {
69
70             final Set<String> prefixesInYam = new HashSet<>();
71
72             final YPrefix yPrefix = yangModel.getYangModelRoot().getPrefix();
73             if (yPrefix != null) {
74                 prefixesInYam.add(yPrefix.getPrefix());
75             }
76
77             for (final YImport yImport : yangModel.getYangModelRoot().getImports()) {
78                 if (yImport.getPrefix() == null) {
79                     /* No need for a finding, a invalid syntax finding would have previously issued. */
80                     continue;
81                 }
82
83                 final String importPrefix = yImport.getPrefix().getValue();
84                 if (prefixesInYam.contains(importPrefix)) {
85                     context.addFinding(new Finding(yImport.getPrefix(), ParserFindingType.P031_PREFIX_NOT_UNIQUE,
86                             "Prefix '" + importPrefix + "' not unique in document."));
87                 }
88                 prefixesInYam.add(importPrefix);
89             }
90         }
91     }
92
93     // ==============================================================================================================================================
94
95     /**
96      * For each YAM check that each import is unique. Note that YANG 1.1 allows the same module to be
97      * imported more than once, but then the revision has to be explicitly specified.
98      */
99     private static void checkImportsUnique(final ParserExecutionContext context, final ModuleRegistry moduleRegistry) {
100
101         for (final YangModel yangModel : moduleRegistry.getAllYangModels()) {
102
103             final Set<String> importedModulesUsingRevision = new HashSet<>();
104             final Set<String> importedModulesWithoutRevision = new HashSet<>();
105             final Set<String> importedModulesNameAndRevision = new HashSet<>();
106
107             for (final YImport yImport : yangModel.getYangModelRoot().getImports()) {
108
109                 final String importedModuleName = yImport.getImportedModuleName();
110                 if (importedModuleName == null) {
111                     /* No need for a finding, a invalid syntax finding would have previously issued. */
112                     continue;
113                 }
114
115                 final String importedModuleRevision = yImport.getRevisionDate() == null ?
116                         null :
117                         yImport.getRevisionDate().getValue();
118
119                 if (importedModuleRevision == null) {
120                     checkOneImportWithoutRevisionIsUnique(context, yImport, importedModuleName,
121                             importedModulesUsingRevision, importedModulesWithoutRevision);
122                 } else {
123                     checkOneImportWithRevisionIsUnique(context, yImport, importedModuleName, importedModuleRevision,
124                             importedModulesUsingRevision, importedModulesWithoutRevision, importedModulesNameAndRevision);
125                 }
126             }
127         }
128     }
129
130     private static void checkOneImportWithoutRevisionIsUnique(final ParserExecutionContext context, final YImport yImport,
131             final String importedModuleName, final Set<String> importedModulesUsingRevision,
132             final Set<String> importedModulesWithoutRevision) {
133
134         /*
135          * Imported without revision, i.e. use "any" version of the module. Check that there is not already
136          * an import with an explicit revision.
137          */
138         if (importedModulesUsingRevision.contains(importedModuleName)) {
139             context.addFinding(new Finding(yImport, ParserFindingType.P036_MODULE_IMPORTED_MULTIPLE_TIMES,
140                     "Module '" + importedModuleName + "' imported more than once - once without revision, once with explicit revision."));
141         }
142         if (importedModulesWithoutRevision.contains(importedModuleName)) {
143             context.addFinding(new Finding(yImport, ParserFindingType.P036_MODULE_IMPORTED_MULTIPLE_TIMES,
144                     "Module '" + importedModuleName + "' imported multiple times without revision."));
145         }
146
147         importedModulesWithoutRevision.add(importedModuleName);
148     }
149
150     private static void checkOneImportWithRevisionIsUnique(final ParserExecutionContext context, final YImport yImport,
151             final String importedModuleName, final String importedModuleRevision,
152             final Set<String> importedModulesUsingRevision, final Set<String> importedModulesWithoutRevision,
153             final Set<String> importedModulesNameAndRevision) {
154         /*
155          * Import with exact revision. Check not already imported without revision, and not doubly-imported.
156          */
157         if (importedModulesWithoutRevision.contains(importedModuleName)) {
158             context.addFinding(new Finding(yImport, ParserFindingType.P036_MODULE_IMPORTED_MULTIPLE_TIMES,
159                     "Module '" + importedModuleName + "' imported more than once - once without revision, once with explicit revision."));
160         }
161
162         final String key = importedModuleName + "/" + importedModuleRevision;
163
164         if (importedModulesNameAndRevision.contains(key)) {
165             context.addFinding(new Finding(yImport, ParserFindingType.P036_MODULE_IMPORTED_MULTIPLE_TIMES,
166                     "Module '" + key + "' imported more than once."));
167         }
168
169         importedModulesUsingRevision.add(importedModuleName);
170         importedModulesNameAndRevision.add(key);
171     }
172
173     // ==============================================================================================================================================
174
175     /**
176      * For each YAM check that all "import" statements can be satisfied,
177      * i.e. the imported YAM is also available.
178      */
179     private static void checkImportsSatisfied(final ParserExecutionContext context, final ModuleRegistry moduleRegistry) {
180
181         for (final YangModel yangModel : moduleRegistry.getAllYangModels()) {
182             for (final YImport yImport : yangModel.getYangModelRoot().getImports()) {
183
184                 final String importedModuleName = yImport.getImportedModuleName();
185                 final String importedModuleRevision = yImport.getRevisionDate() == null ?
186                         null :
187                         yImport.getRevisionDate().getValue();
188
189                 if (importedModuleName == null) {
190                     //No need for a finding, a invalid syntax finding would have previously issued.
191                     continue;
192                 }
193
194                 if (yangModel.getModuleIdentity().getModuleName().equals(importedModuleName)) {
195                     context.addFinding(new Finding(yImport, ParserFindingType.P034_UNRESOLVABLE_IMPORT,
196                             "Module '" + importedModuleName + "' imports itself."));
197                 } else {
198                     if (importedModuleRevision == null) {
199                         checkOneImportWithoutRevision(context, moduleRegistry, yImport, importedModuleName);
200                     } else {
201                         checkOneImportWithExplicitRevision(context, moduleRegistry, yImport, importedModuleName,
202                                 importedModuleRevision);
203                     }
204                 }
205             }
206         }
207     }
208
209     private static void checkOneImportWithoutRevision(final ParserExecutionContext context,
210             final ModuleRegistry moduleRegistry, final YImport yImport, final String importedModuleName) {
211         /*
212          * The import does not have a revisions. So try to find exactly one module of the given name in the input.
213          */
214         final List<YangModel> foundModulesAnyRevision = moduleRegistry.byModuleName(importedModuleName);
215         switch (foundModulesAnyRevision.size()) {
216             case 0:
217                 context.addFinding(new Finding(yImport, ParserFindingType.P034_UNRESOLVABLE_IMPORT,
218                         "Module '" + importedModuleName + "' not found in input."));
219                 break;
220             case 1:
221                 /*
222                  * OK, the import uses "any" revision and exactly one revision of the module was found in the registry.
223                  */
224                 break;
225             default:
226                 /*
227                  * This is ambiguous, so issue a finding.
228                  */
229                 context.addFinding(new Finding(yImport, ParserFindingType.P035_AMBIGUOUS_IMPORT,
230                         "Module '" + importedModuleName + "' has multiple revisions in the input, but desired exact revision not specified in the 'import' statement."));
231         }
232     }
233
234     private static void checkOneImportWithExplicitRevision(final ParserExecutionContext context,
235             final ModuleRegistry moduleRegistry, final YImport yImport, final String importedModuleName,
236             final String importedModuleRevision) {
237         /*
238          * The import has a revisions. So try to find exactly one module of the given name and given revision in the input.
239          */
240         final YangModel foundModulesExactRevision = moduleRegistry.exactMatch(importedModuleName, importedModuleRevision);
241         if (foundModulesExactRevision == null) {
242             /*
243              * That revision does not exist. Maybe a different revision is in the input?
244              */
245             final List<YangModel> foundModulesAnyRevision = moduleRegistry.byModuleName(importedModuleName);
246             if (foundModulesAnyRevision.isEmpty()) {
247                 context.addFinding(new Finding(yImport, ParserFindingType.P034_UNRESOLVABLE_IMPORT,
248                         "Module '" + importedModuleName + "' with revision '" + importedModuleRevision + "' not found in input."));
249             } else {
250                 context.addFinding(new Finding(yImport, ParserFindingType.P034_UNRESOLVABLE_IMPORT,
251                         "Module '" + importedModuleName + "' with revision '" + importedModuleRevision + "' not found in input, but a module with that name and revision '" + foundModulesAnyRevision
252                                 .get(0).getModuleIdentity().getRevision() + "' has been found."));
253             }
254         }
255     }
256
257     // ==============================================================================================================================================
258
259     /**
260      * For each submodule check that the "belongs-to" statement can be satisfied.
261      */
262     private static void checkBelongsTosSatisfied(final ParserExecutionContext context,
263             final ModuleRegistry moduleRegistry) {
264
265         final List<YangModel> subModules = moduleRegistry.getAllYangModels().stream().filter(yangModel -> yangModel
266                 .getYangModelRoot().isSubmodule()).collect(Collectors.toList());
267
268         for (final YangModel subModuleYangModel : subModules) {
269
270             final YBelongsTo belongsTo = subModuleYangModel.getYangModelRoot().getSubmodule().getBelongsTo();
271             if (belongsTo == null) {
272                 /* No need for a finding, would have been previously issued as missing mandatory child statement. */
273                 continue;
274             }
275
276             final String belongsToModuleName = belongsTo.getBelongsToModuleName();
277             if (belongsToModuleName == null) {
278                 /* No need for a finding, a invalid syntax finding would have previously issued. */
279                 continue;
280             }
281
282             final List<YangModel> modules = moduleRegistry.byModuleName(belongsToModuleName);
283             if (modules.isEmpty()) {
284                 context.addFinding(new Finding(belongsTo, ParserFindingType.P039_UNRESOLVABLE_BELONGS_TO,
285                         "Owning module '" + belongsToModuleName + "' not found in input."));
286             } else {
287                 checkBelongsToAgainstModule(context, belongsTo, subModuleYangModel, modules.get(0));
288             }
289         }
290     }
291
292     private static void checkBelongsToAgainstModule(final ParserExecutionContext context, final YBelongsTo belongsTo,
293             final YangModel subModuleModelInput, final YangModel owningModuleModelInput) {
294
295         /*
296          * Check the found module is actually a module, and that it actually references the submodule!
297          */
298         if (!owningModuleModelInput.getYangModelRoot().isModule()) {
299             context.addFinding(new Finding(belongsTo, ParserFindingType.P046_NOT_A_MODULE, "'" + belongsTo
300                     .getBelongsToModuleName() + "' is not a module."));
301         } else {
302             final String subModuleName = subModuleModelInput.getYangModelRoot().getModuleOrSubModuleName();
303             boolean owningModuleIncludesTheSubmodule = false;
304             for (final YInclude include : owningModuleModelInput.getYangModelRoot().getModule().getIncludes()) {
305                 if (subModuleName.equals(include.getIncludedSubModuleName())) {
306                     owningModuleIncludesTheSubmodule = true;
307                     break;
308                 }
309             }
310             if (!owningModuleIncludesTheSubmodule) {
311                 context.addFinding(new Finding(subModuleModelInput, ParserFindingType.P048_ORPHAN_SUBMODULE,
312                         "Owning module '" + owningModuleModelInput.getYangModelRoot()
313                                 .getModuleOrSubModuleName() + "' does not 'include' this submodule."));
314             }
315         }
316     }
317
318     // ==============================================================================================================================================
319
320     /**
321      * Check up the usage of the Conformance Types.
322      */
323     private static void checkImplementsAndImports(final ParserExecutionContext context,
324             final ModuleRegistry moduleRegistry) {
325
326         int nrYangFilesThatImplement = 0;
327
328         for (final YangModel yangModel : moduleRegistry.getAllYangModels()) {
329
330             if (yangModel.getConformanceType() == ConformanceType.IMPLEMENT) {
331                 nrYangFilesThatImplement++;
332             }
333
334             if (yangModel.getYangModelRoot().isSubmodule()) {
335                 final YBelongsTo belongsTo = yangModel.getYangModelRoot().getSubmodule().getBelongsTo();
336                 if (belongsTo == null) {
337                     /* No need for a finding, a missing mandatory child statement would have issued already. */
338                     return;
339                 }
340
341                 final String belongsToModuleName = belongsTo.getBelongsToModuleName();
342                 if (belongsToModuleName == null) {
343                     /* No need for a finding, an invalid syntax finding would have issued already. */
344                     return;
345                 }
346
347                 final List<YangModel> modules = moduleRegistry.byModuleName(belongsToModuleName);
348                 if (!modules.isEmpty() && yangModel.getConformanceType() != modules.get(0).getConformanceType()) {
349                     context.addFinding(new Finding(belongsTo, ParserFindingType.P006_IMPLEMENT_IMPORT_MISMATCH,
350                             "Submodule is " + yangModel.getConformanceType().toString() + " but owning module is " + modules
351                                     .get(0).getConformanceType()));
352                 }
353             }
354         }
355
356         if (nrYangFilesThatImplement == 0) {
357             context.addFinding(new Finding(ParserFindingType.P005_NO_IMPLEMENTS,
358                     "Need at least a single module that IMPLEMENTs."));
359         }
360     }
361
362     // ==============================================================================================================================================
363
364     /**
365      * A module with the exact same name should not be more than once in the input as IMPLEMENT.
366      */
367     private static void checkForDuplicateModules(final ParserExecutionContext context,
368             final ModuleRegistry moduleRegistry) {
369
370         final Set<String> implementingModules = new HashSet<>();
371         final Set<String> importedModules = new HashSet<>();
372
373         for (final YangModel yangModel : moduleRegistry.getAllYangModels()) {
374
375             final String moduleName = yangModel.getModuleIdentity().getModuleName();
376
377             if (yangModel.getConformanceType() == ConformanceType.IMPLEMENT) {
378
379                 if (implementingModules.contains(moduleName)) {
380                     context.addFinding(new Finding(yangModel, ParserFindingType.P043_SAME_MODULE_IMPLEMENTS_MORE_THAN_ONCE,
381                             "Module '" + moduleName + "' multiple times in the input with conformance type IMPLEMENT."));
382                 }
383                 if (importedModules.contains(moduleName)) {
384                     context.addFinding(new Finding(yangModel, ParserFindingType.P044_SAME_MODULE_IMPLEMENTS_AND_IMPORTS,
385                             "Module '" + moduleName + "' multiple times in the input, with both conformance types IMPLEMENT and IMPORT."));
386                 }
387                 implementingModules.add(moduleName);
388
389             } else { // ConformanceType.IMPORT
390
391                 if (implementingModules.contains(moduleName)) {
392                     context.addFinding(new Finding(yangModel, ParserFindingType.P044_SAME_MODULE_IMPLEMENTS_AND_IMPORTS,
393                             "Module '" + moduleName + "' multiple times in the input, with both conformance types IMPLEMENT and IMPORT."));
394                 }
395                 importedModules.add(moduleName);
396             }
397         }
398     }
399
400     // ==============================================================================================================================================
401
402     /**
403      * A module should not have duplicate revisions.
404      */
405     private static void checkForDuplicateRevisions(final ParserExecutionContext context,
406             final ModuleRegistry moduleRegistry) {
407
408         for (final YangModel yangModel : moduleRegistry.getAllYangModels()) {
409
410             final Set<String> uniqueRevisions = new HashSet<>();
411
412             for (final YRevision yRevision : yangModel.getYangModelRoot().getRevisions()) {
413                 final String revision = yRevision.getValue();
414                 if (uniqueRevisions.contains(revision)) {
415                     if (revision.equals(yangModel.getModuleIdentity().getRevision())) {
416                         context.addFinding(new Finding(yangModel, ParserFindingType.P050_DUPLICATE_LATEST_REVISION,
417                                 "Latest revision '" + revision + "' exists more than once in the (sub-)module."));
418                     } else {
419                         context.addFinding(new Finding(yangModel, ParserFindingType.P049_DUPLICATE_REVISION,
420                                 "Prior revision '" + revision + "' exists more than once in the (sub-)module."));
421                     }
422                 }
423                 uniqueRevisions.add(revision);
424             }
425         }
426     }
427
428     // ==============================================================================================================================================
429
430     /**
431      * For each module check that all "include" statements can be satisfied, i.e. are in the input. Also that the submodule
432      * belongs to the module.
433      */
434     private static void checkIncludesSatisfied(final ParserExecutionContext context, final ModuleRegistry moduleRegistry) {
435
436         final List<YangModel> modulesOnly = moduleRegistry.getAllYangModels().stream().filter(yangModel -> yangModel
437                 .getYangModelRoot().isModule()).collect(Collectors.toList());
438
439         for (final YangModel yangModel : modulesOnly) {
440
441             for (final YInclude yInclude : yangModel.getYangModelRoot().getModule().getIncludes()) {
442
443                 final String includedSubmoduleName = yInclude.getIncludedSubModuleName();
444                 if (includedSubmoduleName == null) {
445                     // No need for a finding, an invalid syntax finding would have issued already.
446                     continue;
447                 }
448
449                 final String includedSubmoduleRevision = yInclude.getRevisionDate() == null ?
450                         null :
451                         yInclude.getRevisionDate().getValue();
452
453                 if (includedSubmoduleRevision == null) {
454                     checkOneIncludeWithoutRevision(context, moduleRegistry, yangModel, yInclude, includedSubmoduleName);
455                 } else {
456                     checkOneIncludeWithExplicitRevision(context, moduleRegistry, yangModel, yInclude, includedSubmoduleName,
457                             includedSubmoduleRevision);
458                 }
459             }
460         }
461     }
462
463     private static void checkOneIncludeWithoutRevision(final ParserExecutionContext context,
464             final ModuleRegistry moduleRegistry, final YangModel yangModel, final YInclude yInclude,
465             final String includedSubmoduleName) {
466         /*
467          * The include was done without revision, so try to find "any" revision.
468          */
469         final List<YangModel> subModulesFoundAnyRevision = moduleRegistry.byModuleName(includedSubmoduleName);
470
471         switch (subModulesFoundAnyRevision.size()) {
472             case 0:
473                 context.addFinding(new Finding(yInclude, ParserFindingType.P037_UNRESOLVABLE_INCLUDE,
474                         "Submodule " + includedSubmoduleName + " not found in input."));
475                 break;
476             case 1:
477                 checkIncludedSubmoduleAgainstModule(context, yInclude, subModulesFoundAnyRevision.get(0), yangModel);
478                 break;
479             default:
480                 context.addFinding(new Finding(yInclude, ParserFindingType.P038_AMBIGUOUS_INCLUDE,
481                         "Multiple revisions of submodule " + includedSubmoduleName + " found in input."));
482                 break;
483         }
484     }
485
486     private static void checkOneIncludeWithExplicitRevision(final ParserExecutionContext context,
487             final ModuleRegistry moduleRegistry, final YangModel yangModel, final YInclude yInclude,
488             final String includedSubmoduleName, final String includedSubmoduleRevision) {
489         /*
490          * The include was done with revision, so try to find the exact revision.
491          */
492         final String moduleNameAndRevision = StringHelper.getModuleNameAndRevision(includedSubmoduleName,
493                 includedSubmoduleRevision);
494
495         final YangModel subModuleFoundWithExactRevision = moduleRegistry.exactMatch(includedSubmoduleName,
496                 includedSubmoduleRevision);
497         if (subModuleFoundWithExactRevision == null) {
498             final List<YangModel> subModulesFoundAnyRevision = moduleRegistry.byModuleName(includedSubmoduleName);
499             if (subModulesFoundAnyRevision.isEmpty()) {
500                 context.addFinding(new Finding(yInclude, ParserFindingType.P037_UNRESOLVABLE_INCLUDE,
501                         "Submodule " + moduleNameAndRevision + " not found in input."));
502             } else {
503                 /*
504                  * Different revision found.
505                  */
506                 context.addFinding(new Finding(yInclude, ParserFindingType.P037_UNRESOLVABLE_INCLUDE,
507                         "Submodule " + moduleNameAndRevision + " not found in the input, but a submodule with that name and with revision '" + subModulesFoundAnyRevision
508                                 .get(0).getModuleIdentity().getRevision() + "' is in the input."));
509             }
510         } else {
511             checkIncludedSubmoduleAgainstModule(context, yInclude, subModuleFoundWithExactRevision, yangModel);
512         }
513     }
514
515     private static void checkIncludedSubmoduleAgainstModule(final ParserExecutionContext context,
516             final YInclude includeStatement, final YangModel subModuleModelInput, final YangModel moduleModelInput) {
517
518         final String yangVersionOfSubmodule = subModuleModelInput.getYangModelRoot().getYangVersion();
519         final String yangVersionOfModule = moduleModelInput.getYangModelRoot().getYangVersion();
520
521         /*
522          * Make sure the submodule is actually a submodule.
523          */
524         if (!subModuleModelInput.getYangModelRoot().isSubmodule()) {
525             context.addFinding(new Finding(includeStatement, ParserFindingType.P045_NOT_A_SUBMODULE,
526                     "'" + subModuleModelInput.getYangModelRoot()
527                             .getModuleOrSubModuleName() + "' is not a submodule and can therefore not be included."));
528             return;
529         }
530         /*
531          * YANG versions must match up between module and all its submodules.
532          */
533         if (!yangVersionOfModule.equals(yangVersionOfSubmodule)) {
534             context.addFinding(new Finding(includeStatement,
535                     ParserFindingType.P041_DIFFERENT_YANG_VERSIONS_BETWEEN_MODULE_AND_SUBMODULES,
536                     "The yang versions differ between module and submodule(s)."));
537         }
538         /*
539          * Make sure the submodule actually belongs to the module.
540          */
541         final YBelongsTo belongsTo = subModuleModelInput.getYangModelRoot().getSubmodule().getBelongsTo();
542         if (belongsTo == null) {
543             // No need for a finding, a missing mandatory child statement finding would have issued already.
544             return;
545         }
546
547         final String belongsToModuleName = belongsTo.getBelongsToModuleName();
548         if (belongsToModuleName == null) {
549             // No need for a finding, an invalid syntax finding would have issued already.
550             return;
551         }
552
553         if (!belongsToModuleName.equals(moduleModelInput.getYangModelRoot().getModuleOrSubModuleName())) {
554             context.addFinding(new Finding(includeStatement, ParserFindingType.P047_SUBMODULE_OWNERSHIP_MISMATCH,
555                     "The referenced submodule belongs to '" + belongsToModuleName + "', not this module here."));
556         }
557     }
558 }