/*
* Copyright (C) 2012 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.crsh.console;
import jline.console.Operation;
import org.crsh.keyboard.KeyHandler;
import org.crsh.keyboard.KeyType;
import org.crsh.shell.Shell;
import org.crsh.shell.ShellProcess;
import org.crsh.util.Utils;
import java.io.IOException;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A console state machine, which delegates the state machine to the {@link Plugin} implementation.
*
* @author Julien Viet
*/
public class Console {
/** The logger. */
private static final Logger log = Logger.getLogger(Console.class.getName());
/** . */
static final int RUNNING = 0;
/** . */
static final int CLOSING = 1;
/** . */
static final int CLOSED = 2;
/** . */
final Shell shell;
/** The current handler. */
final AtomicReference<Plugin> handler;
/** The buffer. */
final BlockingDeque<KeyStroke> buffer;
/** . */
final ConsoleDriver driver;
/** . */
final Editor editor;
/** . */
int status;
public Console(Shell shell, ConsoleDriver driver) throws NullPointerException {
if (shell == null) {
throw new NullPointerException("No null shell accepted");
}
this.driver = driver;
this.shell = shell;
this.buffer = new LinkedBlockingDeque<KeyStroke>(1024);
this.handler = new AtomicReference<Plugin>();
this.editor = new Editor(this);
this.status = RUNNING;
}
public void setMode(Mode mode) {
editor.setMode(mode);
}
public void toEmacs() {
setMode(Mode.EMACS);
}
public void toMove() {
setMode(Mode.VI_MOVE);
}
public void toInsert() {
setMode(Mode.VI_INSERT);
}
public Mode getMode() {
return editor.getMode();
}
public void addModeListener(Runnable runnable) {
editor.addModeListener(runnable);
}
public boolean isRunning() {
return status == RUNNING;
}
/**
* Initiali
*/
public void init() {
// Take care of pormpt
String welcome = shell.getWelcome();
if (welcome != null && welcome.length() > 0) {
try {
driver.write(welcome);
driver.flush();
}
catch (IOException e) {
// Log it
}
}
edit();
}
public Iterable<KeyStroke> getKeyBuffer() {
return buffer;
}
public void on(Operation operation, int... buffer) {
on(new KeyStroke(operation, buffer));
}
public void on(KeyStroke keyStroke) {
//
if (keyStroke.operation == Operation.INTERRUPT) {
Plugin current = handler.get();
if (current == null) {
throw new IllegalStateException("Not initialized");
} else if (current instanceof ProcessHandler) {
ProcessHandler processHandler = (ProcessHandler)current;
ProcessHandler.Reader reader = processHandler.editor.get();
if (reader != null) {
reader.thread.interrupt();
}
processHandler.process.cancel();
return;
}
}
buffer.add(keyStroke);
//
iterate();
// This was modified by this thread during the loop
if (status == CLOSING) {
status = CLOSED;
Utils.close(driver);
}
}
public void on(KeyStroke[] keyStrokes) {
for (KeyStroke keyStroke : keyStrokes) {
on(keyStroke);
}
}
void close() {
if (status == RUNNING) {
status = CLOSED;
Utils.close(driver);
}
}
/**
* Switch to edit.
*/
Editor edit() {
String prompt = shell.getPrompt();
if (prompt != null && prompt.length() > 0) {
try {
driver.write(prompt);
driver.flush();
}
catch (IOException e) {
// Swallow for now...
}
}
editor.reset();
handler.set(editor);
return editor;
}
/**
* Process the state machine.
*/
void iterate() {
while (status == RUNNING) {
Plugin current = handler.get();
KeyStroke key = buffer.poll();
if (key != null) {
if (current == null) {
throw new IllegalStateException("Not initialized");
} else if (current instanceof Editor) {
Editor editor = (Editor)current;
EditorAction action = editor.getMode().on(key);
if (action != null) {
String line = editor.append(action, key.sequence);
if (line != null) {
ShellProcess process = shell.createProcess(line);
ProcessHandler context = new ProcessHandler(this, process);
handler.set(context);
process.execute(context);
}
}
} else if (current instanceof ProcessHandler) {
ProcessHandler processHandler = (ProcessHandler)current;
ProcessHandler.Reader reader = processHandler.editor.get();
if (reader != null) {
EditorAction action = editor.getMode().on(key);
if (action != null) {
String s = reader.editor.append(action, key.sequence);
if (s != null) {
reader.line.add(s);
}
}
} else {
KeyHandler keyHandler = processHandler.process.getKeyHandler();
if (keyHandler != null) {
KeyType type = key.map();
try {
keyHandler.handle(type, key.sequence);
}
catch (Throwable t) {
// Perhaps handle better this and treat error / exception differently
log.log(Level.SEVERE, "Key handler " + keyHandler + " failure", t);
}
} else {
buffer.addFirst(key);
}
return;
}
} else {
throw new UnsupportedOperationException();
}
} else {
return;
}
}
}
}