2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2016-2018 Ericsson. All rights reserved.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.apex.auth.clieditor;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
28 * This class chops a command line up into commands, parameters and arguments.
30 * @author Liam Fallon (liam.fallon@ericsson.com)
32 public class CommandLineParser {
35 * This method breaks a line of input up into commands, parameters, and arguments. Commands are standalone words at
36 * the beginning of the line, of which there may be multiple Parameters are single words followed by an '='
37 * character Arguments are single words or a block of quoted text following an '=' character.
39 * <p>Format: command [command....] parameter=argument [parameter = argument]
41 * <p>Examples entity create name=hello description="description of hello" help entity list
43 * @param line The line to parse
44 * @param logicBlock A block of logic code to be taken literally
45 * @return the string array list
47 public List<String> parse(final String line, final String logicBlock) {
49 mergeArguments(mergeEquals(splitOnEquals(
50 stripAndSplitWords(mergeQuotes(splitOnChar(stripComments(line), '\"')))))),
55 * Strip comments from lines, comments start with a # character.
57 * @param line the line
58 * @return the line without comments
60 private String stripComments(final String line) {
61 final int commentPos = line.indexOf('#');
62 if (commentPos == -1) {
65 return line.substring(0, commentPos);
70 * This method merges an array with separate quotes into an array with quotes delimiting the start and end of quoted
71 * words Example [Humpty ],["],[Dumpty sat on the wall],["],[, Humpty Dumpty had ],["],["],a ["],[great],["],[ fall]
72 * becomes [Humpty ],["Dumpty sat on the wall"],[, Humpty Dumpty had ],[""],[a],["great"],[ fall].
74 * @param wordsSplitOnQuotes the words split on quotes
75 * @return the merged array list
77 private ArrayList<String> mergeQuotes(final ArrayList<String> wordsSplitOnQuotes) {
78 final ArrayList<String> wordsWithQuotesMerged = new ArrayList<>();
80 int loopWordIndex = 0;
81 for (int wordIndex = 0; wordIndex < wordsSplitOnQuotes.size(); wordIndex = loopWordIndex) {
82 loopWordIndex = mergeQuote(wordsSplitOnQuotes, wordsWithQuotesMerged, wordIndex);
85 return wordsWithQuotesMerged;
89 * This method merges the next set of quotes.
91 * @param wordsSplitOnQuotes the words split on quotes
92 * @param wordsWithQuotesMerged the merged words
93 * @param wordIndex the current word index
94 * @return the next word index
96 private int mergeQuote(final ArrayList<String> wordsSplitOnQuotes, final ArrayList<String> wordsWithQuotesMerged,
99 if ("\"".equals(wordsSplitOnQuotes.get(wordIndex))) {
100 StringBuilder quotedWord = new StringBuilder(wordsSplitOnQuotes.get(wordIndex++));
102 for (; wordIndex < wordsSplitOnQuotes.size(); wordIndex++) {
103 quotedWord.append(wordsSplitOnQuotes.get(wordIndex));
104 if ("\"".equals(wordsSplitOnQuotes.get(wordIndex))) {
109 String quotedWordToString = quotedWord.toString();
110 if (quotedWordToString.matches("^\".*\"$")) {
111 wordsWithQuotesMerged.add(quotedWordToString);
113 throw new CommandLineException("trailing quote found in input " + wordsSplitOnQuotes);
116 wordsWithQuotesMerged.add(wordsSplitOnQuotes.get(wordIndex++));
122 * This method splits the words on an array list into an array list where each portion of the line is split into
123 * words by '=', quoted words are ignored Example: aaa = bbb = ccc=ddd=eee = becomes [aaa ],[=],[bbb
124 * ],[=],[ccc],[=],[ddd],[=],[eee ],[=].
126 * @param words the words
127 * @return the merged array list
129 private ArrayList<String> splitOnEquals(final ArrayList<String> words) {
130 final ArrayList<String> wordsSplitOnEquals = new ArrayList<>();
132 for (final String word : words) {
133 // Is this a quoted word ?
134 if (word.startsWith("\"")) {
135 wordsSplitOnEquals.add(word);
139 // Split on equals character
140 final ArrayList<String> splitWords = splitOnChar(word, '=');
141 for (final String splitWord : splitWords) {
142 wordsSplitOnEquals.add(splitWord);
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<>();
159 int loopWordIndex = 0;
160 for (int wordIndex = 0; wordIndex < wordsSplitOnEquals.size(); wordIndex = loopWordIndex) {
161 loopWordIndex = wordIndex;
163 // Is this a quoted word ?
164 if (wordsSplitOnEquals.get(loopWordIndex).startsWith("\"")) {
165 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex));
169 if ("=".equals(wordsSplitOnEquals.get(loopWordIndex))) {
170 if (loopWordIndex < wordsSplitOnEquals.size() - 1
171 && !wordsSplitOnEquals.get(loopWordIndex + 1).startsWith("=")) {
172 wordsWithEqualsMerged.add(
173 wordsSplitOnEquals.get(loopWordIndex) + wordsSplitOnEquals.get(loopWordIndex + 1));
176 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex++));
179 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex++));
183 return wordsWithEqualsMerged;
187 * This method merges words that start with an '=' character with the previous word if that word does not start with
190 * @param words the words
191 * @return the merged array list
193 private ArrayList<String> mergeArguments(final ArrayList<String> words) {
194 final ArrayList<String> mergedArguments = new ArrayList<>();
196 for (int i = 0; i < words.size(); i++) {
197 // Is this a quoted word ?
198 if (words.get(i).startsWith("\"")) {
199 mergedArguments.add(words.get(i));
203 if (words.get(i).startsWith("=")) {
204 if (i > 0 && !words.get(i - 1).startsWith("=")) {
205 mergedArguments.remove(mergedArguments.size() - 1);
206 mergedArguments.add(words.get(i - 1) + words.get(i));
208 mergedArguments.add(words.get(i));
211 mergedArguments.add(words.get(i));
215 return mergedArguments;
219 * This method strips all non quoted white space down to single spaces and splits non-quoted words into separate
222 * @param words the words
223 * @return the array list with white space stripped and words split
225 private ArrayList<String> stripAndSplitWords(final ArrayList<String> words) {
226 final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
228 for (String word : words) {
229 // Is this a quoted word
230 if (word.startsWith("\"")) {
231 strippedAndSplitWords.add(word);
233 // Split the word on blanks
234 strippedAndSplitWords.addAll(stripAndSplitWord(word));
238 return strippedAndSplitWords;
242 * Strip and split a word on blanks into an array of words split on blanks.
244 * @param word the word to split
245 * @return the array of split words
247 private Collection<? extends String> stripAndSplitWord(final String word) {
248 final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
250 // Strip white space by replacing all white space with blanks and then removing leading
251 // and trailing blanks
252 String singleSpaceWord = word.replaceAll("\\s+", " ").trim();
254 if (singleSpaceWord.length() == 0) {
255 return strippedAndSplitWords;
258 // Split on space characters
259 final String[] splitWords = singleSpaceWord.split(" ");
260 for (final String splitWord : splitWords) {
261 strippedAndSplitWords.add(splitWord);
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));