2521 Formalize CLI option parsing and document CLI syntax
authorStephen Talley <stephen.talley@sun.com>
Thu, 18 Sep 2008 17:18:30 -0400
changeset 78 6336fcea8db0
parent 77 17490e9582d9
child 79 d22c901edeff
2521 Formalize CLI option parsing and document CLI syntax
usr/src/java/util/org/opensolaris/os/vp/cli/CommandLineParser.java
usr/src/java/util/org/opensolaris/os/vp/cli/CommandLineProcessor.java
usr/src/java/util/org/opensolaris/os/vp/cli/CommandUtil.java
usr/src/java/util/org/opensolaris/os/vp/cli/HelpFormatter.java
usr/src/java/util/org/opensolaris/os/vp/cli/NoOptOptionElement.java
usr/src/java/util/org/opensolaris/os/vp/cli/Option.java
usr/src/java/util/org/opensolaris/os/vp/cli/OptionChoiceGroup.java
usr/src/java/util/org/opensolaris/os/vp/cli/OptionElement.java
usr/src/java/util/org/opensolaris/os/vp/cli/OptionFormatter.java
usr/src/java/util/org/opensolaris/os/vp/cli/OptionGroup.java
usr/src/java/util/org/opensolaris/os/vp/cli/OptionListGroup.java
usr/src/java/util/org/opensolaris/os/vp/cli/OptionMap.java
usr/src/java/util/org/opensolaris/os/vp/cli/ParsedOption.java
usr/src/java/util/org/opensolaris/os/vp/cli/PosixCommandLineParser.java
usr/src/java/util/org/opensolaris/os/vp/cli/PosixOptionFormatter.java
usr/src/java/util/org/opensolaris/os/vp/cli/UsageFormatter.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/ConflictingOptionsException.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/InvalidOptArgException.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/InvalidOptionException.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/MissingOptArgException.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/MissingOptionException.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/OptionException.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/OptionUseExceededException.java
usr/src/java/util/org/opensolaris/os/vp/cli/exception/UnexpectedOptArgException.java
usr/src/java/util/org/opensolaris/os/vp/cli/resources/Resources.properties
usr/src/java/util/org/opensolaris/os/vp/util/BeanUtil.java
usr/src/java/util/org/opensolaris/os/vp/util/TextUtil.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/App.java
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppConstants.java.in
usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/resources/Resources.properties
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/CommandLineParser.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,60 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+public abstract class CommandLineParser {
+    //
+    // Instance data
+    //
+
+    private String[] args;
+
+    //
+    // CommandLineParser methods
+    //
+
+    public String[] getArgs() {
+	return args;
+    }
+
+    public abstract boolean getIsDoneParsing();
+
+    public abstract void getNextOptArg(ParsedOption option);
+
+    public abstract ParsedOption getNextOption();
+
+    public abstract OptionFormatter getOptionFormatter();
+
+    public abstract void reset();
+
+    public abstract void resetToPreviousOption();
+
+    public void setArgs(String[] args) {
+	this.args = args;
+	reset();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/CommandLineProcessor.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,434 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.beans.IntrospectionException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+import org.opensolaris.os.vp.cli.exception.*;
+import org.opensolaris.os.vp.util.*;
+
+public class CommandLineProcessor {
+    //
+    // Instance data
+    //
+
+    private OptionGroup group;
+    private CommandLineParser parser;
+    private Stack<OptionMap> processed;
+    private Map<Option, Integer> used;
+
+    //
+    // Constructors
+    //
+
+    private CommandLineProcessor(OptionGroup group, CommandLineParser parser) {
+	this.group = group;
+	this.parser = parser;
+	this.processed = new Stack<OptionMap>();
+	used = new HashMap<Option, Integer>();
+    }
+
+    //
+    // Private methods
+    //
+
+    private int getUseCount(Option option) {
+	try {
+	    return used.get(option);
+	}
+	catch (NullPointerException e) {
+	    return 0;
+	}
+    }
+
+    private int incUseCount(Option option) {
+	int count = getUseCount(option) + 1;
+	used.put(option, count);
+	return count;
+    }
+
+    private int decUseCount(Option option) {
+	int count = getUseCount(option) - 1;
+	used.put(option, count);
+	return count;
+    }
+
+    private boolean isUsed(Option option) {
+	return getUseCount(option) > 0;
+    }
+
+    private OptionMap[] getProcessed() {
+	return processed.toArray(new OptionMap[processed.size()]);
+    }
+
+    private void process(Option curOpt, OptionMap conflict, String indent)
+	throws OptionException {
+
+	OptionFormatter formatter = parser.getOptionFormatter();
+
+//	System.out.println(indent + "Current option: " +
+//	    TextUtil.getClassBaseName(curOpt) + ": " +
+//	    formatter.getFormatted(curOpt, false));
+
+	if (parser.getIsDoneParsing()) {
+	    // Last check
+	    verifyRequiredOptions(group);
+	    return;
+	}
+
+	OptionException exception = null;
+
+	if (curOpt instanceof OptionElement) {
+	    OptionElement element = (OptionElement)curOpt;
+
+	    ParsedOption parsed = parser.getNextOption();
+//	    System.out.println(indent + "Next option: " +
+//		formatter.getFormatted(parsed.getOpt()));
+
+	    // Does this OptionElement expect an opt arg?
+	    if (element.getTakesArgument()) {
+		// Parse opt arg
+		parser.getNextOptArg(parsed);
+	    }
+
+	    // Create mapping between parsed option and option definition
+	    OptionMap map = new OptionMap(parsed, (OptionElement)curOpt);
+
+//	    System.out.println(indent + "Used options:");
+//	    for (int i = 0, n = processed.size(); i < n; i++) {
+//		System.out.println(indent + "  " + i + ": " +
+//		    processed.get(i).getFormatted(formatter));
+//	    }
+
+	    // Add the mapping to list of saved options
+	    processed.push(map);
+
+	    int useCount = incUseCount(element);
+
+	    try {
+		// Validate syntax (throws OptionException)
+		element.validate(parsed, useCount, formatter, getProcessed());
+
+//		System.out.println(indent + "Validated option: " +
+//		    map.getFormatted(formatter));
+
+		// The option is lexically and syntactically valid --
+		// check for conflict with previously-specified option
+		if (conflict != null) {
+		    throw new ConflictingOptionsException(
+			conflict.getFormatted(formatter),
+			map.getFormatted(formatter), getProcessed());
+		}
+
+		exception = null;
+
+		// Recurse... (throws OptionException)
+		process(group, conflict, indent + "  ");
+
+		// Success!  All args have been parsed.
+		return;
+	    }
+
+	    catch (OptionException e) {
+
+//		System.out.println(indent + "Should I save 1:");
+//		System.out.println(indent + "  " +
+//		    (exception == null ? "null" : exception.getMessage()));
+//		System.out.println(indent + "  " +
+//		    (e == null ? "null" : e.getMessage()));
+
+		// Save only the most significant exception to throw later
+		exception = getMostSignificantException(exception, e);
+
+//		System.out.println(indent + "saved: " +
+//		    (exception == null ? "null" : exception.getMessage()));
+
+		decUseCount(element);
+
+		// Remove the ParsedOption from saved options
+		processed.pop();
+
+		// Un-parse previously-parsed option
+		parser.resetToPreviousOption();
+	    }
+	} else
+
+	if (curOpt instanceof OptionGroup) {
+
+	    OptionMap newConflict = conflict;
+
+	    // If this option is represents a list of options from which only
+	    // one can be chosen...
+	    if (curOpt instanceof OptionChoiceGroup &&
+
+		// ...and no conflict option has thus far been specified...
+		newConflict == null) {
+
+		// Get the first option in this group, if any, which has already
+		// been specified and validated.  Later, if another otherwise
+		// valid option is found, it will conflict with this option and
+		// a ConflictingOptionsException will be thrown.
+		newConflict = getFirstUsedOpt((OptionGroup)curOpt, processed);
+
+//		System.out.println(indent + "Assigning conflict opt: " +
+//		    (newConflict == null ? "null" :
+//		    newConflict.getFormatted(formatter)));
+	    }
+
+	    for (Option subOpt : ((OptionGroup)curOpt).getOptions()) {
+		try {
+		    incUseCount(curOpt);
+
+		    // Recurse... (throws OptionException)
+		    process(subOpt, isUsed(subOpt) ?
+			conflict : newConflict, indent + "  ");
+
+		    // Success!  All args have been parsed.
+		    return;
+		}
+
+		catch (OptionException e) {
+//		    System.out.println(indent + "Should I save 2:");
+//		    System.out.println(indent + "  " +
+//			(exception == null ? "null" : exception.getMessage()));
+//		    System.out.println(indent + "  " +
+//			(e == null ? "null" : e.getMessage()));
+
+		    // Save only the most significant exception to throw later
+		    exception = getMostSignificantException(exception, e);
+
+//		    System.out.println(indent + "saved: " +
+//			(exception == null ? "null" : exception.getMessage()));
+
+		    decUseCount(curOpt);
+		}
+	    }
+	}
+
+	if (exception == null) {
+	    ParsedOption parsed = parser.getNextOption();
+	    exception = new InvalidOptionException(
+		formatter.getFormatted(parsed.getOpt()), getProcessed());
+	    parser.resetToPreviousOption();
+	}
+
+	throw exception;
+    }
+
+    /**
+     * Retrieves the first ParsedOption from this option group.
+     */
+    private OptionMap getFirstUsedOpt(OptionGroup parent,
+	List<OptionMap> maps) {
+
+	for (Option subOpt : parent.getOptions()) {
+
+	    if (isUsed(subOpt)) {
+		if (subOpt instanceof OptionElement) {
+		    // Search for matching ParsedOption
+		    for (OptionMap map : maps) {
+			if (((OptionElement)subOpt).matches(
+			    map.getParsed())) {
+			    return map;
+			}
+		    }
+		    // Shouldn't be here
+		} else
+
+		if (subOpt instanceof OptionGroup) {
+		    OptionMap map = getFirstUsedOpt((OptionGroup)subOpt, maps);
+
+		    if (map != null) {
+			return map;
+		    }
+		}
+	    }
+	}
+
+	return null;
+    }
+
+    private void verifyRequiredOptions(Option option)
+	throws MissingOptionException {
+
+	OptionFormatter formatter = parser.getOptionFormatter();
+
+	if (!isUsed(option)) {
+	    if (option.getIsRequired()) {
+		throw new MissingOptionException(
+		    formatter.getFormatted(option, false),
+		    getProcessed());
+	    }
+	} else {
+	    if (option instanceof OptionGroup) {
+		for (Option subOpt : ((OptionGroup)option).getOptions()) {
+		    verifyRequiredOptions(subOpt);
+		}
+	    }
+	}
+    }
+
+    //
+    // Static methods
+    //
+
+    public static void setInBean(OptionMap[] maps, Object bean)
+	throws InvalidOptArgException, IntrospectionException,
+	IllegalAccessException, NoSuchMethodException {
+
+	// Populate bean
+	for (OptionMap map : maps) {
+
+	    ParsedOption parsed = map.getParsed();
+	    String opt = parsed.getOpt();
+	    String optArg = parsed.getOptArg();
+
+//	    // Call boolean mutators for no-arg options
+//	    if (!map.getOption().getTakesArgument()) {
+//		optArg = Boolean.toString(true);
+//	    }
+
+//	    System.out.println("opt: " + opt);
+//	    System.out.println("optArg: " + optArg);
+//	    System.out.println("argName: " + map.getOption().getArgName());
+
+	    OptionElement option = map.getOption();
+	    String[] names = option.getNames();
+
+	    // Handle no-opt options
+	    if (names.length == 1 && names[0].length() == 0) {
+		names = new String[] {option.getArgName()};
+	    }
+
+	    String[] mutators = new String[names.length];
+
+	    for (int i = 0; i < names.length; i++) {
+		String name = names[i];
+
+//		System.out.println("Looking for property: " + name);
+		try {
+		    BeanUtil.setPropertyInBean(bean, name, optArg);
+		    break;
+		}
+
+		// Thrown by data conversion routines
+		catch (RuntimeException e) {
+//		    System.out.println("Caught RuntimeException: " +
+//			e.getMessage());
+		    throw new InvalidOptArgException(opt, optArg, maps, e);
+		}
+
+		// Thrown by data validation routines
+		catch (InvocationTargetException e) {
+//		    System.out.println("Caught InvocationTargetException: " +
+//			e.getMessage());
+		    throw new InvalidOptArgException(opt, optArg,
+			maps, e.getTargetException());
+		}
+
+		// No appropriate mutator in bean
+		catch (NoSuchMethodException e) {
+//		    System.out.println("Caught NoSuchMethodException: " +
+//			e.getMessage());
+
+		    // Strip class from fully-qualified method
+		    mutators[i] = e.getMessage().replaceFirst(".*\\.", "");
+
+		    if (i == names.length - 1) {
+			// No need to internationalize since this is an error
+			// that should only be seen during development
+			throw new NoSuchMethodException(
+			    "mutator method(s) not found (perhaps these " +
+			    "methods are private?): " +
+			    TextUtil.join(", ", (Object[])mutators));
+		    }
+		}
+	    }
+	}
+    }
+
+    public static OptionMap[] process(String[] args, OptionGroup group,
+	CommandLineParser parser, Object bean) throws OptionException,
+	IntrospectionException, IllegalAccessException, NoSuchMethodException {
+
+	parser.setArgs(args);
+
+	CommandLineProcessor processor =
+	    new CommandLineProcessor(group, parser);
+
+	processor.process(group, null, "");
+
+	OptionMap[] maps = processor.getProcessed();
+
+	if (bean != null) {
+	    processor.setInBean(maps, bean);
+	}
+
+	return maps;
+    }
+
+    private static OptionException getMostSignificantException(
+	OptionException a, OptionException b) {
+
+	// Sorted in order of significance
+	Class[] classes = {
+	    InvalidOptArgException.class,
+	    UnexpectedOptArgException.class,
+	    MissingOptArgException.class,
+	    OptionUseExceededException.class,
+	    ConflictingOptionsException.class,
+	    MissingOptionException.class,
+	    InvalidOptionException.class,
+
+	    // Catch-all (except null a and b)
+	    OptionException.class
+	};
+
+	for (Class clazz : classes) {
+	    boolean aIsInstance = clazz.isInstance(a);
+	    boolean bIsInstance = clazz.isInstance(b);
+
+	    if (aIsInstance) {
+		if (bIsInstance) {
+
+		    // Return the exception that occured when the most options
+		    // had been validated
+		    return a.getProcessed().length >
+			b.getProcessed().length ? a : b;
+		}
+		return a;
+	    }
+
+	    if (bIsInstance) {
+		return b;
+	    }
+	}
+
+	return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/CommandUtil.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,63 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.util.ArrayList;
+import org.opensolaris.os.vp.util.*;
+
+public class CommandUtil {
+    //
+    // Static methods
+    //
+
+    public static void exit(Throwable t, UsageFormatter usage) {
+	ArrayList<String> messages = new ArrayList<String>();
+
+	if (usage != null) {
+	    messages.add(usage.getCommand());
+	}
+
+	while (t != null) {
+	    messages.add(t.getMessage());
+	    t = t.getCause();
+	}
+
+	String delim = Finder.getString("command.error.delim");
+	System.err.println(TextUtil.join(delim, messages));
+
+	if (usage != null) {
+	    System.err.println();
+	    System.err.println(usage.getUsage());
+	}
+
+	System.exit(1);
+    }
+
+    public static void exit(Throwable t) {
+	exit(t, null);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/HelpFormatter.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,149 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.util.*;
+import org.opensolaris.os.vp.util.*;
+
+public class HelpFormatter {
+    //
+    // Instance data
+    //
+
+    private String description;
+    private UsageFormatter formatter;
+    private String optionsLabel = Finder.getString("command.help.options");
+
+    //
+    // Constructors
+    //
+
+    public HelpFormatter(String description, UsageFormatter formatter) {
+	this.description = description;
+	this.formatter = formatter;
+    }
+
+    //
+    // HelpFormatter methods
+    //
+
+    public String getDescription() {
+	return description;
+    }
+
+    public UsageFormatter getUsageFormatter() {
+	return formatter;
+    }
+
+    public void setOptionsLabel(String optionsLabel) {
+	this.optionsLabel = optionsLabel;
+    }
+
+    public String getOptionsLabel() {
+	return optionsLabel;
+    }
+
+    public String getHelp() {
+	UsageFormatter usage = getUsageFormatter();
+	OptionElement[] options = getOptionElements();
+	OptionFormatter formatter = usage.getOptionFormatter();
+
+	StringBuffer buffer = new StringBuffer();
+	int width = usage.getWidth();
+
+	buffer.append(usage.getUsage()).
+	    append("\n\n").
+	    append(TextUtil.format(getDescription(), width, "", "", false));
+
+	if (options.length > 0) {
+	    int indentlen = usage.getIndent();
+	    String indent2 = String.format("%" + indentlen + "s", "");
+
+	    String optionsLabel = getOptionsLabel();
+	    if (optionsLabel != null) {
+		buffer.append("\n\n").append(getOptionsLabel());
+	    }
+
+	    for (OptionElement option : options) {
+		String synopsis = formatter.getFormatted(option, true);
+		buffer.append("\n\n");
+
+		String indent1;
+		// Can synopsis and description be on the same line?
+		if (synopsis.length() + 1 <= indentlen) {
+		    indent1 = String.format("%-" + indentlen + "s", synopsis);
+		} else {
+		    buffer.append(synopsis).append("\n");
+		    indent1 = indent2;
+		}
+
+		buffer.append(TextUtil.format(
+		    option.getDescription(), width, indent1, indent2, false));
+	    }
+	}
+
+	return buffer.toString();
+    }
+
+    //
+    // Private methods
+    //
+
+    private OptionElement[] getOptionElements() {
+	// Recursively retrieve all OptionElements
+	HashMap<String, OptionElement> map =
+	    new HashMap<String, OptionElement>();
+	getOptionElements(getUsageFormatter().getOption(), map);
+
+	ArrayList<OptionElement> elements = new ArrayList<OptionElement>();
+	elements.addAll(map.values());
+
+	Collections.sort(elements,
+	    new Comparator<OptionElement>() {
+		public int compare(OptionElement a, OptionElement b) {
+		    return a.getName().compareTo(b.getName());
+		}
+	    });
+
+	return elements.toArray(new OptionElement[elements.size()]);
+    }
+
+    private void getOptionElements(
+	Option option, HashMap<String, OptionElement> map) {
+	if (option instanceof OptionElement) {
+	    OptionElement element = (OptionElement)option;
+	    String key = element.getName() + element.getArgName();
+	    map.put(key, element);
+	} else
+
+	if (option instanceof OptionGroup) {
+	    for (Option o : ((OptionGroup)option).getOptions()) {
+		getOptionElements(o, map);
+	    }
+	}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/NoOptOptionElement.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,67 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+public class NoOptOptionElement extends OptionElement {
+    //
+    // Constructors
+    //
+
+    public NoOptOptionElement(boolean required,
+	String argName, String description, int useLimit) {
+
+	super(new String[] {""}, required, argName, false, description,
+	    useLimit);
+    }
+
+    public NoOptOptionElement(boolean required, String argName,
+	String description) {
+
+	this(required, argName, description, 1);
+    }
+
+    //
+    // OptionElement methods
+    //
+
+    /**
+     * Returns a boolean indicating whether the given ParsedOption
+     * matches this NoOptOptionElement.
+     *
+     * @param	    option
+     *		    a ParsedOption
+     *
+     * @return	    <code>true</code> if the option flag is
+     *		    <code>null</code> or an empty string,
+     *		    <code>false</code> otherwise
+     */
+    @Override
+    protected boolean matches(ParsedOption option) {
+	String opt = option.getOpt();
+	return opt == null || opt.length() == 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/Option.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,35 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+public abstract class Option {
+    //
+    // Option methods
+    //
+
+    public abstract boolean getIsRequired();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/OptionChoiceGroup.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,41 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+public class OptionChoiceGroup extends OptionGroup {
+    //
+    // Constructors
+    //
+
+    public OptionChoiceGroup(boolean required) {
+	super(required);
+    }
+
+    public OptionChoiceGroup(boolean required, Option... options) {
+	super(required, options);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/OptionElement.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,244 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.util.*;
+import org.opensolaris.os.vp.cli.exception.*;
+
+public class OptionElement extends Option {
+    //
+    // Instance data
+    //
+
+    private boolean required;
+    private List<String> names;
+    private String argName;
+    private boolean argOptional;
+    private String description;
+    private int useLimit;
+
+    //
+    // Constructors
+    //
+
+    public OptionElement(String[] names, boolean required,
+	String argName, boolean argOptional, String description, int useLimit) {
+	setNames(names);
+	setIsRequired(required);
+	setArgName(argName);
+	setArgOptional(argOptional);
+	setDescription(description);
+	setUseLimit(useLimit);
+    }
+
+    public OptionElement(String name, boolean required, String argName,
+	String description) {
+	this(new String[] {name}, required, argName, false, description, 1);
+    }
+
+    public OptionElement(String name, boolean required, String description) {
+	this(name, required, null, description);
+    }
+
+    public OptionElement(String name1, String name2, boolean required,
+	String argName, String description) {
+	this(new String[] {name1, name2},
+	    required, argName, false, description, 1);
+    }
+
+    public OptionElement(String name1, String name2, boolean required,
+	String description) {
+	this(name1, name2, required, null, description);
+    }
+
+    //
+    // Option methods
+    //
+
+    @Override
+    public boolean getIsRequired() {
+	return required;
+    }
+
+    //
+    // OptionElement methods
+    //
+
+    public void setIsRequired(boolean required) {
+	this.required = required;
+    }
+
+    public void setNames(String[] names) {
+	this.names = Arrays.asList(names);
+    }
+
+    public String[] getNames() {
+	return names.toArray(new String[names.size()]);
+    }
+
+    public String getName() {
+	try {
+	    return names.get(0);
+	}
+	catch (IndexOutOfBoundsException e) {
+	    return null;
+	}
+    }
+
+    public void setArgName(String argName) {
+	this.argName = argName;
+    }
+
+    public String getArgName() {
+	return argName;
+    }
+
+    public boolean getTakesArgument() {
+	return argName != null;
+    }
+
+    public void setArgOptional(boolean argOptional) {
+	this.argOptional = argOptional;
+    }
+
+    public boolean getArgOptional() {
+	return argOptional;
+    }
+
+    public void setDescription(String description) {
+	this.description = description;
+    }
+
+    public String getDescription() {
+	return description;
+    }
+
+    public void setUseLimit(int useLimit) {
+	this.useLimit = useLimit;
+    }
+
+    public int getUseLimit() {
+	return useLimit;
+    }
+
+    /**
+     * Returns a boolean indicating whether the given
+     * <code>ParsedOption</code> matches this
+     * <code>OptionElement</code>.
+     */
+    protected boolean matches(ParsedOption processed) {
+	String opt = processed.getOpt();
+	if (opt != null) {
+	    for (String name : getNames()) {
+//		System.out.printf("Testing opt=%s optArg=%s against %s\n",
+//		    opt, processed.getOptArg(), name);
+
+		if (opt.equals(name)) {
+		    return true;
+		}
+	    }
+	}
+	return false;
+    }
+
+    /**
+     * Validates the given option and optional argument against the
+     * option/optArg that this OptionElement expects.  If the option is valid,
+     * does nothing.	Otherwise, an exception is thrown.
+     *
+     * @param	    parsed
+     *		    the option parsed from the command line
+     *
+     * @param	    useCount
+     *		    the number of times this option has been used
+     *
+     * @param	    formatter
+     *		    used to format the option string in exception
+     *		    messages
+     *
+     * @param	    processed
+     *		    the options processed so far, for use in exceptions
+     *
+     * @exception   InvalidOptionException
+     *		    if the given option doesn't match the name of this
+     *		    OptionElement
+     *
+     * @exception   OptionUseExceededException
+     *		    if this OptionElement has been used more times
+     *		    than it is allowed
+     *
+     * @exception   MissingOptArgException
+     *		    if this OptionElement requires an argument but
+     *		    none was specified
+     *
+     * @exception   UnexpectedOptArgException
+     *		    if this OptionElement takes no argument but
+     *		    one was specified
+     *
+     * @exception   InvalidOptArgException
+     *		    if this OptionElement's argument is invalid --
+     *		    not thrown in this default implementation
+     */
+    protected void validate(ParsedOption parsed, int useCount,
+	OptionFormatter formatter, OptionMap[] processed) throws
+	InvalidOptionException, OptionUseExceededException,
+	MissingOptArgException, UnexpectedOptArgException,
+	InvalidOptArgException {
+
+	String opt = parsed.getOpt();
+	String optArg = parsed.getOptArg();
+	String formatted = OptionMap.getFormatted(parsed, this, formatter);
+
+//	System.out.printf("Validating: opt=%s optArg=%s\n", opt, optArg);
+
+	// Verify given option identifies this OptionElement
+	if (!matches(parsed)) {
+	    throw new InvalidOptionException(formatted, processed);
+	}
+
+	// Change message to reflect the option syntax, not the passed-in opt
+	formatted = formatter.getFormatted(this, false);
+
+	// Verify this option hasn't been specified too many times
+	int useLimit = getUseLimit();
+	if (useLimit >= 0 && useCount > useLimit) {
+	    throw new OptionUseExceededException(
+		formatted, useLimit, processed);
+	}
+
+	// Verify optArg if required
+	if (getTakesArgument() && !getArgOptional() && optArg == null) {
+	    throw new MissingOptArgException(formatted, processed);
+	}
+
+	// Verify no optArg if not expected
+	if (!getTakesArgument() && optArg != null) {
+	    throw new UnexpectedOptArgException(
+		formatted, optArg, processed);
+	}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/OptionFormatter.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,122 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.util.ArrayList;
+import org.opensolaris.os.vp.util.TextUtil;
+
+public abstract class OptionFormatter {
+    //
+    // Static data
+    //
+
+    public static final String OPT_LIST_DELIM = " ";
+    public static final String OPT_NAME_DELIM = ",";
+    public static final String OPT_CHOICE_DELIM = " | ";
+    public static final String OPT_REQUIRED_ENCLOSE_LEFT = "[";
+    public static final String OPT_REQUIRED_ENCLOSE_RIGHT = "]";
+    public static final String OPT_GROUP_ENCLOSE_LEFT = "(";
+    public static final String OPT_GROUP_ENCLOSE_RIGHT = ")";
+    public static final String OPT_ARG_ENCLOSE_LEFT = "<";
+    public static final String OPT_ARG_ENCLOSE_RIGHT = ">";
+    public static final String OPT_REPEAT = "...";
+
+    //
+    // OptionFormatter methods
+    //
+
+    public String getFormatted(Option opt, boolean allNames) {
+	return getFormatted(opt, allNames, false, true);
+    }
+
+    public String getFormatted(String opt) {
+	return getFormatted(new String[] {opt});
+    }
+
+    public String getFormatted(String opt, String optArg) {
+	return getFormatted(new String[] {opt}, optArg);
+    }
+
+    public abstract String getFormatted(String[] opts);
+
+    public abstract String getFormatted(String[] opts, String optArg);
+
+    //
+    // Private methods
+    //
+
+    private String getFormatted(Option opt, boolean allNames,
+	boolean showGroup, boolean required) {
+
+	StringBuffer buffer = new StringBuffer();
+
+	if (!required) {
+	    buffer.append(OPT_REQUIRED_ENCLOSE_LEFT);
+	    showGroup = false;
+	}
+
+	if (opt instanceof OptionElement) {
+	    OptionElement element = (OptionElement)opt;
+
+	    buffer.append(allNames ?
+		getFormatted(element.getNames(), element.getArgName()) :
+		getFormatted(element.getName(), element.getArgName()));
+
+	    if (element.getUseLimit() < 0) {
+		buffer.append(OPT_REPEAT);
+	    }
+	} else
+
+	if (opt instanceof OptionGroup) {
+	    ArrayList<String> list = new ArrayList<String>();
+	    String delim = opt instanceof OptionChoiceGroup ?
+		OPT_CHOICE_DELIM : OPT_LIST_DELIM;
+
+	    for (Option subOpt : ((OptionGroup)opt).getOptions()) {
+		list.add(getFormatted(subOpt, allNames, showGroup,
+		    opt instanceof OptionChoiceGroup ?
+		    true : subOpt.getIsRequired()));
+	    }
+
+	    if (showGroup) {
+		buffer.append(OPT_GROUP_ENCLOSE_LEFT);
+	    }
+
+	    buffer.append(TextUtil.join(delim, list));
+
+	    if (showGroup) {
+		buffer.append(OPT_GROUP_ENCLOSE_RIGHT);
+	    }
+	}
+
+	if (!required) {
+	    buffer.append(OPT_REQUIRED_ENCLOSE_RIGHT);
+	}
+
+	return buffer.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/OptionGroup.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,99 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.util.*;
+
+public abstract class OptionGroup extends Option {
+    //
+    // Instance data
+    //
+
+    private boolean required;
+    private List<Option> options = new ArrayList<Option>();
+
+    // Used when processing options
+    private List<Option> usedOptions = new ArrayList<Option>();
+
+    //
+    // Constructors
+    //
+
+    public OptionGroup(boolean required) {
+	this.required = required;
+    }
+
+    public OptionGroup(boolean required, Option[] options) {
+	this(required);
+	this.options.addAll(Arrays.asList(options));
+    }
+
+    //
+    // Option methods
+    //
+
+    @Override
+    public boolean getIsRequired() {
+	return required;
+    }
+
+    //
+    // OptionGroup methods
+    //
+
+    public void addOption(Option option) {
+	synchronized (options) {
+	    options.add(option);
+	}
+    }
+
+    protected List<Option> getOptions() {
+	return options;
+    }
+
+    public List<Option> getUsedOptions() {
+	return usedOptions;
+    }
+
+    protected boolean removeOption(Option option) {
+	synchronized (options) {
+	    return options.remove(option);
+	}
+    }
+
+    //
+    // Private methods
+    //
+
+    private void addUsedOption(Option option) {
+	usedOptions.add(option);
+    }
+
+    private boolean removeUsedOption(Option option) {
+	return usedOptions.remove(option);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/OptionListGroup.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,41 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+public class OptionListGroup extends OptionGroup {
+    //
+    // Constructors
+    //
+
+    public OptionListGroup(boolean required) {
+	super(required);
+    }
+
+    public OptionListGroup(boolean required, Option... options) {
+	super(required, options);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/OptionMap.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,110 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+/**
+ * The <code>OptionMap</code> class provides a 1x1 mapping between a
+ * <code>ParsedOption</code> and an <code>OptionElement</code>.
+ */
+public class OptionMap {
+    //
+    // Instance data
+    //
+
+    private ParsedOption parsed;
+    private OptionElement option;
+
+    //
+    // Constructors
+    //
+
+    public OptionMap(ParsedOption parsed, OptionElement option) {
+	setParsed(parsed);
+	setOption(option);
+    }
+
+    //
+    // OptionMap methods
+    //
+
+    public void setParsed(ParsedOption parsed) {
+	this.parsed = parsed;
+    }
+
+    /**
+     * Gets the <code>ParsedOption</code> that was parsed from
+     * the command line.
+     */
+    public ParsedOption getParsed() {
+	return parsed;
+    }
+
+    public void setOption(OptionElement option) {
+	this.option = option;
+    }
+
+    /**
+     * Gets the <code>OptionElement</code> that matches the parsed
+     * option.
+     */
+    public OptionElement getOption() {
+	return option;
+    }
+
+    /**
+     * Gets a formatted representation of this option.
+     */
+    public String getFormatted(OptionFormatter formatter) {
+	return getFormatted(parsed, option, formatter);
+    }
+
+    //
+    // Static methods
+    //
+
+    /**
+     * Gets a formatted representation of this option.
+     */
+    public static String getFormatted(ParsedOption parsed, OptionElement option,
+	OptionFormatter formatter) {
+
+	// Return the formatted option if one was specified
+	String opt = parsed.getOpt();
+	if (opt != null && !opt.isEmpty()) {
+	    return formatter.getFormatted(opt);
+	}
+
+	// Return the option argument if one was specified
+	String optArg = parsed.getOptArg();
+	if (optArg != null && !optArg.isEmpty()) {
+	    return "\"" + optArg + "\"";
+	}
+
+	// Return the formatted option from the option definition
+	return formatter.getFormatted(option, false);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/ParsedOption.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,74 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+/**
+ * The <code>ParsedOption</code> class represents an option that has been
+ * processed from the command line.  Whereas the <code>OptionElement</code>
+ * class describes the semantics of the option, this class reflects the option
+ * string that was actually parsed.
+ */
+public class ParsedOption {
+    //
+    // Instance data
+    //
+
+    private String opt;
+    private String optArg;
+
+    //
+    // Constructors
+    //
+
+    public ParsedOption(String opt, String optArg) {
+	this.opt = opt;
+	setOptArg(optArg);
+    }
+
+    //
+    // ParsedOption methods
+    //
+
+    /**
+     * Gets the option (sans leading dashes, etc.) that was processed.
+     */
+    public String getOpt() {
+	return opt;
+    }
+
+    public void setOptArg(String optArg) {
+	this.optArg = optArg;
+    }
+
+    /**
+     * Gets the option argument value, if any, that was processed with
+     * this option.
+     */
+    public String getOptArg() {
+	return optArg;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/PosixCommandLineParser.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,203 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.util.*;
+import org.opensolaris.os.vp.util.TextUtil;
+
+public class PosixCommandLineParser extends CommandLineParser {
+    //
+    // Inner classes
+    //
+
+    protected static class Pointer {
+	public int argPointer;
+	public int charIndex;
+	public Pointer(int argPointer, int charIndex) {
+	    this.argPointer = argPointer;
+	    this.charIndex = charIndex;
+	}
+    }
+
+    //
+    // Static data
+    //
+
+    // Options consist of any character except whitespace, '-', and '='
+    private static final String ALLOWED_OPTION_CHARS = "[\\S&&[^-=]]";
+
+    // Group 1 is the short option list
+    private static final String REGEX_SHORT_OPTION =
+	"^" + PosixOptionFormatter.PREFIX_SHORT_OPTION +
+	"(" + ALLOWED_OPTION_CHARS + ".*)";
+
+    // Group 1 is the long option, group 3 is the (optional) value
+    private static final String REGEX_LONG_OPTION =
+	"^" + PosixOptionFormatter.PREFIX_LONG_OPTION +
+	"(" + ALLOWED_OPTION_CHARS + "*)(=(.*))?";
+
+    //
+    // Instance data
+    //
+
+    private int argPointer;
+    private int charIndex;
+    private Stack<Pointer> pointerStack = new Stack<Pointer>();
+    private OptionFormatter formatter = new PosixOptionFormatter();
+
+    //
+    // Constructors
+    //
+
+    public PosixCommandLineParser() {
+	reset();
+    }
+
+    //
+    // PosixCommandLineParser methods
+    //
+
+    public ParsedOption getNextOption() {
+	// Are we at the end of the raw argument list?
+	if (getIsDoneParsing()) {
+	    return null;
+	}
+
+	// Save position of this option
+	pointerStack.push(new Pointer(argPointer, charIndex));
+
+	String arg = getArgs()[argPointer];
+	String opt = null;
+	String optArg = null;
+	String[] groups;
+
+	// Is the current raw argument a short option?
+	if ((groups = TextUtil.match(arg, REGEX_SHORT_OPTION)) != null) {
+
+	    // Group 1 is the short option list
+	    opt = groups[1].substring(charIndex, charIndex + 1);
+
+	    // Have we reached the end of this raw argument?
+	    if (++charIndex == groups[1].length()) {
+		// Go to next raw argument
+		argPointer++;
+		charIndex = 0;
+	    }
+	} else
+
+	// Is the current raw argument a long option?
+	if ((groups = TextUtil.match(arg, REGEX_LONG_OPTION)) != null) {
+
+	    // Group 1 is the long option, group 3 is the (optional) value
+	    opt = groups[1];
+	    optArg = groups[3];
+
+	    // Go to next raw argument
+	    argPointer++;
+	} else {
+
+	    // The current raw argument is not an option -- return an
+	    // empty short option
+	    opt = "";
+	    optArg = arg;
+
+	    // Go to next raw argument
+	    argPointer++;
+	}
+
+//	System.out.println("Found option: " + opt + "=" + optArg);
+
+	ParsedOption option = new ParsedOption(opt, optArg);
+
+	return option;
+    }
+
+    public void getNextOptArg(ParsedOption option) {
+	// An opt arg must not aleady have been found
+	if (option.getOptArg() != null ||
+
+	    // Are we at the end of the raw argument list?
+	    getIsDoneParsing()) {
+	    return;
+	}
+
+	String arg = getArgs()[argPointer];
+	String[] groups;
+
+	// Is the current raw argument a short option?
+	if ((groups = TextUtil.match(arg, REGEX_SHORT_OPTION)) != null) {
+
+	    // If we are at the beginning of this arg, we can't
+	    // retrieve an optArg
+	    if (charIndex != 0) {
+
+		// The remainder of the short option list (group 1) is
+		// the optArg
+		option.setOptArg(groups[1].substring(charIndex));
+
+		argPointer++;
+		charIndex = 0;
+	    }
+	} else
+
+	// Is the current raw argument *not* a long option?
+	if ((groups = TextUtil.match(arg, REGEX_LONG_OPTION)) == null) {
+
+	    // Not an option -- return an empty short option
+	    option.setOptArg(arg);
+
+	    // Go to next raw argument
+	    argPointer++;
+	}
+
+//	System.out.println("Found optArg: " + option.getOpt() + "=" +
+//	    option.getOptArg());
+    }
+
+    public void reset() {
+	argPointer = 0;
+	charIndex = 0;
+	pointerStack.clear();
+    }
+
+    public void resetToPreviousOption() {
+	try {
+	    Pointer pointer = pointerStack.pop();
+	    argPointer = pointer.argPointer;
+	    charIndex = pointer.charIndex;
+	}
+	catch (EmptyStackException ignore) {}
+    }
+
+    public boolean getIsDoneParsing() {
+	return argPointer == getArgs().length;
+    }
+
+    public OptionFormatter getOptionFormatter() {
+	return formatter;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/PosixOptionFormatter.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,86 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+public class PosixOptionFormatter extends OptionFormatter {
+    //
+    // Static data
+    //
+
+    public static final String PREFIX_SHORT_OPTION = "-";
+    public static final String PREFIX_LONG_OPTION = "--";
+    public static final String OPT_ARG_LONG_SEPARATOR = "=";
+    public static final String OPT_ARG_SHORT_SEPARATOR = " ";
+
+    //
+    // OptionFormatter methods
+    //
+
+    @Override
+    public String getFormatted(String[] opts) {
+	return getFormatted(opts, null);
+    }
+
+    @Override
+    public String getFormatted(String[] opts, String optArg) {
+	String separator = "";
+	StringBuffer buffer = new StringBuffer();
+
+	for (int i = 0; i < opts.length; i++) {
+	    String opt = opts[i];
+
+	    if (i != 0) {
+		buffer.append(OPT_NAME_DELIM);
+	    }
+
+	    // Is this a non-option opt?
+	    if (opt.length() == 0) {
+		separator = "";
+	    } else
+
+	    // Is this a short opt?
+	    if (opt.length() == 1) {
+		separator = OPT_ARG_SHORT_SEPARATOR;
+		buffer.append(PREFIX_SHORT_OPTION);
+	    } else {
+
+		// This is a long opt
+		separator = OPT_ARG_LONG_SEPARATOR;
+		buffer.append(PREFIX_LONG_OPTION);
+	    }
+
+	    buffer.append(opt);
+
+	    if (optArg != null) {
+		buffer.append(separator).append(OPT_ARG_ENCLOSE_LEFT).
+		    append(optArg).append(OPT_ARG_ENCLOSE_RIGHT);
+	    }
+	}
+
+	return buffer.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/UsageFormatter.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,135 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli;
+
+import java.util.*;
+import org.opensolaris.os.vp.util.*;
+
+public class UsageFormatter {
+    //
+    // Static data
+    //
+
+    public static final int _WIDTH = 65;
+    public static final int _INDENT = 4;
+
+    //
+    // Instance data
+    //
+
+    private String command;
+    private Option option;
+    private OptionFormatter formatter;
+    private String usageLabel = Finder.getString("command.usage.header");
+    private int width = _WIDTH;
+    private int indent = _INDENT;
+
+    //
+    // Constructors
+    //
+
+    public UsageFormatter(String command, Option option,
+	OptionFormatter formatter) {
+
+	this.command = command;
+	this.option = option;
+	this.formatter = formatter;
+    }
+
+    //
+    // UsageFormatter methods
+    //
+
+    public String getCommand() {
+	return command;
+    }
+
+    public Option getOption() {
+	return option;
+    }
+
+    public OptionFormatter getOptionFormatter() {
+	return formatter;
+    }
+
+    public void setUsageLabel(String usageLabel) {
+	this.usageLabel = usageLabel;
+    }
+
+    public String getUsageLabel() {
+	return usageLabel;
+    }
+
+    public void setWidth(int width) {
+	this.width = width;
+    }
+
+    public int getWidth() {
+	return width;
+    }
+
+    public void setIndent(int indent) {
+	this.indent = indent;
+    }
+
+    public int getIndent() {
+	return indent;
+    }
+
+    public String getUsage() {
+	Option option = getOption();
+	List<Option> options;
+	if (option instanceof OptionGroup) {
+	    options = ((OptionGroup)option).getOptions();
+	} else {
+	    options = new ArrayList<Option>();
+	    options.add(option);
+	}
+
+	String usageLabel = getUsageLabel();
+	String whitespace = usageLabel.replaceAll("\\S", " ");
+	String indent2 = whitespace +
+	    String.format("%" + getIndent() + "s", "");
+	int width = getWidth();
+
+	StringBuffer buffer = new StringBuffer();
+	for (int i = 0, n = options.size(); i < n; i++) {
+	    String indent1 = i == 0 ? usageLabel : whitespace;
+	    String synopsis = command + " " +
+		formatter.getFormatted(options.get(i), false);
+
+	    buffer.append(TextUtil.format(
+		synopsis, width, indent1, indent2, false));
+
+	    if (i != n - 1) {
+		buffer.append("\n");
+	    }
+	}
+
+	return buffer.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/ConflictingOptionsException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,65 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+import org.opensolaris.os.vp.util.Finder;
+
+public class ConflictingOptionsException extends OptionException {
+    //
+    // Instance data
+    //
+
+    private String opt;
+    private String optInUse;
+
+    //
+    // Constructors
+    //
+
+    public ConflictingOptionsException(String optInUse, String opt,
+	OptionMap[] processed) {
+
+	super(Finder.getString("command.error.option.conflict", opt, optInUse),
+	    processed);
+
+	this.opt = opt;
+	this.optInUse = optInUse;
+    }
+
+    //
+    // ConflictingOptionsException methods
+    //
+
+    public String getOpt() {
+	return opt;
+    }
+
+    public String getOptInUse() {
+	return optInUse;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/InvalidOptArgException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,81 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+import org.opensolaris.os.vp.util.Finder;
+
+public class InvalidOptArgException extends OptionException {
+    //
+    // Instance data
+    //
+
+    private String opt;
+    private String optArg;
+    private Throwable cause;
+
+    //
+    // Constructors
+    //
+
+    public InvalidOptArgException(String opt, String optArg,
+	OptionMap[] processed) {
+
+	super(Finder.getString(opt == null || opt.equals("") ?
+	    "command.error.option.argument.invalid.noopt" :
+	    "command.error.option.argument.invalid", opt, optArg), processed);
+	this.opt = opt;
+	this.optArg = optArg;
+    }
+
+    public InvalidOptArgException(String opt, String optArg,
+	OptionMap[] processed, Throwable cause) {
+	this(opt, optArg, processed);
+	this.cause = cause;
+    }
+
+    //
+    // Throwable methods
+    //
+
+    @Override
+    public Throwable getCause() {
+	return cause;
+    }
+
+    //
+    // InvalidOptArgException methods
+    //
+
+    public String getOpt() {
+	return opt;
+    }
+
+    public String getOptArg() {
+	return optArg;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/InvalidOptionException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,55 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+import org.opensolaris.os.vp.util.Finder;
+
+public class InvalidOptionException extends OptionException {
+    //
+    // Instance data
+    //
+
+    private String opt;
+
+    //
+    // Constructors
+    //
+
+    public InvalidOptionException(String opt, OptionMap[] processed) {
+	super(Finder.getString("command.error.option.invalid", opt), processed);
+	this.opt = opt;
+    }
+
+    //
+    // InvalidOptionException methods
+    //
+
+    public String getOpt() {
+	return opt;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/MissingOptArgException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,56 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+import org.opensolaris.os.vp.util.Finder;
+
+public class MissingOptArgException extends OptionException {
+    //
+    // Instance data
+    //
+
+    private String opt;
+
+    //
+    // Constructors
+    //
+
+    public MissingOptArgException(String opt, OptionMap[] processed) {
+	super(Finder.getString("command.error.option.argument.missing", opt),
+	    processed);
+	this.opt = opt;
+    }
+
+    //
+    // MissingOptArgException methods
+    //
+
+    public String getOpt() {
+	return opt;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/MissingOptionException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,55 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+import org.opensolaris.os.vp.util.Finder;
+
+public class MissingOptionException extends OptionException {
+    //
+    // Instance data
+    //
+
+    private String opt;
+
+    //
+    // Constructors
+    //
+
+    public MissingOptionException(String opt, OptionMap[] processed) {
+	super(Finder.getString("command.error.option.missing", opt), processed);
+	this.opt = opt;
+    }
+
+    //
+    // MissingOptionException methods
+    //
+
+    public String getOpt() {
+	return opt;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/OptionException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,53 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+
+public class OptionException extends Exception {
+    //
+    // Instance data
+    //
+
+    private OptionMap[] processed;
+
+    //
+    // Constructors
+    //
+
+    public OptionException(String message, OptionMap[] processed) {
+	super(message);
+	this.processed = processed;
+    }
+
+    /**
+     * Gets the valid options processed before this OptionException occurred.
+     */
+    public OptionMap[] getProcessed() {
+	return processed;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/OptionUseExceededException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,65 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+import org.opensolaris.os.vp.util.Finder;
+
+public class OptionUseExceededException extends OptionException {
+    //
+    // Instance data
+    //
+
+    private String opt;
+    private int max;
+
+    //
+    // Constructors
+    //
+
+    public OptionUseExceededException(String opt, int max,
+	OptionMap[] processed) {
+
+	super(Finder.getString("command.error.option.useexceeded." + max, opt,
+	    max), processed);
+
+	this.opt = opt;
+	this.max = max;
+    }
+
+    //
+    // OptionUseExceededException methods
+    //
+
+    public String getOpt() {
+	return opt;
+    }
+
+    public int getMaximumUses() {
+	return max;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/exception/UnexpectedOptArgException.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,65 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.cli.exception;
+
+import org.opensolaris.os.vp.cli.OptionMap;
+import org.opensolaris.os.vp.util.Finder;
+
+public class UnexpectedOptArgException extends OptionException {
+    //
+    // Instance data
+    //
+
+    private String opt;
+    private String optArg;
+
+    //
+    // Constructors
+    //
+
+    public UnexpectedOptArgException(String opt, String optArg,
+	OptionMap[] processed) {
+
+	super(Finder.getString("command.error.option.argument.unexpected", opt,
+	    optArg), processed);
+
+	this.opt = opt;
+	this.optArg = optArg;
+    }
+
+    //
+    // UnexpectedOptArgException methods
+    //
+
+    public String getOpt() {
+	return opt;
+    }
+
+    public String getOptArg() {
+	return optArg;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/cli/resources/Resources.properties	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,43 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+# Trailing whitespace intentional
+command.error.delim = : 
+
+command.error.option.argument.invalid = {0}: invalid argument: {1}
+command.error.option.argument.invalid.noopt = invalid argument: {1}
+command.error.option.argument.missing = option requires an argument: {0}
+command.error.option.argument.unexpected = {0}: unexpected argument: {1}
+command.error.option.conflict = {0} cannot be specified with {1}
+command.error.option.invalid = unknown option: {0}
+command.error.option.missing = required option not specified: {0}
+command.error.option.useexceeded = option can only be specified {1} times: {0}
+command.error.option.useexceeded.1 = option can only be specified once: {0}
+
+command.help.options = Options:
+
+# Trailing whitespace intentional
+command.usage.header = Usage: 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/java/util/org/opensolaris/os/vp/util/BeanUtil.java	Thu Sep 18 17:18:30 2008 -0400
@@ -0,0 +1,179 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+package org.opensolaris.os.vp.util;
+
+import java.beans.*;
+import java.lang.reflect.*;
+
+public class BeanUtil {
+    //
+    // Static data
+    //
+
+    private static final String MUTATOR_PREFIX = "set";
+
+    //
+    // Static methods
+    //
+
+    public static void setPropertyInBean(Object bean, String property,
+	String valueAsText) throws IntrospectionException,
+	IllegalAccessException, NoSuchMethodException,
+	InvocationTargetException {
+
+	String methodName = getMutatorMethodName(property);
+	Method method = null;
+
+//	System.out.println("Bean: " + bean.getClass().getName());
+
+	if (valueAsText == null) {
+//	    System.out.println("Looking for null setter: " + methodName);
+	    // Find and invoke a mutator method with no parameters
+	    try {
+		method = bean.getClass().getMethod(methodName);
+		method.invoke(bean);
+//		System.out.println("Found method: " + method.getName());
+	    }
+	    catch (NoSuchMethodException ignore) {}
+	}
+
+	if (method == null) {
+	    // Search for an appropriate mutator method (throws
+	    // IntrospectionException)
+	    BeanInfo info = Introspector.getBeanInfo(bean.getClass());
+
+	    if (info != null) {
+		PropertyDescriptor descriptors[] =
+		    info.getPropertyDescriptors();
+
+		for (PropertyDescriptor descriptor : descriptors) {
+		    if (descriptor.getName().equals(property)) {
+			method = descriptor.getWriteMethod();
+
+			if (method != null) {
+			    Class type = descriptor.getPropertyType();
+
+			    // Is there a property editor for this type?
+			    PropertyEditor editor = null;
+			    Class propEdClass =
+				descriptor.getPropertyEditorClass();
+			    if (propEdClass != null) {
+				try {
+				    editor = (PropertyEditor)
+					propEdClass.newInstance();
+				}
+				catch (Exception ignore) {}
+			    }
+
+			    // No editor specifically for this
+			    // property -- check the PropertyEditorManager
+			    if (editor == null) {
+				editor = PropertyEditorManager.findEditor(type);
+			    }
+
+			    Object value = null;
+
+			    // If an editor was found...
+			    if (editor != null) {
+				// Use it to convert text to usable value
+				editor.setAsText(valueAsText);
+				value = editor.getValue();
+			    } else {
+				// Convert text to usable value
+				value = getValueAs(type, valueAsText);
+			    }
+
+			    method.invoke(bean, value);
+			}
+			break;
+		    }
+		}
+	    }
+	}
+
+	if (method == null) {
+	    throw new NoSuchMethodException(
+		bean.getClass().getName() + "." + methodName);
+	}
+    }
+
+    public static String getMutatorMethodName(String property) {
+	if (property == null || property.length() == 0) {
+	    return null;
+	}
+	return MUTATOR_PREFIX +
+	    property.substring(0, 1).toUpperCase() + property.substring(1);
+    }
+
+    //
+    // Private static methods
+    //
+
+    private static Object getValueAs(Class type, String value) {
+	if (value == null) {
+	    return null;
+	}
+
+	if (type.equals(Boolean.class) || type.equals(Boolean.TYPE)) {
+	    return new Boolean(value);
+	}
+
+	if (type.equals(Byte.class) || type.equals(Byte.TYPE)) {
+	    return new Byte(value);
+	}
+
+	if (type.equals(Character.class) || type.equals(Character.TYPE)) {
+	    return value.length() == 0 ? null : new Character(value.charAt(0));
+	}
+
+	if (type.equals(Short.class) || type.equals(Short.TYPE)) {
+	    return new Short(value);
+	}
+
+	if (type.equals(Integer.class) || type.equals(Integer.TYPE)) {
+	    return new Integer(value);
+	}
+
+	if (type.equals(Float.class) || type.equals(Float.TYPE)) {
+	    return new Float(value);
+	}
+
+	if (type.equals(Long.class) || type.equals(Long.TYPE)) {
+	    return new Long(value);
+	}
+
+	if (type.equals(Double.class) || type.equals(Double.TYPE)) {
+	    return new Double(value);
+	}
+
+	if (type.isInstance(value)) {
+	    return value;
+	}
+
+	return null;
+    }
+}
--- a/usr/src/java/util/org/opensolaris/os/vp/util/TextUtil.java	Tue Sep 16 13:32:03 2008 -0400
+++ b/usr/src/java/util/org/opensolaris/os/vp/util/TextUtil.java	Thu Sep 18 17:18:30 2008 -0400
@@ -130,6 +130,12 @@
      * @param	    width
      *		    the maximum width of each line
      *
+     * @param	    indent1
+     *		    the indent to put on the first line
+     *
+     * @param	    indent2
+     *		    the indent to put on the second and remaining lines
+     *
      * @param	    forceBreak
      *		    whether to force a break in the middle of a word
      *		    if the length of the word is greater than
@@ -137,7 +143,9 @@
      *
      * @return	    a wrapped string separated by newlines
      */
-    public static String format(String text, int width, boolean forceBreak) {
+    public static String format(String text, int width,
+	String indent1, String indent2, boolean forceBreak) {
+
 	if (text == null) {
 	    return text;
 	}
@@ -147,7 +155,8 @@
 
 	StringBuffer buffer = new StringBuffer();
 
-	int w = width;
+	String indent = indent1;
+	int w = width - indent.length();
 	if (w < 1) {
 	    w = 1;
 	}
@@ -173,11 +182,12 @@
 		buffer.append("\n");
 	    }
 
-	    buffer.append(groups[1].trim());
+	    buffer.append(indent).append(groups[1].trim());
 	    text = groups[2];
 
 	    if (i == 0) {
-		w = width;
+		indent = indent2;
+		w = width - indent.length();
 		if (w < 1) {
 		    w = 1;
 		}
@@ -187,6 +197,19 @@
 	return buffer.toString();
     }
 
+    /**
+     * Shortcut for:
+     * <p>
+     *	 <code>
+     *	   {@link #format(String,int,String,String,boolean) format}
+     *	   (text, width, "", "", forceBreak);
+     *	 </code>
+     * </p>
+     */
+    public static String format(String text, int width, boolean forceBreak) {
+	return format(text, width, "", "", forceBreak);
+    }
+
     public static boolean isPrintable(char c) {
 	return c >= 32 && c <= 126;
     }
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/App.java	Tue Sep 16 13:32:03 2008 -0400
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/App.java	Thu Sep 18 17:18:30 2008 -0400
@@ -1,5 +1,5 @@
 /*
- * cddl header start
+ * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
  * Common Development and Distribution License (the "License").
@@ -33,6 +33,7 @@
 import javax.management.remote.*;
 import javax.swing.*;
 import org.opensolaris.os.uds.*;
+import org.opensolaris.os.vp.cli.*;
 import org.opensolaris.os.vp.client.common.*;
 import org.opensolaris.os.vp.jmx.connector.UDSConnector;
 import org.opensolaris.os.vp.panel.common.action.ActionAbortedException;
@@ -47,9 +48,143 @@
  */
 public class App extends Thread implements AppConstants {
     //
+    // Inner classes
+    //
+
+    public static class CommandLineOptionsBean {
+	//
+	// Instance data
+	//
+
+	private String host = LOCAL_HOST;
+	private String user = LOCAL_USER;
+	private String role;
+	private String address;
+	private boolean newJVM;
+	private HelpFormatter help;
+
+	//
+	// Constructors
+	//
+
+	public CommandLineOptionsBean(HelpFormatter help) {
+	    this.help = help;
+	}
+
+	//
+	// CommandLineOptionsBean methods
+	//
+
+	public String getAddress() {
+	    return address;
+	}
+
+	public String getHost() {
+	    return host;
+	}
+
+	public boolean getNewjvm() {
+	    return newJVM;
+	}
+
+	public String getRole() {
+	    return role;
+	}
+
+	public String getUser() {
+	    return user;
+	}
+
+	public void setAddress(String address) {
+	    if (!address.startsWith("/")) {
+		// Assume non-relative path is a standalone shortcut
+		address = String.format("/%s/%s/%s",
+		    Control.encode(AppRootControl.ID, null),
+		    Control.encode(StandaloneControl.ID, null),
+		    address);
+	    }
+	    this.address = address;
+	}
+
+	public void setHelp() {
+	    System.out.println(help.getHelp());
+	    System.exit(0);
+	}
+
+	public void setHost(String host) {
+	    this.host = host;
+	}
+
+	public void setNewjvm() {
+	    newJVM = true;
+	}
+
+	public void setRole(String role) {
+	    this.role = role;
+	}
+
+	public void setUser(String user) {
+	    this.user = user;
+	}
+
+	public void setVersion() {
+	    showVersion();
+	    System.exit(0);
+	}
+    }
+
+    //
     // Static data
     //
 
+    private static final String ARG_NONOPT_ADDRESS = "address";
+    private static final String ARG_SHORT_HOST = "h";
+    private static final String ARG_LONG_HOST = "host";
+    private static final String ARG_SHORT_NEWJVM = "j";
+    private static final String ARG_LONG_NEWJVM = "newjvm";
+    private static final String ARG_SHORT_USER = "u";
+    private static final String ARG_LONG_USER = "user";
+    private static final String ARG_SHORT_ROLE = "r";
+    private static final String ARG_LONG_ROLE = "role";
+    private static final String ARG_SHORT_VERSION = "v";
+    private static final String ARG_LONG_VERSION = "version";
+    private static final String ARG_SHORT_HELP = "?";
+    private static final String ARG_LONG_HELP = "help";
+
+    private static final String COMMAND_NAME = "vp";
+    private static final String COMMAND_DESC =
+	Finder.getString("cli.description");
+
+    private static OptionChoiceGroup options;
+    static {
+	OptionElement addressOption = new NoOptOptionElement(
+	    false, ARG_NONOPT_ADDRESS, Finder.getString("cli.arg.address"), 1);
+
+	OptionElement hostOption = new OptionElement(ARG_SHORT_HOST,
+	    ARG_LONG_HOST, false, "host", Finder.getString("cli.arg.host"));
+
+	OptionElement newJVMOption = new OptionElement(ARG_SHORT_NEWJVM,
+	    ARG_LONG_NEWJVM, false, Finder.getString("cli.arg.newjvm"));
+
+	OptionElement userOption = new OptionElement(ARG_SHORT_USER,
+	    ARG_LONG_USER, false, "user", Finder.getString("cli.arg.user"));
+
+	OptionElement roleOption = new OptionElement(ARG_SHORT_ROLE,
+	    ARG_LONG_ROLE, false, "role", Finder.getString("cli.arg.role"));
+
+	OptionElement versionOption = new OptionElement(ARG_SHORT_VERSION,
+	    ARG_LONG_VERSION, false, Finder.getString("cli.arg.version"));
+
+	OptionElement helpOption = new OptionElement(ARG_SHORT_HELP,
+	    ARG_LONG_HELP, false, Finder.getString("cli.arg.help"));
+
+	OptionListGroup mainGroup = new OptionListGroup(false,
+	    hostOption, userOption, roleOption, newJVMOption, addressOption);
+
+	options = new OptionChoiceGroup(false, mainGroup, versionOption,
+	    helpOption);
+    }
+
     public static final String URI_SCHEME = "vp";
     public static final String URI_USER_ROLE_SEPARATOR = ":";
 
@@ -339,6 +474,10 @@
 	thread.setName(name);
     }
 
+    public static void showVersion() {
+	System.out.println(Finder.getString("cli.error.version", VERSION));
+    }
+
     public static URI createURI(String host, String user, String role, int port,
 	String address) throws URISyntaxException {
 
@@ -349,68 +488,30 @@
 	return new URI(URI_SCHEME, user, host, -1, address, null, null);
     }
 
-    private static void die(int exitCode, String resource, Object... args) {
-	String message = Finder.getString(resource, args);
-	PrintStream out = exitCode == 0 ? System.out : System.err;
-	out.println(message);
-	System.exit(exitCode);
-    }
-
     public static void main(String args[]) {
 	addVersionToThreadName(Thread.currentThread());
 
-	String host = LOCAL_HOST;
-	String user = LOCAL_USER;
-	String role = null;
-	String address = null;
-	String usage = "cli.error.usage";
-	String version = "cli.error.version";
-	boolean sharedVM = true;
+	CommandLineParser parser = new PosixCommandLineParser();
+	UsageFormatter usage = new UsageFormatter(
+	    COMMAND_NAME, options, parser.getOptionFormatter());
+	HelpFormatter help = new HelpFormatter(COMMAND_DESC, usage);
+	CommandLineOptionsBean bean = new CommandLineOptionsBean(help);
 
 	if (System.getProperty("vpanels.debug.version") != null) {
-	    System.err.printf("%s\n", Finder.getString(version, VERSION));
+	    showVersion();
 	}
 
-	for (int i = 0; i < args.length; i++) {
-	    String arg = args[i];
-	    if (arg.matches("^-(-help|\\?)$")) {
-		die(0, usage);
-	    } else if (arg.matches("^-(-version|v)$")) {
-		die(0, version, VERSION);
-	    } else if (arg.matches("^-[hru]$")) {
-		if (i == args.length - 1) {
-		    die(1, usage);
-		}
+	try {
+	    // Populate bean
+	    CommandLineProcessor.process(args, options, parser, bean);
+	}
 
-		String optArg = args[++i];
-		if (arg.equals("-h")) {
-		    host = optArg;
-		} else if (arg.equals("-r")) {
-		    role = optArg;
-		} else if (arg.equals("-u")) {
-		    user = optArg;
-		}
-	    } else if (arg.startsWith("--shared-vm=")) {
-		int index = arg.indexOf('=') + 1;
-		String value = arg.substring(index);
-		sharedVM = Boolean.parseBoolean(value);
-	    } else {
-		if (address != null) {
-		    die(1, usage);
-		}
-
-		address = arg;
-		if (!address.startsWith("/")) {
-		    // Assume non-relative path is a standalone shortcut
-		    address = String.format("/%s/%s/%s",
-			Control.encode(AppRootControl.ID, null),
-			Control.encode(StandaloneControl.ID, null),
-			address);
-		}
-	    }
+	catch (Exception e) {
+	    CommandUtil.exit(e, usage);
 	}
 
 	// Navigate to the console if no address given
+	String address = bean.getAddress();
 	if (address == null) {
 	    address = String.format("/%s/%s",
 		Control.encode(AppRootControl.ID, null),
@@ -419,12 +520,14 @@
 
 	URI uri = null;
 	try {
-	    uri = createURI(host, user, role, -1, address);
+	    uri = createURI(bean.getHost(), bean.getUser(), bean.getRole(), -1,
+		address);
 	} catch (URISyntaxException e) {
-	    die(1, "cli.error.uri", e.getInput());
+	    System.err.println(Finder.getString("cli.error.uri", e.getInput()));
+	    System.exit(1);
 	}
 
-	if (sharedVM) {
+	if (!bean.getNewjvm()) {
 	    // Check for running instance
 	    try {
 		UDSocket socket = UnixDomainSocket.connect(VP_UDS);
@@ -453,7 +556,7 @@
 	App app = new App(uri);
 	app.exitIfNoInstances();
 
-	if (sharedVM) {
+	if (!bean.getNewjvm()) {
 	    // Start daemon thread listening for connections
 	    app.start();
 	}
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppConstants.java.in	Tue Sep 16 13:32:03 2008 -0400
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppConstants.java.in	Thu Sep 18 17:18:30 2008 -0400
@@ -1,5 +1,5 @@
 /*
- * cddl header start
+ * CDDL HEADER START
  *
  * The contents of this file are subject to the terms of the
  * Common Development and Distribution License (the "License").
--- a/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/resources/Resources.properties	Tue Sep 16 13:32:03 2008 -0400
+++ b/usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/resources/Resources.properties	Thu Sep 18 17:18:30 2008 -0400
@@ -100,7 +100,15 @@
 panelload.error.fmri = panel returned invalid fmri: {0}
 panelload.error.jmx = could not communicate with the JMX agent on {0}
 
-cli.error.usage = Usage: vp [-h <host>] [-u <user>] [-r <role>] [<address>...]\n       vp -?
+cli.description = Launch Solaris system management panels.
+cli.arg.address = Specify the URI identifying the management panel to display.
+cli.arg.host = Specify an optional host to log into.  The default is the local machine.
+cli.arg.user = Specify an optional user to log in as.  The default is the current user.
+cli.arg.role = Specify an optional role to use when logging in.
+cli.arg.newjvm = Start a new JVM for this invocation instead of using an existing JVM.
+cli.arg.help = Show this message and exit.
+cli.arg.version = Show the version and exit.
+
 cli.error.version = vp {0}
 cli.error.uri = specified arguments resulted in an invalid URI: {0}