da1234da13a3cfcdcb131bbacb51ede8af9882b6
[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.util;
22
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
26
27 /**
28  * Utility class to help with Yang grammar.
29  *
30  * @author Mark Hollmann
31  */
32 public abstract class GrammarHelper {
33
34     /**
35      * Returns whether the supplied string contains a white-space according to the RFC.
36      * White space characters are SPACE (0x20) and HORIZONTAL TAB (0x09).
37      */
38     public static boolean containsYangWhitespace(final String value) {
39         if (value == null || value.isEmpty()) {
40             return false;
41         }
42
43         for (int i = 0; i < value.length(); ++i) {
44             if (value.charAt(i) == 32 || value.charAt(i) == 9) {
45                 return true;
46             }
47         }
48         return false;
49     }
50
51     /**
52      * Returns whether the supplied string is a valid YANG identifier according to the RFC.
53      * <p>
54      * A valid identifier must:
55      * <p>
56      * Start with '_' or 'A-Z' or 'a-z'
57      * Followed by '_' or '-' or '.' or 'A-Z' or 'a-z' or '0-9'
58      * <p>
59      * Note especially that other unicode characters are not allowed, and that an identifier MUST NOT start with a digit.
60      */
61     public static boolean isYangIdentifier(final String stringToCheck) {
62
63         if (stringToCheck == null || stringToCheck.isEmpty()) {
64             return false;
65         }
66
67         final char firstChar = stringToCheck.charAt(0);
68         if (!firstCharacterOkForYangIdentifier(firstChar)) {
69             return false;
70         }
71
72         for (int i = 1; i < stringToCheck.length(); ++i) {
73             final char c = stringToCheck.charAt(i);
74             if (!otherCharacterOkForYangIdentifier(c)) {
75                 return false;
76             }
77         }
78
79         return true;
80     }
81
82     private static boolean firstCharacterOkForYangIdentifier(final char c) {
83         return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c == 95;                        // A-Z a-z _
84     }
85
86     private static boolean otherCharacterOkForYangIdentifier(final char c) {
87         return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || (c >= 48 && c <= 57) || c == '_' || c == '-' || c == '.';
88     }
89
90     /**
91      * Returns whether the supplied string is an identifier reference according to the RFC.
92      *
93      * An identifier is either prefix:identifier or just an identifier.
94      *
95      * The rules for prefix are the same as for identifier.
96      */
97     public static boolean isYangIdentifierReference(final String stringToCheck) {
98
99         if (stringToCheck == null || stringToCheck.isEmpty()) {
100             return false;
101         }
102
103         if (stringToCheck.charAt(0) == ':' || stringToCheck.charAt(stringToCheck.length() - 1) == ':') {
104             return false;
105         }
106
107         if (stringToCheck.contains(":")) {
108             final String[] split = stringToCheck.split(":");
109             if (split.length != 2) {
110                 return false;
111             }
112             return isYangIdentifier(split[0]) && isYangIdentifier(split[1]);
113         } else {
114             return isYangIdentifier(stringToCheck);
115         }
116     }
117
118     /**
119      * Given a string containing white-space separated strings, extracts those. Typically used where an
120      * argument allows for a "space"-separated list of data node names, such as the 'key' or 'unique'
121      * statements.
122      * <p>
123      * The RFC is contradictory: In chapter 7.8.2 it says that leaf names are separated by "space character",
124      * but the official grammar definition in chapter 14 allows any whitespace (and line break). We err
125      * on the side of caution and align with the official grammar.
126      */
127     public static List<String> parseToStringList(final String value) {
128
129         if (value == null || value.trim().isEmpty()) {
130             return Collections.emptyList();
131         }
132
133         final char[] asChars = value.toCharArray();
134
135         if (!containsWhitespace(asChars)) {
136             return Collections.singletonList(value);
137         }
138
139         final List<String> result = new ArrayList<>();
140
141         StringBuilder sb = null;
142
143         for (final char c : asChars) {
144             if (sb != null) {
145                 if (isWhitespace(c)) {                                          // end of a single string reached
146                     result.add(sb.toString());
147                     sb = null;
148                 } else {
149                     sb.append(c);                                                       // add to the single string
150                 }
151             } else {
152                 if (!isWhitespace(c)) {                                         // new string starts
153                     sb = new StringBuilder(20);
154                     sb.append(c);
155                 }
156             }
157         }
158
159         if (sb != null) {                                       // value did not end with whitespace (it rarely does), so add remainder
160             result.add(sb.toString());
161         }
162
163         return result;
164     }
165
166     private static boolean containsWhitespace(final char[] chars) {
167         for (final char c : chars) {
168             if (isWhitespace(c)) {
169                 return true;
170             }
171         }
172         return false;
173     }
174
175     private static boolean isWhitespace(final char c) {
176         return (c == ' ' || c == '\t' || c == '\n');
177     }
178
179     /**
180      * Returns whether the supplied string can be legally used in YANG unquoted.
181      * <p>
182      * In general, any string can be expressed quoted, but for readability this is
183      * omitted where possible.
184      * <p>
185      * From the RFC:
186      *
187      * An unquoted string is any sequence of characters that does not contain any space,
188      * tab, carriage return, or line feed characters, a single or double quote character,
189      * a semicolon (";"), braces ("{" or "}"), or comment sequences (double-slash,
190      * slash-star, star-slash)
191      *
192      * Note that any keyword can legally appear as an unquoted string. Within an unquoted
193      * string, every character is preserved. Note that this means that the backslash
194      * character does not have any special meaning in an unquoted string.
195      */
196     public static boolean isUnquotableString(final String valueToCheck) {
197
198         if (valueToCheck.isEmpty()) {
199             return false;       // correct - empty string must always be represented as "" in YANG.
200         }
201
202         if (valueToCheck.contains("//") || valueToCheck.contains("/*") || valueToCheck.contains("*/")) {
203             return false;
204         }
205
206         for (final char c : valueToCheck.toCharArray()) {
207             if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\'' || c == '"' || c == ';' || c == '{' || c == '}' || c == '+') {
208                 return false;
209             }
210         }
211
212         return true;
213     }
214 }