b56dff70a83d8343ce6e4ba900e0a31f341cc351
[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.findings;
22
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.regex.Pattern;
27
28 import org.oran.smo.yangtools.parser.model.ModuleIdentity;
29 import org.oran.smo.yangtools.parser.model.yangdom.YangDomElement;
30
31 /**
32  * A predicate that takes one or more modules, one or more finding types, and one or more
33  * schema node paths.
34  *
35  * @author Mark Hollmann
36  */
37 public class ModuleAndFindingTypeAndSchemaNodePathFilterPredicate implements FindingFilterPredicate {
38
39     /**
40      * Parses the supplied string into an instance of ModuleAndFindingTypeAndSchemaNodePathFilterPredicate.
41      * <p>
42      * Module names are separated from finding types, and from the node path, by the ";" character.
43      * <p>
44      * Module names are separated by the "," character. Finding types are likewise separated by
45      * the "," character. Only one path may be supplied at most. A value must be supplied for module
46      * names, finding types and path.
47      * <p>
48      * The only allowable wildcard character is a "*", denoting any "character sequence".
49      * <p>
50      * Example 1: The following will suppress all P114 findings in any IETF and IANA modules:
51      * "ietf-*,iana-*;P114_TYPEDEF_NOT_USED;*"
52      * <p>
53      * Example 2: The following will suppress all findings within the "modules-state" container
54      * within the IETF yang library: "ietf-yang-library;*;/container=modules-state"
55      * <p>
56      * Example 3: The following will suppress all P115 findings, in all modules: "*;P115_*;*"
57      */
58     public static ModuleAndFindingTypeAndSchemaNodePathFilterPredicate fromString(final String s) {
59
60         final String[] split = s.split(";");
61         if (split.length != 3) {
62             throw new RuntimeException("Invalid string format for ModuleAndFindingTypeAndSchemaNodePathFilterPredicate.");
63         }
64
65         final List<Pattern> moduleNames = new ArrayList<>();
66         if (!split[0].equals("*")) {
67             final String[] moduleNamesSplit = split[0].contains(",") ? split[0].split(",") : new String[] { split[0] };
68             for (final String stringPattern : moduleNamesSplit) {
69                 moduleNames.add(Pattern.compile(stringPattern.trim().replace(".", "[.]").replace("*", ".*")));
70             }
71         }
72
73         final List<Pattern> findingTypes = new ArrayList<>();
74         if (!split[1].equals("*")) {
75             final String[] findingTypesSplit = split[1].contains(",") ? split[1].split(",") : new String[] { split[1] };
76             for (final String stringPattern : findingTypesSplit) {
77                 findingTypes.add(Pattern.compile(stringPattern.trim().replace(".", "[.]").replace("*", ".*")));
78             }
79         }
80
81         final String schemaNodePath = split[2].equals("*") ? null : split[2];
82
83         return new ModuleAndFindingTypeAndSchemaNodePathFilterPredicate(moduleNames, findingTypes, schemaNodePath);
84     }
85
86     private final List<Pattern> moduleNames;
87     private final List<Pattern> findingTypes;
88     private final String schemaNodePath;
89
90     /**
91      * A finding will be filtered if the statement is part of any of the supplied modules,
92      * and if the finding is of any of the supplied types, and if the path to the statement
93      * is a sub-path of the supplied paths.
94      * <p>
95      * More formally, the name of the module in which the offending statement sits must be
96      * matchable against any of the module name patterns, and the type of the finding must
97      * be matchable against any of the finding type patterns, and the schema node path of
98      * the offending statement must be the same, or a sub-path, of the supplied path.
99      * <p>
100      * Supplying an empty list for module names or finding types will match-all for that
101      * parameter. Supplying null as schema node path will match-all paths.
102      */
103     public ModuleAndFindingTypeAndSchemaNodePathFilterPredicate(final List<Pattern> moduleNames,
104             final List<Pattern> findingTypes, final String schemaNodePath) {
105         this.moduleNames = Objects.requireNonNull(moduleNames);
106         this.findingTypes = Objects.requireNonNull(findingTypes);
107         this.schemaNodePath = schemaNodePath;
108     }
109
110     @Override
111     public boolean test(final Finding f) {
112         return matchOnModule(f) && matchOnFindingType(f) && matchOnSchemaNode(f);
113     }
114
115     private boolean matchOnModule(final Finding finding) {
116
117         if (moduleNames.isEmpty()) {
118             return true;
119         }
120
121         /*
122          * If the finding does not relate to a YAM then obviously we cannot match.
123          */
124         if (finding.getYangModel() == null) {
125             return false;
126         }
127
128         /*
129          * It can happen that we don't have a module identity yet, because a finding was found
130          * before we actually got a chance to extract the module name. In this case we use the
131          * name of the input. This is not foolproof, of course.
132          */
133         final ModuleIdentity moduleIdentity = finding.getYangModel().getModuleIdentity();
134         final String moduleOrSubModuleName = moduleIdentity == null ?
135                 finding.getYangModel().getYangInput().getName() :
136                 moduleIdentity.getModuleName();
137
138         for (final Pattern pattern : moduleNames) {
139             if (pattern.matcher(moduleOrSubModuleName).matches()) {
140                 return true;
141             }
142         }
143
144         return false;
145     }
146
147     private boolean matchOnFindingType(final Finding finding) {
148
149         if (findingTypes.isEmpty()) {
150             return true;
151         }
152
153         final String findingType = finding.getFindingType();
154
155         for (final Pattern pattern : findingTypes) {
156             if (pattern.matcher(findingType).matches()) {
157                 return true;
158             }
159         }
160
161         return false;
162     }
163
164     private boolean matchOnSchemaNode(final Finding finding) {
165
166         if (schemaNodePath == null) {
167             return true;
168         }
169
170         if (finding.getStatement() == null) {
171             return false;
172         }
173
174         final YangDomElement domElement = finding.getStatement().getDomElement();
175         if (domElement == null) {
176             return false;
177         }
178
179         return domElement.getSimplifiedPath().startsWith(schemaNodePath);
180     }
181 }