/*
|
* 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 2008-2009 Sun Microsystems, Inc.
|
* Portions copyright 2011-2014 ForgeRock AS
|
* Portions copyright 2011 Nemanja Lukić
|
*/
|
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.Utils.MAX_LINE_WIDTH;
|
import static com.forgerock.opendj.cli.Utils.wrapText;
|
|
import java.io.BufferedReader;
|
import java.io.Console;
|
import java.io.EOFException;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.InputStreamReader;
|
import java.io.PrintStream;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
|
/**
|
* This class provides an abstract base class which can be used as the basis of a console-based application.
|
*/
|
public abstract class ConsoleApplication {
|
|
private final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
|
private final InputStream in = System.in;
|
|
private final PrintStream out;
|
|
private final PrintStream err;
|
|
private final Console console = System.console();
|
|
/**
|
* Creates a new console application instance.
|
*/
|
public ConsoleApplication() {
|
this(System.out, System.err);
|
}
|
|
/**
|
* Creates a new console application instance with provided standard and error out streams.
|
*
|
* @param out
|
* The output stream.
|
* @param err
|
* The error stream.
|
*/
|
public ConsoleApplication(PrintStream out, PrintStream err) {
|
this.out = out;
|
this.err = err;
|
}
|
|
/**
|
* Returns the application error stream.
|
*
|
* @return The application error stream.
|
*/
|
public final PrintStream getErrorStream() {
|
return err;
|
}
|
|
/**
|
* Returns the application input stream.
|
*
|
* @return The application input stream.
|
*/
|
public final InputStream getInputStream() {
|
return in;
|
}
|
|
/**
|
* Returns the application output stream.
|
*
|
* @return The application output stream.
|
*/
|
public final PrintStream getOutputStream() {
|
return out;
|
}
|
|
/**
|
* Indicates whether or not the user has requested interactive behavior. The default implementation returns
|
* {@code true}.
|
*
|
* @return {@code true} if the user has requested interactive behavior.
|
*/
|
public boolean isInteractive() {
|
return true;
|
}
|
|
/**
|
* Indicates whether or not the user has requested quiet output. The default implementation returns {@code false}.
|
*
|
* @return {@code true} if the user has requested quiet output.
|
*/
|
public boolean isQuiet() {
|
return false;
|
}
|
|
/**
|
* Indicates whether or not the user has requested script-friendly output. The default implementation returns
|
* {@code false}.
|
*
|
* @return {@code true} if the user has requested script-friendly output.
|
*/
|
public boolean isScriptFriendly() {
|
return false;
|
}
|
|
/**
|
* Indicates whether or not the user has requested verbose output. The default implementation returns {@code false}.
|
*
|
* @return {@code true} if the user has requested verbose output.
|
*/
|
public boolean isVerbose() {
|
return false;
|
}
|
|
/**
|
* Indicates whether or not the user has requested advanced mode.
|
*
|
* @return Returns <code>true</code> if the user has requested advanced mode.
|
*/
|
public boolean isAdvancedMode() {
|
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).
|
*/
|
public final void pressReturnToContinue() {
|
final LocalizableMessage msg = INFO_MENU_PROMPT_RETURN_TO_CONTINUE.get();
|
try {
|
readLineOfInput(msg);
|
} catch (final ClientException e) {
|
// Ignore the exception - applications don't care.
|
}
|
}
|
|
/**
|
* Displays a message to the error stream.
|
*
|
* @param msg
|
* The message.
|
*/
|
public final void errPrint(final LocalizableMessage msg) {
|
getErrStream().print(wrap(msg));
|
}
|
|
/**
|
* Displays a blank line to the error stream.
|
*/
|
public final void errPrintln() {
|
getErrStream().println();
|
}
|
|
/**
|
* Displays a message to the error stream.
|
*
|
* @param msg
|
* The message.
|
*/
|
public final void errPrintln(final LocalizableMessage msg) {
|
getErrStream().println(wrap(msg));
|
}
|
|
/**
|
* Displays a message to the error stream indented by the specified number of columns.
|
*
|
* @param msg
|
* The message.
|
* @param indent
|
* The number of columns to indent.
|
*/
|
public final void errPrintln(final LocalizableMessage msg, final int indent) {
|
getErrStream().println(wrapText(msg, MAX_LINE_WIDTH, indent));
|
}
|
|
/**
|
* Displays a message to the error stream if verbose mode is enabled.
|
*
|
* @param msg
|
* The verbose message.
|
*/
|
public final void errPrintVerboseMessage(final LocalizableMessage msg) {
|
if (isVerbose()) {
|
getErrStream().println(wrap(msg));
|
}
|
}
|
|
/**
|
* Displays a message to the output stream.
|
*
|
* @param msg
|
* The message.
|
*/
|
public final void print(final LocalizableMessage msg) {
|
if (!isQuiet()) {
|
out.print(wrap(msg));
|
}
|
}
|
|
/**
|
* Displays a blank line to the output stream.
|
*/
|
public final void println() {
|
if (!isQuiet()) {
|
out.println();
|
}
|
}
|
|
/**
|
* Displays a message to the output stream.
|
*
|
* @param msg
|
* The message.
|
*/
|
public final void println(final LocalizableMessage msg) {
|
if (!isQuiet()) {
|
out.println(wrap(msg));
|
}
|
}
|
|
/**
|
* Displays a message to the output stream indented by the specified number of columns.
|
*
|
* @param msg
|
* The message.
|
* @param indent
|
* The number of columns to indent.
|
*/
|
public final void println(final LocalizableMessage msg, final int indent) {
|
if (!isQuiet()) {
|
out.println(wrapText(msg, MAX_LINE_WIDTH, indent));
|
}
|
}
|
|
/**
|
* Displays a message to the output stream if verbose mode is enabled.
|
*
|
* @param msg
|
* The verbose message.
|
*/
|
public final void printVerboseMessage(final LocalizableMessage msg) {
|
if (isVerbose()) {
|
out.println(wrap(msg));
|
}
|
}
|
|
/**
|
* 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.
|
* @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) throws ClientException {
|
while (true) {
|
if (defaultValue != null) {
|
prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(), defaultValue);
|
}
|
final String response = readLineOfInput(prompt);
|
|
if ("".equals(response)) {
|
if (defaultValue == null) {
|
print(INFO_ERROR_EMPTY_RESPONSE.get());
|
} else {
|
return defaultValue;
|
}
|
} else {
|
return response;
|
}
|
}
|
}
|
|
/**
|
* Interactively reads a password from the console.
|
*
|
* @param prompt
|
* The password prompt.
|
* @return The password.
|
* @throws ClientException
|
* If the password could not be retrieved for some reason.
|
*/
|
public final char[] readPassword(final LocalizableMessage prompt) throws ClientException {
|
if (console != null) {
|
if (prompt != null) {
|
err.print(wrap(prompt));
|
err.print(" ");
|
}
|
try {
|
final char[] password = console.readPassword();
|
if (password == null) {
|
throw new EOFException("End of input");
|
}
|
return password;
|
} catch (final Throwable e) {
|
throw ClientException.adaptInputException(e);
|
}
|
} else {
|
// FIXME: should go direct to char[] and avoid the String.
|
return readLineOfInput(prompt).toCharArray();
|
}
|
}
|
|
/**
|
* Interactively retrieves a line of input from the console.
|
*
|
* @param prompt
|
* The prompt.
|
* @return The line of input.
|
* @throws ClientException
|
* If the line of input could not be retrieved for some reason.
|
*/
|
final String readLineOfInput(final LocalizableMessage prompt) throws ClientException {
|
if (prompt != null) {
|
err.print(wrap(prompt));
|
err.print(" ");
|
}
|
try {
|
final String s = reader.readLine();
|
if (s == null) {
|
throw ClientException.adaptInputException(new EOFException("End of input"));
|
} else {
|
return s;
|
}
|
} catch (final IOException e) {
|
throw ClientException.adaptInputException(e);
|
}
|
}
|
|
/**
|
* Interactively prompts for user input and continues until valid input is provided.
|
*
|
* @param <T>
|
* The type of decoded user input.
|
* @param prompt
|
* The interactive prompt which should be displayed on each input attempt.
|
* @param validator
|
* An input validator responsible for validating and decoding the user's response.
|
* @return Returns the decoded user's response.
|
* @throws ClientException
|
* If an unexpected error occurred which prevented validation.
|
*/
|
public final <T> T readValidatedInput(final LocalizableMessage prompt, final ValidationCallback<T> validator)
|
throws ClientException {
|
while (true) {
|
final String response = readLineOfInput(prompt);
|
final T value = validator.validate(this, response);
|
if (value != null) {
|
return value;
|
}
|
}
|
}
|
|
/**
|
* Interactively prompts for user input and continues until valid input is provided.
|
*
|
* @param <T>
|
* The type of decoded user input.
|
* @param prompt
|
* The interactive prompt which should be displayed on each input attempt.
|
* @param validator
|
* An input validator responsible for validating and decoding the user's response.
|
* @param maxTries
|
* 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.
|
*/
|
public final <T> T readValidatedInput(final LocalizableMessage prompt, final ValidationCallback<T> validator,
|
final int maxTries) throws ClientException {
|
int nTries = 0;
|
while (nTries < maxTries) {
|
final String response = readLineOfInput(prompt);
|
final T value = validator.validate(this, response);
|
if (value != null) {
|
return value;
|
}
|
nTries++;
|
}
|
throw new ClientException(ReturnCode.ERROR_USER_DATA, ERR_TRIES_LIMIT_REACHED.get(maxTries));
|
}
|
|
/**
|
* Inserts line breaks into the provided buffer to wrap text at no more than the specified column width (80).
|
*
|
* @param msg
|
* The message to wrap.
|
* @return The wrapped message.
|
*/
|
private String wrap(final LocalizableMessage msg) {
|
return wrapText(msg, MAX_LINE_WIDTH);
|
}
|
|
/**
|
* Returns the error stream. Effectively, when an application is in "interactive mode" all the informations should
|
* be written in the stdout.
|
*
|
* @return The error stream that should be used with this application.
|
*/
|
private PrintStream getErrStream() {
|
if (isInteractive()) {
|
return out;
|
} else {
|
return err;
|
}
|
}
|
|
}
|