001/*
002 * (C) Copyright 2006-2010 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.shell.cmds;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.PrintWriter;
024import java.io.Writer;
025
026import jline.ANSIBuffer;
027import jline.ConsoleReader;
028import jline.Terminal;
029
030import org.nuxeo.shell.Command;
031import org.nuxeo.shell.Context;
032import org.nuxeo.shell.Shell;
033import org.nuxeo.shell.ShellConsole;
034import org.nuxeo.shell.ShellException;
035import org.nuxeo.shell.cmds.completors.ShellCompletor;
036
037/**
038 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
039 */
040@Command(name = "interactive", help = "Interactive shell")
041public class Interactive implements Runnable, ShellConsole {
042
043    protected static ConsoleReaderFactory factory;
044
045    public static void setConsoleReaderFactory(ConsoleReaderFactory factory) {
046        Interactive.factory = factory;
047    }
048
049    protected static String currentCmdLine;
050
051    @Context
052    protected Shell shell;
053
054    protected ConsoleReader console;
055
056    private static boolean isRunning = false;
057
058    private static InteractiveShellHandler handler;
059
060    public static void setHandler(InteractiveShellHandler exitHandler) {
061        Interactive.handler = exitHandler;
062    }
063
064    public static void reset() {
065        isRunning = false;
066    }
067
068    public Interactive() throws IOException {
069        console = factory != null ? factory.getConsoleReader() : new ConsoleReader();
070    }
071
072    /**
073     * Used in GUI mode
074     *
075     * @param shell
076     * @param in
077     * @param out
078     * @throws IOException
079     */
080    public Interactive(Shell shell, InputStream in, Writer out, Terminal term) throws IOException {
081        this.shell = shell;
082        console = new ConsoleReader(in, out, null, term);
083    }
084
085    public static String getCurrentCmdLine() {
086        return currentCmdLine;
087    }
088
089    public ConsoleReader getConsole() {
090        return console;
091    }
092
093    public Shell getShell() {
094        return shell;
095    }
096
097    public void run() {
098        if (handler != null) {
099            handler.enterInteractiveMode();
100        }
101        if (isRunning) { // avoid entering twice this command
102            return;
103        }
104        isRunning = true;
105        console.addCompletor(new ShellCompletor(this));
106        shell.setConsole(this);
107        console.setDefaultPrompt(getPrompt());
108        try {
109            loadHistory();
110            shell.hello();
111            try {
112                shell.getActiveRegistry().autorun(shell);
113            } catch (Throwable t) {
114                handleError(t);
115            }
116            while (true) { // NOSONAR (exit through System.exit)
117                try {
118                    String cmdline = console.readLine(getPrompt());
119                    currentCmdLine = cmdline;
120                    shell.run(cmdline);
121                } catch (Throwable t) {
122                    int r = handleError(t);
123                    if (r != 0) {
124                        isRunning = false;
125                        if (handler != null) {
126                            if (handler.exitInteractiveMode(r)) {
127                                return;
128                            }
129                        } else { // default exit mechanism
130                            shell.bye();
131                            System.exit(r < 0 ? 0 : r);
132                        }
133                    }
134                }
135            }
136        } catch (IOException e) {
137            e.printStackTrace(); // NOSONAR (log on console on purpose)
138            System.exit(1);
139        } finally {
140            closeHistory();
141        }
142    }
143
144    /**
145     * Return non zero to stop the application. If a negative code is returned the application will stop normally
146     * otherwise it will stop using System.exit with the exit code as argument.
147     *
148     * @param t
149     * @return
150     * @throws IOException
151     */
152    protected int handleError(Throwable t) throws IOException {
153        if (t instanceof ShellException) {
154            ShellException e = (ShellException) t;
155            int r = e.getErrorCode();
156            if (r != 0) {
157                return r;
158            } else {
159                shell.setProperty("last.error", e);
160                console.printString(e.getMessage());
161                console.printNewline();
162                // console.printString(sw.toString());
163            }
164        } else {
165            ANSIBuffer buf = shell.newANSIBuffer();
166            buf.red(Trace.getStackTrace(t));
167            console.printString(buf.toString());
168        }
169        return 0;
170    }
171
172    public String getPrompt() {
173        return shell.getActiveRegistry().getPrompt(shell);
174    }
175
176    public void print(String msg) {
177        try {
178            console.printString(msg);
179        } catch (IOException e) {
180            throw new ShellException(e).setErrorCode(1);
181        }
182    }
183
184    public void println(String msg) {
185        try {
186            console.printString(msg);
187            console.printNewline();
188        } catch (IOException e) {
189            throw new ShellException(e).setErrorCode(1);
190        }
191    }
192
193    public void println() {
194        try {
195            console.printNewline();
196        } catch (IOException e) {
197            throw new ShellException(e).setErrorCode(1);
198        }
199    }
200
201    public String readLine(String prompt, Character mask) {
202        try {
203            return console.readLine(prompt, mask);
204        } catch (IOException e) {
205            throw new ShellException(e).setErrorCode(1);
206        }
207    }
208
209    public void loadHistory() throws IOException {
210        if (!shell.getBooleanSetting("history", true)) {
211            return;
212        }
213        console.getHistory().setHistoryFile(shell.getHistoryFile());
214        console.getHistory().moveToEnd();
215    }
216
217    public void closeHistory() {
218        PrintWriter pw = console.getHistory().getOutput();
219        if (pw != null) {
220            pw.close();
221        }
222    }
223
224    public void removeHistory() {
225        closeHistory();
226        shell.getHistoryFile().delete();
227    }
228
229}