blob: a54f2d8137bfdd5bbe8f1dc2a820fcfd56006064 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.framework.internal.core;
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.*;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Bundle;
/**
* This class knows how to parse and execute the command line arguments to the FrameworkConsole.
* It attempts to pass the command to each registered CommandProvider until it finds one
* that knows what to do with it.
*
* FrameworkCommandInterpreter provides support for the "More" command which allows the operator to configure
* the number of lines to display before being prompted to continue.
*
* FrameworkCommandInterpreter provides several print methods which handle the "More" command.
*/
public class FrameworkCommandInterpreter implements CommandInterpreter {
private static final String WS_DELIM = " \t\n\r\f"; //$NON-NLS-1$
/** The command line in StringTokenizer form */
private StringTokenizer tok;
/** The active CommandProviders */
private Object[] commandProviders;
/** The FrameworkConsole */
private FrameworkConsole con;
/** The stream to send output to */
private PrintWriter out;
/** Strings used to format other strings */
private String tab = "\t"; //$NON-NLS-1$
private String newline = "\r\n"; //$NON-NLS-1$
private boolean firstCommand = true;
/**
* The maximum number of lines to print without user prompt.
* 0 means no user prompt is required, the window is scrollable.
*/
protected static int maxLineCount;
/** The number of lines printed without user prompt.*/
protected int currentLineCount;
/**
* The constructor. It turns the cmdline string into a StringTokenizer and remembers
* the input parms.
*/
public FrameworkCommandInterpreter(String cmdline, Object[] commandProviders, FrameworkConsole con) {
tok = new StringTokenizer(cmdline);
this.commandProviders = commandProviders;
this.con = con;
this.out = con.getWriter();
}
/**
Get the next argument in the input.
E.g. if the commandline is hello world, the _hello method
will get "world" as the first argument.
@return A string containing the next argument on the command line
*/
public String nextArgument() {
if (tok == null || !tok.hasMoreElements())
return null;
return consumeQuotes(tok.nextToken());
}
private String consumeQuotes(String arg) {
if (!(arg.startsWith("\"") || arg.startsWith("'"))) //$NON-NLS-1$//$NON-NLS-2$
return arg;
String quote = arg.substring(0, 1);
if (arg.endsWith(quote)) {
if (arg.length() >= 2)
// strip the beginning and ending quotes
return arg.substring(1, arg.length() - 1);
// single quote case; return empty string
return ""; //$NON-NLS-1$
}
try {
arg = arg.substring(1) + tok.nextToken(quote);
} catch (NoSuchElementException e) {
// should not happen
printStackTrace(e);
return ""; //$NON-NLS-1$
}
try {
// skip to next whitespace separated token
tok.nextToken(WS_DELIM);
} catch (NoSuchElementException e) {
// this is ok we are at the end
}
return arg;
}
/**
Execute a command line as if it came from the end user.
Searches the list of command providers using introspection until
it finds one that contains a matching method. It searches for a method
with the name "_cmd" where cmd is the command to execute. For example,
for a command of "launch" execute searches for a method called "_launch".
@param cmd The name of the command to execute.
@return The object returned by the method executed.
*/
public Object execute(String cmd) {
if (!firstCommand)
return innerExecute(cmd);
firstCommand = false;
resetLineCount();
Object retval = null;
// handle "more" command here
if (cmd.equalsIgnoreCase("more")) { //$NON-NLS-1$
try {
_more();
} catch (Exception e) {
printStackTrace(e);
}
return retval;
}
// handle "disconnect" command here
if (cmd.equalsIgnoreCase("disconnect")) { //$NON-NLS-1$
try {
_disconnect();
} catch (Exception e) {
printStackTrace(e);
}
return retval;
}
Class[] parameterTypes = new Class[] {CommandInterpreter.class};
Object[] parameters = new Object[] {this};
boolean executed = false;
int size = commandProviders.length;
for (int i = 0; !executed && (i < size); i++) {
try {
Object target = commandProviders[i];
Method method = target.getClass().getMethod("_" + cmd, parameterTypes); //$NON-NLS-1$
retval = method.invoke(target, parameters);
executed = true; // stop after the command has been found
} catch (NoSuchMethodException ite) {
// keep going - maybe another command provider will be able to execute this command
} catch (InvocationTargetException ite) {
executed = true; // don't want to keep trying - we found the method but got an error
printStackTrace(ite.getTargetException());
} catch (Exception ee) {
executed = true; // don't want to keep trying - we got an error we don't understand
printStackTrace(ee);
}
}
// if no command was found to execute, display help for all registered command providers
if (!executed) {
for (int i = 0; i < size; i++) {
try {
CommandProvider commandProvider = (CommandProvider) commandProviders[i];
out.print(commandProvider.getHelp());
out.flush();
} catch (Exception ee) {
printStackTrace(ee);
}
}
// call help for the more command provided by this class
out.print(getHelp());
out.flush();
}
return retval;
}
private Object innerExecute(String cmd) {
if (cmd != null && cmd.length() > 0) {
CommandInterpreter intcp = new FrameworkCommandInterpreter(cmd, commandProviders, con);
String command = intcp.nextArgument();
if (command != null)
return intcp.execute(command);
}
return null;
}
/**
* Answers the number of lines output to the console
* window should scroll without user interaction.
*
* @return The number of lines to scroll.
*/
private int getMaximumLinesToScroll() {
return maxLineCount;
}
/**
* Sets the number of lines output to the console
* window will scroll without user interaction.
* <p>
* Note that this number does not include the line
* for the 'more' prompt itself.
* <p>
* If the number of lines is 0 then no 'more' prompt
* is disabled.
*
* @param lines the number of lines to scroll
*/
private void setMaximumLinesToScroll(int lines) {
if (lines < 0) {
throw new IllegalArgumentException(ConsoleMsg.CONSOLE_LINES_TO_SCROLL_NEGATIVE_ERROR);
}
maxLineCount = lines;
}
/**
* Resets the line counter for the 'more' prompt.
*/
private void resetLineCount() {
currentLineCount = 0;
}
/**
* Prints a string to the output medium (appended with newline character).
* <p>
* This method does not increment the line counter for the 'more' prompt.
*
* @param o the string to be printed
*/
private void printline(Object o) {
print(o + newline);
}
/**
* Prints an object to the outputstream
*
* @param o the object to be printed
*/
public void print(Object o) {
synchronized (out) {
check4More();
out.print(o);
out.flush();
}
}
/**
* Prints a empty line to the outputstream
*/
public void println() {
println(""); //$NON-NLS-1$
}
/**
* Print a stack trace including nested exceptions.
* @param t The offending exception
*/
public void printStackTrace(Throwable t) {
t.printStackTrace(out);
Method[] methods = t.getClass().getMethods();
int size = methods.length;
Class throwable = Throwable.class;
for (int i = 0; i < size; i++) {
Method method = methods[i];
if (Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("get") && throwable.isAssignableFrom(method.getReturnType()) && (method.getParameterTypes().length == 0)) { //$NON-NLS-1$
try {
Throwable nested = (Throwable) method.invoke(t, null);
if ((nested != null) && (nested != t)) {
out.println(ConsoleMsg.CONSOLE_NESTED_EXCEPTION);
printStackTrace(nested);
}
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
}
}
}
/**
* Prints an object to the output medium (appended with newline character).
* <p>
* If running on the target environment, the user is prompted with '--more'
* if more than the configured number of lines have been printed without user prompt.
* This enables the user of the program to have control over scrolling.
* <p>
* For this to work properly you should not embed "\n" etc. into the string.
*
* @param o the object to be printed
*/
public void println(Object o) {
if (o == null) {
return;
}
synchronized (out) {
check4More();
printline(o);
currentLineCount++;
currentLineCount += o.toString().length() / 80;
}
}
/**
* Prints the given dictionary sorted by keys.
*
* @param dic the dictionary to print
* @param title the header to print above the key/value pairs
*/
public void printDictionary(Dictionary dic, String title) {
if (dic == null)
return;
int count = dic.size();
String[] keys = new String[count];
Enumeration keysEnum = dic.keys();
int i = 0;
while (keysEnum.hasMoreElements()) {
keys[i++] = (String) keysEnum.nextElement();
}
Util.sortByString(keys);
if (title != null) {
println(title);
}
for (i = 0; i < count; i++) {
println(" " + keys[i] + " = " + dic.get(keys[i])); //$NON-NLS-1$//$NON-NLS-2$
}
println();
}
/**
* Prints the given bundle resource if it exists
*
* @param bundle the bundle containing the resource
* @param resource the resource to print
*/
public void printBundleResource(Bundle bundle, String resource) {
URL entry = null;
entry = bundle.getEntry(resource);
if (entry != null) {
try {
println(resource);
InputStream in = entry.openStream();
byte[] buffer = new byte[1024];
int read = 0;
try {
while ((read = in.read(buffer)) != -1)
print(new String(buffer, 0, read));
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
} catch (Exception e) {
System.err.println(NLS.bind(ConsoleMsg.CONSOLE_ERROR_READING_RESOURCE, resource));
}
} else {
println(NLS.bind(ConsoleMsg.CONSOLE_RESOURCE_NOT_IN_BUNDLE, resource, bundle.toString()));
}
}
/**
* Displays the more... prompt if the max line count has been reached
* and waits for the operator to hit enter.
*
*/
private void check4More() {
int max = getMaximumLinesToScroll();
if (max > 0) {
if (currentLineCount >= max) {
out.print(ConsoleMsg.CONSOLE_MORE);
out.flush();
con.getInput(); // wait for user entry
resetLineCount(); //Reset the line counter for the 'more' prompt
}
}
}
/**
Answer a string (may be as many lines as you like) with help
texts that explain the command.
*/
public String getHelp() {
StringBuffer help = new StringBuffer(256);
help.append(ConsoleMsg.CONSOLE_HELP_CONTROLLING_CONSOLE_HEADING);
help.append(newline);
help.append(tab);
help.append("more - "); //$NON-NLS-1$
help.append(ConsoleMsg.CONSOLE_HELP_MORE);
help.append(newline);
help.append(tab);
help.append("disconnect - "); //$NON-NLS-1$
help.append(ConsoleMsg.CONSOLE_HELP_DISCONNECT);
help.append(newline);
return help.toString();
}
/**
* Toggles the use of the more prompt for displayed output.
*
*/
public void _more() throws Exception {
if (confirm(ConsoleMsg.CONSOLE_CONFIRM_MORE, true)) {
int lines = prompt(newline + ConsoleMsg.CONSOLE_MORE_ENTER_LINES, 24);
setMaximumLinesToScroll(lines);
} else {
setMaximumLinesToScroll(0);
}
}
private void _disconnect() throws Exception {
if (confirm(ConsoleMsg.CONSOLE_CONFIRM_DISCONNECT, true)) {
con.shutdown();
}
}
/**
* Prompts the user for confirmation.
*
* @param string the message to present to the user to confirm
* @param defaultAnswer the default result
*
* @return <code>true</code> if the user confirms; <code>false</code> otherwise.
*/
protected boolean confirm(String string, boolean defaultAnswer) {
synchronized (out) {
if (string.length() > 0) {
print(string);
} else {
print(ConsoleMsg.CONSOLE_CONFIRM);
}
print(" (" + ConsoleMsg.CONSOLE_CONFIRM_VALUES); //$NON-NLS-1$
if (defaultAnswer) {
print(ConsoleMsg.CONSOLE_Y + ") "); //$NON-NLS-1$
} else {
print(ConsoleMsg.CONSOLE_N + ") "); //$NON-NLS-1$
}
}
String input = con.getInput();
resetLineCount();
if (input.length() == 0) {
return defaultAnswer;
}
return input.toLowerCase().charAt(0) == ConsoleMsg.CONSOLE_Y.charAt(0);
}
/**
* Prompts the user for input from the input medium providing a default value.
*
* @param string the message to present to the user
* @param defaultAnswer the string to use as a default return value
*
* @return The user provided string or the defaultAnswer,
* if user provided string was empty.
*/
protected String prompt(String string, String defaultAnswer) {
if (string.length() > 0) {
if (defaultAnswer.length() > 0) {
StringBuffer buf = new StringBuffer(256);
buf.append(string);
buf.append(" "); //$NON-NLS-1$
buf.append(ConsoleMsg.CONSOLE_PROMPT_DEFAULT);
buf.append("="); //$NON-NLS-1$
buf.append(defaultAnswer);
buf.append(") "); //$NON-NLS-1$
print(buf.toString());
} else {
print(string);
}
}
String input = con.getInput();
resetLineCount();
if (input.length() > 0) {
return input;
}
return defaultAnswer;
}
/**
* Prompts the user for input of a positive integer.
*
* @param string the message to present to the user
* @param defaultAnswer the integer to use as a default return value
*
* @return The user provided integer or the defaultAnswer,
* if user provided an empty input.
*/
protected int prompt(String string, int defaultAnswer) {
Integer i = new Integer(defaultAnswer);
int answer;
for (int j = 0; j < 3; j++) {
String s = prompt(string, i.toString());
try {
answer = Integer.parseInt(s);
if (answer >= 0) {
return answer;
}
} catch (NumberFormatException e) {
}
println(ConsoleMsg.CONSOLE_INVALID_INPUT);
}
println(ConsoleMsg.CONSOLE_TOO_MUCH_INVALID_INPUT);
return defaultAnswer;
}
}