package winterwell.utils;
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import winterwell.utils.containers.Containers;
import winterwell.utils.containers.Pair2;
import winterwell.utils.io.FastByteArrayInputStream;
import winterwell.utils.io.FastByteArrayOutputStream;
import winterwell.utils.io.FileUtils;
import winterwell.utils.reporting.Log;
import winterwell.utils.time.Time;
import winterwell.utils.web.XStreamUtils;
/**
* @testedby {@link UtilsTest}
* @author daniel
*
*/
public class Utils {
private static final char[] consonants = "bcdfghjklmnpqrstvwxz"
.toCharArray();
private static final AtomicInteger id = new AtomicInteger(1);
private final static Pattern notBlank = Pattern.compile("\\S");
/**
* Note Random is thread safe. Is using it across threads a bottleneck? If
* you need repeatable results, you should create your own Random where you
* can control both the seed and the usage.
*/
private static final Random r = new Random();
private static final char[] vowels = "aeiouy".toCharArray();
/**
* Check inputs are all non-null
*
* @param args
* @throws NullPointerException
* if any of args are null
*/
public static void check4null(Object... args) throws NullPointerException {
for (int i = 0; i < args.length; i++) {
if (args[i] == null)
throw new NullPointerException("Argument " + i + " in "
+ Printer.toString(args));
}
}
/**
* A version of {@link Comparable#compareTo(Object)} for <i>any</i> two
* objects. Arguments can be null (which come last).
*
* @param a
* @param b
* @return equivalent to a.compareTo(b) if that makes sense
*/
public static int compare(Object a, Object b) {
if (a == b)
return 0;
if (a == null)
return 1;
if (b == null)
return -1;
try {
return ((Comparable) a).compareTo(b);
} catch (ClassCastException e) {
int ahash = a.hashCode();
int bhash = b.hashCode();
if (ahash == bhash)
return 0;
return ahash < bhash ? -1 : 1;
}
}
/**
* Perform a DEEP copy of the object, using XStream
*
* @param object
* @return a copy of object, which should share no structure
* @testedby {@link UtilsTest#testCopy()}
*/
public static <X> X copy(X object) {
FastByteArrayOutputStream out = new FastByteArrayOutputStream();
XStreamUtils.xstream().toXML(object, out);
FastByteArrayInputStream in = new FastByteArrayInputStream(
out.getByteArray(), out.getSize());
Object copy = XStreamUtils.xstream().fromXML(in);
return (X) copy;
}
/**
* Convenience for a==null? b==null : a.equals(b)
* <p>
* Beware: numerical values of different classes are NOT tested for
* numerical equivalence. E.g. 1 != 1L != 1.0. You must cast them into
* yourself if you need this behaviour. Note: experimented with this, but
* there are issues, e.g. with large longs vs large doubles
*
* @param a
* @param b
* @return true if a.equals(b)
*/
public static boolean equals(Object a, Object b) {
if (a == null)
return b == null;
return a.equals(b);
}
/**
* Who called this method?
*
* @param ignore
* list of class or method names to ignore (will then search
* higher up the stack)
* @return Can be a dummy entry if the filters exclude everything. Never
* null.
*/
public static StackTraceElement getCaller(String... ignore) {
List<String> ignoreNames = Arrays.asList(ignore);
try {
throw new Exception();
} catch (Exception e) {
StackTraceElement[] trace = e.getStackTrace();
for (int i = 2; i < trace.length; i++) {
String clazz = trace[2].getClassName();
String method = trace[2].getMethodName();
if (ignoreNames.contains(clazz) || ignoreNames.contains(method)) {
continue;
}
return trace[2]; // new Pair<String>(clazz, method);
}
return new StackTraceElement("filtered", "?", null, -1);
}
}
/**
*
* @param f
* @return time-of-commit, SHA1-key
*/
public static Pair2<Time, String> getGitRevision(File f) {
// git log --shortstat -n 1
Process p = new Process("git log --shortstat -n 1 "
+ f.getAbsolutePath());
p.run();
p.waitFor();
String output = p.getOutput();
String[] tBits = StrUtils.find("Date:\\s*(.+)", output);
String[] cBits = StrUtils.find("commit\\s*(\\w+)", output);
assert tBits != null && tBits.length > 1 : f.getAbsolutePath() + "\n"
+ output;
assert cBits != null && cBits.length > 1 : f.getAbsolutePath() + "\n"
+ output;
Time time = new Time(tBits[1].trim());
String key = cBits[1];
return new Pair2<Time, String>(time, key);
}
/**
* @return a number, starting with 1 and incremented each time. This is
* guaranteed to be unique _within this run of the jVM_, upto
* overflow.
*
* @see #getUID()
*/
public static int getId() {
return id.getAndIncrement();
}
/**
* @return lower case string for the operating system. E.g. ??
*/
public static String getOperatingSystem() {
String osName = System.getProperty("os.name");
return osName.toLowerCase();
}
/**
* Load a password from HOME/.winterwell/password
*/
public static String getPassword() {
String home = System.getProperty("user.home");
File pwdf = new File(home, ".winterwell/password");
String pwd = FileUtils.read(pwdf).trim();
return pwd;
}
/**
* @return a Random instance for generating random numbers. This is to avoid
* generating new Random instances for each number as the results
* aren't well distributed.
* <p>
* If you need repeatable results, you should create your own
* Random.
* <p>
* Note: Java's Random <i>is</i> thread safe, and can be used by
* many threads - although for high simultaneous usage, you may wish
* to create your own Randoms.
*/
public static Random getRandom() {
return r;
}
/**
* @param prob
* @return true with P(prob)
*/
public static boolean getRandomChoice(double prob) {
assert MathUtils.isProb(prob) : prob;
if (prob == 0)
return false;
if (prob == 1)
return true;
return getRandom().nextDouble() < prob;
}
/**
* Pick an element using uniform random choice
*
* @param <X>
* @param list
* Must not be empty
* @return
*/
public static <X> X getRandomMember(Collection<X> list) {
assert list.size() != 0;
int i = getRandom().nextInt(list.size());
return Containers.get(list, i);
}
/**
* Return a set of (uniformly) randomly selected elements of the specified
* collection. Equality in the set is determined in the usual way i.e. by
* calling equals(). If num is greater than choices return all the choices
* in a fresh object.
*
* @param num
* @param choices
* @return set of randomly selected choices.
*/
public static <X> Collection<X> getRandomSelection(int num,
Collection<X> choices) {
if (num >= choices.size())
return new ArrayList<X>(choices);
List<X> listChoices = Containers.getList(choices);
Set<X> selected = new HashSet<X>(num);
int iter = 0;
while (selected.size() < num) {
int i = r.nextInt(choices.size());
X x = listChoices.get(i);
selected.add(x);
// time out if we get stuck
iter++;
if (iter == num * 100) {
Log.report("Breaking out of random-selection loop early: Failed to select a full "
+ num + " from " + choices);
break;
}
}
return selected;
}
/**
* A random lower case string
*
* @param len
* @return
*/
public static String getRandomString(int len) {
Random r = getRandom();
char[] s = new char[len];
for (int i = 0; i < len; i++) {
// roughly consonant-consonant-vowel for pronounceability
char c;
if (r.nextInt(3) == 0) {
c = vowels[r.nextInt(vowels.length)];
} else {
c = consonants[r.nextInt(consonants.length)];
}
s[i] = c;
}
return new String(s);
}
/**
* The original exception within a nested exception. Has some knowledge of
* SQLExceptions
*
* @param e
* Any exception
* @return the root cause, or e if it is the root cause. Never null.
*/
public static Throwable getRootCause(Throwable e) {
// Chained SQL exceptions?
// SQL exceptions are horrible - they hide their true cause.
if (e instanceof SQLException) {
SQLException ex = (SQLException) e;
SQLException ex2 = ex.getNextException();
if (ex2 != null)
return getRootCause(ex2);
}
Throwable cause = e.getCause();
if (cause == null || cause == e)
return e;
return getRootCause(cause);
}
@Deprecated
// since we don't use SVN anymore. Much
public static int getSVNRevision(File f) {
// workaround: svn can be a bit slow - which returns early & blank!
for (int sleep : new int[] { 100, 2000 }) {
Process p = new Process("svn info " + f.getAbsolutePath());
p.run();
Utils.sleep(sleep);
p.waitFor();
String output = p.getOutput();
if (!output.contains("Revision")) {
continue;
}
String[] bits = StrUtils.find("Revision:\\s*(\\d+)", output);
assert bits != null && bits.length > 1 : f.getAbsolutePath() + " "
+ output;
return Integer.valueOf(bits[1]);
}
throw new FailureException("svn info request failed");
}
/**
* @return an id that shouldn't be in use anywhere else (inc. on any
* server).
* @see #getId()
*/
public static String getUID() {
return getRandomString(6)
+ Long.toHexString(System.currentTimeMillis());
}
/**
* @param line
* @return true if line is null, empty, or contains nothing but whitespace
*/
public static boolean isBlank(CharSequence line) {
if (line == null || line.length() == 0)
return true;
Matcher m = notBlank.matcher(line);
boolean nb = m.find();
return !nb;
}
/**
* Convenience for null or []
*
* @param list
* @return true if list == null || list.isEmpty()
*/
public static boolean isEmpty(Collection list) {
return list == null || list.isEmpty();
}
public static boolean isEmpty(Map map) {
return map == null || map.isEmpty();
}
public static boolean isInt(String matchid) {
try {
Integer.valueOf(matchid);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Generalized version of Math.max
*/
public static <T extends Comparable<T>> T max(T a, T b) {
if (a.compareTo(b) <= 0)
return b;
return a;
}
/**
* Generalized version of Math.min
*/
public static <T extends Comparable<T>> T min(T a, T b) {
if (a.compareTo(b) <= 0)
return a;
return b;
}
/**
* Convenience filter.
*
* @param objects
* Can be null
* @return first non-null non-blank object (zero-length CharSequences count
* as blank), or null if all are null.
*/
// Sadly no lazy evaluation, so this is less useful than it's lisp
// counterpart.
public static <X> X or(X... objects) {
if (objects == null)
return null;
for (X object : objects) {
if (object == null) {
continue;
}
if (object instanceof CharSequence
&& ((CharSequence) object).length() == 0) {
continue;
}
return object;
}
return null;
}
/**
* @return true for linux or unix
*/
public static boolean OSisUnix() {
String os = getOperatingSystem();
return os.contains("linux") || os.contains("unix");
}
/**
* Includes some special case handling for SQL exceptions, to make debugging
* a bit less painful (trys to avoid the "SQLException caused by: see next
* exception, which you can't do now, mwhaha" message).
*
* @param e
* @return
*/
public static RuntimeException runtime(Throwable e) {
if (e instanceof RuntimeException)
return (RuntimeException) e;
// SQL exceptions are horrible - throw the cause instead
if (e instanceof SQLException) {
e = getRootCause(e);
}
// get some more info out of Hibernate... would be nice but the
// dependencies are horrible
// if (e instanceof JDBCException) e.getSQL()
return new WrappedException(e);
}
/**
* Sleep. Converts InterruptedExceptions into unchecked wrapped exceptions.
*
* @param millis
*/
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw Utils.runtime(e);
}
}
}