package org.basex.util; import static org.basex.core.Text.*; import java.io.File; import java.io.IOException; import java.net.BindException; import java.net.ConnectException; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.security.ProtectionDomain; import java.util.Scanner; import org.basex.core.Prop; import org.basex.io.IO; import org.basex.server.LoginException; import org.basex.util.list.StringList; /** * This class contains static methods, which are used throughout the project. * The methods are used for dumping error output, debugging information, * getting the application path, etc. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class Util { /** Flag for using default standard input. */ private static final boolean NOCONSOLE = System.console() == null; /** Language (applied after restart). */ public static String language = Prop.LANG; /** Flag for showing language keys. */ public static boolean langkeys; /** Debug mode. */ public static boolean debug; /** Hidden constructor. */ private Util() { } /** * Returns an information string for an unexpected exception. * @param ex exception * @return dummy object */ public static String bug(final Throwable ex) { final TokenBuilder tb = new TokenBuilder(BUGINFO); tb.add(NL).add("Contact: ").add(MAIL); tb.add(NL).add("Version: ").add(TITLE); tb.add(NL).add("Java: ").add(System.getProperty("java.vendor")); tb.add(", ").add(System.getProperty("java.version")); tb.add(NL).add("OS: ").add(System.getProperty("os.name")); tb.add(", ").add(System.getProperty("os.arch")); tb.add(NL).add("Stack Trace: "); for(final String e : toArray(ex)) tb.add(NL).add(e); return tb.toString(); } /** * Throws a runtime exception for an unexpected exception. * @param ext optional extension * @return runtime exception (indicates that an error is raised) */ public static RuntimeException notexpected(final Object... ext) { final TokenBuilder tb = new TokenBuilder("Not expected"); if(ext.length != 0) tb.addExt(": %", ext); throw new RuntimeException(tb.add('.').toString()); } /** * Throws a runtime exception for an unimplemented method. * @param ext optional extension * @return runtime exception (indicates that an error is raised) */ public static RuntimeException notimplemented(final Object... ext) { final TokenBuilder tb = new TokenBuilder("Not Implemented"); if(ext.length != 0) tb.addExt(" (%)", ext); throw new RuntimeException(tb.add('.').toString()); } /** * Returns the class name of the specified object. * @param o object * @return class name */ public static String name(final Object o) { return name(o.getClass()); } /** * Returns the name of the specified class. * @param o object * @return class name */ public static String name(final Class<?> o) { return o.getSimpleName(); } /** * Returns a single line from standard input. * @return string */ public static String input() { final Scanner sc = new Scanner(System.in); return sc.hasNextLine() ? sc.nextLine().trim() : ""; } /** * Returns a password from standard input. * @return password */ public static String password() { // use standard input if no console if defined (such as in Eclipse) if(NOCONSOLE) return input(); // hide password final char[] pw = System.console().readPassword(); return pw != null ? new String(pw) : ""; } /** * Prints a newline to standard output. */ public static void outln() { out(NL); } /** * Prints a string to standard output, followed by a newline. * @param str output string * @param ext text optional extensions */ public static void outln(final Object str, final Object... ext) { out(str + NL, ext); } /** * Prints a string to standard output. * @param str output string * @param ext text optional extensions */ public static void out(final Object str, final Object... ext) { System.out.print(info(str, ext)); } /** * Prints a string to standard error, followed by a newline. * @param obj error string * @param ext text optional extensions */ public static void errln(final Object obj, final Object... ext) { err((obj instanceof Throwable ? message((Throwable) obj) : obj) + NL, ext); } /** * Prints a string to standard error. * @param string debug string * @param ext text optional extensions */ public static void err(final String string, final Object... ext) { System.err.print(info(string, ext)); } /** * Returns a better understandable error message for the specified exception. * @param ex throwable reference * @return error message */ public static String message(final Throwable ex) { debug(ex); final String msg = ex.getMessage(); if(ex instanceof BindException) return SRV_RUNNING; else if(ex instanceof LoginException) return ACCESS_DENIED; else if(ex instanceof ConnectException) return CONNECTION_ERROR; else if(ex instanceof SocketTimeoutException) return TIMEOUT_EXCEEDED; else if(ex instanceof SocketException) return CONNECTION_ERROR; else if(ex instanceof UnknownHostException) return info(UNKNOWN_HOST, msg); return msg != null && !msg.isEmpty() ? msg : ex.toString(); } /** * Prints the exception stack trace if the {@link #debug} flag is set. * @param ex exception * @return always false */ public static boolean debug(final Throwable ex) { if(debug && ex != null) stack(ex); return false; } /** * Prints a string to standard error if the {@link #debug} flag is set. * @param str debug string * @param ext text optional extensions */ public static void debug(final Object str, final Object... ext) { if(debug) errln(str, ext); } /** * Performs garbage collection and prints performance information. * if the debug flag is set. * @param perf performance reference */ public static void gc(final Performance perf) { if(!debug) return; Performance.gc(4); errln(" " + perf + " (" + Performance.getMem() + ')'); } /** * Returns a string and replaces all % characters by the specified extensions * (see {@link TokenBuilder#addExt} for details). * @param str string to be extended * @param ext text text extensions * @return extended string */ public static String info(final Object str, final Object... ext) { return Token.string(inf(str, ext)); } /** * Returns a token and replaces all % characters by the specified extensions * (see {@link TokenBuilder#addExt} for details). * @param str string to be extended * @param ext text text extensions * @return token */ public static byte[] inf(final Object str, final Object... ext) { return new TokenBuilder().addExt(str, ext).finish(); } /** * Prints the current stack trace to System.err. * @param i number of steps to print */ public static void stack(final int i) { errln("You're here:"); stack(new Throwable(), i); } /** * Prints the stack of the specified error to standard error. * @param th error/exception instance */ public static void stack(final Throwable th) { stack(th, 0); } /** * Prints the stack of the specified error to standard error. * @param th error/exception instance * @param i number of steps to print */ private static void stack(final Throwable th, final int i) { final String[] stack = toArray(th); int l = stack.length; if(i > 0 && i < l) l = i; for(int s = 0; s < l; ++s) errln(stack[s]); } /** * Returns an string array representation of the specified throwable. * @param th throwable * @return string array */ private static String[] toArray(final Throwable th) { final StackTraceElement[] st = th.getStackTrace(); final String[] obj = new String[st.length + 1]; obj[0] = th.toString(); for(int i = 0; i < st.length; i++) obj[i + 1] = " " + st[i]; return obj; } /** * <p>Determines the project's home directory for storing property files * and directories. The directory is chosen as follows:</p> * <ol> * <li>First, the <b>system property</b> {@code "org.basex.path"} is checked. * If it contains a value, it is chosen as directory path.</li> * <li>If not, the <b>current user directory</b> (defined by the system * property {@code "user.dir"}) is chosen if the {@code .basex} * configuration file is found in this directory.</li> * <li>Otherwise, the configuration file is searched in the <b>application * directory</b> (the folder in which the project is located).</li> * <li>In all other cases, the <b>user's home directory</b> (defined in * {@code "user.home"}) is chosen.</li> * </ol> * @return home directory */ public static String homeDir() { // check user specific property String path = System.getProperty("org.basex.path"); if(path != null) return path + File.separator; // check working directory for property file path = System.getProperty("user.dir"); File config = new File(path, IO.BASEXSUFFIX); if(config.exists()) return config.getParent() + File.separator; // not found; check application directory path = applicationPath(); if(path != null) { final File app = new File(path); final String dir = app.isFile() ? app.getParent() : app.getPath(); config = new File(dir, IO.BASEXSUFFIX); if(config.exists()) return config.getParent() + File.separator; } // not found; choose user home directory as default return Prop.USERHOME; } /** * Returns the absolute path to this application, or {@code null} if the * path cannot be evaluated. * @return application path. */ private static String applicationPath() { final ProtectionDomain pd = Util.class.getProtectionDomain(); if(pd == null) return null; // raw application path final String path = pd.getCodeSource().getLocation().getPath(); // decode path; URLDecode returns wrong results final TokenBuilder tb = new TokenBuilder(); final int pl = path.length(); for(int p = 0; p < pl; ++p) { final char ch = path.charAt(p); if(ch == '%' && p + 2 < pl) { tb.addByte((byte) Integer.parseInt(path.substring(p + 1, p + 3), 16)); p += 2; } else { tb.add(ch); } } try { // return path, using the correct encoding return new String(tb.finish(), Prop.ENCODING); } catch(final Exception ex) { // use default path; not expected to occur stack(ex); return tb.toString(); } } /** * Starts the specified class in a separate process. * @param clz class to start * @param args command-line arguments */ public static void start(final Class<?> clz, final String... args) { final String[] largs = { "java", "-Xmx" + Runtime.getRuntime().maxMemory(), "-cp", System.getProperty("java.class.path"), clz.getName(), "-D", }; final StringList sl = new StringList().add(largs).add(args); try { new ProcessBuilder(sl.toArray()).start(); } catch(final IOException ex) { notexpected(ex); } } /** * Checks if the specified string is "yes", "true" or "on". * @param string string to be checked * @return result of check */ public static boolean yes(final String string) { return Token.eqic(string, YES, TRUE, ON, INFOON); } /** * Checks if the specified string is "no", "false" or "off". * @param string string to be checked * @return result of check */ public static boolean no(final String string) { return Token.eqic(string, NO, FALSE, OFF, INFOOFF); } /** * Returns an info message for the specified flag. * @param flag current flag status * @return ON/OFF message */ public static String flag(final boolean flag) { return flag ? INFOON : INFOOFF; } }