001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019package org.crsh.console;
020
021import jline.console.Operation;
022import org.crsh.keyboard.KeyHandler;
023import org.crsh.keyboard.KeyType;
024import org.crsh.shell.Shell;
025import org.crsh.shell.ShellProcess;
026import org.crsh.util.Utils;
027
028import java.io.IOException;
029import java.util.concurrent.BlockingDeque;
030import java.util.concurrent.LinkedBlockingDeque;
031import java.util.concurrent.atomic.AtomicReference;
032import java.util.logging.Level;
033import java.util.logging.Logger;
034
035/**
036 * A console state machine, which delegates the state machine to the {@link Plugin} implementation.
037 *
038 * @author Julien Viet
039 */
040public class Console {
041
042  /** The logger. */
043  private static final Logger log = Logger.getLogger(Console.class.getName());
044
045  /** . */
046  static final int RUNNING = 0;
047
048  /** . */
049  static final int CLOSING = 1;
050
051  /** . */
052  static final int CLOSED = 2;
053
054  /** . */
055  final Shell shell;
056
057  /** The current handler. */
058  final AtomicReference<Plugin> handler;
059
060  /** The buffer. */
061  final BlockingDeque<KeyStroke> buffer;
062
063  /** . */
064  final ConsoleDriver driver;
065
066  /** . */
067  final Editor editor;
068
069  /** . */
070  int status;
071
072  public Console(Shell shell, ConsoleDriver driver) throws NullPointerException {
073    if (shell == null) {
074      throw new NullPointerException("No null shell accepted");
075    }
076    this.driver = driver;
077    this.shell = shell;
078    this.buffer = new LinkedBlockingDeque<KeyStroke>(1024);
079    this.handler = new AtomicReference<Plugin>();
080    this.editor = new Editor(this);
081    this.status = RUNNING;
082  }
083
084  public void setMode(Mode mode) {
085    editor.setMode(mode);
086  }
087
088  public void toEmacs() {
089    setMode(Mode.EMACS);
090  }
091
092  public void toMove() {
093    setMode(Mode.VI_MOVE);
094  }
095
096  public void toInsert() {
097    setMode(Mode.VI_INSERT);
098  }
099
100  public Mode getMode() {
101    return editor.getMode();
102  }
103
104  public void addModeListener(Runnable runnable) {
105    editor.addModeListener(runnable);
106  }
107
108  public boolean isRunning() {
109    return status == RUNNING;
110  }
111
112  /**
113   * Initiali
114   */
115  public void init() {
116    // Take care of pormpt
117    String welcome = shell.getWelcome();
118    if (welcome != null && welcome.length() > 0) {
119      try {
120        driver.write(welcome);
121        driver.flush();
122      }
123      catch (IOException e) {
124        // Log it
125      }
126    }
127    edit();
128  }
129
130  public Iterable<KeyStroke> getKeyBuffer() {
131    return buffer;
132  }
133
134  public void on(Operation operation, int... buffer) {
135    on(new KeyStroke(operation, buffer));
136  }
137
138  public void on(KeyStroke keyStroke) {
139
140    //
141    if (keyStroke.operation == Operation.INTERRUPT) {
142      Plugin current = handler.get();
143      if (current == null) {
144        throw new IllegalStateException("Not initialized");
145      } else if (current instanceof ProcessHandler) {
146        ProcessHandler processHandler = (ProcessHandler)current;
147        ProcessHandler.Reader reader = processHandler.editor.get();
148        if (reader != null) {
149          reader.thread.interrupt();
150        }
151        processHandler.process.cancel();
152        return;
153      }
154    }
155    buffer.add(keyStroke);
156
157    //
158    iterate();
159
160    // This was modified by this thread during the loop
161    if (status == CLOSING) {
162      status = CLOSED;
163      Utils.close(driver);
164    }
165  }
166
167  public void on(KeyStroke[] keyStrokes) {
168    for (KeyStroke keyStroke : keyStrokes) {
169      on(keyStroke);
170    }
171  }
172
173
174  void close() {
175    if (status == RUNNING) {
176      status = CLOSED;
177      Utils.close(driver);
178    }
179  }
180
181  /**
182   * Switch to edit.
183   */
184  Editor edit() {
185    String prompt = shell.getPrompt();
186    if (prompt != null && prompt.length() > 0) {
187      try {
188        driver.write(prompt);
189        driver.flush();
190      }
191      catch (IOException e) {
192        // Swallow for now...
193      }
194    }
195    editor.reset();
196    handler.set(editor);
197    return editor;
198  }
199
200  /**
201   * Process the state machine.
202   */
203  void iterate() {
204    while (status == RUNNING) {
205      Plugin current = handler.get();
206      KeyStroke key = buffer.poll();
207      if (key != null) {
208        if (current == null) {
209          throw new IllegalStateException("Not initialized");
210        } else if (current instanceof Editor) {
211          Editor editor = (Editor)current;
212          EditorAction action = editor.getMode().on(key);
213          if (action != null) {
214            String line = editor.append(action, key.sequence);
215            if (line != null) {
216              ShellProcess process = shell.createProcess(line);
217              ProcessHandler context = new ProcessHandler(this, process);
218              handler.set(context);
219              process.execute(context);
220            }
221          }
222        } else if (current instanceof ProcessHandler) {
223          ProcessHandler processHandler = (ProcessHandler)current;
224          ProcessHandler.Reader reader = processHandler.editor.get();
225          if (reader != null) {
226            EditorAction action = editor.getMode().on(key);
227            if (action != null) {
228              String s = reader.editor.append(action, key.sequence);
229              if (s != null) {
230                reader.line.add(s);
231              }
232            }
233          } else {
234            KeyHandler keyHandler = processHandler.process.getKeyHandler();
235            if (keyHandler != null) {
236              KeyType type = key.map();
237              try {
238                keyHandler.handle(type, key.sequence);
239              }
240              catch (Throwable t) {
241                // Perhaps handle better this and treat error / exception differently
242                log.log(Level.SEVERE, "Key handler " + keyHandler + " failure", t);
243              }
244            } else {
245              buffer.addFirst(key);
246            }
247            return;
248          }
249        } else {
250          throw new UnsupportedOperationException();
251        }
252      } else {
253        return;
254      }
255    }
256  }
257}