From 3711cef73dac69815f93c82324ead507ee1cc230 Mon Sep 17 00:00:00 2001
From: Violette Roche-Montane <violette.roche-montane@forgerock.com>
Date: Tue, 18 Feb 2014 14:05:45 +0000
Subject: [PATCH] Checkpoint OPENDJ-1343 Migrate dsconfig / OPENDJ-1303 "opendj-cli" - Added methods to ConsoleApplication which are going to be used by the server. - Added APPLICATION_ERROR to ReturnCode. - Added methods to Utils (used by opendj3)

---
 opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java |  341 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 329 insertions(+), 12 deletions(-)

diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
index a17ca6f..c023260 100755
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
@@ -27,12 +27,11 @@
  */
 package com.forgerock.opendj.cli;
 
-import static com.forgerock.opendj.cli.CliMessages.INFO_ERROR_EMPTY_RESPONSE;
-import static com.forgerock.opendj.cli.CliMessages.INFO_MENU_PROMPT_RETURN_TO_CONTINUE;
-import static com.forgerock.opendj.cli.CliMessages.INFO_PROMPT_SINGLE_DEFAULT;
-import static com.forgerock.opendj.cli.CliMessages.ERR_TRIES_LIMIT_REACHED;
+import static com.forgerock.opendj.cli.CliMessages.*;
 import static com.forgerock.opendj.cli.Utils.MAX_LINE_WIDTH;
+import static com.forgerock.opendj.cli.Utils.CONFIRMATION_MAX_TRIES;
 import static com.forgerock.opendj.cli.Utils.wrapText;
+import static com.forgerock.opendj.util.StaticUtils.EOL;
 
 import java.io.BufferedReader;
 import java.io.Console;
@@ -43,6 +42,7 @@
 import java.io.PrintStream;
 
 import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
 
 /**
  * This class provides an abstract base class which can be used as the basis of a console-based application.
@@ -60,6 +60,36 @@
     private final Console console = System.console();
 
     /**
+     * Defines the different line styles for output.
+     */
+    public enum Style {
+        /**
+         * Defines a title.
+         */
+        TITLE,
+        /**
+         * Defines a subtitle.
+         */
+        SUBTITLE,
+        /**
+         * Defines a notice.
+         */
+        NOTICE,
+        /**
+         * Defines a normal line.
+         */
+        NORMAL,
+        /**
+         * Defines an error.
+         */
+        ERROR,
+        /**
+         * Defines a warning.
+         */
+        WARNING
+    }
+
+    /**
      * Creates a new console application instance.
      */
     public ConsoleApplication() {
@@ -154,6 +184,17 @@
     }
 
     /**
+     * Indicates whether or not this console application is running in its menu-driven mode. This can be used to dictate
+     * whether output should go to the error stream or not. In addition, it may also dictate whether or not sub-menus
+     * should display a cancel option as well as a quit option.
+     *
+     * @return Returns <code>true</code> if this console application is running in its menu-driven mode.
+     */
+    public boolean isMenuDrivenMode() {
+        return false;
+    }
+
+    /**
      * Interactively prompts the user to press return to continue. This method should be called in situations where a
      * user needs to be given a chance to read some documentation before continuing (continuing may cause the
      * documentation to be scrolled out of view).
@@ -266,6 +307,92 @@
     }
 
     /**
+     * Prints a progress bar on the same output stream line if not in quiet mode.
+     *
+     * <pre>
+     * Like
+     *   msg......   50%
+     *   if progress is up to 100 :
+     *   msg.....................  100%
+     *   if progress is < 0 :
+     *   msg....  FAIL
+     *   msg.....................  FAIL
+     * </pre>
+     *
+     * @param linePos
+     *            The progress bar starts at this position on the line.
+     * @param progress
+     *            The current percentage progress to print.
+     */
+    public final void printProgressBar(final int linePos, final int progress) {
+        if (!isQuiet()) {
+            final int spacesLeft = MAX_LINE_WIDTH - linePos - 10;
+            StringBuilder bar = new StringBuilder();
+            if (progress != 0) {
+                for (int i = 0; i < 50; i++) {
+                    if ((i < (Math.abs(progress) / 2)) && (bar.length() < spacesLeft)) {
+                        bar.append(".");
+                    }
+                }
+            }
+            bar.append(".   ");
+            if (progress >= 0) {
+                bar.append(progress).append("%     ");
+            } else {
+                bar.append("FAIL");
+            }
+            final int endBuilder = linePos + bar.length();
+            for (int i = 0; i < endBuilder; i++) {
+                bar.append("\b");
+            }
+            if (progress >= 100 || progress < 0) {
+                bar.append(EOL);
+            }
+            out.print(bar.toString());
+        }
+    }
+
+    /**
+     * Print a line with EOL in the output stream.
+     *
+     * @param msgStyle
+     *            The type of formatted output desired.
+     * @param msg
+     *            The message to display in normal mode.
+     * @param indent
+     *            The indentation.
+     */
+    public final void println(final Style msgStyle, final LocalizableMessage msg, final int indent) {
+        if (!isQuiet()) {
+            switch (msgStyle) {
+            case TITLE:
+                out.println();
+                out.println(">>>> " + wrapText(msg, MAX_LINE_WIDTH, indent));
+                out.println();
+                break;
+            case SUBTITLE:
+                out.println(wrapText(msg, MAX_LINE_WIDTH, indent));
+                out.println();
+                break;
+            case NOTICE:
+                out.println(wrapText("* " + msg, MAX_LINE_WIDTH, indent));
+                break;
+            case ERROR:
+                out.println();
+                out.println(wrapText("** " + msg, MAX_LINE_WIDTH, indent));
+                out.println();
+                break;
+            case WARNING:
+                out.println(wrapText("[!] " + msg, MAX_LINE_WIDTH, indent));
+                break;
+            default:
+                out.println(wrapText(msg, MAX_LINE_WIDTH, indent));
+                break;
+            }
+        }
+    }
+
+    /**
      * Displays a message to the output stream if verbose mode is enabled.
      *
      * @param msg
@@ -291,12 +418,39 @@
      * @return The string value read from the user.
      */
     public final String readInput(LocalizableMessage prompt, final String defaultValue) throws ClientException {
+        return readInput(prompt, defaultValue, null);
+    }
+
+    /**
+     * Interactively prompts (on error output) the user to provide a string value. Any non-empty string will be allowed
+     * (the empty string will indicate that the default should be used, if there is one).
+     *
+     * @param prompt
+     *            The prompt to present to the user.
+     * @param defaultValue
+     *            The default value to assume if the user presses ENTER without typing anything, or {@code null} if
+     *            there should not be a default and the user must explicitly provide a value.
+     * @param msgStyle
+     *            The formatted style chosen.
+     * @throws ClientException
+     *             If the line of input could not be retrieved for some reason.
+     * @return The string value read from the user.
+     */
+    public final String readInput(LocalizableMessage prompt, final String defaultValue, final Style msgStyle)
+            throws ClientException {
+        if (msgStyle != null && msgStyle == Style.TITLE) {
+            println();
+        }
         while (true) {
             if (defaultValue != null) {
                 prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(), defaultValue);
             }
             final String response = readLineOfInput(prompt);
 
+            if (msgStyle != null && (msgStyle == Style.TITLE || msgStyle == Style.SUBTITLE)) {
+                println();
+            }
+
             if ("".equals(response)) {
                 if (defaultValue == null) {
                     print(INFO_ERROR_EMPTY_RESPONSE.get());
@@ -321,8 +475,8 @@
     public final char[] readPassword(final LocalizableMessage prompt) throws ClientException {
         if (console != null) {
             if (prompt != null) {
-                err.print(wrap(prompt));
-                err.print(" ");
+                out.print(wrap(prompt));
+                out.print(" ");
             }
             try {
                 final char[] password = console.readPassword();
@@ -348,10 +502,10 @@
      * @throws ClientException
      *             If the line of input could not be retrieved for some reason.
      */
-    final String readLineOfInput(final LocalizableMessage prompt) throws ClientException {
+    public final String readLineOfInput(final LocalizableMessage prompt) throws ClientException {
         if (prompt != null) {
-            err.print(wrap(prompt));
-            err.print(" ");
+            out.print(wrap(prompt));
+            out.print(" ");
         }
         try {
             final String s = reader.readLine();
@@ -366,6 +520,49 @@
     }
 
     /**
+     * Interactively retrieves a port value from the console.
+     *
+     * @param prompt
+     *            The port prompt.
+     * @param defaultValue
+     *            The port default value.
+     * @return Returns the port.
+     * @throws ClientException
+     *             If the port could not be retrieved for some reason.
+     */
+    public final int readPort(LocalizableMessage prompt, final int defaultValue) throws ClientException {
+        final ValidationCallback<Integer> callback = new ValidationCallback<Integer>() {
+            @Override
+            public Integer validate(ConsoleApplication app, String input) throws ClientException {
+                final String ninput = input.trim();
+                if (ninput.length() == 0) {
+                    return defaultValue;
+                } else {
+                    try {
+                        int i = Integer.parseInt(ninput);
+                        if (i < 1 || i > 65535) {
+                            throw new NumberFormatException();
+                        }
+                        return i;
+                    } catch (NumberFormatException e) {
+                        // Try again...
+                        app.println();
+                        app.println(ERR_BAD_PORT_NUMBER.get(ninput));
+                        app.println();
+                        return null;
+                    }
+                }
+            }
+
+        };
+        if (defaultValue != -1) {
+            prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt, defaultValue);
+        }
+
+        return readValidatedInput(prompt, callback, CONFIRMATION_MAX_TRIES);
+    }
+
+    /**
      * Interactively prompts for user input and continues until valid input is provided.
      *
      * @param <T>
@@ -402,8 +599,8 @@
      *            The maximum number of tries that we can make.
      * @return Returns the decoded user's response.
      * @throws ClientException
-     *             If an unexpected error occurred which prevented validation or
-     *             if the maximum number of tries was reached.
+     *             If an unexpected error occurred which prevented validation or if the maximum number of tries was
+     *             reached.
      */
     public final <T> T readValidatedInput(final LocalizableMessage prompt, final ValidationCallback<T> validator,
             final int maxTries) throws ClientException {
@@ -432,7 +629,7 @@
 
     /**
      * Returns the error stream. Effectively, when an application is in "interactive mode" all the informations should
-     * be written in the stdout.
+     * be written in the STDout.
      *
      * @return The error stream that should be used with this application.
      */
@@ -444,4 +641,124 @@
         }
     }
 
+    /**
+     * Commodity method that interactively confirms whether a user wishes to perform an action. If
+     * the application is non-interactive, then the provided default is returned automatically. If there is an error an
+     * error message is logged to the provided Logger and the default value is returned.
+     *
+     * @param prompt
+     *            The prompt describing the action.
+     * @param defaultValue
+     *            The default value for the confirmation message. This will be returned if the application is
+     *            non-interactive or if the user just presses return.
+     * @param logger
+     *            the Logger to be used to log the error message.
+     * @return Returns <code>true</code> if the user wishes the action to be performed, or <code>false</code> if they
+     *         refused.
+     * @throws ClientException
+     *             if the user did not provide valid answer after a certain number of tries
+     *             (ConsoleApplication.CONFIRMATION_MAX_TRIES)
+     */
+    protected final boolean askConfirmation(LocalizableMessage prompt, boolean defaultValue, LocalizedLogger logger)
+            throws ClientException {
+        boolean v = defaultValue;
+
+        boolean done = false;
+        int nTries = 0;
+
+        while (!done && (nTries < CONFIRMATION_MAX_TRIES)) {
+            nTries++;
+            try {
+                v = confirmAction(prompt, defaultValue);
+                done = true;
+            } catch (ClientException ce) {
+                if (ce.getMessageObject().toString().contains(ERR_CONFIRMATION_TRIES_LIMIT_REACHED.get(nTries))) {
+                    throw ce;
+                }
+                logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
+                // Try again...
+                println();
+            }
+        }
+
+        if (!done) {
+            // This means we reached the maximum number of tries
+            throw new ClientException(ReturnCode.ERROR_USER_DATA,
+                    ERR_CONFIRMATION_TRIES_LIMIT_REACHED.get(CONFIRMATION_MAX_TRIES));
+        }
+        return v;
+    }
+
+    /**
+     * Interactively confirms whether a user wishes to perform an action.
+     * If the application is non-interactive, then the provided default is returned automatically.
+     *
+     * @param prompt
+     *            The prompt describing the action.
+     * @param defaultValue
+     *            The default value for the confirmation message. This will be returned if the application is
+     *            non-interactive or if the user just presses return.
+     * @return Returns <code>true</code> if the user wishes the action to be performed, or <code>false</code> if they
+     *         refused, or if an exception occurred.
+     * @throws ClientException
+     *             If the user's response could not be read from the console for some reason.
+     */
+    public final boolean confirmAction(LocalizableMessage prompt, final boolean defaultValue) throws ClientException {
+        if (!isInteractive()) {
+            return defaultValue;
+        }
+
+        final LocalizableMessage yes = INFO_GENERAL_YES.get();
+        final LocalizableMessage no = INFO_GENERAL_NO.get();
+        final LocalizableMessage errMsg = ERR_CONSOLE_APP_CONFIRM.get(yes, no);
+        prompt = INFO_MENU_PROMPT_CONFIRM.get(prompt, yes, no, defaultValue ? yes : no);
+
+        ValidationCallback<Boolean> validator = new ValidationCallback<Boolean>() {
+
+            @Override
+            public Boolean validate(ConsoleApplication app, String input) {
+                String ninput = input.toLowerCase().trim();
+                if (ninput.length() == 0) {
+                    return defaultValue;
+                } else if (no.toString().toLowerCase().startsWith(ninput)) {
+                    return false;
+                } else if (yes.toString().toLowerCase().startsWith(ninput)) {
+                    return true;
+                } else {
+                    // Try again...
+                    app.println();
+                    app.println(errMsg);
+                    app.println();
+                }
+
+                return null;
+            }
+        };
+
+        return readValidatedInput(prompt, validator, CONFIRMATION_MAX_TRIES);
+    }
+
+    /**
+     * Commodity method used to repeatedly ask the user to provide a port value.
+     *
+     * @param prompt
+     *            the prompt message.
+     * @param defaultValue
+     *            the default value of the port to be proposed to the user.
+     * @param logger
+     *            the logger where the errors will be written.
+     * @return the port value provided by the user.
+     */
+    protected int askPort(LocalizableMessage prompt, int defaultValue, LocalizedLogger logger) {
+        int port = -1;
+        while (port == -1) {
+            try {
+                port = readPort(prompt, defaultValue);
+            } catch (ClientException ce) {
+                port = -1;
+                logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
+            }
+        }
+        return port;
+    }
 }

--
Gitblit v1.10.0