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<String> <jv>a2</jv> = <jv>args</jv>.getArgs(<js>"a2"</js>); <jc>// Contains ["v2a","v2b"]</jc> 082 * List<String> <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<String> <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<String> <jv>list1</jv> = <jv>args</jv>.getArgs(<js>"extraArgs"</js>); <jc>// ['foo','bar','baz']</jc> 265 * List<String> <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}