/* * 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 true 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 * 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 readValidatedInput(final LocalizableMessage prompt, final ValidationCallback 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 * 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 readValidatedInput(final LocalizableMessage prompt, final ValidationCallback 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; } } }