package org.basex.util; import static org.basex.core.Text.*; import java.io.*; import java.lang.reflect.*; import org.basex.core.parse.*; import org.basex.core.parse.Commands.Cmd; import org.basex.io.*; /** * Console reader. * * @author BaseX Team 2005-17, BSD License * @author Dimitar Popov */ public abstract class ConsoleReader implements AutoCloseable { /** Password prompt. */ private static final String PW_PROMPT = PASSWORD + COLS; /** Password reader. */ private final PasswordReader pwReader = new PasswordReader() { @Override public String password() { return readPassword(); } }; /** * Reads next line. If no input, then the method blocks the thread. * @param prompt prompt * @return next line or {@code null} if EOF is reached */ public abstract String readLine(String prompt); /** * Reads a password. * @return password as plain text */ protected abstract String readPassword(); @Override public abstract void close(); /** * Create a new password reader for this console. * @return a new instance of {@link PasswordReader} */ public final PasswordReader pwReader() { return pwReader; } /** * Creates a new instance. * @return instance of console reader */ public static ConsoleReader get() { if(JLineConsoleReader.isAvailable()) { try { return new JLineConsoleReader(); } catch(final Exception ex) { Util.errln(ex); } } return new SimpleConsoleReader(); } /** Simple console reader implementation. */ private static class SimpleConsoleReader extends ConsoleReader { /** Input reader. */ private final BufferedReader in; /** Constructor. */ SimpleConsoleReader() { in = new BufferedReader(new InputStreamReader(System.in)); } @Override public String readLine(final String prompt) { try { Util.out(prompt); return in.readLine(); } catch(final IOException ex) { // should not happen throw new RuntimeException(ex); } } @Override public String readPassword() { Util.out(PW_PROMPT); return Util.password(); } @Override public void close() { } } /** Implementation which provides advanced features, such as history. */ private static class JLineConsoleReader extends ConsoleReader { /** JLine console reader class name. */ private static final String JLINE_CONSOLE_READER = "jline.console.ConsoleReader"; /** JLine file history class name. */ private static final String JLINE_FILE_HISTORY = "jline.console.history.FileHistory"; /** JLine history class name. */ private static final String JLINE_HISTORY = "jline.console.history.History"; /** JLine history class name. */ private static final String JLINE_COMPLETER = "jline.console.completer.Completer"; /** JLine history class name. */ private static final String JLINE_ENUM_COMPLETER = "jline.console.completer.EnumCompleter"; /** JLine history class name. */ private static final String JLINE_FILE_NAME_COMPLETER = "jline.console.completer.FileNameCompleter"; /** Command history file. */ private static final String HISTORY_FILE = IO.BASEXSUFFIX + "history"; /** Password echo character. */ private static final Character PASSWORD_ECHO = (char) 0; /** Method to read the next line. */ private final Method readLine; /** Method to read the next line with echoing a character. */ private final Method readEcho; /** Implementation. */ private final Object reader; /** File history class. */ private final Class<?> fileHistoryC; /** File history. */ private final Object fileHistory; /** * Checks if JLine implementation is available? * @return {@code true} if JLine is in the classpath */ static boolean isAvailable() { return Reflect.available(JLINE_CONSOLE_READER); } /** * Constructor. * @throws Exception error */ JLineConsoleReader() throws Exception { // reflection final Class<?> readerC = Reflect.find(JLINE_CONSOLE_READER); readLine = Reflect.method(readerC, "readLine", String.class); readEcho = Reflect.method(readerC, "readLine", String.class, Character.class); // initialization reader = readerC.newInstance(); final Class<?> history = Reflect.find(JLINE_HISTORY); fileHistoryC = Reflect.find(JLINE_FILE_HISTORY); fileHistory = Reflect.get(Reflect.find(fileHistoryC, File.class), new File(Prop.HOME, HISTORY_FILE)); final Class<?> completer = Reflect.find(JLINE_COMPLETER); final Class<?> enumCompleter = Reflect.find(JLINE_ENUM_COMPLETER); final Class<?> fileNameCompleter = Reflect.find(JLINE_FILE_NAME_COMPLETER); Reflect.invoke(Reflect.method(readerC, "setBellEnabled", boolean.class), reader, false); Reflect.invoke(Reflect.method(readerC, "setHistory", history), reader, fileHistory); Reflect.invoke(Reflect.method(readerC, "setHistoryEnabled", boolean.class), reader, true); // command completions Reflect.invoke(Reflect.method(readerC, "addCompleter", completer), reader, Reflect.get(Reflect.find(enumCompleter, Class.class), Cmd.class)); Reflect.invoke(Reflect.method(readerC, "addCompleter", completer), reader, Reflect.get(Reflect.find(fileNameCompleter))); } @Override public String readLine(final String prompt) { return (String) Reflect.invoke(readLine, reader, prompt); } @Override public String readPassword() { return (String) Reflect.invoke(readEcho, reader, PW_PROMPT, PASSWORD_ECHO); } @Override public void close() { Reflect.invoke(Reflect.method(fileHistoryC, "flush"), fileHistory); } } }