/**
*
*/
package vnet.sms.common.shell.clamshellspring.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jline.CandidateListCompletionHandler;
import jline.ConsoleReader;
import jline.SimpleCompletor;
import org.clamshellcli.api.Context;
import org.clamshellcli.api.IOConsole;
import org.clamshellcli.api.InputController;
import org.clamshellcli.api.Prompt;
import org.clamshellcli.api.SplashScreen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author obergner
*
*/
public class ControllerDispatchingIOConsole implements IOConsole {
private final Logger log = LoggerFactory.getLogger(getClass());
private Context context;
private Prompt prompt;
private ConsoleReader console;
private List<InputController> controllers;
private InputStream input;
private OutputStream output;
private Thread consoleThread;
@Override
public InputStream getInputStream() {
return this.input;
}
@Override
public OutputStream getOutputStream() {
return this.output;
}
@Override
public void plug(final Context plug) {
this.log.info("Initializing IOConsole [{}] ...", this);
this.context = plug;
this.prompt = plug.getPrompt();
this.input = (InputStream) this.context
.getValue(Context.KEY_INPUT_STREAM);
this.output = (OutputStream) this.context
.getValue(Context.KEY_OUTPUT_STREAM);
this.console = createConsoleReader();
this.log.debug("Console reader [{}] created", this.console);
// plug in installed input controllers
this.controllers = createControllers(plug);
this.log.debug("Created and registered input controllers [{}]",
this.controllers);
aggregateExpectedInputs();
// show splash on the default OutputStream
renderSplashScreens(plug);
this.consoleThread = createConsoleThread();
this.consoleThread.start();
this.log.info("Console thread [{}] started", this.consoleThread);
}
private ConsoleReader createConsoleReader() throws RuntimeException {
try {
final ConsoleReader result = new ConsoleReader(this.input,
new OutputStreamWriter(this.output));
result.setCompletionHandler(new CandidateListCompletionHandler());
return result;
} catch (final IOException ex) {
throw new RuntimeException("Unable to initialize the console. "
+ " Program will stop now.", ex);
}
}
private List<InputController> createControllers(final Context plug)
throws RuntimeException {
final List<InputController> result = plug
.getPluginsByType(InputController.class);
if (result.size() == 0) {
throw new RuntimeException(
"Unable to initialize Clamshell-Cli. "
+ " No InputController instances found in plugin registry . Exiting...");
}
for (final InputController ctrl : result) {
ctrl.plug(plug);
}
return result;
}
private void renderSplashScreens(final Context plug) {
final List<SplashScreen> screens = plug
.getPluginsByType(SplashScreen.class);
if ((screens != null) && (screens.size() > 0)) {
for (final SplashScreen sc : screens) {
sc.plug(plug);
sc.render(plug);
}
}
}
@Override
public void writeOutput(final String val) {
try {
this.console.printString(val);
} catch (final IOException ex) {
throw new RuntimeException("Unable to invoke print on console: ",
ex);
}
}
@Override
public String readInput(final String prompt) {
try {
return this.console.readLine(prompt);
} catch (final IOException ex) {
throw new RuntimeException("Unable to read input: ", ex);
}
}
private Thread createConsoleThread() {
final Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()) {
// reset command line arguments from previous command
ControllerDispatchingIOConsole.this.context.putValue(
Context.KEY_COMMAND_LINE_ARGS, null);
boolean handled = false;
final String line = readInput(ControllerDispatchingIOConsole.this.prompt
.getValue(ControllerDispatchingIOConsole.this.context));
if ((line == null) || line.trim().isEmpty()) {
continue;
}
ControllerDispatchingIOConsole.this.log.trace(
"Handling input line [{}] ...", line);
ControllerDispatchingIOConsole.this.context.putValue(
Context.KEY_COMMAND_LINE_INPUT, line);
if (!controllersExist()) {
writeOutput(String
.format("Warning: no controllers(s) found.%n"));
ControllerDispatchingIOConsole.this.log
.warn("No input controller(s) found");
continue;
}
for (final InputController controller : ControllerDispatchingIOConsole.this.controllers) {
ControllerDispatchingIOConsole.this.log
.trace("Dispatching line [{}] to input controller [{}] ...",
new Object[] { line, controller });
final boolean ctrlResult = controller
.handle(ControllerDispatchingIOConsole.this.context);
handled = handled || ctrlResult;
}
// was command line handled.
if (!handled) {
writeOutput(String
.format("%nCommand unhandled. "
+ "%nNo controller found to respond to [%s].%n%n",
line));
ControllerDispatchingIOConsole.this.log
.warn("Could not process line [{}] - no matching input controller found",
line);
}
}
}
});
return t;
}
/**
* Are there any controllers installed?
*
* @param controllers
*/
private boolean controllersExist() {
return ((this.controllers != null) && (this.controllers.size() > 0)) ? true
: false;
}
/**
* Collection expected input values to build suggestion lists.
*/
private void aggregateExpectedInputs() {
final List<String> inputs = new ArrayList<String>();
for (final InputController ctrl : this.controllers) {
final String[] expectedInputs = ctrl.getExpectedInputs();
if (expectedInputs != null) {
Collections.addAll(inputs, expectedInputs);
}
}
this.console.addCompletor(new SimpleCompletor(inputs
.toArray(new String[0])));
}
}