2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * Modifications Copyright (C) 2020 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<>();
82 int loopWordIndex = 0;
83 for (int wordIndex = 0; wordIndex < wordsSplitOnQuotes.size(); wordIndex = loopWordIndex) {
84 loopWordIndex = 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 StringBuilder 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 String 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 for (final String splitWord : splitWords) {
144 wordsSplitOnEquals.add(splitWord);
148 return wordsSplitOnEquals;
152 * This method merges an array with separate equals into an array with equals delimiting the start of words Example:
153 * [aaa ],[=],[bbb ],[=],[ccc],[=],[ddd],[=],[eee ],[=] becomes [aaa ],[= bbb ],[= ccc],[=ddd],[=eee ],[=].
155 * @param wordsSplitOnEquals the words split on equals
156 * @return the merged array list
158 private ArrayList<String> mergeEquals(final ArrayList<String> wordsSplitOnEquals) {
159 final ArrayList<String> wordsWithEqualsMerged = new ArrayList<>();
161 int loopWordIndex = 0;
162 for (int wordIndex = 0; wordIndex < wordsSplitOnEquals.size(); wordIndex = loopWordIndex) {
163 loopWordIndex = wordIndex;
165 // Is this a quoted word ?
166 if (wordsSplitOnEquals.get(loopWordIndex).startsWith("\"")) {
167 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex));
171 if ("=".equals(wordsSplitOnEquals.get(loopWordIndex))) {
172 if (loopWordIndex < wordsSplitOnEquals.size() - 1
173 && !wordsSplitOnEquals.get(loopWordIndex + 1).startsWith("=")) {
174 wordsWithEqualsMerged.add(
175 wordsSplitOnEquals.get(loopWordIndex) + wordsSplitOnEquals.get(loopWordIndex + 1));
178 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex++));
181 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex++));
185 return wordsWithEqualsMerged;
189 * This method merges words that start with an '=' character with the previous word if that word does not start with
192 * @param words the words
193 * @return the merged array list
195 private ArrayList<String> mergeArguments(final ArrayList<String> words) {
196 final ArrayList<String> mergedArguments = new ArrayList<>();
198 for (int i = 0; i < words.size(); i++) {
199 // Is this a quoted word ?
200 if (words.get(i).startsWith("\"")) {
201 mergedArguments.add(words.get(i));
205 if (words.get(i).startsWith("=")) {
206 if (i > 0 && !words.get(i - 1).startsWith("=")) {
207 mergedArguments.remove(mergedArguments.size() - 1);
208 mergedArguments.add(words.get(i - 1) + words.get(i));
210 mergedArguments.add(words.get(i));
213 mergedArguments.add(words.get(i));
217 return mergedArguments;
221 * This method strips all non quoted white space down to single spaces and splits non-quoted words into separate
224 * @param words the words
225 * @return the array list with white space stripped and words split
227 private ArrayList<String> stripAndSplitWords(final ArrayList<String> words) {
228 final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
230 for (String word : words) {
231 // Is this a quoted word
232 if (word.startsWith("\"")) {
233 strippedAndSplitWords.add(word);
235 // Split the word on blanks
236 strippedAndSplitWords.addAll(stripAndSplitWord(word));
240 return strippedAndSplitWords;
244 * Strip and split a word on blanks into an array of words split on blanks.
246 * @param word the word to split
247 * @return the array of split words
249 private Collection<? extends String> stripAndSplitWord(final String word) {
250 final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
252 // Strip white space by replacing all white space with blanks and then removing leading
253 // and trailing blanks
254 String singleSpaceWord = word.replaceAll("\\s+", " ").trim();
256 if (singleSpaceWord.length() == 0) {
257 return strippedAndSplitWords;
260 // Split on space characters
261 final String[] splitWords = singleSpaceWord.split(" ");
262 Collections.addAll(strippedAndSplitWords, splitWords);
264 return strippedAndSplitWords;
268 * This method splits a line of text into an array list where each portion of the line is split into words by a
269 * character, with the characters themselves as separate words Example: Humpty "Dumpty sat on the wall", Humpty
270 * Dumpty had ""a "great" fall becomes [Humpty ],["],[Dumpty sat on the wall],["],[, Humpty Dumpty had ],["],["],a
271 * ["],[great],["],[ fall].
273 * @param line the input line
274 * @param splitChar the split char
275 * @return the split array list
277 private ArrayList<String> splitOnChar(final String line, final char splitChar) {
278 final ArrayList<String> wordsSplitOnQuotes = new ArrayList<>();
281 while (currentPos != -1) {
282 final int quotePos = line.indexOf(splitChar, currentPos);
283 if (quotePos != -1) {
284 if (currentPos < quotePos) {
285 wordsSplitOnQuotes.add(line.substring(currentPos, quotePos));
287 wordsSplitOnQuotes.add("" + splitChar);
288 currentPos = quotePos + 1;
290 if (currentPos == line.length()) {
294 wordsSplitOnQuotes.add(line.substring(currentPos));
295 currentPos = quotePos;
299 return wordsSplitOnQuotes;
303 * This method checks that an array list containing a command is in the correct format.
305 * @param commandWords the command words
306 * @param logicBlock A block of logic code to be taken literally
307 * @return the checked array list
309 private ArrayList<String> checkFormat(final ArrayList<String> commandWords, final String logicBlock) {
310 // There should be at least one word
311 if (commandWords.isEmpty()) {
315 // The first word must be alphanumeric, that is a command
316 if (!commandWords.get(0).matches("^[a-zA-Z0-9]*$")) {
317 throw new CommandLineException(
318 "first command word is not alphanumeric or is not a command: " + commandWords.get(0));
321 // Now check that we have a sequence of commands at the beginning
322 int currentWordPos = 0;
323 while (currentWordPos < commandWords.size()) {
324 if (commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]*$")) {
331 while (currentWordPos < commandWords.size()) {
332 // From now on we should have a sequence of parameters with arguments delimited by a
333 // single '=' character
334 if (currentWordPos < commandWords.size() - 1 || logicBlock == null) {
336 if (commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]+=[a-zA-Z0-9/\"].*$")) {
339 throw new CommandLineException(
340 "command argument is not properly formed: " + commandWords.get(currentWordPos));
344 if (commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]+=")) {
345 commandWords.set(currentWordPos, commandWords.get(currentWordPos) + logicBlock);
348 throw new CommandLineException(
349 "command argument is not properly formed: " + commandWords.get(currentWordPos));