/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.tools.dsconfig; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.messages.ToolMessages.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import java.io.BufferedReader; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.Reader; import java.util.List; import org.opends.server.admin.client.ManagementContext; import org.opends.server.tools.ClientException; import org.opends.server.types.NullOutputStream; import org.opends.server.util.PasswordReader; import org.opends.server.util.Validator; import org.opends.server.util.args.ArgumentException; import org.opends.server.util.table.TableBuilder; import org.opends.server.util.table.TextTablePrinter; /** * This class provides an abstract base class which can be used as the * basis of a console-based application. */ public abstract class ConsoleApplication { // The error stream which this application should use. private final PrintStream err; // The input stream reader which this application should use. private final BufferedReader in; // The output stream which this application should use. private final PrintStream out; /** * Creates a new console application instance. * * @param in * The application input stream. * @param out * The application output stream. * @param err * The application error stream. */ protected ConsoleApplication(InputStream in, OutputStream out, OutputStream err) { if (in != null) { this.in = new BufferedReader(new InputStreamReader(in)); } else { this.in = new BufferedReader(new Reader() { /** * {@inheritDoc} */ @Override public void close() throws IOException { // Do nothing. } /** * {@inheritDoc} */ @Override public int read(char[] cbuf, int off, int len) throws IOException { return -1; } }); } if (out != null) { this.out = new PrintStream(out); } else { this.out = NullOutputStream.printStream(); } if (err != null) { this.err = new PrintStream(err); } else { this.err = NullOutputStream.printStream(); } } /** * Interactively confirms whether a user wishes to perform an * action. If the application is non-interactive, then the action is * granted by default. * * @param prompt * The prompt describing the action. * @return Returns true if the user wishes the action * to be performed, or false if they refused, * or if an exception occurred. * @throws ArgumentException * If the user's response could not be read from the * console for some reason. */ public final boolean confirmAction(String prompt) throws ArgumentException { if (!isInteractive()) { return true; } final String yes = getMessage(MSGID_DSCFG_GENERAL_CONFIRM_YES); final String no = getMessage(MSGID_DSCFG_GENERAL_CONFIRM_NO); final String errMsg = getMessage(MSGID_DSCFG_ERROR_GENERAL_CONFIRM, yes, no); prompt = prompt + String.format(" (%s / %s): ", yes, no); ValidationCallback validator = new ValidationCallback() { public Boolean validate(ConsoleApplication app, String input) { String ninput = input.toLowerCase().trim(); if (ninput.length() == 0) { // Empty input. app.println(); app.printMessage(errMsg); } else if (no.startsWith(ninput)) { return false; } else if (yes.startsWith(ninput)) { return true; } else { // Try again... app.println(); app.printMessage(errMsg); } return null; } }; try { return readValidatedInput(prompt, validator); } catch (ClientException e) { // Should never happen. throw new RuntimeException(e); } } /** * Displays a message to the error stream. * * @param msg * The message. */ public final void printMessage(String msg) { err.println(wrapText(msg, MAX_LINE_WIDTH)); } /** * Displays a blank line to the error stream. */ public final void println() { err.println(); } /** * Displays a message to the error stream if verbose mode is * enabled. * * @param msg * The verbose message. */ public final void printVerboseMessage(String msg) { if (isVerbose() || isInteractive()) { err.println(wrapText(msg, MAX_LINE_WIDTH)); } } /** * Gets the application error stream. * * @return Returns the application error stream. */ public final PrintStream getErrorStream() { return err; } /** * Gets the application input stream. * * @return Returns the application input stream. */ public final BufferedReader getInputStream() { return in; } /** * Gets the management context which sub-commands should use in * order to manage the directory server. * * @return Returns the management context which sub-commands should * use in order to manage the directory server. * @throws ArgumentException * If a management context related argument could not be * parsed successfully. * @throws ClientException * If the management context could not be created. */ public abstract ManagementContext getManagementContext() throws ArgumentException, ClientException; /** * Gets the application output stream. * * @return Returns the application output stream. */ public final PrintStream getOutputStream() { return out; } /** * Indicates whether or not the user has requested interactive * behavior. * * @return Returns true if the user has requested * interactive behavior. */ public abstract boolean isInteractive(); /** * Indicates whether or not the user has requested quiet output. * * @return Returns true if the user has requested * quiet output. */ public abstract boolean isQuiet(); /** * Indicates whether or not the user has requested script-friendly * output. * * @return Returns true if the user has requested * script-friendly output. */ public abstract boolean isScriptFriendly(); /** * Indicates whether or not the user has requested verbose output. * * @return Returns true if the user has requested * verbose output. */ public abstract boolean isVerbose(); /** * Interactively prompts the user to select from a choice of * options. * * @param * The type of the values represented by each choice. * @param prompt * The prompt which should appear before the list of * choices. * @param descriptions * The descriptions of each choice. * @param values * The choices. * @param helpCallback * An optional help call-back which can be used to display * additional help. * @return Returns the selected value. * @throws ArgumentException * If the user input could not be retrieved for some * reason. */ public final T readChoice(final String prompt, List descriptions, List values, final HelpCallback helpCallback) throws ArgumentException { Validator.ensureTrue(descriptions.size() == values.size()); // Output main prompt. println(); printMessage(prompt); println(); // Build the table of choices. final TableBuilder builder = new TableBuilder(); final int size = descriptions.size(); for (int i = 0; i < size; i++) { builder.startRow(); builder.appendCell("[" + (i + 1) + "]"); builder.appendCell(descriptions.get(i)); } // Display the table of choices. final TextTablePrinter printer = new TextTablePrinter(err); printer.setDisplayHeadings(false); printer.setColumnWidth(1, 0); builder.print(printer); // Get the user input. String promptMsg; if (helpCallback != null) { promptMsg = getMessage(MSGID_DSCFG_GENERAL_CHOICE_PROMPT_HELP, size); } else { promptMsg = getMessage(MSGID_DSCFG_GENERAL_CHOICE_PROMPT_NOHELP, size); } ValidationCallback validator = new ValidationCallback() { public Integer validate(ConsoleApplication app, String input) { String ninput = input.trim(); if (ninput.equals("?") && helpCallback != null) { app.println(); helpCallback.display(app); app.println(); // Output main prompt. printMessage(prompt); println(); builder.print(printer); return null; } else { try { int i = Integer.parseInt(ninput); if (i < 1 || i > size) { throw new NumberFormatException(); } return i; } catch (NumberFormatException e) { app.println(); String errMsg = getMessage(MSGID_DSCFG_ERROR_GENERAL_CHOICE, size); app.printMessage(errMsg); return null; } } } }; // Get the choice. int choice; try { choice = readValidatedInput(promptMsg, validator); } catch (ClientException e) { // Should never happen. throw new RuntimeException(e); } return values.get(choice - 1); } /** * Interactively retrieves a line of input from the console. * * @param prompt * The prompt. * @return Returns the line of input, or null if the * end of input has been reached. * @throws ArgumentException * If the line of input could not be retrieved for some * reason. */ public final String readLineOfInput(String prompt) throws ArgumentException { err.println(); err.print(wrapText(prompt.trim() + " ", MAX_LINE_WIDTH)); try { return in.readLine(); } catch (IOException e) { throw ArgumentExceptionFactory.unableToReadConsoleInput(e); } } /** * Interactively retrieves a password from the console. * * @param prompt * The password prompt. * @return Returns the password. * @throws ArgumentException * If the password could not be retrieved for some reason. */ public final String readPassword(String prompt) throws ArgumentException { err.print(wrapText(prompt + " ", MAX_LINE_WIDTH)); char[] pwChars; try { pwChars = PasswordReader.readPassword(); } catch (Exception e) { throw ArgumentExceptionFactory.unableToReadConsoleInput(e); } return new String(pwChars); } /** * 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 ArgumentException * If the line of input could not be retrieved for some * reason. * @throws ClientException * If an unexpected error occurred which prevented * validation. */ public final T readValidatedInput(String prompt, ValidationCallback validator) throws ArgumentException, ClientException { while (true) { String response = readLineOfInput(prompt); if (response == null) { throw ArgumentExceptionFactory .unableToReadConsoleInput(new EOFException("End of input")); } T value = validator.validate(this, response); if (value != null) { return value; } } } }