2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Ericsson
4 * Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
21 package org.oran.smo.yangtools.parser.model.statements.yang;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
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;
37 * Type-safe Yang core statement.
39 * @author Mark Hollmann
41 public class YIfFeature extends SimpleStatement {
43 public YIfFeature(final AbstractStatement parentStatement, final YangDomElement domNode) {
44 super(parentStatement, domNode);
48 public StatementArgumentType getArgumentType() {
49 return StatementArgumentType.NAME;
53 public StatementModuleAndName getStatementModuleAndName() {
54 return CY.STMT_IF_FEATURE;
57 protected void validate(final ParserExecutionContext context) {
58 if (!validateArgumentNotNullNotEmpty(context)) {
59 /* no point trying to perform more validation */
64 * The 'if-feature' is either simply the (possibly prefixed) name of a feature, or it has special syntax, which the RFC defines as:
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
70 * (This actually looks wrong - basically, the (possibly prefixed) names of features, with 'and' 'or' 'not' thrown in, possibly in parenthesis)
72 final List<Token> tokens = parseIfFeatureValueIntoTokens(getValue());
73 validateTokens(context, tokens, true);
76 public boolean areTokensValid(final ParserExecutionContext context, final List<Token> tokens) {
77 return validateTokens(context, tokens, false);
80 private boolean validateTokens(final ParserExecutionContext context, final List<Token> tokens,
81 final boolean issueFindings) {
83 if (tokens == null || tokens.isEmpty()) {
85 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
86 "Missing feature name."));
92 * Check the tokens - usually only a single token will exist (old Yang 1.0 syntax), but we never know...
94 int parenthesisOpenCount = 0;
95 int featureNameCount = 0;
97 Type previousTokenType = null;
99 for (final Token token : tokens) {
101 if (token.type == Type.LEFT_PARENTHESIS) {
102 parenthesisOpenCount++;
103 } else if (token.type == Type.RIGHT_PARENTHESIS) {
104 parenthesisOpenCount--;
105 if (parenthesisOpenCount < 0) {
107 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
108 "Unexpected closing parenthesis."));
112 } else if (token.type == Type.FEATURE_NAME) {
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);
130 previousTokenType = token.type;
134 * Last token must be either a feature-name or a right parenthesis.
136 final Token lastToken = tokens.get(tokens.size() - 1);
137 if (lastToken.type != Type.FEATURE_NAME && lastToken.type != Type.RIGHT_PARENTHESIS) {
139 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
140 "Unexpected end of if-feature expression."));
146 * There must be at least one feature, and opening/closing parenthesis must be balanced.
148 if (featureNameCount == 0) {
150 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
151 "Missing feature name."));
155 if (parenthesisOpenCount != 0) {
157 context.addFinding(new Finding(this, ParserFindingType.P103_ILLEGAL_IF_FEATURE_SYNTAX.toString(),
158 "Parenthesis not balanced."));
166 private static final List<Type> VALID_TOKENS_AT_START = Arrays.asList(Type.FEATURE_NAME, Type.LEFT_PARENTHESIS,
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);
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 + "'."));
186 public List<Token> getTokens() {
187 return parseIfFeatureValueIntoTokens(this.getValue());
190 public static List<Token> parseIfFeatureValueIntoTokens(final String input) {
193 return Collections.<Token> emptyList();
196 boolean inString = false;
197 StringBuilder sb = null;
198 final List<Token> nodes = new ArrayList<>();
200 for (final char c : input.toCharArray()) {
203 if (c == ' ' || c == '\t' || c == '\n' || c == '(' || c == ')') {
206 nodes.add(tokenFromString(sb.toString()));
208 nodes.add(new Token(Type.LEFT_PARENTHESIS, "("));
209 } else if (c == ')') {
210 nodes.add(new Token(Type.RIGHT_PARENTHESIS, ")"));
213 // still in string, so add to it.
217 // currently not in string
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, ")"));
226 // some other character, so start of some string
228 sb = new StringBuilder(100);
235 nodes.add(tokenFromString(sb.toString()));
250 public static class Token {
251 public final Type type;
252 public final String name;
254 public Token(final Type type, final String name) {
260 public int hashCode() {
261 return name.hashCode();
265 public boolean equals(final Object obj) {
266 return obj instanceof Token && (((Token) obj).type == this.type) && (((Token) obj).name.equals(this.name));
270 private static Token tokenFromString(final String string) {
273 return new Token(Type.AND, "and");
275 return new Token(Type.OR, "or");
277 return new Token(Type.NOT, "not");
280 return new Token(Type.FEATURE_NAME, string);