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