package org.basex;
import static org.basex.core.Text.*;
import java.io.*;
import java.util.*;
import org.basex.core.*;
import org.basex.core.cmd.*;
import org.basex.core.cmd.Set;
import org.basex.core.parse.*;
import org.basex.io.*;
import org.basex.io.out.*;
import org.basex.io.serial.*;
import org.basex.util.*;
import org.basex.util.list.*;
/**
* This is the starter class for the stand-alone console mode.
* It executes all commands locally.
*
* @author BaseX Team 2005-17, BSD License
* @author Christian Gruen
*/
public class BaseX extends CLI {
/** Default prompt. */
private static final String PROMPT = "> ";
/** Commands to be executed. */
private IntList ops;
/** Command arguments. */
private StringList vals;
/** Console mode. May be set to {@code false} during execution. */
private volatile boolean console;
/**
* Main method, launching the standalone mode.
* Command-line arguments are listed with the {@code -h} argument.
* @param args command-line arguments
*/
public static void main(final String... args) {
try {
new BaseX(args);
} catch(final IOException ex) {
Util.errln(ex);
System.exit(1);
}
}
/**
* Constructor.
* @param args command-line arguments
* @throws IOException I/O exception
*/
public BaseX(final String... args) throws IOException {
super(args);
// create session to show optional login request
session();
console = true;
try {
// loop through all commands
final StringBuilder bind = new StringBuilder();
SerializerOptions sopts = null;
boolean v = false, qi = false, qp = false;
final int os = ops.size();
for(int o = 0; o < os; o++) {
final int c = ops.get(o);
String value = vals.get(o);
if(c == 'b') {
// set/add variable binding
if(bind.length() != 0) bind.append(',');
// commas are escaped by a second comma
value = bind.append(value.replaceAll(",", ",,")).toString();
execute(new Set(MainOptions.BINDINGS, value), false);
} else if(c == 'c') {
// evaluate commands
final Pair<String, String> input = input(value);
execute(input.value(), input.name());
console = false;
} else if(c == 'D') {
// hidden option: show/hide dot query graph
execute(new Set(MainOptions.DOTPLAN, null), false);
} else if(c == 'i') {
// open database or create main memory representation
execute(new Set(MainOptions.MAINMEM, true), false);
execute(new Check(value), verbose);
execute(new Set(MainOptions.MAINMEM, false), false);
} else if(c == 'I') {
// set/add variable binding
if(bind.length() != 0) bind.append(',');
// commas are escaped by a second comma
value = bind.append('=').append(value.replaceAll(",", ",,")).toString();
execute(new Set(MainOptions.BINDINGS, value), false);
} else if(c == 'o') {
// change output stream
if(out != System.out) out.close();
out = new PrintOutput(value);
session().setOutputStream(out);
} else if(c == 'q') {
// evaluate query
execute(new XQuery(value), verbose);
console = false;
} else if(c == 'Q') {
// evaluate file contents or string as query
final Pair<String, String> input = input(value);
execute(new XQuery(input.value()).baseURI(input.name()), verbose);
console = false;
} else if(c == 'r') {
// parse number of runs
execute(new Set(MainOptions.RUNS, Strings.toInt(value)), false);
} else if(c == 'R') {
// toggle query evaluation
execute(new Set(MainOptions.RUNQUERY, null), false);
} else if(c == 's') {
// set/add serialization parameter
if(sopts == null) sopts = new SerializerOptions();
final String[] kv = value.split("=", 2);
sopts.assign(kv[0], kv.length > 1 ? kv[1] : "");
execute(new Set(MainOptions.SERIALIZER, sopts), false);
} else if(c == 't') {
// evaluate query
execute(new Test(value), verbose);
console = false;
} else if(c == 'u') {
// (de)activate write-back for updates
execute(new Set(MainOptions.WRITEBACK, null), false);
} else if(c == 'v') {
// show/hide verbose mode
v ^= true;
} else if(c == 'V') {
// show/hide query info
qi ^= true;
execute(new Set(MainOptions.QUERYINFO, null), false);
} else if(c == 'w') {
// toggle chopping of whitespaces
execute(new Set(MainOptions.CHOP, null), false);
} else if(c == 'x') {
// show/hide xml query plan
execute(new Set(MainOptions.XMLPLAN, null), false);
qp ^= true;
} else if(c == 'X') {
// show query plan before/after query compilation
execute(new Set(MainOptions.COMPPLAN, null), false);
} else if(c == 'z') {
// toggle result serialization
execute(new Set(MainOptions.SERIALIZE, null), false);
}
verbose = qi || qp || v;
}
if(console) console();
} finally {
quit();
}
}
/**
* Launches the console mode, which reads and executes user input.
*/
private void console() {
Util.outln(header() + NL + TRY_MORE_X);
verbose = true;
// create console reader
try(ConsoleReader cr = ConsoleReader.get()) {
// loop until console is set to false (may happen in server mode)
while(console) {
// get next line
final String in = cr.readLine(PROMPT);
// end of input: break loop
if(in == null) break;
// skip empty lines
if(in.isEmpty()) continue;
try {
if(!execute(CommandParser.get(in, context).pwReader(cr.pwReader()))) {
// show goodbye message if method returns false
Util.outln(BYE[new Random().nextInt(4)]);
break;
}
} catch(final IOException ex) {
// output error messages
Util.errln(ex);
}
}
}
}
/**
* Quits the console mode.
* @throws IOException I/O exception
*/
private void quit() throws IOException {
if(out == System.out || out == System.err) out.flush();
else out.close();
}
/**
* Returns the base URI and the query string for the specified input.
* @param input input
* @return return base URI and query string
* @throws IOException I/O exception
*/
private Pair<String, String> input(final String input) throws IOException {
final IO io = IO.get(input);
final boolean file = !(io instanceof IOContent) && io.exists() && !io.isDir();
return new Pair<>(file ? io.path() : "./", file ? io.string() : input);
}
/**
* Tests if this is a local client.
* @return local mode
*/
protected boolean local() {
return true;
}
@Override
protected final void parseArgs() throws IOException {
ops = new IntList();
vals = new StringList();
final MainParser arg = new MainParser(this);
while(arg.more()) {
final char c;
String v = null;
if(arg.dash()) {
c = arg.next();
if(c == 'd') {
// activate debug mode
Prop.debug = true;
} else if(c == 'b' || c == 'c' || c == 'C' || c == 'i' || c == 'I' || c == 'o' ||
c == 'q' || c == 'r' || c == 's' || c == 't' && local()) {
// options followed by a string
v = arg.string();
} else if(c == 'D' && local() || c == 'u' && local() || c == 'R' ||
c == 'v' || c == 'V' || c == 'w' || c == 'x' || c == 'X' || c == 'z') {
// options to be toggled
v = "";
} else if(!local()) {
// client options: need to be set before other options
if(c == 'n') {
// set server name
context.soptions.set(StaticOptions.HOST, arg.string());
} else if(c == 'p') {
// set server port
context.soptions.set(StaticOptions.PORT, arg.number());
} else if(c == 'P') {
// specify password
context.soptions.set(StaticOptions.PASSWORD, arg.string());
} else if(c == 'U') {
// specify user name
context.soptions.set(StaticOptions.USER, arg.string());
} else {
throw arg.usage();
}
} else {
throw arg.usage();
}
} else {
v = arg.string().trim();
// interpret as command file if input string ends with command script suffix
c = v.endsWith(IO.BXSSUFFIX) ? 'c' : 'Q';
}
if(v != null) {
ops.add(c);
vals.add(v);
}
}
}
@Override
public String header() {
return Util.info(S_CONSOLE_X, local() ? S_STANDALONE : S_CLIENT);
}
@Override
public String usage() {
return local() ? S_LOCALINFO : S_CLIENTINFO;
}
}