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     */
019    package org.crsh.console;
020    
021    import jline.console.Operation;
022    import org.crsh.keyboard.KeyHandler;
023    import org.crsh.keyboard.KeyType;
024    import org.crsh.shell.Shell;
025    import org.crsh.shell.ShellProcess;
026    import org.crsh.util.Utils;
027    
028    import java.io.IOException;
029    import java.util.concurrent.BlockingDeque;
030    import java.util.concurrent.LinkedBlockingDeque;
031    import java.util.concurrent.atomic.AtomicReference;
032    import java.util.logging.Level;
033    import 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     */
040    public 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    }