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.common.utils.StringUtils.*;
021
022import java.util.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.common.utils.*;
026
027/**
028 * Utility class to make it easier to work with command-line arguments pass in through a
029 * <c>main(String[] args)</c> method.
030 *
031 * <p>
032 * Used to parse command-line arguments of the form
033 * <js>"[zero or more main arguments] [zero or more optional arguments]"</js>.
034 *
035 * <p>
036 * The format of a main argument is a token that does not start with <js>'-'</js>.
037 *
038 * <p>
039 * The format of an optional argument is <js>"-argName [zero or more tokens]"</js>.
040 *
041 * <h5 class='topic'>Command-line examples</h5>
042 * <ul>
043 *    <li><c>java com.sample.MyClass mainArg1</c>
044 *    <li><c>java com.sample.MyClass mainArg1 mainArg2</c>
045 *    <li><c>java com.sample.MyClass mainArg1 -optArg1</c>
046 *    <li><c>java com.sample.MyClass -optArg1</c>
047 *    <li><c>java com.sample.MyClass mainArg1 -optArg1 optArg1Val</c>
048 *    <li><c>java com.sample.MyClass mainArg1 -optArg1 optArg1Val1 optArg1Val2</c>
049 *    <li><c>java com.sample.MyClass mainArg1 -optArg1 optArg1Val1 -optArg1 optArg1Val2</c>
050 * </ul>
051 *
052 * <h5 class='section'>Example:</h5>
053 * <p class='bjava'>
054 *
055 *    <jc>// Main method with arguments</jc>
056 *    <jk>public static void</jk> <jsm>main</jsm>(String[] <jv>_args</jv>) {
057 *
058 *       <jc>// Wrap in Args</jc>
059 *       Args <jv>args</jv> = <jk>new</jk> Args(<jv>_args</jv>);
060 *
061 *       <jc>// One main argument</jc>
062 *       <jc>// a1</jc>
063 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0); <jc>// "a1"</jc>
064 *       String <jv>a2</jv> = <jv>args</jv>.getArg(1); <jc>// null</jc>
065 *
066 *       <jc>// Two main arguments</jc>
067 *       <jc>// a1 a2</jc>
068 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0); <jc>// "a1"</jc>
069 *       String <jv>a2</jv> = <jv>args</jv>.getArg(1); <jc>// "a2"</jc>
070 *
071 *       <jc>// One main argument and one optional argument with no value</jc>
072 *       <jc>// a1 -a2</jc>
073 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0);
074 *       <jk>boolean</jk> <jv>hasA2</jv> = <jv>args</jv>.hasArg(<js>"a2"</js>); <jc>// true</jc>
075 *       <jk>boolean</jk> <jv>hasA3</jv> = <jv>args</jv>.hasArg(<js>"a3"</js>); <jc>// false</jc>
076 *
077 *       <jc>// One main argument and one optional argument with one value</jc>
078 *       <jc>// a1 -a2 v2</jc>
079 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0);
080 *       String <jv>a2</jv> = <jv>args</jv>.getArg(<js>"a2"</js>); <jc>// "v2"</jc>
081 *       String <jv>a3</jv> = <jv>args</jv>.getArg(<js>"a3"</js>); <jc>// null</jc>
082 *
083 *       <jc>// One main argument and one optional argument with two values</jc>
084 *       <jc>// a1 -a2 v2a v2b</jc>
085 *       String a1 = a.getArg(0);
086 *       List&lt;String&gt; <jv>a2</jv> = <jv>args</jv>.getArgs(<js>"a2"</js>); <jc>// Contains ["v2a","v2b"]</jc>
087 *       List&lt;String&gt; <jv>a3</jv> = <jv>args</jv>.getArgs(<js>"a3"</js>); <jc>// Empty list</jc>
088 *
089 *       <jc>// Same as previous, except specify optional argument name multiple times</jc>
090 *       <jc>// a1 -a2 v2a -a2 v2b</jc>
091 *       String <jv>a1</jv> = <jv>args</jv>.getArg(0);
092 *       List&lt;String&gt; <jv>a2</jv> = <jv>args</jv>.getArgs(<js>"a2"</js>); <jc>// Contains ["v2a","v2b"]</jc>
093 *    }
094 * </p>
095 *
096 * <p>
097 * Main arguments are available through numeric string keys (e.g. <js>"0"</js>, <js>"1"</js>, ...).
098 * So you could use the {@link JsonMap} API to convert main arguments directly to POJOs, such as an <c>Enum</c>
099 * <p class='bjava'>
100 *    <jc>// Get 1st main argument as an Enum</jc>
101 *    MyEnum <jv>_enum</jv> = <jv>args</jv>.get(MyEnum.<jk>class</jk>, <js>"0"</js>);
102 *
103 *    <jc>// Get 1st main argument as an integer</jc>
104 *    <jk>int</jk> <jv>_int</jv> = <jv>args</jv>.get(<jk>int</jk>.<jk>class</jk>, <js>"0"</js>);
105 * </p>
106 *
107 * <p>
108 * Equivalent operations are available on optional arguments through the {@link #getArg(Class, String)} method.
109 *
110 * <h5 class='section'>Notes:</h5><ul>
111 *    <li class='warn'>This class is not thread safe.
112 * </ul>
113 *
114 * <h5 class='section'>See Also:</h5><ul>
115
116 * </ul>
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 through a <c>main(String[] args)</c> method.
128    */
129   public Args(String[] args) {
130      List<String> argList = Arrays.stream(args).collect(toCollection(LinkedList::new));
131
132      // Capture the main arguments.
133      int i = 0;
134      while (! argList.isEmpty()) {
135         String s = argList.get(0);
136         if (startsWith(s,'-'))
137            break;
138         put(Integer.toString(i), argList.remove(0));
139         i++;
140      }
141
142      // Capture the mapped arguments.
143      String key = null;
144      while (! argList.isEmpty()) {
145         String s = argList.remove(0);
146         if (startsWith(s, '-')) {
147            key = s.substring(1);
148            if (key.matches("\\d*"))
149               throw new BasicRuntimeException("Invalid optional key name ''{0}''", key);
150            if (! containsKey(key))
151               put(key, new JsonList());
152         } else {
153            ((JsonList)get(key)).add(s);
154         }
155      }
156   }
157
158   /**
159    * Constructor.
160    *
161    * @param args Arguments passed in as a raw command line.
162    */
163   public Args(String args) {
164      this(Utils.splitQuoted(args));
165   }
166
167   /**
168    * Returns main argument at the specified index, or <jk>null</jk> if the index is out of range.
169    *
170    * <p>
171    * Can be used in conjunction with {@link #hasArg(int)} to check for existence of arg.
172    * <p class='bjava'>
173    *    <jc>// Check for no arguments</jc>
174    *    <jk>if</jk> (! <jv>args</jv>.hasArg(0))
175    *       <jsm>printUsageAndExit</jsm>();
176    *
177    *    <jc>// Get the first argument</jc>
178    *    String <jv>firstArg</jv> = <jv>args</jv>.getArg(0);
179    * </p>
180    *
181    * <p>
182    * Since main arguments are stored as numeric keys, this method is essentially equivalent to...
183    * <p class='bjava'>
184    *    <jc>// Check for no arguments</jc>
185    *    <jk>if</jk> (! <jv>args</jv>.containsKey(<js>"0"</js>))
186    *       <jsm>printUsageAndExit</jsm>();
187    *
188    *    <jc>// Get the first argument</jc>
189    *    String <jv>firstArg</jv> = <jv>args</jv>.getString(<js>"0"</js>);
190    * </p>
191    *
192    * @param i The index position of the main argument (zero-indexed).
193    * @return The main argument value, or <js>""</js> if argument doesn't exist at that position.
194    */
195   public String getArg(int i) {
196      return getString(Integer.toString(i));
197   }
198
199   /**
200    * Returns <jk>true</jk> if argument exists at specified index.
201    *
202    * @param i The zero-indexed position of the argument.
203    * @return <jk>true</jk> if argument exists at specified index.
204    */
205   public boolean hasArg(int i) {
206      return containsKey(Integer.toString(i));
207   }
208
209   /**
210    * Returns <jk>true</jk> if the named argument exists.
211    *
212    * @param name The argument name.
213    * @return <jk>true</jk> if the named argument exists.
214    */
215   public boolean hasArg(String name) {
216      JsonList l = (JsonList)get(name);
217      return l != null;
218   }
219
220   /**
221    * Returns the optional argument value, or blank if the optional argument was not specified.
222    *
223    * <p>
224    * If the optional arg has multiple values, returns values as a comma-delimited list.
225    *
226    * @param name The optional argument name.
227    * @return The optional argument value, or blank if the optional argument was not specified.
228    */
229   public String getArg(String name) {
230      JsonList l = (JsonList)get(name);
231      if (l == null || l.isEmpty())
232         return null;
233      if (l.size() == 1)
234         return l.get(0).toString();
235      return Arrays.toString(l.toArray()).replaceAll("[\\[\\]]", "");
236   }
237
238   /**
239    * Returns the optional argument value converted to the specified object type.
240    *
241    * <p>
242    * If the optional arg has multiple values, returns only the first converted value.
243    *
244    * <h5 class='section'>Example:</h5>
245    * <p class='bjava'>
246    *    <jc>// Command:  java com.sample.MyClass -verbose true -debug 5</jc>
247    *    <jk>boolean</jk> <jv>bool</jv> = <jv>args</jv>.getArg(<jk>boolean</jk>.<jk>class</jk>, <js>"verbose"</js>);
248    *    <jk>int</jk> <jv>_int</jv> = <jv>args</jv>.getArg(<jk>int</jk>.<jk>class</jk>, <js>"debug"</js>);
249    * </p>
250    *
251    * @param c The class type to convert the value to.
252    * @param <T> The class type to convert the value to.
253    * @param name The optional argument name.
254    * @return The optional argument value, or blank if the optional argument was not specified.
255    */
256   public <T> T getArg(Class<T> c, String name) {
257      JsonList l = (JsonList)get(name);
258      if (l == null || l.isEmpty())
259         return null;
260      return l.get(0, c);
261   }
262
263   /**
264    * Returns the optional argument values as a list of strings.
265    *
266    * <h5 class='section'>Example:</h5>
267    * <p class='bjava'>
268    *    <jc>// Command:  java com.sample.MyClass -extraArgs foo bar baz</jc>
269    *    List&lt;String&gt; <jv>list1</jv> = <jv>args</jv>.getArgs(<js>"extraArgs"</js>); <jc>// ['foo','bar','baz']</jc>
270    *    List&lt;String&gt; <jv>list2</jv> = <jv>args</jv>.getArgs(<js>"nonExistentArgs"</js>); <jc>// An empty list</jc>
271    * </p>
272    *
273    * @param name The optional argument name.
274    * @return The optional argument values, or an empty list if the optional argument was not specified.
275    */
276   @SuppressWarnings({"rawtypes", "unchecked"})
277   public List<String> getArgs(String name) {
278      List l = (JsonList)get(name);
279      if (l == null)
280         return Collections.emptyList();
281      return l;
282   }
283}