2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * Modifications Copyright (C) 2020-2021 Nordix Foundation.
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=========================================================
22 package org.onap.policy.apex.auth.clieditor;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.List;
30 * This class chops a command line up into commands, parameters and arguments.
32 * @author Liam Fallon (liam.fallon@ericsson.com)
34 public class CommandLineParser {
37 * This method breaks a line of input up into commands, parameters, and arguments. Commands are standalone words at
38 * the beginning of the line, of which there may be multiple Parameters are single words followed by an '='
39 * character Arguments are single words or a block of quoted text following an '=' character.
41 * <p>Format: command [command....] parameter=argument [parameter = argument]
43 * <p>Examples entity create name=hello description="description of hello" help entity list
45 * @param line The line to parse
46 * @param logicBlock A block of logic code to be taken literally
47 * @return the string array list
49 public List<String> parse(final String line, final String logicBlock) {
51 mergeArguments(mergeEquals(splitOnEquals(
52 stripAndSplitWords(mergeQuotes(splitOnChar(stripComments(line), '\"')))))),
57 * Strip comments from lines, comments start with a # character.
59 * @param line the line
60 * @return the line without comments
62 private String stripComments(final String line) {
63 final int commentPos = line.indexOf('#');
64 if (commentPos == -1) {
67 return line.substring(0, commentPos);
72 * This method merges an array with separate quotes into an array with quotes delimiting the start and end of quoted
73 * words Example [Humpty ],["],[Dumpty sat on the wall],["],[, Humpty Dumpty had ],["],["],a ["],[great],["],[ fall]
74 * becomes [Humpty ],["Dumpty sat on the wall"],[, Humpty Dumpty had ],[""],[a],["great"],[ fall].
76 * @param wordsSplitOnQuotes the words split on quotes
77 * @return the merged array list
79 private ArrayList<String> mergeQuotes(final ArrayList<String> wordsSplitOnQuotes) {
80 final ArrayList<String> wordsWithQuotesMerged = new ArrayList<>();
83 while (wordIndex < wordsSplitOnQuotes.size()) {
84 wordIndex = mergeQuote(wordsSplitOnQuotes, wordsWithQuotesMerged, wordIndex);
87 return wordsWithQuotesMerged;
91 * This method merges the next set of quotes.
93 * @param wordsSplitOnQuotes the words split on quotes
94 * @param wordsWithQuotesMerged the merged words
95 * @param wordIndex the current word index
96 * @return the next word index
98 private int mergeQuote(final ArrayList<String> wordsSplitOnQuotes, final ArrayList<String> wordsWithQuotesMerged,
101 if ("\"".equals(wordsSplitOnQuotes.get(wordIndex))) {
102 var quotedWord = new StringBuilder(wordsSplitOnQuotes.get(wordIndex++));
104 for (; wordIndex < wordsSplitOnQuotes.size(); wordIndex++) {
105 quotedWord.append(wordsSplitOnQuotes.get(wordIndex));
106 if ("\"".equals(wordsSplitOnQuotes.get(wordIndex))) {
111 var quotedWordToString = quotedWord.toString();
112 if (quotedWordToString.matches("^\".*\"$")) {
113 wordsWithQuotesMerged.add(quotedWordToString);
115 throw new CommandLineException("trailing quote found in input " + wordsSplitOnQuotes);
118 wordsWithQuotesMerged.add(wordsSplitOnQuotes.get(wordIndex++));
124 * This method splits the words on an array list into an array list where each portion of the line is split into
125 * words by '=', quoted words are ignored Example: aaa = bbb = ccc=ddd=eee = becomes [aaa ],[=],[bbb
126 * ],[=],[ccc],[=],[ddd],[=],[eee ],[=].
128 * @param words the words
129 * @return the merged array list
131 private ArrayList<String> splitOnEquals(final ArrayList<String> words) {
132 final ArrayList<String> wordsSplitOnEquals = new ArrayList<>();
134 for (final String word : words) {
135 // Is this a quoted word ?
136 if (word.startsWith("\"")) {
137 wordsSplitOnEquals.add(word);
141 // Split on equals character
142 final ArrayList<String> splitWords = splitOnChar(word, '=');
143 wordsSplitOnEquals.addAll(splitWords);
146 return wordsSplitOnEquals;
150 * This method merges an array with separate equals into an array with equals delimiting the start of words Example:
151 * [aaa ],[=],[bbb ],[=],[ccc],[=],[ddd],[=],[eee ],[=] becomes [aaa ],[= bbb ],[= ccc],[=ddd],[=eee ],[=].
153 * @param wordsSplitOnEquals the words split on equals
154 * @return the merged array list
156 private ArrayList<String> mergeEquals(final ArrayList<String> wordsSplitOnEquals) {
157 final ArrayList<String> wordsWithEqualsMerged = new ArrayList<>();
160 while (wordIndex < wordsSplitOnEquals.size()) {
162 // Is this a quoted word ?
163 if (wordsSplitOnEquals.get(wordIndex).startsWith("\"")) {
164 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(wordIndex));
168 if ("=".equals(wordsSplitOnEquals.get(wordIndex))) {
169 if (wordIndex < wordsSplitOnEquals.size() - 1
170 && !wordsSplitOnEquals.get(wordIndex + 1).startsWith("=")) {
171 wordsWithEqualsMerged.add(
172 wordsSplitOnEquals.get(wordIndex) + wordsSplitOnEquals.get(wordIndex + 1));
175 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(wordIndex++));
178 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(wordIndex++));
182 return wordsWithEqualsMerged;
186 * This method merges words that start with an '=' character with the previous word if that word does not start with
189 * @param words the words
190 * @return the merged array list
192 private ArrayList<String> mergeArguments(final ArrayList<String> words) {
193 final ArrayList<String> mergedArguments = new ArrayList<>();
195 for (var i = 0; i < words.size(); i++) {
196 // Is this a quoted word ?
197 if (words.get(i).startsWith("\"")) {
198 mergedArguments.add(words.get(i));
202 if (words.get(i).startsWith("=")) {
203 if (i > 0 && !words.get(i - 1).startsWith("=")) {
204 mergedArguments.remove(mergedArguments.size() - 1);
205 mergedArguments.add(words.get(i - 1) + words.get(i));
207 mergedArguments.add(words.get(i));
210 mergedArguments.add(words.get(i));
214 return mergedArguments;
218 * This method strips all non quoted white space down to single spaces and splits non-quoted words into separate
221 * @param words the words
222 * @return the array list with white space stripped and words split
224 private ArrayList<String> stripAndSplitWords(final ArrayList<String> words) {
225 final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
227 for (String word : words) {
228 // Is this a quoted word
229 if (word.startsWith("\"")) {
230 strippedAndSplitWords.add(word);
232 // Split the word on blanks
233 strippedAndSplitWords.addAll(stripAndSplitWord(word));
237 return strippedAndSplitWords;
241 * Strip and split a word on blanks into an array of words split on blanks.
243 * @param word the word to split
244 * @return the array of split words
246 private Collection<? extends String> stripAndSplitWord(final String word) {
247 final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
249 // Strip white space by replacing all white space with blanks and then removing leading
250 // and trailing blanks
251 String singleSpaceWord = word.replaceAll("\\s+", " ").trim();
253 if (singleSpaceWord.length() == 0) {
254 return strippedAndSplitWords;
257 // Split on space characters
258 final String[] splitWords = singleSpaceWord.split(" ");
259 Collections.addAll(strippedAndSplitWords, splitWords);
261 return strippedAndSplitWords;
265 * This method splits a line of text into an array list where each portion of the line is split into words by a
266 * character, with the characters themselves as separate words Example: Humpty "Dumpty sat on the wall", Humpty
267 * Dumpty had ""a "great" fall becomes [Humpty ],["],[Dumpty sat on the wall],["],[, Humpty Dumpty had ],["],["],a
268 * ["],[great],["],[ fall].
270 * @param line the input line
271 * @param splitChar the split char
272 * @return the split array list
274 private ArrayList<String> splitOnChar(final String line, final char splitChar) {
275 final ArrayList<String> wordsSplitOnQuotes = new ArrayList<>();
278 while (currentPos != -1) {
279 final int quotePos = line.indexOf(splitChar, currentPos);
280 if (quotePos != -1) {
281 if (currentPos < quotePos) {
282 wordsSplitOnQuotes.add(line.substring(currentPos, quotePos));
284 wordsSplitOnQuotes.add("" + splitChar);
285 currentPos = quotePos + 1;
287 if (currentPos == line.length()) {
291 wordsSplitOnQuotes.add(line.substring(currentPos));
292 currentPos = quotePos;
296 return wordsSplitOnQuotes;
300 * This method checks that an array list containing a command is in the correct format.
302 * @param commandWords the command words
303 * @param logicBlock A block of logic code to be taken literally
304 * @return the checked array list
306 private ArrayList<String> checkFormat(final ArrayList<String> commandWords, final String logicBlock) {
307 // There should be at least one word
308 if (commandWords.isEmpty()) {
312 // The first word must be alphanumeric, that is a command
313 if (!commandWords.get(0).matches("^[a-zA-Z0-9]*$")) {
314 throw new CommandLineException(
315 "first command word is not alphanumeric or is not a command: " + commandWords.get(0));
318 // Now check that we have a sequence of commands at the beginning
319 var currentWordPos = 0;
320 for (; currentWordPos < commandWords.size(); currentWordPos++) {
321 if (!commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]*$")) {
326 for (; currentWordPos < commandWords.size(); ++currentWordPos) {
327 if (currentWordPos == commandWords.size() - 1 && logicBlock != null) {
328 // for the last command, if the command ends with = and there is a Logic block
329 if (commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]+=")) {
330 commandWords.set(currentWordPos, commandWords.get(currentWordPos) + logicBlock);
332 throw new CommandLineException(
333 "command argument is not properly formed: " + commandWords.get(currentWordPos));
335 } else if (!commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]+=[a-zA-Z0-9/\"].*$")) {
336 // Not a last command, or the last command, but there is no logic block - wrong pattern
337 throw new CommandLineException(
338 "command argument is not properly formed: " + commandWords.get(currentWordPos));