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