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