001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.collections;
018
019import static java.util.stream.Collectors.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021import static org.apache.juneau.commons.utils.ThrowableUtils.*;
022import static org.apache.juneau.commons.utils.Utils.*;
023
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.commons.utils.*;
029
030/**
031 * Utility class to make it easier to work with command-line arguments pass in through a
032 * <c>main(String[] args)</c> method.
033 *
034 * <p>
035 * Used to parse command-line arguments of the form
036 * <js>"[zero or more main arguments] [zero or more optional arguments]"</js>.
037 *
038 * <p>
039 * The format of a main argument is a token that does not start with <js>'-'</js>.
040 *
041 * <p>
042 * The format of an optional argument is <js>"-argName [zero or more tokens]"</js>.
043 *
044 * <h5 class='topic'>Command-line examples</h5>
045 * <ul>
046 *    <li><c>java com.sample.MyClass mainArg1</c>
047 *    <li><c>java com.sample.MyClass mainArg1 mainArg2</c>
048 *    <li><c>java com.sample.MyClass mainArg1 -optArg1</c>
049 *    <li><c>java com.sample.MyClass -optArg1</c>
050 *    <li><c>java com.sample.MyClass mainArg1 -optArg1 optArg1Val</c>
051 *    <li><c>java com.sample.MyClass mainArg1 -optArg1 optArg1Val1 optArg1Val2</c>
052 *    <li><c>java com.sample.MyClass mainArg1 -optArg1 optArg1Val1 -optArg1 optArg1Val2</c>
053 * </ul>
054 *
055 * <h5 class='section'>Example:</h5>
056 * <p class='bjava'>
057 *
058 *    <jc>// Main method with arguments</jc>
059 *    <jk>public static void</jk> <jsm>main</jsm>(String[] <jv>_args</jv>) {
060 *
061 *       <jc>// Wrap in Args</jc>
062 *       Args <jv>args</jv> = <jk>new</jk> Args(<jv>_args</jv>);
063 *
064 *       <jc>// One main argument</jc>
065 *       <jc>// a1</jc>
066 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0); <jc>// "a1"</jc>
067 *       String <jv>a2</jv> = <jv>args</jv>.getArg(1); <jc>// null</jc>
068 *
069 *       <jc>// Two main arguments</jc>
070 *       <jc>// a1 a2</jc>
071 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0); <jc>// "a1"</jc>
072 *       String <jv>a2</jv> = <jv>args</jv>.getArg(1); <jc>// "a2"</jc>
073 *
074 *       <jc>// One main argument and one optional argument with no value</jc>
075 *       <jc>// a1 -a2</jc>
076 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0);
077 *       <jk>boolean</jk> <jv>hasA2</jv> = <jv>args</jv>.hasArg(<js>"a2"</js>); <jc>// true</jc>
078 *       <jk>boolean</jk> <jv>hasA3</jv> = <jv>args</jv>.hasArg(<js>"a3"</js>); <jc>// false</jc>
079 *
080 *       <jc>// One main argument and one optional argument with one value</jc>
081 *       <jc>// a1 -a2 v2</jc>
082 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0);
083 *       String <jv>a2</jv> = <jv>args</jv>.getArg(<js>"a2"</js>); <jc>// "v2"</jc>
084 *       String <jv>a3</jv> = <jv>args</jv>.getArg(<js>"a3"</js>); <jc>// null</jc>
085 *
086 *       <jc>// One main argument and one optional argument with two values</jc>
087 *       <jc>// a1 -a2 v2a v2b</jc>
088 *       String a1 = a.getArg(0);
089 *       List&lt;String&gt; <jv>a2</jv> = <jv>args</jv>.getArgs(<js>"a2"</js>); <jc>// Contains ["v2a","v2b"]</jc>
090 *       List&lt;String&gt; <jv>a3</jv> = <jv>args</jv>.getArgs(<js>"a3"</js>); <jc>// Empty list</jc>
091 *
092 *       <jc>// Same as previous, except specify optional argument name multiple times</jc>
093 *       <jc>// a1 -a2 v2a -a2 v2b</jc>
094 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0);
095 *       List&lt;String&gt; <jv>a2</jv> = <jv>args</jv>.getArgs(<js>"a2"</js>); <jc>// Contains ["v2a","v2b"]</jc>
096 *    }
097 * </p>
098 *
099 * <p>
100 * Main arguments are available through numeric string keys (e.g. <js>"0"</js>, <js>"1"</js>, ...).
101 * So you could use the {@link JsonMap} API to convert main arguments directly to POJOs, such as an <c>Enum</c>
102 * <p class='bjava'>
103 *    <jc>// Get 1st main argument as an Enum</jc>
104 *    MyEnum <jv>_enum</jv> = <jv>args</jv>.get(MyEnum.<jk>class</jk>, <js>"0"</js>);
105 *
106 *    <jc>// Get 1st main argument as an integer</jc>
107 *    <jk>int</jk> <jv>_int</jv> = <jv>args</jv>.get(<jk>int</jk>.<jk>class</jk>, <js>"0"</js>);
108 * </p>
109 *
110 * <p>
111 * Equivalent operations are available on optional arguments through the {@link #getArg(Class, String)} method.
112 *
113 * <h5 class='section'>Notes:</h5><ul>
114 *    <li class='warn'>This class is not thread safe.
115 * </ul>
116 *
117 *
118 * @serial exclude
119 */
120public class Args extends JsonMap {
121
122   private static final long serialVersionUID = 1L;
123
124   /**
125    * Constructor.
126    *
127    * @param args Arguments passed in as a raw command line.
128    */
129   public Args(String args) {
130      this(StringUtils.splitQuoted(args));
131   }
132
133   /**
134    * Constructor.
135    *
136    * @param args Arguments passed in through a <c>main(String[] args)</c> method.
137    */
138   public Args(String[] args) {
139      List<String> argList = Arrays.stream(args).collect(toCollection(LinkedList::new));
140
141      // Capture the main arguments.
142      int i = 0;
143      while (! argList.isEmpty()) {
144         String s = argList.get(0);
145         if (startsWith(s, '-'))
146            break;
147         put(Integer.toString(i), argList.remove(0));
148         i++;
149      }
150
151      // Capture the mapped arguments.
152      var key = (String)null;
153      while (! argList.isEmpty()) {
154         String s = argList.remove(0);
155         if (startsWith(s, '-')) {
156            key = s.substring(1);
157            if (key.matches("\\d*"))
158               throw rex("Invalid optional key name ''{0}''", key);
159            if (! containsKey(key))
160               put(key, new JsonList());
161         } else {
162            ((JsonList)get(key)).add(s);
163         }
164      }
165   }
166
167   @Override /* Overridden from JsonMap */
168   public Args append(Map<String,Object> values) {
169      super.append(values);
170      return this;
171   }
172
173   @Override /* Overridden from JsonMap */
174   public Args append(String key, Object value) {
175      super.append(key, value);
176      return this;
177   }
178
179   @Override /* Overridden from JsonMap */
180   public Args appendIf(boolean flag, String key, Object value) {
181      super.appendIf(flag, key, value);
182      return this;
183   }
184
185   @Override /* Overridden from JsonMap */
186   public Args filtered(Predicate<Object> value) {
187      super.filtered(value);
188      return this;
189   }
190
191   /**
192    * Returns the optional argument value converted to the specified object type.
193    *
194    * <p>
195    * If the optional arg has multiple values, returns only the first converted value.
196    *
197    * <h5 class='section'>Example:</h5>
198    * <p class='bjava'>
199    *    <jc>// Command:  java com.sample.MyClass -verbose true -debug 5</jc>
200    *    <jk>boolean</jk> <jv>bool</jv> = <jv>args</jv>.getArg(<jk>boolean</jk>.<jk>class</jk>, <js>"verbose"</js>);
201    *    <jk>int</jk> <jv>_int</jv> = <jv>args</jv>.getArg(<jk>int</jk>.<jk>class</jk>, <js>"debug"</js>);
202    * </p>
203    *
204    * @param c The class type to convert the value to.
205    * @param <T> The class type to convert the value to.
206    * @param name The optional argument name.
207    * @return The optional argument value, or blank if the optional argument was not specified.
208    */
209   public <T> T getArg(Class<T> c, String name) {
210      var l = (JsonList)get(name);
211      if (l == null || l.isEmpty())
212         return null;
213      return l.get(0, c);
214   }
215
216   /**
217    * Returns main argument at the specified index, or <jk>null</jk> if the index is out of range.
218    *
219    * <p>
220    * Can be used in conjunction with {@link #hasArg(int)} to check for existence of arg.
221    * <p class='bjava'>
222    *    <jc>// Check for no arguments</jc>
223    *    <jk>if</jk> (! <jv>args</jv>.hasArg(0))
224    *       <jsm>printUsageAndExit</jsm>();
225    *
226    *    <jc>// Get the first argument</jc>
227    *    String <jv>firstArg</jv> = <jv>args</jv>.getArg(0);
228    * </p>
229    *
230    * <p>
231    * Since main arguments are stored as numeric keys, this method is essentially equivalent to...
232    * <p class='bjava'>
233    *    <jc>// Check for no arguments</jc>
234    *    <jk>if</jk> (! <jv>args</jv>.containsKey(<js>"0"</js>))
235    *       <jsm>printUsageAndExit</jsm>();
236    *
237    *    <jc>// Get the first argument</jc>
238    *    String <jv>firstArg</jv> = <jv>args</jv>.getString(<js>"0"</js>);
239    * </p>
240    *
241    * @param i The index position of the main argument (zero-indexed).
242    * @return The main argument value, or <js>""</js> if argument doesn't exist at that position.
243    */
244   public String getArg(int i) {
245      return getString(Integer.toString(i));
246   }
247
248   /**
249    * Returns the optional argument value, or blank if the optional argument was not specified.
250    *
251    * <p>
252    * If the optional arg has multiple values, returns values as a comma-delimited list.
253    *
254    * @param name The optional argument name.
255    * @return The optional argument value, or blank if the optional argument was not specified.
256    */
257   public String getArg(String name) {
258      var l = (JsonList)get(name);
259      if (l == null || l.isEmpty())
260         return null;
261      if (l.size() == 1)
262         return l.get(0).toString();
263      return Arrays.toString(l.toArray()).replaceAll("[\\[\\]]", "");
264   }
265
266   /**
267    * Returns the optional argument values as a list of strings.
268    *
269    * <h5 class='section'>Example:</h5>
270    * <p class='bjava'>
271    *    <jc>// Command:  java com.sample.MyClass -extraArgs foo bar baz</jc>
272    *    List&lt;String&gt; <jv>list1</jv> = <jv>args</jv>.getArgs(<js>"extraArgs"</js>); <jc>// ['foo','bar','baz']</jc>
273    *    List&lt;String&gt; <jv>list2</jv> = <jv>args</jv>.getArgs(<js>"nonExistentArgs"</js>); <jc>// An empty list</jc>
274    * </p>
275    *
276    * @param name The optional argument name.
277    * @return The optional argument values, or an empty list if the optional argument was not specified.
278    */
279   @SuppressWarnings({ "rawtypes", "unchecked" })
280   public List<String> getArgs(String name) {
281      List l = (JsonList)get(name);
282      if (l == null)
283         return Collections.emptyList();
284      return l;
285   }
286
287   /**
288    * Returns <jk>true</jk> if argument exists at specified index.
289    *
290    * @param i The zero-indexed position of the argument.
291    * @return <jk>true</jk> if argument exists at specified index.
292    */
293   public boolean hasArg(int i) {
294      return containsKey(Integer.toString(i));
295   }
296
297   /**
298    * Returns <jk>true</jk> if the named argument exists.
299    *
300    * @param name The argument name.
301    * @return <jk>true</jk> if the named argument exists.
302    */
303   public boolean hasArg(String name) {
304      return nn(get(name));
305   }
306
307   @Override /* Overridden from JsonMap */
308   public Args inner(Map<String,Object> inner) {
309      super.inner(inner);
310      return this;
311   }
312
313   @Override /* Overridden from JsonMap */
314   public Args keepAll(String...keys) {
315      super.keepAll(keys);
316      return this;
317   }
318
319   @Override /* Overridden from JsonMap */
320   public Args modifiable() {
321      return this;
322   }
323
324   @Override /* Overridden from JsonMap */
325   public Args session(BeanSession session) {
326      super.session(session);
327      return this;
328   }
329
330   @Override /* Overridden from JsonMap */
331   public Args setBeanSession(BeanSession value) {
332      super.setBeanSession(value);
333      return this;
334   }
335
336   @Override /* Overridden from JsonMap */
337   public Args unmodifiable() {
338      return this;
339   }
340}