Sonar Fixes
[policy/apex-pdp.git] / auth / cli-editor / src / main / java / org / onap / policy / apex / auth / clieditor / CommandLineParser.java
1 /*-
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
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
22 package org.onap.policy.apex.auth.clieditor;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.List;
28
29 /**
30  * This class chops a command line up into commands, parameters and arguments.
31  *
32  * @author Liam Fallon (liam.fallon@ericsson.com)
33  */
34 public class CommandLineParser {
35
36     /**
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.
40      *
41      * <p>Format: command [command....] parameter=argument [parameter = argument]
42      *
43      * <p>Examples entity create name=hello description="description of hello" help entity list
44      *
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
48      */
49     public List<String> parse(final String line, final String logicBlock) {
50         return checkFormat(
51                         mergeArguments(mergeEquals(splitOnEquals(
52                                         stripAndSplitWords(mergeQuotes(splitOnChar(stripComments(line), '\"')))))),
53                         logicBlock);
54     }
55
56     /**
57      * Strip comments from lines, comments start with a # character.
58      *
59      * @param line the line
60      * @return the line without comments
61      */
62     private String stripComments(final String line) {
63         final int commentPos = line.indexOf('#');
64         if (commentPos == -1) {
65             return line;
66         } else {
67             return line.substring(0, commentPos);
68         }
69     }
70
71     /**
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].
75      *
76      * @param wordsSplitOnQuotes the words split on quotes
77      * @return the merged array list
78      */
79     private ArrayList<String> mergeQuotes(final ArrayList<String> wordsSplitOnQuotes) {
80         final ArrayList<String> wordsWithQuotesMerged = new ArrayList<>();
81
82         int loopWordIndex = 0;
83         for (int wordIndex = 0; wordIndex < wordsSplitOnQuotes.size(); wordIndex = loopWordIndex) {
84             loopWordIndex = mergeQuote(wordsSplitOnQuotes, wordsWithQuotesMerged, wordIndex);
85         }
86
87         return wordsWithQuotesMerged;
88     }
89
90     /**
91      * This method merges the next set of quotes.
92      *
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
97      */
98     private int mergeQuote(final ArrayList<String> wordsSplitOnQuotes, final ArrayList<String> wordsWithQuotesMerged,
99                     int wordIndex) {
100
101         if ("\"".equals(wordsSplitOnQuotes.get(wordIndex))) {
102             StringBuilder quotedWord = new StringBuilder(wordsSplitOnQuotes.get(wordIndex++));
103
104             for (; wordIndex < wordsSplitOnQuotes.size(); wordIndex++) {
105                 quotedWord.append(wordsSplitOnQuotes.get(wordIndex));
106                 if ("\"".equals(wordsSplitOnQuotes.get(wordIndex))) {
107                     wordIndex++;
108                     break;
109                 }
110             }
111             String quotedWordToString = quotedWord.toString();
112             if (quotedWordToString.matches("^\".*\"$")) {
113                 wordsWithQuotesMerged.add(quotedWordToString);
114             } else {
115                 throw new CommandLineException("trailing quote found in input " + wordsSplitOnQuotes);
116             }
117         } else {
118             wordsWithQuotesMerged.add(wordsSplitOnQuotes.get(wordIndex++));
119         }
120         return wordIndex;
121     }
122
123     /**
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 ],[=].
127      *
128      * @param words the words
129      * @return the merged array list
130      */
131     private ArrayList<String> splitOnEquals(final ArrayList<String> words) {
132         final ArrayList<String> wordsSplitOnEquals = new ArrayList<>();
133
134         for (final String word : words) {
135             // Is this a quoted word ?
136             if (word.startsWith("\"")) {
137                 wordsSplitOnEquals.add(word);
138                 continue;
139             }
140
141             // Split on equals character
142             final ArrayList<String> splitWords = splitOnChar(word, '=');
143             for (final String splitWord : splitWords) {
144                 wordsSplitOnEquals.add(splitWord);
145             }
146         }
147
148         return wordsSplitOnEquals;
149     }
150
151     /**
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 ],[=].
154      *
155      * @param wordsSplitOnEquals the words split on equals
156      * @return the merged array list
157      */
158     private ArrayList<String> mergeEquals(final ArrayList<String> wordsSplitOnEquals) {
159         final ArrayList<String> wordsWithEqualsMerged = new ArrayList<>();
160
161         int loopWordIndex = 0;
162         for (int wordIndex = 0; wordIndex < wordsSplitOnEquals.size(); wordIndex = loopWordIndex) {
163             loopWordIndex = wordIndex;
164
165             // Is this a quoted word ?
166             if (wordsSplitOnEquals.get(loopWordIndex).startsWith("\"")) {
167                 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex));
168                 continue;
169             }
170
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));
176                     loopWordIndex += 2;
177                 } else {
178                     wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex++));
179                 }
180             } else {
181                 wordsWithEqualsMerged.add(wordsSplitOnEquals.get(loopWordIndex++));
182             }
183         }
184
185         return wordsWithEqualsMerged;
186     }
187
188     /**
189      * This method merges words that start with an '=' character with the previous word if that word does not start with
190      * an '='.
191      *
192      * @param words the words
193      * @return the merged array list
194      */
195     private ArrayList<String> mergeArguments(final ArrayList<String> words) {
196         final ArrayList<String> mergedArguments = new ArrayList<>();
197
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));
202                 continue;
203             }
204
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));
209                 } else {
210                     mergedArguments.add(words.get(i));
211                 }
212             } else {
213                 mergedArguments.add(words.get(i));
214             }
215         }
216
217         return mergedArguments;
218     }
219
220     /**
221      * This method strips all non quoted white space down to single spaces and splits non-quoted words into separate
222      * words.
223      *
224      * @param words the words
225      * @return the array list with white space stripped and words split
226      */
227     private ArrayList<String> stripAndSplitWords(final ArrayList<String> words) {
228         final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
229
230         for (String word : words) {
231             // Is this a quoted word
232             if (word.startsWith("\"")) {
233                 strippedAndSplitWords.add(word);
234             } else {
235                 // Split the word on blanks
236                 strippedAndSplitWords.addAll(stripAndSplitWord(word));
237             }
238         }
239
240         return strippedAndSplitWords;
241     }
242
243     /**
244      * Strip and split a word on blanks into an array of words split on blanks.
245      *
246      * @param word the word to split
247      * @return the array of split words
248      */
249     private Collection<? extends String> stripAndSplitWord(final String word) {
250         final ArrayList<String> strippedAndSplitWords = new ArrayList<>();
251
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();
255
256         if (singleSpaceWord.length() == 0) {
257             return strippedAndSplitWords;
258         }
259
260         // Split on space characters
261         final String[] splitWords = singleSpaceWord.split(" ");
262         Collections.addAll(strippedAndSplitWords, splitWords);
263
264         return strippedAndSplitWords;
265     }
266
267     /**
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].
272      *
273      * @param line the input line
274      * @param splitChar the split char
275      * @return the split array list
276      */
277     private ArrayList<String> splitOnChar(final String line, final char splitChar) {
278         final ArrayList<String> wordsSplitOnQuotes = new ArrayList<>();
279
280         int currentPos = 0;
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));
286                 }
287                 wordsSplitOnQuotes.add("" + splitChar);
288                 currentPos = quotePos + 1;
289
290                 if (currentPos == line.length()) {
291                     currentPos = -1;
292                 }
293             } else {
294                 wordsSplitOnQuotes.add(line.substring(currentPos));
295                 currentPos = quotePos;
296             }
297         }
298
299         return wordsSplitOnQuotes;
300     }
301
302     /**
303      * This method checks that an array list containing a command is in the correct format.
304      *
305      * @param commandWords the command words
306      * @param logicBlock A block of logic code to be taken literally
307      * @return the checked array list
308      */
309     private ArrayList<String> checkFormat(final ArrayList<String> commandWords, final String logicBlock) {
310         // There should be at least one word
311         if (commandWords.isEmpty()) {
312             return commandWords;
313         }
314
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));
319         }
320
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]*$")) {
325                 currentWordPos++;
326             } else {
327                 break;
328             }
329         }
330
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) {
335                 // No logic block
336                 if (commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]+=[a-zA-Z0-9/\"].*$")) {
337                     currentWordPos++;
338                 } else {
339                     throw new CommandLineException(
340                                     "command argument is not properly formed: " + commandWords.get(currentWordPos));
341                 }
342             } else {
343                 // Logic block
344                 if (commandWords.get(currentWordPos).matches("^[a-zA-Z0-9]+=")) {
345                     commandWords.set(currentWordPos, commandWords.get(currentWordPos) + logicBlock);
346                     currentWordPos++;
347                 } else {
348                     throw new CommandLineException(
349                                     "command argument is not properly formed: " + commandWords.get(currentWordPos));
350                 }
351             }
352         }
353
354         return commandWords;
355     }
356 }