1/* 2 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*- 26 27import java.util.*; 28/* 29 * @author jrose 30 */ 31public class CommandLineParser { 32 33 public CommandLineParser(String optionString) { 34 setOptionMap(optionString); 35 } 36 TreeMap<String, String[]> optionMap; 37 38 public void setOptionMap(String options) { 39 // Convert options string into optLines dictionary. 40 TreeMap<String, String[]> optmap = new TreeMap<String, String[]>(); 41 loadOptmap: 42 for (String optline : options.split("\n")) { 43 String[] words = optline.split("\\p{Space}+"); 44 if (words.length == 0) { 45 continue loadOptmap; 46 } 47 String opt = words[0]; 48 words[0] = ""; // initial word is not a spec 49 if (opt.length() == 0 && words.length >= 1) { 50 opt = words[1]; // initial "word" is empty due to leading ' ' 51 words[1] = ""; 52 } 53 if (opt.length() == 0) { 54 continue loadOptmap; 55 } 56 String[] prevWords = optmap.put(opt, words); 57 if (prevWords != null) { 58 throw new RuntimeException("duplicate option: " 59 + optline.trim()); 60 } 61 } 62 optionMap = optmap; 63 } 64 65 public String getOptionMap() { 66 TreeMap<String, String[]> optmap = optionMap; 67 StringBuffer sb = new StringBuffer(); 68 for (String opt : optmap.keySet()) { 69 sb.append(opt); 70 for (String spec : optmap.get(opt)) { 71 sb.append(' ').append(spec); 72 } 73 sb.append('\n'); 74 } 75 return sb.toString(); 76 } 77 78 /** 79 * Remove a set of command-line options from args, 80 * storing them in the properties map in a canonicalized form. 81 */ 82 public String parse(List<String> args, Map<String, String> properties) { 83 //System.out.println(args+" // "+properties); 84 85 String resultString = null; 86 TreeMap<String, String[]> optmap = optionMap; 87 88 // State machine for parsing a command line. 89 ListIterator<String> argp = args.listIterator(); 90 ListIterator<String> pbp = new ArrayList<String>().listIterator(); 91 doArgs: 92 for (;;) { 93 // One trip through this loop per argument. 94 // Multiple trips per option only if several options per argument. 95 String arg; 96 if (pbp.hasPrevious()) { 97 arg = pbp.previous(); 98 pbp.remove(); 99 } else if (argp.hasNext()) { 100 arg = argp.next(); 101 } else { 102 // No more arguments at all. 103 break doArgs; 104 } 105 tryOpt: 106 for (int optlen = arg.length();; optlen--) { 107 // One time through this loop for each matching arg prefix. 108 String opt; 109 // Match some prefix of the argument to a key in optmap. 110 findOpt: 111 for (;;) { 112 opt = arg.substring(0, optlen); 113 if (optmap.containsKey(opt)) { 114 break findOpt; 115 } 116 if (optlen == 0) { 117 break tryOpt; 118 } 119 // Decide on a smaller prefix to search for. 120 SortedMap<String, String[]> pfxmap = optmap.headMap(opt); 121 // pfxmap.lastKey is no shorter than any prefix in optmap. 122 int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey().length(); 123 optlen = Math.min(len, optlen - 1); 124 opt = arg.substring(0, optlen); 125 // (Note: We could cut opt down to its common prefix with 126 // pfxmap.lastKey, but that wouldn't save many cycles.) 127 } 128 opt = opt.intern(); 129 assert (arg.startsWith(opt)); 130 assert (opt.length() == optlen); 131 String val = arg.substring(optlen); // arg == opt+val 132 133 // Execute the option processing specs for this opt. 134 // If no actions are taken, then look for a shorter prefix. 135 boolean didAction = false; 136 boolean isError = false; 137 138 int pbpMark = pbp.nextIndex(); // in case of backtracking 139 String[] specs = optmap.get(opt); 140 eachSpec: 141 for (String spec : specs) { 142 if (spec.length() == 0) { 143 continue eachSpec; 144 } 145 if (spec.startsWith("#")) { 146 break eachSpec; 147 } 148 int sidx = 0; 149 char specop = spec.charAt(sidx++); 150 151 // Deal with '+'/'*' prefixes (spec conditions). 152 boolean ok; 153 switch (specop) { 154 case '+': 155 // + means we want an non-empty val suffix. 156 ok = (val.length() != 0); 157 specop = spec.charAt(sidx++); 158 break; 159 case '*': 160 // * means we accept empty or non-empty 161 ok = true; 162 specop = spec.charAt(sidx++); 163 break; 164 default: 165 // No condition prefix means we require an exact 166 // match, as indicated by an empty val suffix. 167 ok = (val.length() == 0); 168 break; 169 } 170 if (!ok) { 171 continue eachSpec; 172 } 173 174 String specarg = spec.substring(sidx); 175 switch (specop) { 176 case '.': // terminate the option sequence 177 resultString = (specarg.length() != 0) ? specarg.intern() : opt; 178 break doArgs; 179 case '?': // abort the option sequence 180 resultString = (specarg.length() != 0) ? specarg.intern() : arg; 181 isError = true; 182 break eachSpec; 183 case '@': // change the effective opt name 184 opt = specarg.intern(); 185 break; 186 case '>': // shift remaining arg val to next arg 187 pbp.add(specarg + val); // push a new argument 188 val = ""; 189 break; 190 case '!': // negation option 191 String negopt = (specarg.length() != 0) ? specarg.intern() : opt; 192 properties.remove(negopt); 193 properties.put(negopt, null); // leave placeholder 194 didAction = true; 195 break; 196 case '$': // normal "boolean" option 197 String boolval; 198 if (specarg.length() != 0) { 199 // If there is a given spec token, store it. 200 boolval = specarg; 201 } else { 202 String old = properties.get(opt); 203 if (old == null || old.length() == 0) { 204 boolval = "1"; 205 } else { 206 // Increment any previous value as a numeral. 207 boolval = "" + (1 + Integer.parseInt(old)); 208 } 209 } 210 properties.put(opt, boolval); 211 didAction = true; 212 break; 213 case '=': // "string" option 214 case '&': // "collection" option 215 // Read an option. 216 boolean append = (specop == '&'); 217 String strval; 218 if (pbp.hasPrevious()) { 219 strval = pbp.previous(); 220 pbp.remove(); 221 } else if (argp.hasNext()) { 222 strval = argp.next(); 223 } else { 224 resultString = arg + " ?"; 225 isError = true; 226 break eachSpec; 227 } 228 if (append) { 229 String old = properties.get(opt); 230 if (old != null) { 231 // Append new val to old with embedded delim. 232 String delim = specarg; 233 if (delim.length() == 0) { 234 delim = " "; 235 } 236 strval = old + specarg + strval; 237 } 238 } 239 properties.put(opt, strval); 240 didAction = true; 241 break; 242 default: 243 throw new RuntimeException("bad spec for " 244 + opt + ": " + spec); 245 } 246 } 247 248 // Done processing specs. 249 if (didAction && !isError) { 250 continue doArgs; 251 } 252 253 // The specs should have done something, but did not. 254 while (pbp.nextIndex() > pbpMark) { 255 // Remove anything pushed during these specs. 256 pbp.previous(); 257 pbp.remove(); 258 } 259 260 if (isError) { 261 throw new IllegalArgumentException(resultString); 262 } 263 264 if (optlen == 0) { 265 // We cannot try a shorter matching option. 266 break tryOpt; 267 } 268 } 269 270 // If we come here, there was no matching option. 271 // So, push back the argument, and return to caller. 272 pbp.add(arg); 273 break doArgs; 274 } 275 // Report number of arguments consumed. 276 args.subList(0, argp.nextIndex()).clear(); 277 // Report any unconsumed partial argument. 278 while (pbp.hasPrevious()) { 279 args.add(0, pbp.previous()); 280 } 281 //System.out.println(args+" // "+properties+" -> "+resultString); 282 return resultString; 283 } 284} 285