From 67fe39c9b91686bb1a8d2fc5feaf5de096061a58 Mon Sep 17 00:00:00 2001
From: Violette Roche-Montane <violette.roche-montane@forgerock.com>
Date: Tue, 04 Feb 2014 13:51:34 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-1303 Split out CLI support from opendj-ldap-toolkit into a separate Maven module, "opendj-cli" - Added SubCommand && SubCommandParser. - Added more commonsArguments. - Replaced "double" in IntegerArgument to "int". - Added LINE_SEPARATOR in Utils. - Updated Constants. - Updated messages.
---
opendj-cli/src/main/java/com/forgerock/opendj/cli/Utils.java | 3
opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java | 2
opendj-cli/src/main/java/com/forgerock/opendj/cli/IntegerArgument.java | 149 --
opendj-cli/src/main/java/com/forgerock/opendj/cli/CommonArguments.java | 427 +++++++++
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommand.java | 359 +++++++
opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties | 198 ++++
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java | 1531 +++++++++++++++++++++++++++++++++
opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java | 42
opendj-cli/src/main/java/com/forgerock/opendj/cli/CliConstants.java | 16
9 files changed, 2,582 insertions(+), 145 deletions(-)
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java
index e75df99..6e3cb89 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java
@@ -40,7 +40,7 @@
* for an application. This is an abstract class that must be subclassed in
* order to provide specific functionality.
*/
-abstract class Argument {
+public abstract class Argument {
// Indicates whether this argument should be hidden in the usage
// information.
private boolean isHidden;
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
index 0b1a392..264ead1 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
@@ -44,6 +44,7 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
+import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
@@ -62,7 +63,7 @@
* file to obtain default values for arguments there if they are not specified
* on the command-line.
*/
-public final class ArgumentParser {
+public class ArgumentParser {
/**
* The argument that will be used to indicate the file properties.
*/
@@ -143,7 +144,7 @@
private String[] rawArguments;
/** Set of argument groups. */
- private Set<ArgumentGroup> argumentGroups;
+ protected Set<ArgumentGroup> argumentGroups;
/**
* Group for arguments that have not been explicitly grouped. These will
@@ -1593,4 +1594,41 @@
buffer.append(EOL);
}
}
+
+ void normalizeArguments(final Properties argumentProperties, final List<Argument> arguments)
+ throws ArgumentException {
+ for (final Argument a : arguments) {
+ if (!a.isPresent()
+ // See if there is a value in the properties that can be used
+ && argumentProperties != null && a.getPropertyName() != null) {
+ final String value = argumentProperties.getProperty(a.getPropertyName().toLowerCase());
+ final LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
+ if (value != null) {
+ Boolean addValue = true;
+ if (!(a instanceof BooleanArgument)) {
+ addValue = a.valueIsAcceptable(value, invalidReason);
+ }
+ if (addValue) {
+ a.addValue(value);
+ if (a.needsValue()) {
+ a.setPresent(true);
+ }
+ a.setValueSetByProperty(true);
+ }
+ }
+ }
+
+ if (!a.isPresent() && a.needsValue()) {
+ // See if the argument defines a default.
+ if (a.getDefaultValue() != null) {
+ a.addValue(a.getDefaultValue());
+ }
+
+ // If there is still no value and the argument is required, then that's a problem.
+ if (!a.hasValue() && a.isRequired()) {
+ throw new ArgumentException(ERR_ARGPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName()));
+ }
+ }
+ }
+ }
}
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/CliConstants.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/CliConstants.java
index 70354ce..bf34149 100755
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/CliConstants.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/CliConstants.java
@@ -617,6 +617,22 @@
*/
public static final String OPTION_LONG_OUTPUT_LDIF_FILENAME = "outputLDIF";
+ /**
+ * The value for the long option to automatically accept the license
+ * if present.
+ */
+ public static final String OPTION_LONG_ACCEPT_LICENSE = "acceptLicense";
+
+ /**
+ * The value for the short option rootUserDN.
+ */
+ public static final char OPTION_SHORT_ROOT_USER_DN = 'D';
+
+ /**
+ * The value for the long option rootUserDN.
+ */
+ public static final String OPTION_LONG_ROOT_USER_DN = "rootUserDN";
+
// Prevent instantiation.
private CliConstants() {
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/CommonArguments.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/CommonArguments.java
index 038ed2a..d9b4d40 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/CommonArguments.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/CommonArguments.java
@@ -46,8 +46,7 @@
* If there is a problem with any of the parameters used to create this argument.
*/
public static final BooleanArgument getShowUsage() throws ArgumentException {
- return new BooleanArgument("showUsage", OPTION_SHORT_HELP, OPTION_LONG_HELP,
- INFO_DESCRIPTION_SHOWUSAGE.get());
+ return new BooleanArgument("showUsage", OPTION_SHORT_HELP, OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
}
/**
@@ -71,9 +70,8 @@
* If there is a problem with any of the parameters used to create this argument.
*/
public static final StringArgument getPropertiesFileArgument() throws ArgumentException {
- return new StringArgument("propertiesFilePath", null, OPTION_LONG_PROP_FILE_PATH,
- false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
- INFO_DESCRIPTION_PROP_FILE_PATH.get());
+ return new StringArgument("propertiesFilePath", null, OPTION_LONG_PROP_FILE_PATH, false, false, true,
+ INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null, INFO_DESCRIPTION_PROP_FILE_PATH.get());
}
/**
@@ -117,4 +115,423 @@
return version;
}
+ /**
+ * Returns the "quiet" boolean argument.
+ *
+ * @return The "quiet" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getQuiet() throws ArgumentException {
+ return new BooleanArgument(OPTION_LONG_QUIET, OPTION_SHORT_QUIET, OPTION_LONG_QUIET,
+ INFO_DESCRIPTION_QUIET.get());
+ }
+
+ /**
+ * Returns the "no-prompt" boolean argument.
+ *
+ * @return The "no-prompt" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getNoPrompt() throws ArgumentException {
+ return new BooleanArgument(OPTION_LONG_NO_PROMPT, OPTION_SHORT_NO_PROMPT, OPTION_LONG_NO_PROMPT,
+ INFO_DESCRIPTION_NO_PROMPT.get());
+ }
+
+ /**
+ * Returns the "acceptLicense" boolean argument.
+ *
+ * @return The "acceptLicense" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getAcceptLicense() throws ArgumentException {
+ return new BooleanArgument(OPTION_LONG_ACCEPT_LICENSE, null, OPTION_LONG_ACCEPT_LICENSE,
+ INFO_OPTION_ACCEPT_LICENSE.get());
+ }
+
+ /**
+ * Returns the "test only" boolean argument.
+ *
+ * @return The "test only" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getTestOnly() throws ArgumentException {
+ final BooleanArgument testOnly = new BooleanArgument("testOnly".toLowerCase(), 't', "testOnly",
+ INFO_ARGUMENT_DESCRIPTION_TESTONLY.get());
+ testOnly.setHidden(true);
+ testOnly.setPropertyName("testOnly");
+ return testOnly;
+ }
+
+ /**
+ * Returns the "CLI" boolean argument.
+ *
+ * @return The "CLI" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getCLI() throws ArgumentException {
+ final BooleanArgument cli = new BooleanArgument(OPTION_LONG_CLI.toLowerCase(), OPTION_SHORT_CLI,
+ OPTION_LONG_CLI, INFO_ARGUMENT_DESCRIPTION_CLI.get());
+ cli.setPropertyName(OPTION_LONG_CLI);
+ return cli;
+ }
+
+ /**
+ * Returns the "baseDN" string argument.
+ *
+ * @return The "baseDN" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getBaseDN() throws ArgumentException {
+ return new StringArgument(OPTION_LONG_BASEDN.toLowerCase(), OPTION_SHORT_BASEDN, OPTION_LONG_BASEDN, false,
+ true, true, INFO_BASEDN_PLACEHOLDER.get(), null, OPTION_LONG_BASEDN,
+ INFO_ARGUMENT_DESCRIPTION_BASEDN.get());
+ }
+
+ /**
+ * Returns the "add base entry" boolean argument.
+ *
+ * @return The "addBaseEntry" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getAddBaseEntry() throws ArgumentException {
+ final BooleanArgument addBaseEntryArg = new BooleanArgument("addBaseEntry".toLowerCase(), 'a', "addBaseEntry",
+ INFO_ARGUMENT_DESCRIPTION_ADDBASE.get());
+ addBaseEntryArg.setPropertyName("addBaseEntry");
+ return addBaseEntryArg;
+ }
+
+ /**
+ * Returns the "import LDIF" string argument.
+ *
+ * @return The "import LDIF" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getImportLDIF() throws ArgumentException {
+ return new StringArgument(OPTION_LONG_LDIF_FILE.toLowerCase(), OPTION_SHORT_LDIF_FILE, OPTION_LONG_LDIF_FILE,
+ false, true, true, INFO_LDIFFILE_PLACEHOLDER.get(), null, OPTION_LONG_LDIF_FILE,
+ INFO_ARGUMENT_DESCRIPTION_IMPORTLDIF.get());
+ }
+
+ /**
+ * Returns the "rejected import ldif file" string argument.
+ *
+ * @return The "rejectFile" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getRejectedImportLdif() throws ArgumentException {
+ return new StringArgument("rejectFile".toLowerCase(), 'R', "rejectFile", false, false, true,
+ INFO_REJECT_FILE_PLACEHOLDER.get(), null, "rejectFile", INFO_GENERAL_DESCRIPTION_REJECTED_FILE.get());
+ }
+
+ /**
+ * Returns the "skip file" string argument.
+ *
+ * @return The "skipFile" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getSkippedImportFile() throws ArgumentException {
+ return new StringArgument("skipFile".toLowerCase(), null, "skipFile", false, false, true,
+ INFO_SKIP_FILE_PLACEHOLDER.get(), null, "skipFile", INFO_GENERAL_DESCRIPTION_SKIPPED_FILE.get());
+ }
+
+ /**
+ * Returns the "sample data" integer argument.
+ *
+ * @return The "sampleData" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final IntegerArgument getSampleData() throws ArgumentException {
+ return new IntegerArgument("sampleData".toLowerCase(), 'd', "sampleData", false, false, true,
+ INFO_NUM_ENTRIES_PLACEHOLDER.get(), 0, "sampleData", true, 0, false, 0,
+ INFO_SETUP_DESCRIPTION_SAMPLE_DATA.get());
+ }
+
+ /**
+ * Returns the "LDAP port" integer argument.
+ *
+ * @param defaultLdapPort
+ * Default LDAP Connector port.
+ * @return The "ldapPort" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final IntegerArgument getLDAPPort(final int defaultLdapPort) throws ArgumentException {
+ return new IntegerArgument("ldapPort".toLowerCase(), OPTION_SHORT_PORT, "ldapPort", false, false, true,
+ INFO_PORT_PLACEHOLDER.get(), defaultLdapPort, "ldapPort", true, 1, true, 65535,
+ INFO_ARGUMENT_DESCRIPTION_LDAPPORT.get());
+ }
+
+ /**
+ * Returns the "Admin port" integer argument.
+ *
+ * @param defaultAdminPort
+ * Default Administration Connector port.
+ * @return The "adminConnectorPort" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final IntegerArgument getAdminLDAPPort(final int defaultAdminPort) throws ArgumentException {
+ return new IntegerArgument("adminConnectorPort".toLowerCase(), null, "adminConnectorPort", false, false, true,
+ INFO_PORT_PLACEHOLDER.get(), defaultAdminPort, "adminConnectorPort", true, 1, true, 65535,
+ INFO_ARGUMENT_DESCRIPTION_ADMINCONNECTORPORT.get());
+ }
+
+ /**
+ * Returns the "JMX port" integer argument.
+ *
+ * @param defaultJMXPort
+ * Default JMX port.
+ * @return The "jmxPort" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final IntegerArgument getJMXPort(final int defaultJMXPort) throws ArgumentException {
+ return new IntegerArgument("jmxPort".toLowerCase(), 'x', "jmxPort", false, false, true,
+ INFO_JMXPORT_PLACEHOLDER.get(), defaultJMXPort, "jmxPort", true, 1, true, 65535,
+ INFO_ARGUMENT_DESCRIPTION_SKIPPORT.get());
+ }
+
+ /**
+ * Returns the "skip port check" boolean argument.
+ *
+ * @return The "getSkipPortCheck" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getSkipPortCheck() throws ArgumentException {
+ final BooleanArgument skipPortCheck = new BooleanArgument("skipPortCheck".toLowerCase(), 'S', "skipPortCheck",
+ INFO_ARGUMENT_DESCRIPTION_SKIPPORT.get());
+ skipPortCheck.setPropertyName("skipPortCheck");
+ return skipPortCheck;
+ }
+
+ /**
+ * Returns the "directory manager DN" string argument.
+ *
+ * @return The "rootUserDN" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getRootDN() throws ArgumentException {
+ return new StringArgument(OPTION_LONG_ROOT_USER_DN.toLowerCase(), OPTION_SHORT_ROOT_USER_DN,
+ OPTION_LONG_ROOT_USER_DN, false, false, true, INFO_ROOT_USER_DN_PLACEHOLDER.get(),
+ "cn=Directory Manager", OPTION_LONG_ROOT_USER_DN, INFO_ARGUMENT_DESCRIPTION_ROOTDN.get());
+ }
+
+ /**
+ * Returns the "directory manager DN password" string argument.
+ *
+ * @return The "rootUserPassword" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getRootDNPwd() throws ArgumentException {
+ return new StringArgument("rootUserPassword".toLowerCase(), OPTION_SHORT_BINDPWD, "rootUserPassword", false,
+ false, true, INFO_ROOT_USER_PWD_PLACEHOLDER.get(), null, "rootUserPassword",
+ INFO_ROOT_USER_PWD_PLACEHOLDER.get());
+ }
+
+ /**
+ * Returns the "directory manager DN password file" file argument.
+ *
+ * @return The "rootUserPasswordFile" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final FileBasedArgument getRootDNPwdFile() throws ArgumentException {
+ return new FileBasedArgument("rootUserPasswordFile".toLowerCase(), OPTION_SHORT_BINDPWD_FILE,
+ "rootUserPasswordFile", false, false, INFO_ROOT_USER_PWD_FILE_PLACEHOLDER.get(), null,
+ "rootUserPasswordFile", INFO_ARGUMENT_DESCRIPTION_ROOTPWFILE.get());
+ }
+
+ /**
+ * Returns the "enable window service" integer argument.
+ *
+ * @return The "enableWindowsService" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getEnableWindowsService() throws ArgumentException {
+ final BooleanArgument enableWindowsServiceArg = new BooleanArgument("enableWindowsService".toLowerCase(), 'e',
+ "enableWindowsService", INFO_ARGUMENT_DESCRIPTION_ENABLE_WINDOWS_SERVICE.get());
+ enableWindowsServiceArg.setPropertyName("enableWindowsService");
+ return enableWindowsServiceArg;
+ }
+
+ /**
+ * Returns the "do not start" boolean argument.
+ *
+ * @return The "doNotStart" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getDoNotStart() throws ArgumentException {
+ final BooleanArgument doNotStartArg = new BooleanArgument("doNotStart".toLowerCase(), 'O', "doNotStart",
+ INFO_SETUP_DESCRIPTION_DO_NOT_START.get());
+ doNotStartArg.setPropertyName("doNotStart");
+ return doNotStartArg;
+ }
+
+ /**
+ * Returns the "enable start TLS" boolean argument.
+ *
+ * @return The "enableStartTLS" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getEnableTLS() throws ArgumentException {
+ final BooleanArgument enableStartTLS = new BooleanArgument("enableStartTLS".toLowerCase(),
+ OPTION_SHORT_START_TLS, "enableStartTLS", INFO_SETUP_DESCRIPTION_ENABLE_STARTTLS.get());
+ enableStartTLS.setPropertyName("enableStartTLS");
+ return enableStartTLS;
+ }
+
+ /**
+ * Returns the "ldaps port" integer argument.
+ *
+ * @param defaultSecurePort
+ * Default value for the LDAPS port.
+ * @return The "ldapsPort" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final IntegerArgument getLDAPSPort(final int defaultSecurePort) throws ArgumentException {
+ return new IntegerArgument("ldapsPort".toLowerCase(), OPTION_SHORT_USE_SSL, "ldapsPort", false, false, true,
+ INFO_PORT_PLACEHOLDER.get(), defaultSecurePort, "ldapsPort", true, 1, true, 65535,
+ INFO_ARGUMENT_DESCRIPTION_LDAPSPORT.get());
+ }
+
+ /**
+ * Returns the "generate self certificate" boolean argument.
+ *
+ * @return The "generateSelfSignedCertificate" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getGenerateSelfSigned() throws ArgumentException {
+ final BooleanArgument generateSelfSigned = new BooleanArgument("generateSelfSignedCertificate".toLowerCase(),
+ null, "generateSelfSignedCertificate", INFO_ARGUMENT_DESCRIPTION_USE_SELF_SIGNED_CERTIFICATE.get());
+ generateSelfSigned.setPropertyName("generateSelfSignedCertificate");
+ return generateSelfSigned;
+ }
+
+ /**
+ * Returns the "host name" string argument.
+ *
+ * @param defaultHostName
+ * The default host name value.
+ * @return The "hostname" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getHostName(final String defaultHostName) throws ArgumentException {
+ final StringArgument hostName = new StringArgument(OPTION_LONG_HOST.toLowerCase(), OPTION_SHORT_HOST,
+ OPTION_LONG_HOST, false, false, true, INFO_HOST_PLACEHOLDER.get(), defaultHostName, null,
+ INFO_ARGUMENT_DESCRIPTION_HOST_NAME.get());
+ hostName.setPropertyName(OPTION_LONG_HOST);
+ return hostName;
+ }
+
+ /**
+ * Returns the "use PKCS11 key store" boolean argument.
+ *
+ * @return The "usePkcs11Keystore" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final BooleanArgument getUsePKCS11Keystore() throws ArgumentException {
+ final BooleanArgument usePkcs11 = new BooleanArgument("usePkcs11Keystore".toLowerCase(), null,
+ "usePkcs11Keystore", INFO_ARGUMENT_DESCRIPTION_USE_PKCS11.get());
+ usePkcs11.setPropertyName("usePkcs11Keystore");
+ return usePkcs11;
+ }
+
+ /**
+ * Returns the "use java key store" string argument.
+ *
+ * @return The "useJavaKeystore" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getUseJavaKeyStore() throws ArgumentException {
+ return new StringArgument("useJavaKeystore".toLowerCase(), null, "useJavaKeystore", false, false, true,
+ INFO_KEYSTOREPATH_PLACEHOLDER.get(), null, "useJavaKeystore",
+ INFO_ARGUMENT_DESCRIPTION_USE_JAVAKEYSTORE.get());
+ }
+
+ /**
+ * Returns the "use JCEKS" string argument.
+ *
+ * @return The "useJCEKS" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getUseJCEKS() throws ArgumentException {
+ return new StringArgument("useJCEKS".toLowerCase(), null, "useJCEKS", false, false, true,
+ INFO_KEYSTOREPATH_PLACEHOLDER.get(), null, "useJCEKS", INFO_ARGUMENT_DESCRIPTION_USE_JCEKS.get());
+ }
+
+ /**
+ * Returns the "use PKCS12 key store" string argument.
+ *
+ * @return The "usePkcs12keyStore" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getUsePKCS12KeyStore() throws ArgumentException {
+ return new StringArgument("usePkcs12keyStore".toLowerCase(), null, "usePkcs12keyStore", false, false, true,
+ INFO_KEYSTOREPATH_PLACEHOLDER.get(), null, "usePkcs12keyStore",
+ INFO_ARGUMENT_DESCRIPTION_USE_PKCS12.get());
+ }
+
+ /**
+ * Returns the "key store password" string argument.
+ *
+ * @return The "keyStorePassword" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getKeyStorePassword() throws ArgumentException {
+ return new StringArgument(OPTION_LONG_KEYSTORE_PWD.toLowerCase(), OPTION_SHORT_KEYSTORE_PWD,
+ OPTION_LONG_KEYSTORE_PWD, false, false, true, INFO_KEYSTORE_PWD_PLACEHOLDER.get(), null,
+ OPTION_LONG_KEYSTORE_PWD, INFO_ARGUMENT_DESCRIPTION_KEYSTOREPASSWORD.get());
+ }
+
+ /**
+ * Returns the "key store password file" file argument.
+ *
+ * @return The "keyStorePassword" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final FileBasedArgument getKeyStorePasswordFile() throws ArgumentException {
+ return new FileBasedArgument(OPTION_LONG_KEYSTORE_PWD_FILE.toLowerCase(), OPTION_SHORT_KEYSTORE_PWD_FILE,
+ OPTION_LONG_KEYSTORE_PWD_FILE, false, false, INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get(), null,
+ OPTION_LONG_KEYSTORE_PWD_FILE, INFO_ARGUMENT_DESCRIPTION_KEYSTOREPASSWORD_FILE.get());
+ }
+
+ /**
+ * Returns the "key store password file" string argument.
+ *
+ * @return The "keyStorePassword" argument.
+ * @throws ArgumentException
+ * If there is a problem with any of the parameters used to create this argument.
+ */
+ public static final StringArgument getCertNickName() throws ArgumentException {
+ return new StringArgument(OPTION_LONG_CERT_NICKNAME.toLowerCase(), OPTION_SHORT_CERT_NICKNAME,
+ OPTION_LONG_CERT_NICKNAME, false, false, true, INFO_NICKNAME_PLACEHOLDER.get(), null,
+ OPTION_LONG_CERT_NICKNAME, INFO_ARGUMENT_DESCRIPTION_CERT_NICKNAME.get());
+ }
+
}
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/IntegerArgument.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/IntegerArgument.java
index 5ede248..cb57056 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/IntegerArgument.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/IntegerArgument.java
@@ -44,133 +44,10 @@
private final boolean hasUpperBound;
// The lower bound that will be enforced for this argument.
- private final double lowerBound;
+ private final int lowerBound;
// The upper bound that will be enforced for this argument.
- private final double upperBound;
-
- /**
- * Creates a new integer argument with the provided information.
- *
- * @param name
- * The generic name that should be used to refer to this
- * argument.
- * @param shortIdentifier
- * The single-character identifier for this argument, or
- * <CODE>null</CODE> if there is none.
- * @param longIdentifier
- * The long identifier for this argument, or <CODE>null</CODE> if
- * there is none.
- * @param isRequired
- * Indicates whether this argument must be specified on the
- * command line.
- * @param isMultiValued
- * Indicates whether this argument may be specified more than
- * once to provide multiple values.
- * @param needsValue
- * Indicates whether this argument requires a value.
- * @param valuePlaceholder
- * The placeholder for the argument value that will be displayed
- * in usage information, or <CODE>null</CODE> if this argument
- * does not require a value.
- * @param defaultValue
- * The default value that should be used for this argument if
- * none is provided in a properties file or on the command line.
- * This may be <CODE>null</CODE> if there is no generic default.
- * @param propertyName
- * The name of the property in a property file that may be used
- * to override the default value but will be overridden by a
- * command-line argument.
- * @param hasLowerBound
- * Indicates whether a lower bound should be enforced for values
- * of this argument.
- * @param lowerBound
- * The lower bound that should be enforced for values of this
- * argument.
- * @param hasUpperBound
- * Indicates whether an upperbound should be enforced for values
- * of this argument.
- * @param upperBound
- * The upper bound that should be enforced for values of this
- * argument.
- * @param description
- * LocalizableMessage for the description of this argument.
- * @throws ArgumentException
- * If there is a problem with any of the parameters used to
- * create this argument.
- */
- public IntegerArgument(final String name, final Character shortIdentifier,
- final String longIdentifier, final boolean isRequired, final boolean isMultiValued,
- final boolean needsValue, final LocalizableMessage valuePlaceholder,
- final double defaultValue, final String propertyName, final boolean hasLowerBound,
- final double lowerBound, final boolean hasUpperBound, final double upperBound,
- final LocalizableMessage description) throws ArgumentException {
- super(name, shortIdentifier, longIdentifier, isRequired, isMultiValued, needsValue,
- valuePlaceholder, String.valueOf(defaultValue), propertyName, description);
-
- this.hasLowerBound = hasLowerBound;
- this.hasUpperBound = hasUpperBound;
- this.lowerBound = lowerBound;
- this.upperBound = upperBound;
-
- if (hasLowerBound && hasUpperBound && (lowerBound > upperBound)) {
- final LocalizableMessage message =
- ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND.get(name, lowerBound, upperBound);
- throw new ArgumentException(message);
- }
- }
-
- /**
- * Creates a new integer argument with the provided information.
- *
- * @param name
- * The generic name that should be used to refer to this
- * argument.
- * @param shortIdentifier
- * The single-character identifier for this argument, or
- * <CODE>null</CODE> if there is none.
- * @param longIdentifier
- * The long identifier for this argument, or <CODE>null</CODE> if
- * there is none.
- * @param isRequired
- * Indicates whether this argument must be specified on the
- * command line.
- * @param isMultiValued
- * Indicates whether this argument may be specified more than
- * once to provide multiple values.
- * @param needsValue
- * Indicates whether this argument requires a value.
- * @param valuePlaceholder
- * The placeholder for the argument value that will be displayed
- * in usage information, or <CODE>null</CODE> if this argument
- * does not require a value.
- * @param defaultValue
- * The default value that should be used for this argument if
- * none is provided in a properties file or on the command line.
- * This may be <CODE>null</CODE> if there is no generic default.
- * @param propertyName
- * The name of the property in a property file that may be used
- * to override the default value but will be overridden by a
- * command-line argument.
- * @param description
- * LocalizableMessage for the description of this argument.
- * @throws ArgumentException
- * If there is a problem with any of the parameters used to
- * create this argument.
- */
- public IntegerArgument(final String name, final Character shortIdentifier,
- final String longIdentifier, final boolean isRequired, final boolean isMultiValued,
- final boolean needsValue, final LocalizableMessage valuePlaceholder,
- final double defaultValue, final String propertyName,
- final LocalizableMessage description) throws ArgumentException {
- super(name, shortIdentifier, longIdentifier, isRequired, isMultiValued, needsValue,
- valuePlaceholder, String.format("%f", defaultValue), propertyName, description);
-
- hasLowerBound = false;
- hasUpperBound = false;
- lowerBound = Integer.MIN_VALUE;
- upperBound = Integer.MAX_VALUE;
- }
+ private final int upperBound;
/**
* Creates a new integer argument with the provided information.
@@ -226,7 +103,7 @@
final String longIdentifier, final boolean isRequired, final boolean isMultiValued,
final boolean needsValue, final LocalizableMessage valuePlaceholder,
final int defaultValue, final String propertyName, final boolean hasLowerBound,
- final double lowerBound, final boolean hasUpperBound, final double upperBound,
+ final int lowerBound, final boolean hasUpperBound, final int upperBound,
final LocalizableMessage description) throws ArgumentException {
super(name, shortIdentifier, longIdentifier, isRequired, isMultiValued, needsValue,
valuePlaceholder, String.valueOf(defaultValue), propertyName, description);
@@ -337,7 +214,7 @@
public IntegerArgument(final String name, final Character shortIdentifier,
final String longIdentifier, final boolean isRequired, final boolean needsValue,
final LocalizableMessage valuePlaceholder, final boolean hasLowerBound,
- final double lowerBound, final boolean hasUpperBound, final double upperBound,
+ final int lowerBound, final boolean hasUpperBound, final int upperBound,
final LocalizableMessage description) throws ArgumentException {
super(name, shortIdentifier, longIdentifier, isRequired, false, needsValue,
valuePlaceholder, null, null, description);
@@ -390,8 +267,8 @@
hasLowerBound = false;
hasUpperBound = false;
- lowerBound = Double.MIN_VALUE;
- upperBound = Double.MAX_VALUE;
+ lowerBound = Integer.MIN_VALUE;
+ upperBound = Integer.MAX_VALUE;
}
/**
@@ -400,7 +277,7 @@
*
* @return The lower bound that may be enforced for values of this argument.
*/
- public double getLowerBound() {
+ public int getLowerBound() {
return lowerBound;
}
@@ -410,7 +287,7 @@
*
* @return The upper bound that may be enforced for values of this argument.
*/
- public double getUpperBound() {
+ public int getUpperBound() {
return upperBound;
}
@@ -452,18 +329,18 @@
public boolean valueIsAcceptable(final String valueString,
final LocalizableMessageBuilder invalidReason) {
// First, the value must be decodable as an integer.
- double intValue;
+ int intValue;
try {
- intValue = Double.parseDouble(valueString);
+ intValue = Integer.parseInt(valueString);
} catch (final Exception e) {
- invalidReason.append(ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, getName()));
+ invalidReason.append(ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, getPropertyName()));
return false;
}
// If there is a lower bound, then the value must be greater than or
// equal to it.
if (hasLowerBound && (intValue < lowerBound)) {
- invalidReason.append(ERR_INTARG_VALUE_BELOW_LOWER_BOUND.get(getName(), intValue,
+ invalidReason.append(ERR_INTARG_VALUE_BELOW_LOWER_BOUND.get(getPropertyName(), intValue,
lowerBound));
return false;
}
@@ -472,7 +349,7 @@
// equal to it.
if (hasUpperBound && (intValue > upperBound)) {
- invalidReason.append(ERR_INTARG_VALUE_ABOVE_UPPER_BOUND.get(getName(), intValue,
+ invalidReason.append(ERR_INTARG_VALUE_ABOVE_UPPER_BOUND.get(getPropertyName(), intValue,
upperBound));
return false;
}
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommand.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommand.java
new file mode 100644
index 0000000..f320edc
--- /dev/null
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommand.java
@@ -0,0 +1,359 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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 2006-2008 Sun Microsystems, Inc.
+ * Portions Copyright 2014 ForgeRock AS
+ */
+package com.forgerock.opendj.cli;
+
+import static com.forgerock.opendj.util.StaticUtils.toLowerCase;
+import static com.forgerock.opendj.cli.CliMessages.*;
+
+import org.forgerock.i18n.LocalizableMessage;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * This class defines a data structure for holding information about a subcommand that may be used with the subcommand
+ * argument parser. The subcommand has a name, a description, and a set of arguments.
+ */
+public class SubCommand {
+ // Indicates whether this subCommand should be hidden in the usage
+ // information.
+ private boolean isHidden;
+
+ // The mapping between the short argument IDs and the arguments for this
+ // subcommand.
+ private HashMap<Character, Argument> shortIDMap;
+
+ // The mapping between the long argument IDs and the arguments for this
+ // subcommand.
+ private HashMap<String, Argument> longIDMap;
+
+ // The list of arguments associated with this subcommand.
+ private LinkedList<Argument> arguments;
+
+ // The description for this subcommand.
+ private LocalizableMessage description;
+
+ // The name of this subcommand.
+ private String name;
+
+ // The argument parser with which this subcommand is associated.
+ private SubCommandArgumentParser parser;
+
+ // Indicates whether this parser will allow additional unnamed
+ // arguments at the end of the list.
+ private boolean allowsTrailingArguments;
+
+ // The maximum number of unnamed trailing arguments that may be
+ // provided.
+ private int maxTrailingArguments;
+
+ // The minimum number of unnamed trailing arguments that may be
+ // provided.
+ private int minTrailingArguments;
+
+ // The display name that will be used for the trailing arguments in
+ // the usage information.
+ private String trailingArgsDisplayName;
+
+ /**
+ * Creates a new subcommand with the provided information. The subcommand will be automatically registered with the
+ * associated parser.
+ *
+ * @param parser
+ * The argument parser with which this subcommand is associated.
+ * @param name
+ * The name of this subcommand.
+ * @param description
+ * The description of this subcommand.
+ * @throws ArgumentException
+ * If the associated argument parser already has a subcommand with the same name.
+ */
+ public SubCommand(SubCommandArgumentParser parser, String name, LocalizableMessage description)
+ throws ArgumentException {
+ this(parser, name, false, 0, 0, null, description);
+ }
+
+ /**
+ * Creates a new subcommand with the provided information. The subcommand will be automatically registered with the
+ * associated parser.
+ *
+ * @param parser
+ * The argument parser with which this subcommand is associated.
+ * @param name
+ * The name of this subcommand.
+ * @param allowsTrailingArguments
+ * Indicates whether this parser allows unnamed trailing arguments to be provided.
+ * @param minTrailingArguments
+ * The minimum number of unnamed trailing arguments that must be provided. A value less than or equal to
+ * zero indicates that no minimum will be enforced.
+ * @param maxTrailingArguments
+ * The maximum number of unnamed trailing arguments that may be provided. A value less than or equal to
+ * zero indicates that no maximum will be enforced.
+ * @param trailingArgsDisplayName
+ * The display name that should be used as a placeholder for unnamed trailing arguments in the generated
+ * usage information.
+ * @param description
+ * The description of this subcommand.
+ * @throws ArgumentException
+ * If the associated argument parser already has a subcommand with the same name.
+ */
+ public SubCommand(SubCommandArgumentParser parser, String name, boolean allowsTrailingArguments,
+ int minTrailingArguments, int maxTrailingArguments, String trailingArgsDisplayName,
+ LocalizableMessage description) throws ArgumentException {
+ this.parser = parser;
+ this.name = name;
+ this.description = description;
+ this.allowsTrailingArguments = allowsTrailingArguments;
+ this.minTrailingArguments = minTrailingArguments;
+ this.maxTrailingArguments = maxTrailingArguments;
+ this.trailingArgsDisplayName = trailingArgsDisplayName;
+ this.isHidden = false;
+
+ String nameToCheck = name;
+ if (parser.longArgumentsCaseSensitive()) {
+ nameToCheck = toLowerCase(name);
+ }
+
+ if (parser.hasSubCommand(nameToCheck)) {
+ LocalizableMessage message = ERR_ARG_SUBCOMMAND_DUPLICATE_SUBCOMMAND.get(name);
+ throw new ArgumentException(message);
+ }
+
+ parser.addSubCommand(this);
+ shortIDMap = new HashMap<Character, Argument>();
+ longIDMap = new HashMap<String, Argument>();
+ arguments = new LinkedList<Argument>();
+ }
+
+ /**
+ * Retrieves the name of this subcommand.
+ *
+ * @return The name of this subcommand.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Retrieves the description for this subcommand.
+ *
+ * @return The description for this subcommand.
+ */
+ public LocalizableMessage getDescription() {
+ return description;
+ }
+
+ /**
+ * Retrieves the set of arguments for this subcommand.
+ *
+ * @return The set of arguments for this subcommand.
+ */
+ public LinkedList<Argument> getArguments() {
+ return arguments;
+ }
+
+ /**
+ * Retrieves the subcommand argument with the specified short identifier.
+ *
+ * @param shortID
+ * The short identifier of the argument to retrieve.
+ * @return The subcommand argument with the specified short identifier, or <CODE>null</CODE> if there is none.
+ */
+ public Argument getArgument(Character shortID) {
+ return shortIDMap.get(shortID);
+ }
+
+ /**
+ * Retrieves the subcommand argument with the specified long identifier.
+ *
+ * @param longID
+ * The long identifier of the argument to retrieve.
+ * @return The subcommand argument with the specified long identifier, or <CODE>null</CODE> if there is none.
+ */
+ public Argument getArgument(String longID) {
+ return longIDMap.get(longID);
+ }
+
+ /**
+ * Retrieves the subcommand argument with the specified name.
+ *
+ * @param name
+ * The name of the argument to retrieve.
+ * @return The subcommand argument with the specified name, or <CODE>null</CODE> if there is no such argument.
+ */
+ public Argument getArgumentForName(String name) {
+ for (Argument a : arguments) {
+ if (a.getName().equals(name)) {
+ return a;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds the provided argument for use with this subcommand.
+ *
+ * @param argument
+ * The argument to add for use with this subcommand.
+ * @throws ArgumentException
+ * If either the short ID or long ID for the argument conflicts with that of another argument already
+ * associated with this subcommand.
+ */
+ public void addArgument(Argument argument) throws ArgumentException {
+ String argumentName = argument.getName();
+ for (Argument a : arguments) {
+ if (argumentName.equals(a.getName())) {
+ LocalizableMessage message = ERR_ARG_SUBCOMMAND_DUPLICATE_ARGUMENT_NAME.get(name, argumentName);
+ throw new ArgumentException(message);
+ }
+ }
+
+ if (parser.hasGlobalArgument(argumentName)) {
+ LocalizableMessage message = ERR_ARG_SUBCOMMAND_ARGUMENT_GLOBAL_CONFLICT.get(argumentName, name);
+ throw new ArgumentException(message);
+ }
+
+ Character shortID = argument.getShortIdentifier();
+ if (shortID != null) {
+ if (shortIDMap.containsKey(shortID)) {
+ LocalizableMessage message = ERR_ARG_SUBCOMMAND_DUPLICATE_SHORT_ID.get(argumentName, name,
+ String.valueOf(shortID), shortIDMap.get(shortID).getName());
+ throw new ArgumentException(message);
+ }
+
+ Argument arg = parser.getGlobalArgumentForShortID(shortID);
+ if (arg != null) {
+ LocalizableMessage message = ERR_ARG_SUBCOMMAND_ARGUMENT_SHORT_ID_GLOBAL_CONFLICT.get(argumentName,
+ name, String.valueOf(shortID), arg.getName());
+ throw new ArgumentException(message);
+ }
+ }
+
+ String longID = argument.getLongIdentifier();
+ if (longID != null) {
+ if (!parser.longArgumentsCaseSensitive()) {
+ longID = toLowerCase(longID);
+ }
+
+ if (longIDMap.containsKey(longID)) {
+ LocalizableMessage message = ERR_ARG_SUBCOMMAND_DUPLICATE_LONG_ID.get(argumentName, name,
+ argument.getLongIdentifier(), longIDMap.get(longID).getName());
+ throw new ArgumentException(message);
+ }
+
+ Argument arg = parser.getGlobalArgumentForLongID(longID);
+ if (arg != null) {
+ LocalizableMessage message = ERR_ARG_SUBCOMMAND_ARGUMENT_LONG_ID_GLOBAL_CONFLICT.get(argumentName,
+ name, argument.getLongIdentifier(), arg.getName());
+ throw new ArgumentException(message);
+ }
+ }
+
+ arguments.add(argument);
+
+ if (shortID != null) {
+ shortIDMap.put(shortID, argument);
+ }
+
+ if (longID != null) {
+ longIDMap.put(longID, argument);
+ }
+ }
+
+ /**
+ * Indicates whether this sub-command will allow unnamed trailing arguments. These will be arguments at the end of
+ * the list that are not preceded by either a long or short identifier and will need to be manually parsed by the
+ * application using this parser. Note that once an unnamed trailing argument has been identified, all remaining
+ * arguments will be classified as such.
+ *
+ * @return <CODE>true</CODE> if this sub-command allows unnamed trailing arguments, or <CODE>false</CODE> if it does
+ * not.
+ */
+ public boolean allowsTrailingArguments() {
+ return allowsTrailingArguments;
+ }
+
+ /**
+ * Retrieves the minimum number of unnamed trailing arguments that must be provided.
+ *
+ * @return The minimum number of unnamed trailing arguments that must be provided, or a value less than or equal to
+ * zero if no minimum will be enforced.
+ */
+ public int getMinTrailingArguments() {
+ return minTrailingArguments;
+ }
+
+ /**
+ * Retrieves the maximum number of unnamed trailing arguments that may be provided.
+ *
+ * @return The maximum number of unnamed trailing arguments that may be provided, or a value less than or equal to
+ * zero if no maximum will be enforced.
+ */
+ public int getMaxTrailingArguments() {
+ return maxTrailingArguments;
+ }
+
+ /**
+ * Retrieves the trailing arguments display name.
+ *
+ * @return Returns the trailing arguments display name.
+ */
+ public String getTrailingArgumentsDisplayName() {
+ return trailingArgsDisplayName;
+ }
+
+ /**
+ * Retrieves the set of unnamed trailing arguments that were provided on the command line.
+ *
+ * @return The set of unnamed trailing arguments that were provided on the command line.
+ */
+ public ArrayList<String> getTrailingArguments() {
+ return parser.getTrailingArguments();
+ }
+
+ /**
+ * Indicates whether this subcommand should be hidden from the usage information.
+ *
+ * @return <CODE>true</CODE> if this subcommand should be hidden from the usage information, or <CODE>false</CODE>
+ * if not.
+ */
+ public boolean isHidden() {
+ return isHidden;
+ }
+
+ /**
+ * Specifies whether this subcommand should be hidden from the usage information.
+ *
+ * @param isHidden
+ * Indicates whether this subcommand should be hidden from the usage information.
+ */
+ public void setHidden(boolean isHidden) {
+ this.isHidden = isHidden;
+ }
+}
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
new file mode 100644
index 0000000..2ca9dcf
--- /dev/null
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
@@ -0,0 +1,1531 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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 2006-2010 Sun Microsystems, Inc.
+ * Portions Copyright 2011-2014 ForgeRock AS
+ */
+package com.forgerock.opendj.cli;
+
+import static com.forgerock.opendj.util.StaticUtils.toLowerCase;
+import static com.forgerock.opendj.util.StaticUtils.EOL;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizableMessageBuilder;
+
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static com.forgerock.opendj.cli.CliConstants.*;
+import static com.forgerock.opendj.cli.Utils.*;
+
+/**
+ * This class defines a variant of the argument parser that can be used with applications that use subcommands to
+ * customize their behavior and that have a different set of options per subcommand (e.g, "cvs checkout" takes different
+ * options than "cvs commit"). This parser also has the ability to use global options that will always be applicable
+ * regardless of the subcommand in addition to the subcommand-specific arguments. There must not be any conflicts
+ * between the global options and the option for any subcommand, but it is allowed to re-use subcommand-specific options
+ * for different purposes between different subcommands.
+ */
+public class SubCommandArgumentParser extends ArgumentParser {
+ /**
+ * The argument that will be used to trigger the display of usage information.
+ */
+ private Argument usageArgument;
+
+ /**
+ * The arguments that will be used to trigger the display of usage information for groups of sub-commands.
+ */
+ private final Map<Argument, Collection<SubCommand>> usageGroupArguments;
+
+ /**
+ * The set of unnamed trailing arguments that were provided for this parser.
+ */
+ private ArrayList<String> trailingArguments;
+
+ /**
+ * Indicates whether subcommand and long argument names should be treated in a case-sensitive manner.
+ */
+ private final boolean longArgumentsCaseSensitive;
+
+ /** Indicates whether the usage information has been displayed. */
+ private boolean usageOrVersionDisplayed;
+
+ /**
+ * The set of global arguments defined for this parser, referenced by short ID.
+ */
+ private final Map<Character, Argument> globalShortIDMap;
+
+ /**
+ * The set of global arguments defined for this parser, referenced by argument name.
+ */
+ private final Map<String, Argument> globalArgumentMap;
+
+ /**
+ * The set of global arguments defined for this parser, referenced by long ID.
+ */
+ private final Map<String, Argument> globalLongIDMap;
+
+ /**
+ * The set of subcommands defined for this parser, referenced by subcommand name.
+ */
+ private final SortedMap<String, SubCommand> subCommands;
+
+ /** The total set of global arguments defined for this parser. */
+ private final List<Argument> globalArgumentList;
+
+ /** The output stream to which usage information should be printed. */
+ private OutputStream usageOutputStream;
+
+ /**
+ * The fully-qualified name of the Java class that should be invoked to launch the program with which this argument
+ * parser is associated.
+ */
+ private final String mainClassName;
+
+ /**
+ * A human-readable description for the tool, which will be included when displaying usage information.
+ */
+ private final LocalizableMessage toolDescription;
+
+ /** The raw set of command-line arguments that were provided. */
+ private String[] rawArguments;
+
+ /**
+ * The subcommand requested by the user as part of the command-line arguments.
+ */
+ private SubCommand subCommand;
+
+ /** Indicates whether the version argument was provided. */
+ private boolean versionPresent;
+
+ private static final String INDENT = " ";
+ private final static int MAX_LENGTH = 80;
+
+ /**
+ * Creates a new instance of this subcommand argument parser with no arguments.
+ *
+ * @param mainClassName
+ * The fully-qualified name of the Java class that should be invoked to launch the program with which
+ * this argument parser is associated.
+ * @param toolDescription
+ * A human-readable description for the tool, which will be included when displaying usage information.
+ * @param longArgumentsCaseSensitive
+ * Indicates whether subcommand and long argument names should be treated in a case-sensitive manner.
+ */
+ public SubCommandArgumentParser(String mainClassName, LocalizableMessage toolDescription,
+ boolean longArgumentsCaseSensitive) {
+ super(mainClassName, toolDescription, longArgumentsCaseSensitive);
+ this.mainClassName = mainClassName;
+ this.toolDescription = toolDescription;
+ this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
+
+ trailingArguments = new ArrayList<String>();
+ globalArgumentList = new LinkedList<Argument>();
+ globalArgumentMap = new HashMap<String, Argument>();
+ globalShortIDMap = new HashMap<Character, Argument>();
+ globalLongIDMap = new HashMap<String, Argument>();
+ usageGroupArguments = new HashMap<Argument, Collection<SubCommand>>();
+ subCommands = new TreeMap<String, SubCommand>();
+ usageOrVersionDisplayed = false;
+ rawArguments = null;
+ subCommand = null;
+ usageArgument = null;
+ usageOutputStream = null;
+ }
+
+ /**
+ * Retrieves the fully-qualified name of the Java class that should be invoked to launch the program with which this
+ * argument parser is associated.
+ *
+ * @return The fully-qualified name of the Java class that should be invoked to launch the program with which this
+ * argument parser is associated.
+ */
+ @Override
+ public String getMainClassName() {
+ return mainClassName;
+ }
+
+ /**
+ * Retrieves a human-readable description for this tool, which should be included at the top of the command-line
+ * usage information.
+ *
+ * @return A human-readable description for this tool, or {@code null} if none is available.
+ */
+ @Override
+ public LocalizableMessage getToolDescription() {
+ return toolDescription;
+ }
+
+ /**
+ * Indicates whether subcommand names and long argument strings should be treated in a case-sensitive manner.
+ *
+ * @return <CODE>true</CODE> if subcommand names and long argument strings should be treated in a case-sensitive
+ * manner, or <CODE>false</CODE> if they should not.
+ */
+ public boolean longArgumentsCaseSensitive() {
+ return longArgumentsCaseSensitive;
+ }
+
+ /**
+ * Retrieves the list of all global arguments that have been defined for this argument parser.
+ *
+ * @return The list of all global arguments that have been defined for this argument parser.
+ */
+ public List<Argument> getGlobalArgumentList() {
+ return globalArgumentList;
+ }
+
+ /**
+ * Indicates whether this argument parser contains a global argument with the specified name.
+ *
+ * @param argumentName
+ * The name for which to make the determination.
+ * @return <CODE>true</CODE> if a global argument exists with the specified name, or <CODE>false</CODE> if not.
+ */
+ public boolean hasGlobalArgument(String argumentName) {
+ return globalArgumentMap.containsKey(argumentName);
+ }
+
+ /**
+ * Retrieves the global argument with the specified name.
+ *
+ * @param name
+ * The name of the global argument to retrieve.
+ * @return The global argument with the specified name, or <CODE>null</CODE> if there is no such argument.
+ */
+ public Argument getGlobalArgument(String name) {
+ return globalArgumentMap.get(name);
+ }
+
+ /**
+ * Retrieves the set of global arguments mapped by the short identifier that may be used to reference them. Note
+ * that arguments that do not have a short identifier will not be present in this list.
+ *
+ * @return The set of global arguments mapped by the short identifier that may be used to reference them.
+ */
+ public Map<Character, Argument> getGlobalArgumentsByShortID() {
+ return globalShortIDMap;
+ }
+
+ /**
+ * Indicates whether this argument parser has a global argument with the specified short ID.
+ *
+ * @param shortID
+ * The short ID character for which to make the determination.
+ * @return <CODE>true</CODE> if a global argument exists with the specified short ID, or <CODE>false</CODE> if not.
+ */
+ public boolean hasGlobalArgumentWithShortID(Character shortID) {
+ return globalShortIDMap.containsKey(shortID);
+ }
+
+ /**
+ * Retrieves the global argument with the specified short identifier.
+ *
+ * @param shortID
+ * The short identifier for the global argument to retrieve.
+ * @return The global argument with the specified short identifier, or <CODE>null</CODE> if there is no such
+ * argument.
+ */
+ public Argument getGlobalArgumentForShortID(Character shortID) {
+ return globalShortIDMap.get(shortID);
+ }
+
+ /**
+ * Retrieves the set of global arguments mapped by the long identifier that may be used to reference them. Note that
+ * arguments that do not have a long identifier will not be present in this list.
+ *
+ * @return The set of global arguments mapped by the long identifier that may be used to reference them.
+ */
+ public Map<String, Argument> getGlobalArgumentsByLongID() {
+ return globalLongIDMap;
+ }
+
+ /**
+ * Indicates whether this argument parser has a global argument with the specified long ID.
+ *
+ * @param longID
+ * The long ID string for which to make the determination.
+ * @return <CODE>true</CODE> if a global argument exists with the specified long ID, or <CODE>false</CODE> if not.
+ */
+ public boolean hasGlobalArgumentWithLongID(String longID) {
+ return globalLongIDMap.containsKey(longID);
+ }
+
+ /**
+ * Retrieves the global argument with the specified long identifier.
+ *
+ * @param longID
+ * The long identifier for the global argument to retrieve.
+ * @return The global argument with the specified long identifier, or <CODE>null</CODE> if there is no such
+ * argument.
+ */
+ public Argument getGlobalArgumentForLongID(String longID) {
+ return globalLongIDMap.get(longID);
+ }
+
+ /**
+ * Retrieves the set of subcommands defined for this argument parser, referenced by subcommand name.
+ *
+ * @return The set of subcommands defined for this argument parser, referenced by subcommand name.
+ */
+ public SortedMap<String, SubCommand> getSubCommands() {
+ return subCommands;
+ }
+
+ /**
+ * Indicates whether this argument parser has a subcommand with the specified name.
+ *
+ * @param name
+ * The subcommand name for which to make the determination.
+ * @return <CODE>true</CODE> if this argument parser has a subcommand with the specified name, or <CODE>false</CODE>
+ * if it does not.
+ */
+ public boolean hasSubCommand(String name) {
+ return subCommands.containsKey(name);
+ }
+
+ /**
+ * Retrieves the subcommand with the specified name.
+ *
+ * @param name
+ * The name of the subcommand to retrieve.
+ * @return The subcommand with the specified name, or <CODE>null</CODE> if no such subcommand is defined.
+ */
+ public SubCommand getSubCommand(String name) {
+ return subCommands.get(name);
+ }
+
+ /**
+ * Retrieves the subcommand that was selected in the set of command-line arguments.
+ *
+ * @return The subcommand that was selected in the set of command-line arguments, or <CODE>null</CODE> if none was
+ * selected.
+ */
+ public SubCommand getSubCommand() {
+ return subCommand;
+ }
+
+ /**
+ * Retrieves the raw set of arguments that were provided.
+ *
+ * @return The raw set of arguments that were provided, or <CODE>null</CODE> if the argument list has not yet been
+ * parsed.
+ */
+ @Override
+ public String[] getRawArguments() {
+ return rawArguments;
+ }
+
+ /**
+ * Adds the provided argument to the set of global arguments handled by this parser.
+ *
+ * @param argument
+ * The argument to be added.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another global or subcommand argument that has already been
+ * defined.
+ */
+ public void addGlobalArgument(Argument argument) throws ArgumentException {
+ addGlobalArgument(argument, null);
+ }
+
+ /**
+ * Adds the provided argument to the set of global arguments handled by this parser.
+ *
+ * @param argument
+ * The argument to be added.
+ * @param group
+ * The argument group to which the argument belongs.
+ * @throws ArgumentException
+ * If the provided argument conflicts with another global or subcommand argument that has already been
+ * defined.
+ */
+ public void addGlobalArgument(Argument argument, ArgumentGroup group) throws ArgumentException {
+
+ String argumentName = argument.getName();
+ if (globalArgumentMap.containsKey(argumentName)) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_NAME.get(argumentName);
+ throw new ArgumentException(message);
+ }
+ for (SubCommand s : subCommands.values()) {
+ if (s.getArgumentForName(argumentName) != null) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_GLOBAL_ARG_NAME_SUBCMD_CONFLICT.get(argumentName,
+ s.getName());
+ throw new ArgumentException(message);
+ }
+ }
+
+ Character shortID = argument.getShortIdentifier();
+ if (shortID != null) {
+ if (globalShortIDMap.containsKey(shortID)) {
+ String name = globalShortIDMap.get(shortID).getName();
+
+ LocalizableMessage message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_SHORT_ID.get(
+ String.valueOf(shortID), argumentName, name);
+ throw new ArgumentException(message);
+ }
+
+ for (SubCommand s : subCommands.values()) {
+ if (s.getArgument(shortID) != null) {
+ String cmdName = s.getName();
+ String name = s.getArgument(shortID).getName();
+
+ LocalizableMessage message = ERR_SUBCMDPARSER_GLOBAL_ARG_SHORT_ID_CONFLICT.get(
+ String.valueOf(shortID), argumentName, name, cmdName);
+ throw new ArgumentException(message);
+ }
+ }
+ }
+
+ String longID = argument.getLongIdentifier();
+ if (longID != null) {
+ if (!longArgumentsCaseSensitive) {
+ longID = toLowerCase(longID);
+ }
+
+ if (globalLongIDMap.containsKey(longID)) {
+ String name = globalLongIDMap.get(longID).getName();
+
+ LocalizableMessage message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_LONG_ID.get(
+ argument.getLongIdentifier(), argumentName, name);
+ throw new ArgumentException(message);
+ }
+
+ for (SubCommand s : subCommands.values()) {
+ if (s.getArgument(longID) != null) {
+ String cmdName = s.getName();
+ String name = s.getArgument(longID).getName();
+
+ LocalizableMessage message = ERR_SUBCMDPARSER_GLOBAL_ARG_LONG_ID_CONFLICT.get(
+ argument.getLongIdentifier(), argumentName, name, cmdName);
+ throw new ArgumentException(message);
+ }
+ }
+ }
+
+ if (shortID != null) {
+ globalShortIDMap.put(shortID, argument);
+ }
+
+ if (longID != null) {
+ globalLongIDMap.put(longID, argument);
+ }
+
+ globalArgumentList.add(argument);
+
+ if (group == null) {
+ group = getStandardGroup(argument);
+ }
+ group.addArgument(argument);
+ argumentGroups.add(group);
+ }
+
+ /**
+ * Removes the provided argument from the set of global arguments handled by this parser.
+ *
+ * @param argument
+ * The argument to be removed.
+ */
+ protected void removeGlobalArgument(Argument argument) {
+ String argumentName = argument.getName();
+ globalArgumentMap.remove(argumentName);
+
+ Character shortID = argument.getShortIdentifier();
+ if (shortID != null) {
+ globalShortIDMap.remove(shortID);
+ }
+
+ String longID = argument.getLongIdentifier();
+ if (longID != null) {
+ if (!longArgumentsCaseSensitive) {
+ longID = toLowerCase(longID);
+ }
+
+ globalLongIDMap.remove(longID);
+ }
+
+ globalArgumentList.remove(argument);
+ }
+
+ /**
+ * Sets the provided argument as one which will automatically trigger the output of full usage information if it is
+ * provided on the command line and no further argument validation will be performed.
+ * <p>
+ * If sub-command groups are defined using the {@link #setUsageGroupArgument(Argument, Collection)} method, then
+ * this usage argument, when specified, will result in usage information being displayed which does not include
+ * information on sub-commands.
+ * <p>
+ * Note that the caller will still need to add this argument to the parser with the
+ * {@link #addGlobalArgument(Argument)} method, and the argument should not be required and should not take a value.
+ * Also, the caller will still need to check for the presence of the usage argument after calling
+ * {@link #parseArguments(String[])} to know that no further processing will be required.
+ *
+ * @param argument
+ * The argument whose presence should automatically trigger the display of full usage information.
+ * @param outputStream
+ * The output stream to which the usage information should be written.
+ */
+ @Override
+ public void setUsageArgument(Argument argument, OutputStream outputStream) {
+ usageArgument = argument;
+ usageOutputStream = outputStream;
+
+ usageGroupArguments.put(argument, Collections.<SubCommand> emptySet());
+ }
+
+ /**
+ * Sets the provided argument as one which will automatically trigger the output of partial usage information if it
+ * is provided on the command line and no further argument validation will be performed.
+ * <p>
+ * Partial usage information will include a usage synopsis, a summary of each of the sub-commands listed in the
+ * provided sub-commands collection, and a summary of the global options.
+ * <p>
+ * Note that the caller will still need to add this argument to the parser with the
+ * {@link #addGlobalArgument(Argument)} method, and the argument should not be required and should not take a value.
+ * Also, the caller will still need to check for the presence of the usage argument after calling
+ * {@link #parseArguments(String[])} to know that no further processing will be required.
+ *
+ * @param argument
+ * The argument whose presence should automatically trigger the display of partial usage information.
+ * @param subCommands
+ * The list of sub-commands which should have their usage displayed.
+ */
+ public void setUsageGroupArgument(Argument argument, Collection<SubCommand> subCommands) {
+ usageGroupArguments.put(argument, subCommands);
+ }
+
+ /**
+ * Parses the provided set of arguments and updates the information associated with this parser accordingly.
+ *
+ * @param rawArguments
+ * The raw set of arguments to parse.
+ * @throws ArgumentException
+ * If a problem was encountered while parsing the provided arguments.
+ */
+ @Override
+ public void parseArguments(String[] rawArguments) throws ArgumentException {
+ parseArguments(rawArguments, null);
+ }
+
+ /**
+ * Parses the provided set of arguments and updates the information associated with this parser accordingly. Default
+ * values for unspecified arguments may be read from the specified properties if any are provided.
+ *
+ * @param rawArguments
+ * The set of raw arguments to parse.
+ * @param argumentProperties
+ * A set of properties that may be used to provide default values for arguments not included in the given
+ * raw arguments.
+ * @throws ArgumentException
+ * If a problem was encountered while parsing the provided arguments.
+ */
+ @Override
+ public void parseArguments(String[] rawArguments, Properties argumentProperties) throws ArgumentException {
+ this.rawArguments = rawArguments;
+ this.subCommand = null;
+ this.trailingArguments = new ArrayList<String>();
+ this.usageOrVersionDisplayed = false;
+
+ boolean inTrailingArgs = false;
+
+ int numArguments = rawArguments.length;
+ for (int i = 0; i < numArguments; i++) {
+ final String arg = rawArguments[i];
+
+ if (inTrailingArgs) {
+ trailingArguments.add(arg);
+
+ if (subCommand == null) {
+ throw new ArgumentException(ERR_ARG_SUBCOMMAND_INVALID.get());
+ }
+
+ if (subCommand.getMaxTrailingArguments() > 0
+ && trailingArguments.size() > subCommand.getMaxTrailingArguments()) {
+ LocalizableMessage message = ERR_ARGPARSER_TOO_MANY_TRAILING_ARGS.get(subCommand
+ .getMaxTrailingArguments());
+ throw new ArgumentException(message);
+ }
+
+ continue;
+ }
+
+ if (arg.equals("--")) {
+ inTrailingArgs = true;
+ } else if (arg.startsWith("--")) {
+ // This indicates that we are using the long name to reference the
+ // argument. It may be in any of the following forms:
+ // --name
+ // --name value
+ // --name=value
+
+ String argName = arg.substring(2);
+ String argValue = null;
+ int equalPos = argName.indexOf('=');
+ if (equalPos < 0) {
+ // This is fine. The value is not part of the argument name token.
+ } else if (equalPos == 0) {
+ // The argument starts with "--=", which is not acceptable.
+ LocalizableMessage message = ERR_SUBCMDPARSER_LONG_ARG_WITHOUT_NAME.get(arg);
+ throw new ArgumentException(message);
+ } else {
+ // The argument is in the form --name=value, so parse them both out.
+ argValue = argName.substring(equalPos + 1);
+ argName = argName.substring(0, equalPos);
+ }
+
+ // If we're not case-sensitive, then convert the name to lowercase.
+ String origArgName = argName;
+ if (!longArgumentsCaseSensitive) {
+ argName = toLowerCase(argName);
+ }
+
+ // See if the specified name references a global argument. If not, then
+ // see if it references a subcommand argument.
+ Argument a = globalLongIDMap.get(argName);
+ if (a == null) {
+ if (subCommand == null) {
+ if (argName.equals(OPTION_LONG_HELP)) {
+ // "--help" will always be interpreted as requesting usage
+ // information.
+ getUsage(usageOutputStream);
+ return;
+ } else if (argName.equals(OPTION_LONG_PRODUCT_VERSION)) {
+ // "--version" will always be interpreted as requesting usage
+ // information.
+ versionPresent = true;
+ usageOrVersionDisplayed = true;
+ printVersion();
+ return;
+ } else {
+ // There is no such global argument.
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_LONG_ID
+ .get(origArgName);
+ throw new ArgumentException(message);
+ }
+ } else {
+ a = subCommand.getArgument(argName);
+ if (a == null) {
+ if (argName.equals(OPTION_LONG_HELP)) {
+ // "--help" will always be interpreted as requesting usage
+ // information.
+ getUsage(usageOutputStream);
+ return;
+ } else if (argName.equals(OPTION_LONG_PRODUCT_VERSION)) {
+ // "--version" will always be interpreted as requesting usage
+ // information.
+ versionPresent = true;
+ usageOrVersionDisplayed = true;
+ printVersion();
+ return;
+ } else {
+ // There is no such global or subcommand argument.
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_LONG_ID.get(origArgName);
+ throw new ArgumentException(message);
+ }
+ }
+ }
+ }
+
+ a.setPresent(true);
+
+ // If this is a usage argument, then immediately stop and print
+ // usage information.
+ if (usageGroupArguments.containsKey(a)) {
+ getUsage(a, usageOutputStream);
+ return;
+ }
+
+ // See if the argument takes a value. If so, then make sure one was
+ // provided. If not, then make sure none was provided.
+ if (a.needsValue()) {
+ if (argValue == null) {
+ if ((i + 1) == numArguments) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID
+ .get(argName);
+ throw new ArgumentException(message);
+ }
+
+ argValue = rawArguments[++i];
+ }
+
+ LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
+ if (!a.valueIsAcceptable(argValue, invalidReason)) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID.get(argValue,
+ argName, invalidReason.toString());
+ throw new ArgumentException(message);
+ }
+
+ // If the argument already has a value, then make sure it is
+ // acceptable to have more than one.
+ if (a.hasValue() && !a.isMultiValued()) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_LONG_ID.get(origArgName);
+ throw new ArgumentException(message);
+ }
+
+ a.addValue(argValue);
+ } else {
+ if (argValue != null) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE
+ .get(origArgName);
+ throw new ArgumentException(message);
+ }
+ }
+ } else if (arg.startsWith("-")) {
+ // This indicates that we are using the 1-character name to reference
+ // the argument. It may be in any of the following forms:
+ // -n
+ // -nvalue
+ // -n value
+ if (arg.equals("-")) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_INVALID_DASH_AS_ARGUMENT.get();
+ throw new ArgumentException(message);
+ }
+
+ char argCharacter = arg.charAt(1);
+ String argValue;
+ if (arg.length() > 2) {
+ argValue = arg.substring(2);
+ } else {
+ argValue = null;
+ }
+
+ // Get the argument with the specified short ID. It may be either a
+ // global argument or a subcommand-specific argument.
+ Argument a = globalShortIDMap.get(argCharacter);
+ if (a == null) {
+ if (subCommand == null) {
+ if (argCharacter == '?') {
+ // "-?" will always be interpreted as requesting usage.
+ getUsage(usageOutputStream);
+ if (usageArgument != null) {
+ usageArgument.setPresent(true);
+ }
+ return;
+ } else if (argCharacter == OPTION_SHORT_PRODUCT_VERSION) {
+ // "-V" will always be interpreted as requesting
+ // version information except if it's already defined.
+ if (dashVAccepted()) {
+ usageOrVersionDisplayed = true;
+ versionPresent = true;
+ printVersion();
+ return;
+ } else {
+ // -V is defined in another suncommand, so we can
+ // accepted it as the version information argument
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID
+ .get(String.valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+ } else {
+ // There is no such argument registered.
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID.get(String
+ .valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+ } else {
+ a = subCommand.getArgument(argCharacter);
+ if (a == null) {
+ if (argCharacter == '?') {
+ // "-?" will always be interpreted as requesting usage.
+ getUsage(usageOutputStream);
+ return;
+ } else if (argCharacter == OPTION_SHORT_PRODUCT_VERSION) {
+ if (dashVAccepted()) {
+ usageOrVersionDisplayed = true;
+ versionPresent = true;
+ printVersion();
+ return;
+ }
+ } else {
+ // There is no such argument registered.
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID.get(String
+ .valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+ }
+ }
+ }
+
+ a.setPresent(true);
+
+ // If this is the usage argument, then immediately stop and print
+ // usage information.
+ if (usageGroupArguments.containsKey(a)) {
+ getUsage(a, usageOutputStream);
+ return;
+ }
+
+ // See if the argument takes a value. If so, then make sure one was
+ // provided. If not, then make sure none was provided.
+ if (a.needsValue()) {
+ if (argValue == null) {
+ if ((i + 1) == numArguments) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID
+ .get(String.valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+
+ argValue = rawArguments[++i];
+ }
+
+ LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
+ if (!a.valueIsAcceptable(argValue, invalidReason)) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID.get(argValue,
+ String.valueOf(argCharacter), invalidReason.toString());
+ throw new ArgumentException(message);
+ }
+
+ // If the argument already has a value, then make sure it is
+ // acceptable to have more than one.
+ if (a.hasValue() && !a.isMultiValued()) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_SHORT_ID.get(String
+ .valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+
+ a.addValue(argValue);
+ } else {
+ if (argValue != null) {
+ // If we've gotten here, then it means that we're in a scenario like
+ // "-abc" where "a" is a valid argument that doesn't take a value.
+ // However, this could still be valid if all remaining characters in
+ // the value are also valid argument characters that don't take
+ // values.
+ int valueLength = argValue.length();
+ for (int j = 0; j < valueLength; j++) {
+ char c = argValue.charAt(j);
+ Argument b = globalShortIDMap.get(c);
+ if (b == null) {
+ if (subCommand == null) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID
+ .get(String.valueOf(argCharacter));
+ throw new ArgumentException(message);
+ } else {
+ b = subCommand.getArgument(c);
+ if (b == null) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID
+ .get(String.valueOf(argCharacter));
+ throw new ArgumentException(message);
+ }
+ }
+ }
+
+ if (b.needsValue()) {
+ // This means we're in a scenario like "-abc" where b is a
+ // valid argument that takes a value. We don't support that.
+ LocalizableMessage message = ERR_SUBCMDPARSER_CANT_MIX_ARGS_WITH_VALUES.get(
+ String.valueOf(argCharacter), argValue, String.valueOf(c));
+ throw new ArgumentException(message);
+ } else {
+ b.setPresent(true);
+
+ // If this is the usage argument, then immediately stop and
+ // print usage information.
+ if (usageGroupArguments.containsKey(b)) {
+ getUsage(b, usageOutputStream);
+ return;
+ }
+ }
+ }
+ }
+ }
+ } else if (subCommand != null) {
+ // It's not a short or long identifier and the sub-command has
+ // already been specified, so it must be the first trailing argument.
+ if (subCommand.allowsTrailingArguments()) {
+ trailingArguments.add(arg);
+ inTrailingArgs = true;
+ } else {
+ // Trailing arguments are not allowed for this sub-command.
+ LocalizableMessage message = ERR_ARGPARSER_DISALLOWED_TRAILING_ARGUMENT.get(arg);
+ throw new ArgumentException(message);
+ }
+ } else {
+ // It must be the sub-command.
+ String nameToCheck = arg;
+ if (!longArgumentsCaseSensitive) {
+ nameToCheck = toLowerCase(arg);
+ }
+
+ SubCommand sc = subCommands.get(nameToCheck);
+ if (sc == null) {
+ LocalizableMessage message = ERR_SUBCMDPARSER_INVALID_ARGUMENT.get(arg);
+ throw new ArgumentException(message);
+ } else {
+ subCommand = sc;
+ }
+ }
+ }
+
+ // If we have a sub-command and it allows trailing arguments and
+ // there is a minimum number, then make sure at least that many
+ // were provided.
+ if (subCommand != null) {
+ int minTrailingArguments = subCommand.getMinTrailingArguments();
+ if (subCommand.allowsTrailingArguments() && minTrailingArguments > 0
+ && trailingArguments.size() < minTrailingArguments) {
+ LocalizableMessage message = ERR_ARGPARSER_TOO_FEW_TRAILING_ARGUMENTS.get(minTrailingArguments);
+ throw new ArgumentException(message);
+ }
+ }
+
+ // If we don't have the argumentProperties, try to load a properties file.
+ if (argumentProperties == null) {
+ argumentProperties = checkExternalProperties();
+ }
+
+ // Iterate through all the global arguments
+ normalizeArguments(argumentProperties, globalArgumentList);
+
+ // Iterate through all the subcommand-specific arguments
+ if (subCommand != null) {
+ normalizeArguments(argumentProperties, subCommand.getArguments());
+ }
+ }
+
+ private boolean dashVAccepted() {
+ if (globalShortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION)) {
+ return false;
+ }
+ for (SubCommand subCmd : subCommands.values()) {
+ if (subCmd.getArgument(OPTION_SHORT_PRODUCT_VERSION) != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Appends usage information for the specified subcommand to the provided buffer.
+ *
+ * @param buffer
+ * The buffer to which the usage information should be appended.
+ * @param subCommand
+ * The subcommand for which to display the usage information.
+ */
+ public void getSubCommandUsage(LocalizableMessageBuilder buffer, SubCommand subCommand) {
+ usageOrVersionDisplayed = true;
+ String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+ if (scriptName == null || scriptName.length() == 0) {
+ scriptName = "java " + mainClassName;
+ }
+ buffer.append(INFO_ARGPARSER_USAGE_JAVA_SCRIPTNAME.get(scriptName));
+ buffer.append(" ");
+ buffer.append(scriptName);
+
+ buffer.append(" ");
+ buffer.append(subCommand.getName());
+ buffer.append(" ").append(INFO_SUBCMDPARSER_OPTIONS.get());
+ if (subCommand.allowsTrailingArguments()) {
+ buffer.append(' ');
+ buffer.append(subCommand.getTrailingArgumentsDisplayName());
+ }
+ buffer.append(EOL);
+ buffer.append(subCommand.getDescription());
+ buffer.append(EOL);
+
+ if (!globalArgumentList.isEmpty()) {
+ buffer.append(EOL);
+ buffer.append(INFO_GLOBAL_OPTIONS.get());
+ buffer.append(EOL);
+ buffer.append(" ");
+ buffer.append(INFO_GLOBAL_OPTIONS_REFERENCE.get(scriptName));
+ buffer.append(EOL);
+ }
+
+ if (!subCommand.getArguments().isEmpty()) {
+ buffer.append(EOL);
+ buffer.append(INFO_SUBCMD_OPTIONS.get());
+ buffer.append(EOL);
+ }
+ for (Argument a : subCommand.getArguments()) {
+ // If this argument is hidden, then skip it.
+ if (a.isHidden()) {
+ continue;
+ }
+
+ // Write a line with the short and/or long identifiers that may be used
+ // for the argument.
+ Character shortID = a.getShortIdentifier();
+ String longID = a.getLongIdentifier();
+ if (shortID != null) {
+ int currentLength = buffer.length();
+
+ if (a.equals(usageArgument)) {
+ buffer.append("-?, ");
+ }
+
+ buffer.append("-");
+ buffer.append(shortID.charValue());
+
+ if (a.needsValue() && longID == null) {
+ buffer.append(" ");
+ buffer.append(a.getValuePlaceholder());
+ }
+
+ if (longID != null) {
+ StringBuilder newBuffer = new StringBuilder();
+ newBuffer.append(", --");
+ newBuffer.append(longID);
+
+ if (a.needsValue()) {
+ newBuffer.append(" ");
+ newBuffer.append(a.getValuePlaceholder());
+ }
+
+ int lineLength = (buffer.length() - currentLength) + newBuffer.length();
+ if (lineLength > MAX_LENGTH) {
+ buffer.append(EOL);
+ }
+ buffer.append(newBuffer.toString());
+ }
+
+ buffer.append(EOL);
+ } else {
+ if (longID != null) {
+ if (a.equals(usageArgument)) {
+ buffer.append("-?, ");
+ }
+ buffer.append("--");
+ buffer.append(longID);
+
+ if (a.needsValue()) {
+ buffer.append(" ");
+ buffer.append(a.getValuePlaceholder());
+ }
+
+ buffer.append(EOL);
+ }
+ }
+
+ indentAndWrap2(INDENT, a.getDescription(), buffer);
+ if (a.needsValue() && a.getDefaultValue() != null && a.getDefaultValue().length() > 0) {
+ indentAndWrap2(INDENT, INFO_ARGPARSER_USAGE_DEFAULT_VALUE.get(a.getDefaultValue()), buffer);
+ }
+ }
+ }
+
+ /**
+ * Write one or more lines with the description of the argument. We will indent the description five characters and
+ * try our best to wrap at or before column 79 so it will be friendly to 80-column displays.
+ * <p>
+ * FIXME Try to merge with #indentAndWrap(LocalizableMessage, LocalizableMessage, LocalizableMessageBuilder).
+ */
+ private void indentAndWrap2(String indent, LocalizableMessage text, LocalizableMessageBuilder buffer) {
+ int actualSize = MAX_LENGTH - indent.length() - 1;
+ if (text.length() <= actualSize) {
+ buffer.append(indent);
+ buffer.append(text);
+ buffer.append(EOL);
+ } else {
+ String s = text.toString();
+ while (s.length() > actualSize) {
+ int spacePos = s.lastIndexOf(' ', actualSize);
+ if (spacePos > 0) {
+ buffer.append(indent);
+ buffer.append(s.substring(0, spacePos).trim());
+ s = s.substring(spacePos + 1).trim();
+ buffer.append(EOL);
+ } else {
+ // There are no spaces in the first 74 columns.
+ // See if there is one after that point.
+ // If so, then break there. If not, then don't break at all.
+ spacePos = s.indexOf(' ');
+ if (spacePos > 0) {
+ buffer.append(indent);
+ buffer.append(s.substring(0, spacePos).trim());
+ s = s.substring(spacePos + 1).trim();
+ buffer.append(EOL);
+ } else {
+ buffer.append(indent);
+ buffer.append(s);
+ s = "";
+ buffer.append(EOL);
+ }
+ }
+ }
+
+ if (s.length() > 0) {
+ buffer.append(indent);
+ buffer.append(s);
+ buffer.append(EOL);
+ }
+ }
+ }
+
+ /**
+ * Retrieves a string containing usage information based on the defined arguments.
+ *
+ * @return A string containing usage information based on the defined arguments.
+ */
+ @Override
+ public String getUsage() {
+ LocalizableMessageBuilder buffer = new LocalizableMessageBuilder();
+
+ if (subCommand == null) {
+ if (System.getProperty("org.forgerock.opendj.gendoc") != null) {
+ // Generate reference documentation for dsconfig subcommands
+ for (SubCommand s : subCommands.values()) {
+ buffer.append(toRefSect2(s));
+ }
+ } else if (usageGroupArguments.size() > 1) {
+ // We have sub-command groups, so don't display any
+ // sub-commands by default.
+ getFullUsage(Collections.<SubCommand> emptySet(), true, buffer);
+ } else {
+ // No grouping, so display all sub-commands.
+ getFullUsage(subCommands.values(), true, buffer);
+ }
+ } else {
+ getSubCommandUsage(buffer, subCommand);
+ }
+
+ return buffer.toMessage().toString();
+ }
+
+ /**
+ * Retrieves a string describing how the user can get more help.
+ *
+ * @return A string describing how the user can get more help.
+ */
+ public LocalizableMessage getHelpUsageReference() {
+ usageOrVersionDisplayed = true;
+ String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+ if (scriptName == null || scriptName.length() == 0) {
+ scriptName = "java " + mainClassName;
+ }
+
+ LocalizableMessageBuilder buffer = new LocalizableMessageBuilder();
+ buffer.append(INFO_GLOBAL_HELP_REFERENCE.get(scriptName));
+ buffer.append(EOL);
+ return buffer.toMessage();
+ }
+
+ /**
+ * Retrieves the set of unnamed trailing arguments that were provided on the command line.
+ *
+ * @return The set of unnamed trailing arguments that were provided on the command line.
+ */
+ @Override
+ public ArrayList<String> getTrailingArguments() {
+ return trailingArguments;
+ }
+
+ /**
+ * Indicates whether the usage information has been displayed to the end user either by an explicit argument like
+ * "-H" or "--help", or by a built-in argument like "-?".
+ *
+ * @return {@code true} if the usage information has been displayed, or {@code false} if not.
+ */
+ @Override
+ public boolean usageOrVersionDisplayed() {
+ return usageOrVersionDisplayed;
+ }
+
+ /**
+ * Adds the provided subcommand to this argument parser. This is only intended for use by the
+ * <CODE>SubCommand</CODE> constructor and does not do any validation of its own to ensure that there are no
+ * conflicts with the subcommand or any of its arguments.
+ *
+ * @param subCommand
+ * The subcommand to add to this argument parser.
+ */
+ void addSubCommand(SubCommand subCommand) {
+ subCommands.put(toLowerCase(subCommand.getName()), subCommand);
+ }
+
+ /** Get usage for a specific usage argument. */
+ private void getUsage(Argument a, OutputStream outputStream) {
+ LocalizableMessageBuilder buffer = new LocalizableMessageBuilder();
+
+ if (a.equals(usageArgument) && subCommand != null) {
+ getSubCommandUsage(buffer, subCommand);
+ } else if (a.equals(usageArgument) && usageGroupArguments.size() <= 1) {
+ // No groups - so display all sub-commands.
+ getFullUsage(subCommands.values(), true, buffer);
+ } else if (a.equals(usageArgument)) {
+ // Using groups - so display all sub-commands group help.
+ getFullUsage(Collections.<SubCommand> emptySet(), true, buffer);
+ } else {
+ // Requested help on specific group - don't display global
+ // options.
+ getFullUsage(usageGroupArguments.get(a), false, buffer);
+ }
+
+ try {
+ outputStream.write(buffer.toString().getBytes());
+ } catch (Exception e) {
+ // TODO empty catch
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void getUsage(OutputStream outputStream) {
+ try {
+ outputStream.write(getUsage().getBytes());
+ } catch (IOException e) {
+ // TODO empty catch
+ }
+ }
+
+ /**
+ * Appends complete usage information for the specified set of sub-commands.
+ */
+ private void getFullUsage(Collection<SubCommand> c, boolean showGlobalOptions, LocalizableMessageBuilder buffer) {
+ usageOrVersionDisplayed = true;
+ if (toolDescription != null && toolDescription.length() > 0) {
+ buffer.append(wrapText(toolDescription, MAX_LENGTH - 1));
+ buffer.append(EOL);
+ buffer.append(EOL);
+ }
+
+ String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+ if (scriptName == null || scriptName.length() == 0) {
+ scriptName = "java " + mainClassName;
+ }
+ buffer.append(INFO_ARGPARSER_USAGE.get());
+ buffer.append(" ");
+ buffer.append(scriptName);
+
+ if (subCommands.isEmpty()) {
+ buffer.append(" ").append(INFO_SUBCMDPARSER_OPTIONS.get());
+ } else {
+ buffer.append(" ").append(INFO_SUBCMDPARSER_SUBCMD_AND_OPTIONS.get());
+ }
+
+ if (!subCommands.isEmpty()) {
+ buffer.append(EOL);
+ buffer.append(EOL);
+ if (c.isEmpty()) {
+ buffer.append(INFO_SUBCMDPARSER_SUBCMD_HELP_HEADING.get());
+ } else {
+ buffer.append(INFO_SUBCMDPARSER_SUBCMD_HEADING.get());
+ }
+ buffer.append(EOL);
+ }
+
+ if (c.isEmpty()) {
+ // Display usage arguments (except the default one).
+ for (Argument a : globalArgumentList) {
+ if (a.isHidden()) {
+ continue;
+ }
+
+ if (usageGroupArguments.containsKey(a) && !a.equals(usageArgument)) {
+ printArgumentUsage(a, buffer);
+ }
+ }
+ } else {
+ boolean isFirst = true;
+ for (SubCommand sc : c) {
+ if (sc.isHidden()) {
+ continue;
+ }
+ if (isFirst) {
+ buffer.append(EOL);
+ }
+ buffer.append(sc.getName());
+ buffer.append(EOL);
+ indentAndWrap(LocalizableMessage.raw(INDENT), sc.getDescription(), buffer);
+ buffer.append(EOL);
+ isFirst = false;
+ }
+ }
+
+ buffer.append(EOL);
+
+ if (showGlobalOptions) {
+ if (subCommands.isEmpty()) {
+ buffer.append(INFO_SUBCMDPARSER_WHERE_OPTIONS_INCLUDE.get());
+ } else {
+ buffer.append(INFO_SUBCMDPARSER_GLOBAL_HEADING.get());
+ }
+ buffer.append(EOL);
+ buffer.append(EOL);
+
+ boolean printGroupHeaders = printUsageGroupHeaders();
+
+ // Display non-usage arguments.
+ for (ArgumentGroup argGroup : argumentGroups) {
+ if (argGroup.containsArguments() && printGroupHeaders) {
+ // Print the groups description if any
+ LocalizableMessage groupDesc = argGroup.getDescription();
+ if (groupDesc != null && !LocalizableMessage.EMPTY.equals(groupDesc)) {
+ buffer.append(EOL);
+ buffer.append(wrapText(groupDesc.toString(), MAX_LENGTH - 1));
+ buffer.append(EOL);
+ buffer.append(EOL);
+ }
+ }
+
+ for (Argument a : argGroup.getArguments()) {
+ if (a.isHidden()) {
+ continue;
+ }
+
+ if (!usageGroupArguments.containsKey(a)) {
+ printArgumentUsage(a, buffer);
+ }
+ }
+ }
+
+ // Finally print default usage argument.
+ if (usageArgument != null) {
+ printArgumentUsage(usageArgument, buffer);
+ } else {
+ buffer.append("-?");
+ }
+ buffer.append(EOL);
+ }
+ }
+
+ /**
+ * Appends argument usage information to the provided buffer.
+ *
+ * @param a
+ * The argument to handle.
+ * @param buffer
+ * The buffer to which the usage information should be appended.
+ */
+ private void printArgumentUsage(Argument a, LocalizableMessageBuilder buffer) {
+ String value;
+ if (a.needsValue()) {
+ LocalizableMessage pHolder = a.getValuePlaceholder();
+ if (pHolder == null) {
+ value = " {value}";
+ } else {
+ value = " " + pHolder;
+ }
+ } else {
+ value = "";
+ }
+
+ Character shortIDChar = a.getShortIdentifier();
+ if (shortIDChar != null) {
+ if (a.equals(usageArgument)) {
+ buffer.append("-?, ");
+ }
+ buffer.append("-");
+ buffer.append(shortIDChar);
+
+ String longIDString = a.getLongIdentifier();
+ if (longIDString != null) {
+ buffer.append(", --");
+ buffer.append(longIDString);
+ }
+ buffer.append(value);
+ } else {
+ String longIDString = a.getLongIdentifier();
+ if (longIDString != null) {
+ if (a.equals(usageArgument)) {
+ buffer.append("-?, ");
+ }
+ buffer.append("--");
+ buffer.append(longIDString);
+ buffer.append(value);
+ }
+ }
+
+ buffer.append(EOL);
+
+ indentAndWrap(LocalizableMessage.raw(INDENT), a.getDescription(), buffer);
+ if (a.needsValue() && a.getDefaultValue() != null && a.getDefaultValue().length() > 0) {
+ indentAndWrap(LocalizableMessage.raw(INDENT), INFO_ARGPARSER_USAGE_DEFAULT_VALUE.get(a.getDefaultValue()),
+ buffer);
+ }
+ }
+
+ /**
+ * Write one or more lines with the description of the argument. We will indent the description five characters and
+ * try our best to wrap at or before column 79 so it will be friendly to 80-column displays.
+ */
+ private void indentAndWrap(LocalizableMessage indent, LocalizableMessage text, LocalizableMessageBuilder buffer) {
+ int actualSize = MAX_LENGTH - indent.length();
+ if (text.length() <= actualSize) {
+ buffer.append(indent);
+ buffer.append(text);
+ buffer.append(EOL);
+ } else {
+ String s = text.toString();
+ while (s.length() > actualSize) {
+ int spacePos = s.lastIndexOf(' ', actualSize);
+ if (spacePos > 0) {
+ buffer.append(indent);
+ buffer.append(s.substring(0, spacePos).trim());
+ s = s.substring(spacePos + 1).trim();
+ buffer.append(EOL);
+ } else {
+ // There are no spaces in the first actualSize -1 columns.
+ // See if there is one after that point.
+ // If so, then break there. If not, then don't break at all.
+ spacePos = s.indexOf(' ');
+ if (spacePos > 0) {
+ buffer.append(indent);
+ buffer.append(s.substring(0, spacePos).trim());
+ s = s.substring(spacePos + 1).trim();
+ buffer.append(EOL);
+ } else {
+ buffer.append(indent);
+ buffer.append(s);
+ s = "";
+ buffer.append(EOL);
+ }
+ }
+ }
+
+ if (s.length() > 0) {
+ buffer.append(indent);
+ buffer.append(s);
+ buffer.append(EOL);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the usage argument was provided or not. This method should be called after a call to
+ * parseArguments.
+ *
+ * @return <CODE>true</CODE> if the usage argument was provided and <CODE>false</CODE> otherwise.
+ */
+ @Override
+ public boolean isUsageArgumentPresent() {
+ return usageArgument != null && usageArgument.isPresent();
+ }
+
+ /**
+ * Returns whether the version argument was provided or not. This method should be called after a call to
+ * parseArguments.
+ *
+ * @return <CODE>true</CODE> if the version argument was provided and <CODE>false</CODE> otherwise.
+ */
+ @Override
+ public boolean isVersionArgumentPresent() {
+ return super.isVersionArgumentPresent() && !versionPresent;
+ }
+
+ /**
+ * Generate reference documentation for dsconfig subcommands in DocBook 5 XML format. As the number of categories is
+ * large, the subcommand entries are sorted here by name for inclusion in a <refsect1> covering all dsconfig
+ * Subcommands as part of the <refentry> for dsconfig (in man-dsconfig.xml).
+ * <p>
+ * Although it would be possible to categorize subcommands in the same way as they are categorized in dsconfig
+ * interactive mode, this generator does not use the categories.
+ * <p>
+ * It would also be possible to generate the sort of information provided by the configuration reference, such that
+ * this reference would not stop at simply listing an option like --set {PROP:VAL}, but instead would also provide
+ * the list of PROPs and their possible VALs. A future improvement could no doubt merge the configuration reference
+ * with this content, though perhaps the problem calls for hypertext rather than the linear structure of a
+ * <refentry>.
+ * <p>
+ * Each individual subcommand results in a <refsect2> element similar to the following.
+ *
+ * <pre>
+ * <refsect2 xml:id="dsconfig-create-local-db-index">
+ * <title>dsconfig create-local-db-index</title>
+ * <para>Creates Local DB Indexes</para>
+ * <variablelist>
+ * <varlistentry>
+ * <term><option>--backend-name {name}
+ * </option></term>
+ * <listitem>
+ * <para>The name of the Local DB Backend</para>
+ * </listitem>
+ * </varlistentry>
+ * <varlistentry>
+ * <term><option>--index-name {OID}</option></term>
+ * <listitem>
+ * <para>The name of the new Local DB Index which will also be used
+ * as the value of the "attribute" property: Specifies the name
+ * of the attribute for which the index is to be maintained.</para>
+ * </listitem>
+ * </varlistentry>
+ * <varlistentry>
+ * <term><option>--set {PROP:VALUE}</option></term>
+ * <listitem>
+ * <para>Assigns a value to a property where PROP is the name of the
+ * property and VALUE is the single value to be assigned. Specify the same
+ * property multiple times in order to assign more than one value to
+ * it</para>
+ * </listitem>
+ * </varlistentry>
+ * </variablelist>
+ * </refsect2>
+ * </pre>
+ *
+ * @param sc
+ * The SubCommand containing reference information.
+ * @return Refsect2 representation of the subcommand.
+ */
+ private String toRefSect2(SubCommand sc) {
+ final StringBuilder options = new StringBuilder();
+ if (!sc.getArguments().isEmpty()) {
+ options.append(" <variablelist>").append(EOL);
+ for (Argument a : sc.getArguments()) {
+ options.append(" <varlistentry>").append(EOL);
+ options.append(" <term><option>");
+ Character shortID = a.getShortIdentifier();
+ if (shortID != null) {
+ options.append("-").append(shortID.charValue());
+ }
+ String longID = a.getLongIdentifier();
+ if (shortID != null && longID != null) {
+ options.append(" | ");
+ }
+ if (longID != null) {
+ options.append("--").append(longID);
+ }
+ if (a.needsValue()) {
+ options.append(" ").append(a.getValuePlaceholder());
+ }
+ options.append("</option></term>").append(EOL);
+ options.append(" <listitem>").append(EOL);
+ options.append(" <para>");
+ options.append(a.getDescription());
+ options.append("</para>").append(EOL);
+ options.append(" </listitem>").append(EOL);
+ options.append(" </varlistentry>").append(EOL);
+ }
+ options.append(" </variablelist>").append(EOL);
+ }
+
+ return "<refsect2 xml:id=\"dsconfig-" + sc.getName() + "\">" + EOL + " <title>dsconfig " + sc.getName()
+ + "</title>" + EOL + " <para>" + sc.getDescription() + "</para>" + EOL + options + "</refsect2>" + EOL;
+ }
+
+ void printVersion() {
+ // TODO
+ // DirectoryServer.printVersion(usageOutputStream);
+ }
+}
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/Utils.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/Utils.java
index a5c7012..3d99f61 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/Utils.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/Utils.java
@@ -40,6 +40,9 @@
* This class provides utility functions for all the client side tools.
*/
final public class Utils {
+ /** Platform appropriate line separator. */
+ static public final String LINE_SEPARATOR = System.getProperty("line.separator");
+
/**
* The name of a command-line script used to launch a tool.
*/
diff --git a/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties b/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties
index bfb49e9..1395801 100755
--- a/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties
+++ b/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties
@@ -137,6 +137,17 @@
INFO_ARGPARSER_USAGE_JAVA_SCRIPTNAME=Usage: %s {options}
INFO_ARGPARSER_USAGE_TRAILINGARGS={trailing-arguments}
INFO_ARGPARSER_USAGE_DEFAULT_VALUE=Default value: %s
+INFO_SUBCMDPARSER_OPTIONS={options}
+INFO_GLOBAL_OPTIONS=Global Options:
+INFO_GLOBAL_OPTIONS_REFERENCE=See "%s --help"
+INFO_GLOBAL_HELP_REFERENCE=See "%s --help" to get more usage help
+INFO_SUBCMD_OPTIONS=SubCommand Options:
+INFO_ARGPARSER_USAGE=Usage:
+INFO_SUBCMDPARSER_SUBCMD_AND_OPTIONS={subcommand} {options}
+INFO_SUBCMDPARSER_SUBCMD_HELP_HEADING=To get the list of subcommands use:
+INFO_SUBCMDPARSER_SUBCMD_HEADING=Available subcommands:
+ERR_ARG_SUBCOMMAND_INVALID=Invalid subcommand
+INFO_SUBCMDPARSER_GLOBAL_HEADING=The global options are:
#
# Extension messages
#
@@ -146,7 +157,7 @@
ERR_CANNOT_INITIALIZE_ARGS=An unexpected error occurred while \
attempting to initialize the command-line arguments: %s
ERR_ERROR_PARSING_ARGS=An error occurred while parsing the \
- command-line arguments: %s
+ command-line arguments: %s
INFO_PROCESSING_OPERATION=Processing %s request for %s
INFO_OPERATION_FAILED=%s operation failed
INFO_OPERATION_SUCCESSFUL=%s operation successful for DN %s
@@ -501,3 +512,188 @@
processing : %s
ERR_CONSTANT_ARG_CANNOT_DECODE=Unable to parse a constant argument, \
expecting name=value but got %s
+INFO_DESCRIPTION_QUIET=Use quiet mode
+INFO_DESCRIPTION_NO_PROMPT=Use non-interactive mode. If data in \
+the command is missing, the user is not prompted and the tool will fail
+INFO_OPTION_ACCEPT_LICENSE=Automatically accepts the product license \
+(if present)
+#
+# Setup messages
+#
+INFO_SETUP_TITLE=OPENDJ3 Setup tool
+INFO_SETUP_DESCRIPTION=This utility can be used to setup the Directory Server
+INFO_ARGUMENT_DESCRIPTION_TESTONLY=Just verify that the JVM can be \
+ started properly
+INFO_ARGUMENT_DESCRIPTION_CLI=Use the command line install. \
+ If not specified the graphical interface will be launched. The rest of the \
+ options (excluding help and version) will only be taken into account if this \
+ option is specified
+INFO_ARGUMENT_DESCRIPTION_BASEDN=Base DN for user \
+ information in the Directory Server. Multiple base DNs may be provided by \
+ using this option multiple times
+INFO_ARGUMENT_DESCRIPTION_ADDBASE=Indicates whether to create the base \
+ entry in the Directory Server database
+INFO_ARGUMENT_DESCRIPTION_IMPORTLDIF=Path to an LDIF file \
+ containing data that should be added to the Directory Server database. \
+ Multiple LDIF files may be provided by using this option multiple times
+INFO_LDIFFILE_PLACEHOLDER={ldifFile}
+INFO_REJECT_FILE_PLACEHOLDER={rejectFile}
+INFO_SKIP_FILE_PLACEHOLDER={skipFile}
+INFO_JMXPORT_PLACEHOLDER={jmxPort}
+INFO_ROOT_USER_DN_PLACEHOLDER={rootUserDN}
+INFO_ROOT_USER_PWD_PLACEHOLDER={rootUserPassword}
+INFO_ROOT_USER_PWD_FILE_PLACEHOLDER={rootUserPasswordFile}
+INFO_HOST_PLACEHOLDER={host}
+INFO_GENERAL_DESCRIPTION_REJECTED_FILE=Write rejected entries to the \
+ specified file
+INFO_GENERAL_DESCRIPTION_SKIPPED_FILE=Write skipped entries to the \
+ specified file
+INFO_SETUP_DESCRIPTION_SAMPLE_DATA=Specifies that the database should \
+ be populated with the specified number of sample entries
+INFO_ARGUMENT_DESCRIPTION_LDAPPORT=Port on which the \
+ Directory Server should listen for LDAP communication
+INFO_ARGUMENT_DESCRIPTION_ADMINCONNECTORPORT=Port on which the \
+ Administration Connector should listen for communication
+INFO_ARGUMENT_DESCRIPTION_JMXPORT=Port on which the \
+ Directory Server should listen for JMX communication
+INFO_ARGUMENT_DESCRIPTION_SKIPPORT=Skip the check to determine whether \
+ the specified ports are usable
+INFO_ARGUMENT_DESCRIPTION_ROOTDN=DN for the initial root \
+ user for the Directory Server
+INFO_ARGUMENT_DESCRIPTION_ROOTPW=Password for the initial \
+ root user for the Directory Server
+INFO_ARGUMENT_DESCRIPTION_ROOTPWFILE=Path to a file \
+ containing the password for the initial root user for the Directory Server
+ INFO_ARGUMENT_DESCRIPTION_ENABLE_WINDOWS_SERVICE=Enable the server to run \
+ as a Windows Service
+INFO_ARGUMENT_DESCRIPTION_LDAPSPORT=Port on which the \
+ Directory Server should listen for LDAPS communication. The LDAPS port will \
+ be configured and SSL will be enabled only if this argument is explicitly \
+ specified
+INFO_ARGUMENT_DESCRIPTION_HOST_NAME=The fully-qualified directory server \
+ host name that will be used when generating self-signed \
+ certificates for LDAP SSL/StartTLS, the administration connector, and \
+ replication
+INFO_ARGUMENT_DESCRIPTION_USE_SELF_SIGNED_CERTIFICATE=Generate a \
+ self-signed certificate that the server should use when accepting SSL-based \
+ connections or performing StartTLS negotiation
+INFO_ARGUMENT_DESCRIPTION_USE_PKCS11=Use a certificate in a \
+ PKCS#11 token that the server should use when accepting SSL-based \
+ connections or performing StartTLS negotiation
+INFO_ARGUMENT_DESCRIPTION_USE_JAVAKEYSTORE=Path of a Java \
+ Key Store (JKS) containing a certificate to be used as the server certificate
+INFO_ARGUMENT_DESCRIPTION_USE_JCEKS=Path of a JCEKS containing a \
+ certificate to be used as the server certificate
+INFO_ARGUMENT_DESCRIPTION_USE_PKCS12=Path of a PKCS#12 key \
+ store containing the certificate that the server should use when accepting \
+ SSL-based connections or performing StartTLS negotiation
+INFO_ARGUMENT_CERT_OPTION_JCEKS=Use an existing certificate located on a \
+ JCEKS key store
+INFO_ARGUMENT_DESCRIPTION_CERT_NICKNAME=Nickname of the \
+ certificate that the server should use when accepting SSL-based \
+ connections or performing StartTLS negotiation
+INFO_ARGUMENT_DESCRIPTION_KEYSTOREPASSWORD=Certificate key store PIN. \
+ A PIN is required when you specify to use an existing certificate (JKS, \
+ JCEKS, PKCS#12 or PKCS#11) as server certificate
+INFO_ARGUMENT_DESCRIPTION_KEYSTOREPASSWORD_FILE=Certificate key store \
+ PIN file. A PIN is required when you specify to use an existing certificate \
+ (JKS, JCEKS, PKCS#12 or PKCS#11) as server certificate
+INFO_SETUP_DESCRIPTION_DO_NOT_START=Do not start the server when the \
+ configuration is completed
+INFO_SETUP_DESCRIPTION_ENABLE_STARTTLS=Enable StartTLS to allow \
+ secure communication with the server using the LDAP port
+#
+# SubCommandes messages
+#
+ERR_ARG_SUBCOMMAND_DUPLICATE_SUBCOMMAND=The argument parser already \
+ has a %s subcommand
+ERR_ARG_SUBCOMMAND_DUPLICATE_ARGUMENT_NAME=There are multiple \
+ arguments for subcommand %s with name %s
+ERR_ARG_SUBCOMMAND_ARGUMENT_GLOBAL_CONFLICT=Argument %s for \
+ subcommand %s conflicts with a global argument with the same name
+ERR_ARG_SUBCOMMAND_DUPLICATE_SHORT_ID=Argument %s for subcommand %s \
+ has a short identifier -%s that conflicts with that of argument %s
+ERR_ARG_SUBCOMMAND_ARGUMENT_SHORT_ID_GLOBAL_CONFLICT=Argument %s \
+ for subcommand %s has a short ID -%s that conflicts with that of global \
+ argument %s
+ERR_ARG_SUBCOMMAND_DUPLICATE_LONG_ID=Argument %s for subcommand %s \
+ has a long identifier --%s that conflicts with that of argument %s
+ERR_ARG_SUBCOMMAND_ARGUMENT_LONG_ID_GLOBAL_CONFLICT=Argument %s for \
+ subcommand %s has a long ID --%s that conflicts with that of global argument \
+ %s
+ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_NAME=There is already another \
+ global argument named "%s"
+ERR_SUBCMDPARSER_GLOBAL_ARG_NAME_SUBCMD_CONFLICT=The argument name \
+ %s conflicts with the name of another argument associated with the %s \
+ subcommand
+ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_SHORT_ID=Short ID -%s for \
+ global argument %s conflicts with the short ID of another global argument %s
+ERR_SUBCMDPARSER_GLOBAL_ARG_SHORT_ID_CONFLICT=Short ID -%s for \
+ global argument %s conflicts with the short ID for the %s argument associated \
+ with subcommand %s
+ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_LONG_ID=Long ID --%s for \
+ global argument %s conflicts with the long ID of another global argument %s
+ERR_SUBCMDPARSER_GLOBAL_ARG_LONG_ID_CONFLICT=Long ID --%s for \
+ global argument %s conflicts with the long ID for the %s argument associated \
+ with subcommand %s
+ERR_SUBCMDPARSER_CANNOT_READ_PROPERTIES_FILE=An error occurred \
+ while attempting to read the contents of the argument properties file %s: %s
+ERR_SUBCMDPARSER_LONG_ARG_WITHOUT_NAME=The provided command-line \
+ argument %s does not contain an argument name
+ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_LONG_ID=The provided \
+ argument --%s is not a valid global argument identifier
+ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_LONG_ID=The provided argument --%s \
+ is not a valid global or subcommand argument identifier
+ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID=Command-line \
+ argument --%s requires a value but none was given
+ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID=The provided value \
+ "%s" for argument --%s is not acceptable: %s
+ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_LONG_ID=The argument --%s was \
+ included multiple times in the provided set of arguments but it does not \
+ allow multiple values
+ERR_SUBCMDPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE=A value was \
+ provided for argument --%s but that argument does not take a value
+ERR_SUBCMDPARSER_INVALID_DASH_AS_ARGUMENT=The dash character by \
+ itself is invalid for use as an argument name
+ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID=The provided \
+ argument -%s is not a valid global argument identifier
+ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID=The provided argument \
+ -%s is not a valid global or subcommand argument identifier
+ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID=Argument -%s \
+ requires a value but none was provided
+ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID=The provided \
+ value "%s" for argument -%s is not acceptable: %s
+ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_SHORT_ID=The argument -%s was \
+ included multiple times in the provided set of arguments but it does not \
+ allow multiple values
+ERR_SUBCMDPARSER_CANT_MIX_ARGS_WITH_VALUES=The provided argument \
+ block '-%s%s' is illegal because the '%s' argument requires a value but is in \
+ the same block as at least one other argument that doesn't require a value
+ERR_SUBCMDPARSER_INVALID_ARGUMENT=The provided argument "%s" is \
+ not recognized
+ERR_SUBCMDPARSER_NO_VALUE_FOR_REQUIRED_ARG=The argument %s is \
+ required to have a value but none was provided in the argument list and no \
+ default value is available
+ERR_ARGUMENT_NO_BASE_DN_SPECIFIED=You have specified \
+ not to create a base DN. If no base DN is to be created you cannot specify \
+ argument '%s'
+ERR_PORT_ALREADY_SPECIFIED=ERROR: You have specified \
+ the value %s for different ports
+ERR_SEVERAL_CERTIFICATE_TYPE_SPECIFIED=You have \
+ specified several certificate types to be used. Only one certificate type \
+ (self-signed, JKS, JCEKS, PKCS#12 or PCKS#11) is allowed
+ERR_CERTIFICATE_REQUIRED_FOR_SSL_OR_STARTTLS=You have \
+ chosen to enable SSL or StartTLS. You must specify which type of certificate \
+ you want the server to use
+ERR_TWO_CONFLICTING_ARGUMENTS=ERROR: You may not \
+ provide both the %s and the %s arguments at the same time
+ERR_NO_KEYSTORE_PASSWORD=You must provide the PIN of the \
+ keystore to retrieve the certificate to be used by the server. You can use \
+ {%s} or {%s}
+ERR_SSL_OR_STARTTLS_REQUIRED=You have specified to use a \
+ certificate as server certificate. You must enable SSL (using option {%s}) \
+ or Start TLS (using option %s)
+ERR_NO_ROOT_PASSWORD=ERROR: No password was provided \
+ for the initial root user. When performing a non-interactive installation, \
+ this must be provided using either the %s or the %s argument
+
--
Gitblit v1.10.0