df9343958f976cd8f467175dd820b961a3184d78
[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.statements.yang;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
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.statements.AbstractStatement;
32 import org.oran.smo.yangtools.parser.model.statements.SimpleStatement;
33 import org.oran.smo.yangtools.parser.model.statements.StatementModuleAndName;
34 import org.oran.smo.yangtools.parser.model.yangdom.YangDomElement;
35
36 /**
37  * Type-safe Yang core statement.
38  *
39  * @author Mark Hollmann
40  */
41 public class YIfFeature extends SimpleStatement {
42
43     public YIfFeature(final AbstractStatement parentStatement, final YangDomElement domNode) {
44         super(parentStatement, domNode);
45     }
46
47     @Override
48     public StatementArgumentType getArgumentType() {
49         return StatementArgumentType.NAME;
50     }
51
52     @Override
53     public StatementModuleAndName getStatementModuleAndName() {
54         return CY.STMT_IF_FEATURE;
55     }
56
57     protected void validate(final ParserExecutionContext context) {
58         if (!validateArgumentNotNullNotEmpty(context)) {
59             /* no point trying to perform more validation */
60             return;
61         }
62
63         /*
64          * The 'if-feature' is either simply the (possibly prefixed) name of a feature, or it has special syntax, which the RFC defines as:
65          *
66          * if-feature-expr = if-feature-term [sep or-keyword sep if-feature-expr]
67          * if-feature-term = if-feature-factor [sep and-keyword sep if-feature-term]
68          * if-feature-factor = not-keyword sep if-feature-factor / "(" optsep if-feature-expr optsep ")" / identifier-ref-arg
69          *
70          * (This actually looks wrong - basically, the (possibly prefixed) names of features, with 'and' 'or' 'not' thrown in, possibly in parenthesis)
71          */
72         final List<Token> tokens = parseIfFeatureValueIntoTokens(getValue());
73         validateTokens(context, tokens, true);
74     }
75
76     public boolean areTokensValid(final ParserExecutionContext context, final List<Token> tokens) {
77         return validateTokens(context, tokens, false);
78     }
79
80     private boolean validateTokens(final ParserExecutionContext context, final List<Token> tokens,
81             final boolean issueFindings) {
82
83         if (tokens == null || tokens.isEmpty()) {
84             if (issueFindings) {
85                 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
86                         "Missing feature name."));
87             }
88             return false;
89         }
90
91         /*
92          * Check the tokens - usually only a single token will exist (old Yang 1.0 syntax), but we never know...
93          */
94         int parenthesisOpenCount = 0;
95         int featureNameCount = 0;
96
97         Type previousTokenType = null;
98
99         for (final Token token : tokens) {
100
101             if (token.type == Type.LEFT_PARENTHESIS) {
102                 parenthesisOpenCount++;
103             } else if (token.type == Type.RIGHT_PARENTHESIS) {
104                 parenthesisOpenCount--;
105                 if (parenthesisOpenCount < 0) {
106                     if (issueFindings) {
107                         context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
108                                 "Unexpected closing parenthesis."));
109                     }
110                     return false;
111                 }
112             } else if (token.type == Type.FEATURE_NAME) {
113                 featureNameCount++;
114             }
115
116             if (previousTokenType == null) {
117                 checkToken(context, token, VALID_TOKENS_AT_START, issueFindings);
118             } else if (previousTokenType == Type.FEATURE_NAME) {
119                 checkToken(context, token, VALID_TOKENS_AFTER_FEATURE_NAME, issueFindings);
120             } else if (previousTokenType == Type.AND || previousTokenType == Type.OR) {
121                 checkToken(context, token, VALID_TOKENS_AFTER_AND_OR, issueFindings);
122             } else if (previousTokenType == Type.NOT) {
123                 checkToken(context, token, VALID_TOKENS_AFTER_NOT, issueFindings);
124             } else if (previousTokenType == Type.LEFT_PARENTHESIS) {
125                 checkToken(context, token, VALID_TOKENS_AFTER_LEFT_PARENTHESIS, issueFindings);
126             } else { // (previousTokenType == Type.RIGHT_PARENTHESIS) {
127                 checkToken(context, token, VALID_TOKENS_AFTER_RIGHT_PARENTHESIS, issueFindings);
128             }
129
130             previousTokenType = token.type;
131         }
132
133         /*
134          * Last token must be either a feature-name or a right parenthesis.
135          */
136         final Token lastToken = tokens.get(tokens.size() - 1);
137         if (lastToken.type != Type.FEATURE_NAME && lastToken.type != Type.RIGHT_PARENTHESIS) {
138             if (issueFindings) {
139                 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
140                         "Unexpected end of if-feature expression."));
141             }
142             return false;
143         }
144
145         /*
146          * There must be at least one feature, and opening/closing parenthesis must be balanced.
147          */
148         if (featureNameCount == 0) {
149             if (issueFindings) {
150                 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
151                         "Missing feature name."));
152             }
153             return false;
154         }
155         if (parenthesisOpenCount != 0) {
156             if (issueFindings) {
157                 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
158                         "Parenthesis not balanced."));
159             }
160             return false;
161         }
162
163         return true;
164     }
165
166     private static final List<Type> VALID_TOKENS_AT_START = Arrays.asList(Type.FEATURE_NAME, Type.LEFT_PARENTHESIS,
167             Type.NOT);
168     private static final List<Type> VALID_TOKENS_AFTER_FEATURE_NAME = Arrays.asList(Type.AND, Type.OR,
169             Type.RIGHT_PARENTHESIS);
170     private static final List<Type> VALID_TOKENS_AFTER_AND_OR = Arrays.asList(Type.FEATURE_NAME, Type.NOT,
171             Type.LEFT_PARENTHESIS);
172     private static final List<Type> VALID_TOKENS_AFTER_NOT = Arrays.asList(Type.FEATURE_NAME, Type.LEFT_PARENTHESIS);
173     private static final List<Type> VALID_TOKENS_AFTER_LEFT_PARENTHESIS = Arrays.asList(Type.FEATURE_NAME, Type.NOT,
174             Type.LEFT_PARENTHESIS);
175     private static final List<Type> VALID_TOKENS_AFTER_RIGHT_PARENTHESIS = Arrays.asList(Type.AND, Type.OR,
176             Type.RIGHT_PARENTHESIS);
177
178     private void checkToken(final ParserExecutionContext context, final Token token, final List<Type> mustBeOfType,
179             final boolean issueFindings) {
180         if (!mustBeOfType.contains(token.type) && issueFindings) {
181             context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
182                     "Illegal syntax '" + token.name + "'."));
183         }
184     }
185
186     public List<Token> getTokens() {
187         return parseIfFeatureValueIntoTokens(this.getValue());
188     }
189
190     public static List<Token> parseIfFeatureValueIntoTokens(final String input) {
191
192         if (input == null) {
193             return Collections.<Token> emptyList();
194         }
195
196         boolean inString = false;
197         StringBuilder sb = null;
198         final List<Token> nodes = new ArrayList<>();
199
200         for (final char c : input.toCharArray()) {
201
202             if (inString) {
203                 if (c == ' ' || c == '\t' || c == '\n' || c == '(' || c == ')') {
204                     // String is over
205                     inString = false;
206                     nodes.add(tokenFromString(sb.toString()));
207                     if (c == '(') {
208                         nodes.add(new Token(Type.LEFT_PARENTHESIS, "("));
209                     } else if (c == ')') {
210                         nodes.add(new Token(Type.RIGHT_PARENTHESIS, ")"));
211                     }
212                 } else {
213                     // still in string, so add to it.
214                     sb.append(c);
215                 }
216             } else {
217                 // currently not in string
218
219                 if (c == ' ' || c == '\t' || c == '\n') {
220                     // Have encountered whitespace, ignore.
221                 } else if (c == '(') {
222                     nodes.add(new Token(Type.LEFT_PARENTHESIS, "("));
223                 } else if (c == ')') {
224                     nodes.add(new Token(Type.RIGHT_PARENTHESIS, ")"));
225                 } else {
226                     // some other character, so start of some string
227                     inString = true;
228                     sb = new StringBuilder(100);
229                     sb.append(c);
230                 }
231             }
232         }
233
234         if (inString) {
235             nodes.add(tokenFromString(sb.toString()));
236         }
237
238         return nodes;
239     }
240
241     public enum Type {
242         FEATURE_NAME,
243         AND,
244         OR,
245         NOT,
246         LEFT_PARENTHESIS,
247         RIGHT_PARENTHESIS
248     }
249
250     public static class Token {
251         public final Type type;
252         public final String name;
253
254         public Token(final Type type, final String name) {
255             this.type = type;
256             this.name = name;
257         }
258
259         @Override
260         public int hashCode() {
261             return name.hashCode();
262         }
263
264         @Override
265         public boolean equals(final Object obj) {
266             return obj instanceof Token && (((Token) obj).type == this.type) && (((Token) obj).name.equals(this.name));
267         }
268     }
269
270     private static Token tokenFromString(final String string) {
271         switch (string) {
272             case "and":
273                 return new Token(Type.AND, "and");
274             case "or":
275                 return new Token(Type.OR, "or");
276             case "not":
277                 return new Token(Type.NOT, "not");
278             default:
279         }
280         return new Token(Type.FEATURE_NAME, string);
281     }
282 }