2668N/A/*
2668N/A * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
2668N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
2668N/A *
2668N/A * This code is free software; you can redistribute it and/or modify it
2668N/A * under the terms of the GNU General Public License version 2 only, as
2668N/A * published by the Free Software Foundation. Oracle designates this
2668N/A * particular file as subject to the "Classpath" exception as provided
2668N/A * by Oracle in the LICENSE file that accompanied this code.
2668N/A *
2668N/A * This code is distributed in the hope that it will be useful, but WITHOUT
2668N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
2668N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
2668N/A * version 2 for more details (a copy is included in the LICENSE file that
2668N/A * accompanied this code).
2668N/A *
2668N/A * You should have received a copy of the GNU General Public License version
2668N/A * 2 along with this work; if not, write to the Free Software Foundation,
2668N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2668N/A *
2668N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2668N/A * or visit www.oracle.com if you need additional information or have any
2668N/A * questions.
2668N/A */
2668N/Apackage xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
2668N/A
2668N/Aimport java.util.*;
2668N/A/*
2668N/A * @author jrose
2668N/A */
2668N/Apublic class CommandLineParser {
2668N/A
2668N/A public CommandLineParser(String optionString) {
2668N/A setOptionMap(optionString);
2668N/A }
2668N/A TreeMap<String, String[]> optionMap;
2668N/A
2668N/A public void setOptionMap(String options) {
2668N/A // Convert options string into optLines dictionary.
2668N/A TreeMap<String, String[]> optmap = new TreeMap<String, String[]>();
2668N/A loadOptmap:
2668N/A for (String optline : options.split("\n")) {
2668N/A String[] words = optline.split("\\p{Space}+");
2668N/A if (words.length == 0) {
2668N/A continue loadOptmap;
2668N/A }
2668N/A String opt = words[0];
2668N/A words[0] = ""; // initial word is not a spec
2668N/A if (opt.length() == 0 && words.length >= 1) {
2668N/A opt = words[1]; // initial "word" is empty due to leading ' '
2668N/A words[1] = "";
2668N/A }
2668N/A if (opt.length() == 0) {
2668N/A continue loadOptmap;
2668N/A }
2668N/A String[] prevWords = optmap.put(opt, words);
2668N/A if (prevWords != null) {
2668N/A throw new RuntimeException("duplicate option: "
2668N/A + optline.trim());
2668N/A }
2668N/A }
2668N/A optionMap = optmap;
2668N/A }
2668N/A
2668N/A public String getOptionMap() {
2668N/A TreeMap<String, String[]> optmap = optionMap;
2668N/A StringBuffer sb = new StringBuffer();
2668N/A for (String opt : optmap.keySet()) {
2668N/A sb.append(opt);
2668N/A for (String spec : optmap.get(opt)) {
2668N/A sb.append(' ').append(spec);
2668N/A }
2668N/A sb.append('\n');
2668N/A }
2668N/A return sb.toString();
2668N/A }
2668N/A
2668N/A /**
2668N/A * Remove a set of command-line options from args,
2668N/A * storing them in the properties map in a canonicalized form.
2668N/A */
2668N/A public String parse(List<String> args, Map<String, String> properties) {
2668N/A //System.out.println(args+" // "+properties);
2668N/A
2668N/A String resultString = null;
2668N/A TreeMap<String, String[]> optmap = optionMap;
2668N/A
2668N/A // State machine for parsing a command line.
2668N/A ListIterator<String> argp = args.listIterator();
2668N/A ListIterator<String> pbp = new ArrayList<String>().listIterator();
2668N/A doArgs:
2668N/A for (;;) {
2668N/A // One trip through this loop per argument.
2668N/A // Multiple trips per option only if several options per argument.
2668N/A String arg;
2668N/A if (pbp.hasPrevious()) {
2668N/A arg = pbp.previous();
2668N/A pbp.remove();
2668N/A } else if (argp.hasNext()) {
2668N/A arg = argp.next();
2668N/A } else {
2668N/A // No more arguments at all.
2668N/A break doArgs;
2668N/A }
2668N/A tryOpt:
2668N/A for (int optlen = arg.length();; optlen--) {
2668N/A // One time through this loop for each matching arg prefix.
2668N/A String opt;
2668N/A // Match some prefix of the argument to a key in optmap.
2668N/A findOpt:
2668N/A for (;;) {
2668N/A opt = arg.substring(0, optlen);
2668N/A if (optmap.containsKey(opt)) {
2668N/A break findOpt;
2668N/A }
2668N/A if (optlen == 0) {
2668N/A break tryOpt;
2668N/A }
2668N/A // Decide on a smaller prefix to search for.
2668N/A SortedMap<String, String[]> pfxmap = optmap.headMap(opt);
2668N/A // pfxmap.lastKey is no shorter than any prefix in optmap.
2668N/A int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey().length();
2668N/A optlen = Math.min(len, optlen - 1);
2668N/A opt = arg.substring(0, optlen);
2668N/A // (Note: We could cut opt down to its common prefix with
2668N/A // pfxmap.lastKey, but that wouldn't save many cycles.)
2668N/A }
2668N/A opt = opt.intern();
2668N/A assert (arg.startsWith(opt));
2668N/A assert (opt.length() == optlen);
2668N/A String val = arg.substring(optlen); // arg == opt+val
2668N/A
2668N/A // Execute the option processing specs for this opt.
2668N/A // If no actions are taken, then look for a shorter prefix.
2668N/A boolean didAction = false;
2668N/A boolean isError = false;
2668N/A
2668N/A int pbpMark = pbp.nextIndex(); // in case of backtracking
2668N/A String[] specs = optmap.get(opt);
2668N/A eachSpec:
2668N/A for (String spec : specs) {
2668N/A if (spec.length() == 0) {
2668N/A continue eachSpec;
2668N/A }
2668N/A if (spec.startsWith("#")) {
2668N/A break eachSpec;
2668N/A }
2668N/A int sidx = 0;
2668N/A char specop = spec.charAt(sidx++);
2668N/A
2668N/A // Deal with '+'/'*' prefixes (spec conditions).
2668N/A boolean ok;
2668N/A switch (specop) {
2668N/A case '+':
2668N/A // + means we want an non-empty val suffix.
2668N/A ok = (val.length() != 0);
2668N/A specop = spec.charAt(sidx++);
2668N/A break;
2668N/A case '*':
2668N/A // * means we accept empty or non-empty
2668N/A ok = true;
2668N/A specop = spec.charAt(sidx++);
2668N/A break;
2668N/A default:
2668N/A // No condition prefix means we require an exact
2668N/A // match, as indicated by an empty val suffix.
2668N/A ok = (val.length() == 0);
2668N/A break;
2668N/A }
2668N/A if (!ok) {
2668N/A continue eachSpec;
2668N/A }
2668N/A
2668N/A String specarg = spec.substring(sidx);
2668N/A switch (specop) {
2668N/A case '.': // terminate the option sequence
2668N/A resultString = (specarg.length() != 0) ? specarg.intern() : opt;
2668N/A break doArgs;
2668N/A case '?': // abort the option sequence
2668N/A resultString = (specarg.length() != 0) ? specarg.intern() : arg;
2668N/A isError = true;
2668N/A break eachSpec;
2668N/A case '@': // change the effective opt name
2668N/A opt = specarg.intern();
2668N/A break;
2668N/A case '>': // shift remaining arg val to next arg
2668N/A pbp.add(specarg + val); // push a new argument
2668N/A val = "";
2668N/A break;
2668N/A case '!': // negation option
2668N/A String negopt = (specarg.length() != 0) ? specarg.intern() : opt;
2668N/A properties.remove(negopt);
2668N/A properties.put(negopt, null); // leave placeholder
2668N/A didAction = true;
2668N/A break;
2668N/A case '$': // normal "boolean" option
2668N/A String boolval;
2668N/A if (specarg.length() != 0) {
2668N/A // If there is a given spec token, store it.
2668N/A boolval = specarg;
2668N/A } else {
2668N/A String old = properties.get(opt);
2668N/A if (old == null || old.length() == 0) {
2668N/A boolval = "1";
2668N/A } else {
2668N/A // Increment any previous value as a numeral.
2668N/A boolval = "" + (1 + Integer.parseInt(old));
2668N/A }
2668N/A }
2668N/A properties.put(opt, boolval);
2668N/A didAction = true;
2668N/A break;
2668N/A case '=': // "string" option
2668N/A case '&': // "collection" option
2668N/A // Read an option.
2668N/A boolean append = (specop == '&');
2668N/A String strval;
2668N/A if (pbp.hasPrevious()) {
2668N/A strval = pbp.previous();
2668N/A pbp.remove();
2668N/A } else if (argp.hasNext()) {
2668N/A strval = argp.next();
2668N/A } else {
2668N/A resultString = arg + " ?";
2668N/A isError = true;
2668N/A break eachSpec;
2668N/A }
2668N/A if (append) {
2668N/A String old = properties.get(opt);
2668N/A if (old != null) {
2668N/A // Append new val to old with embedded delim.
2668N/A String delim = specarg;
2668N/A if (delim.length() == 0) {
2668N/A delim = " ";
2668N/A }
2668N/A strval = old + specarg + strval;
2668N/A }
2668N/A }
2668N/A properties.put(opt, strval);
2668N/A didAction = true;
2668N/A break;
2668N/A default:
2668N/A throw new RuntimeException("bad spec for "
2668N/A + opt + ": " + spec);
2668N/A }
2668N/A }
2668N/A
2668N/A // Done processing specs.
2668N/A if (didAction && !isError) {
2668N/A continue doArgs;
2668N/A }
2668N/A
2668N/A // The specs should have done something, but did not.
2668N/A while (pbp.nextIndex() > pbpMark) {
2668N/A // Remove anything pushed during these specs.
2668N/A pbp.previous();
2668N/A pbp.remove();
2668N/A }
2668N/A
2668N/A if (isError) {
2668N/A throw new IllegalArgumentException(resultString);
2668N/A }
2668N/A
2668N/A if (optlen == 0) {
2668N/A // We cannot try a shorter matching option.
2668N/A break tryOpt;
2668N/A }
2668N/A }
2668N/A
2668N/A // If we come here, there was no matching option.
2668N/A // So, push back the argument, and return to caller.
2668N/A pbp.add(arg);
2668N/A break doArgs;
2668N/A }
2668N/A // Report number of arguments consumed.
2668N/A args.subList(0, argp.nextIndex()).clear();
2668N/A // Report any unconsumed partial argument.
2668N/A while (pbp.hasPrevious()) {
2668N/A args.add(0, pbp.previous());
2668N/A }
2668N/A //System.out.println(args+" // "+properties+" -> "+resultString);
2668N/A return resultString;
2668N/A }
2668N/A}