import java.util.*;
import java.util.zip.*;
import java.util.List;
import java.util.regex.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.table.*;
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import java.lang.ref.*;
import java.lang.management.*;
import java.security.*;
import java.security.spec.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.imageio.*;
import java.math.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.text.SimpleDateFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.*;

class main {




static NanoHTTPD.Response serveFile(File file) {
  return serveFile(file, "application/octet-stream");
}

static NanoHTTPD.Response serveFile(File file, String mimeType) { try {
  return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Status.OK, mimeType, newFileInputStream(file), file.length());
} catch (Exception __e) { throw rethrow(__e); } }

static FileInputStream newFileInputStream(File path) throws IOException {
  return newFileInputStream(path.getPath());
}

static FileInputStream newFileInputStream(String path) throws IOException {
  FileInputStream f = new FileInputStream(path);
  _registerIO(f, path, true);
  return f;
}
static RuntimeException rethrow(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  throw t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}

static RuntimeException rethrow(String msg, Throwable t) {
  throw new RuntimeException(msg, t);
}


static void _registerIO(Object object, String path, boolean opened) {
}
static void _handleError(Error e) {
  call(javax(), "_handleError", e);
}


static Object call(Object o) {
  return callF(o);
}

// varargs assignment fixer for a single string array argument
static Object call(Object o, String method, String[] arg) {
  return call(o, method, new Object[] {arg});
}

static Object call(Object o, String method, Object... args) {
  //ret call_cached(o, method, args);
  return call_withVarargs(o, method, args);
}
static Class javax() {
  return getJavaX();
}


static Map<Class, ArrayList<Method>> callF_cache = newDangerousWeakHashMap();




  static <A, B> B callF(F1<A, B> f, A a) {
    return f == null ? null : f.get(a);
  }



  static <A> A callF(IF0<A> f) {
    return f == null ? null : f.get();
  }









  static <A> void callF(VF1<A> f, A a) {
    if (f != null) f.get(a);
  }


static Object callF(Object f, Object... args) {
  if (f instanceof String)
    return callMCWithVarArgs((String) f, args); // possible SLOWDOWN over callMC
    
  return safeCallF(f, args);
}

static Object safeCallF(Object f, Object... args) {
  if (f instanceof Runnable) {
    ((Runnable) f).run();
    return null;
  }
  if (f == null) return null;
  
  Class c = f.getClass();
  ArrayList<Method> methods;
  synchronized(callF_cache) {
    methods = callF_cache.get(c);
    if (methods == null)
      methods = callF_makeCache(c);
  }
  
  int n = l(methods);
  if (n == 0) {
    
    throw fail("No get method in " + getClassName(c));
  }
  if (n == 1) return invokeMethod(methods.get(0), f, args);
  for (int i = 0; i < n; i++) {
    Method m = methods.get(i);
    if (call_checkArgs(m, args, false))
      return invokeMethod(m, f, args);
  }
  throw fail("No matching get method in " + getClassName(c));
}

// used internally
static ArrayList<Method> callF_makeCache(Class c) {
  ArrayList<Method> l = new ArrayList();
  Class _c = c;
  do {
    for (Method m : _c.getDeclaredMethods())
      if (m.getName().equals("get")) {
        makeAccessible(m);
        l.add(m);
      }
    if (!l.isEmpty()) break;
    _c = _c.getSuperclass();
  } while (_c != null);
  callF_cache.put(c, l);
  return l;
}
static Object call_withVarargs(Object o, String method, Object... args) { try {
  if (o == null) return null;
  
  if (o instanceof Class) {
    Class c = (Class) o;
    _MethodCache cache = callOpt_getCache(c);
    
    Method me = cache.findStaticMethod(method, args);
    if (me != null)
      return invokeMethod(me, null, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(method);
    if (methods != null) methodSearch: for (Method m : methods) {
      { if (!(m.isVarArgs())) continue; }
      { if (!(isStaticMethod(m))) continue; }
      Object[] newArgs = massageArgsForVarArgsCall(m, args);
      if (newArgs != null)
        return invokeMethod(m, null, newArgs);
    }
    
    throw fail("Method " + c.getName() + "." + method + "(" + joinWithComma(classNames(args)) + ") not found");
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

    Method me = cache.findMethod(method, args);
    if (me != null)
      return invokeMethod(me, o, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(method);
    if (methods != null) methodSearch: for (Method m : methods) {
      { if (!(m.isVarArgs())) continue; }
      Object[] newArgs = massageArgsForVarArgsCall(m, args);
      if (newArgs != null)
        return invokeMethod(m, o, newArgs);
    }
    
    throw fail("Method " + c.getName() + "." + method + "(" + joinWithComma(classNames(args)) + ") not found");
  }
} catch (Exception __e) { throw rethrow(__e); } }
static Class __javax;

static Class getJavaX() { try {
  
  return __javax;
} catch (Exception __e) { throw rethrow(__e); } }


static <A, B> Map<A, B> newDangerousWeakHashMap() {
  return _registerDangerousWeakMap(synchroMap(new WeakHashMap()));
}

// initFunction: voidfunc(Map) - is called initially, and after clearing the map
static <A, B> Map<A, B> newDangerousWeakHashMap(Object initFunction) {
  return _registerDangerousWeakMap(synchroMap(new WeakHashMap()), initFunction);
}
static Object callMCWithVarArgs(String method, Object... args) {
  return call_withVarargs(mc(), method, args);
}
static int l(Object[] a) { return a == null ? 0 : a.length; }
static int l(boolean[] a) { return a == null ? 0 : a.length; }
static int l(byte[] a) { return a == null ? 0 : a.length; }
static int l(short[] a) { return a == null ? 0 : a.length; }
static int l(long[] a) { return a == null ? 0 : a.length; }
static int l(int[] a) { return a == null ? 0 : a.length; }
static int l(float[] a) { return a == null ? 0 : a.length; }
static int l(double[] a) { return a == null ? 0 : a.length; }
static int l(char[] a) { return a == null ? 0 : a.length; }
static int l(Collection c) { return c == null ? 0 : c.size(); }

static int l(Iterator i) { return iteratorCount_int_close(i); } // consumes the iterator && closes it if possible

static int l(Map m) { return m == null ? 0 : m.size(); }
static int l(CharSequence s) { return s == null ? 0 : s.length(); }
static long l(File f) { return f == null ? 0 : f.length(); }



static int l(Object o) {
  return o == null ? 0
    : o instanceof String ? l((String) o)
    : o instanceof Map ? l((Map) o)
    : o instanceof Collection ? l((Collection) o)
    : o instanceof Object[] ? l((Object[]) o)
    : o instanceof boolean[] ? l((boolean[]) o)
    : o instanceof byte[] ? l((byte[]) o)
    : o instanceof char[] ? l((char[]) o)
    : o instanceof short[] ? l((short[]) o)
    : o instanceof int[] ? l((int[]) o)
    : o instanceof float[] ? l((float[]) o)
    : o instanceof double[] ? l((double[]) o)
    : o instanceof long[] ? l((long[]) o)
    : (Integer) call(o, "size");
}







  static int l(IntRange r) { return r == null ? 0 : r.length(); }








static RuntimeException fail() { throw new RuntimeException("fail"); }
static RuntimeException fail(Throwable e) { throw asRuntimeException(e); }
static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); }
static RuntimeException fail(String msg) { throw new RuntimeException(msg == null ? "" : msg); }
static RuntimeException fail(String msg, Throwable innerException) { throw new RuntimeException(msg, innerException); }

static String getClassName(Object o) {
  return o == null ? "null" : o instanceof Class ? ((Class) o).getName() : o.getClass().getName();
}
static Object invokeMethod(Method m, Object o, Object... args) { try {
  try {
    return m.invoke(o, args);
  } catch (InvocationTargetException e) {
    throw rethrow(getExceptionCause(e));
  } catch (IllegalArgumentException e) {
    throw new IllegalArgumentException(e.getMessage() + " - was calling: " + m + ", args: " + joinWithSpace(classNames(args)));
  }
} catch (Exception __e) { throw rethrow(__e); } }
static boolean call_checkArgs(Method m, Object[] args, boolean debug) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length) {
    if (debug)
      print("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++) {
    Object arg = args[i];
    if (!(arg == null ? !types[i].isPrimitive()
      : isInstanceX(types[i], arg))) {
      if (debug)
        print("Bad parameter " + i + ": " + arg + " vs " + types[i]);
      return false;
    }
  }
  return true;
}
static Field makeAccessible(Field f) {
  try {
    f.setAccessible(true);
  } catch (Throwable e) {
    // Note: The error reporting only works with Java VM option --illegal-access=deny
    
    vmBus_send("makeAccessible_error",e, f);
    
  }
  return f;
}

static Method makeAccessible(Method m) {
  try {
    m.setAccessible(true);
  } catch (Throwable e) {
    
    vmBus_send("makeAccessible_error",e, m);
    
  }
  return m;
}

static Constructor makeAccessible(Constructor c) {
  try {
    c.setAccessible(true);
  } catch (Throwable e) {
    
    vmBus_send("makeAccessible_error",e, c);
    
  }
  return c;
}
static final Map<Class, _MethodCache> callOpt_cache = newDangerousWeakHashMap();

static Object callOpt_cached(Object o, String methodName, Object... args) { try {
  if (o == null) return null;
  
  if (o instanceof Class) {
    Class c = (Class) o;
    _MethodCache cache = callOpt_getCache(c);
    
    // TODO: (super-rare) case where method exists static and non-static
    // with different args
    
    Method me = cache.findMethod(methodName, args);
    if (me == null || (me.getModifiers() & Modifier.STATIC) == 0) return null;
    return invokeMethod(me, null, args);
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

    Method me = cache.findMethod(methodName, args);
    if (me == null) return null;
    return invokeMethod(me, o, args);
  }
} catch (Exception __e) { throw rethrow(__e); } }

static _MethodCache callOpt_getCache(Class c) {
  synchronized(callOpt_cache) {
    _MethodCache cache = callOpt_cache.get(c);
    if (cache == null)
      callOpt_cache.put(c, cache = new _MethodCache(c));
    return cache;
  }
}
static boolean isStaticMethod(Method m) {
  return methodIsStatic(m);
}
static Object[] massageArgsForVarArgsCall(Method m, Object[] args) {
  Class<?>[] types = m.getParameterTypes();
  int n = types.length-1, nArgs = args.length;
  if (nArgs < n) return null;
  for (int i = 0; i < n; i++)
    if (!argumentCompatibleWithType(args[i], types[i]))
      return null;
  Class varArgType = types[n].getComponentType();
  for (int i = n; i < nArgs; i++)
    if (!argumentCompatibleWithType(args[i], varArgType))
      return null;
  Object[] newArgs = new Object[n+1];
  arraycopy(args, 0, newArgs, 0, n);
  Object[] varArgs = arrayOfType(varArgType, nArgs-n);
  arraycopy(args, n, varArgs, 0, nArgs-n);
  newArgs[n] = varArgs;
  return newArgs;
}
static <A> String joinWithComma(Collection<A> c) {
  return join(", ", c);
}

static String joinWithComma(String... c) {
  return join(", ", c);
}


static String joinWithComma(Pair p) {
  return p == null ? "" : joinWithComma(str(p.a), str(p.b));
}

static List<String> classNames(Collection l) {
  return getClassNames(l);
}

static List<String> classNames(Object[] l) {
  return getClassNames(Arrays.asList(l));
}


static List<Pair> _registerDangerousWeakMap_preList;

static <A> A _registerDangerousWeakMap(A map) {
  return _registerDangerousWeakMap(map, null);
}

static <A> A _registerDangerousWeakMap(A map, Object init) {
  
  callF(init, map);
  
  if (init instanceof String) {
    final String f =  (String) init;
    init = new VF1<Map>() { public void get(Map map) { try {  callMC(f, map) ; } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "callMC(f, map)"; }};
  }
    
  if (javax() == null) {
    // We're in class init
    if (_registerDangerousWeakMap_preList == null) _registerDangerousWeakMap_preList = synchroList();
    _registerDangerousWeakMap_preList.add(pair(map, init));
    return map;
  }
  
  call(javax(), "_registerDangerousWeakMap", map, init);
  
  return map;
}

static void _onLoad_registerDangerousWeakMap() {
  
  assertNotNull(javax());
  if (_registerDangerousWeakMap_preList == null) return;
  for (Pair p : _registerDangerousWeakMap_preList)
    _registerDangerousWeakMap(p.a, p.b);
  _registerDangerousWeakMap_preList = null;
  
}
static Map synchroMap() {
  return synchroHashMap();
}

static <A, B> Map<A, B> synchroMap(Map<A, B> map) {
  return Collections.synchronizedMap(map);
}
static Class mc() {
  return main.class;
}
static <A> int iteratorCount_int_close(Iterator<A> i) { try {
  int n = 0;
  if (i != null) while (i.hasNext()) { i.next(); ++n; }
  if (i instanceof AutoCloseable) ((AutoCloseable) i).close();
  return n;
} catch (Exception __e) { throw rethrow(__e); } }
static RuntimeException asRuntimeException(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}
static Throwable getExceptionCause(Throwable e) {
  Throwable c = e.getCause();
  return c != null ? c : e;
}
static String joinWithSpace(Iterable c) {
  return join(" ", c);
}

static String joinWithSpace(String... c) {
  return join(" ", c);
}

static volatile StringBuffer local_log = new StringBuffer(); // not redirected




static volatile Appendable print_log = local_log; // might be redirected, e.g. to main bot

// in bytes - will cut to half that
static volatile int print_log_max = 1024*1024;
static volatile int local_log_max = 100*1024;

static boolean print_silent = false; // total mute if set

static Object print_byThread_lock = new Object();
static volatile ThreadLocal<Object> print_byThread; // special handling by thread - prefers F1<S, Bool>
static volatile Object print_allThreads;
static volatile Object print_preprocess;

static void print() {
  print("");
}

static <A> A print(String s, A o) {
  print((endsWithLetterOrDigit(s) ? s + ": " : s) + o);
  return o;
}

// slightly overblown signature to return original object...
static <A> A print(A o) {
  ping_okInCleanUp();
  if (print_silent) return o;
  String s = o + "\n";
  print_noNewLine(s);
  return o;
}

static void print_noNewLine(String s) {
  
  Object f = getThreadLocal(print_byThread_dontCreate());
  if (f == null) f = print_allThreads;
  if (f != null)
    // We do need the general callF machinery here as print_byThread is sometimes shared between modules
    if (isFalse(
      
        f instanceof F1 ? ((F1) f).get(s) :
      
      callF(f, s))) return;
  

  print_raw(s);
}

static void print_raw(String s) {
  
  if (print_preprocess != null) s = (String) callF(print_preprocess, s);
  s = fixNewLines(s);
  
  Appendable loc = local_log;
  Appendable buf = print_log;
  int loc_max = print_log_max;
  if (buf != loc && buf != null) {
    print_append(buf, s, print_log_max);
    loc_max = local_log_max;
  }
  if (loc != null) 
    print_append(loc, s, loc_max);
  
  
    System.out.print(s);
  
  vmBus_send("printed",mc(), s);
}

static void print_autoRotate() {
  
}
static boolean isInstanceX(Class type, Object arg) {
  if (type == boolean.class) return arg instanceof Boolean;
  if (type == int.class) return arg instanceof Integer;
  if (type == long.class) return arg instanceof Long;
  if (type == float.class) return arg instanceof Float;
  if (type == short.class) return arg instanceof Short;
  if (type == char.class) return arg instanceof Character;
  if (type == byte.class) return arg instanceof Byte;
  if (type == double.class) return arg instanceof Double;
  return type.isInstance(arg);
}
static void vmBus_send(String msg, Object... args) {
  Object arg = vmBus_wrapArgs(args);
  pcallFAll(vm_busListeners_live(), msg, arg);
  pcallFAll(vm_busListenersByMessage_live().get(msg), msg, arg);
}

static void vmBus_send(String msg) {
  vmBus_send(msg, (Object) null);
}
static boolean methodIsStatic(Method m) {
  return (m.getModifiers() & Modifier.STATIC) != 0;
}
static boolean argumentCompatibleWithType(Object arg, Class type) {
  return arg == null ? !type.isPrimitive() : isInstanceX(type, arg);
}
static void arraycopy(Object[] a, Object[] b) {
  if (a != null && b != null)
    arraycopy(a, 0, b, 0, Math.min(a.length, b.length));
}

static void arraycopy(Object src, int srcPos, Object dest, int destPos, int n) {
  if (n != 0)
    System.arraycopy(src, srcPos, dest, destPos, n);
}
static <A> A[] arrayOfType(Class<A> type, int n) {
  return makeArray(type, n);
}

static <A> A[] arrayOfType(int n, Class<A> type) {
  return arrayOfType(type, n);
}
public static <A> String join(String glue, Iterable<A> strings) {
  if (strings == null) return "";
  if (strings instanceof Collection) {
    if (((Collection) strings).size() == 1) return str(first(((Collection) strings)));
  }
  StringBuilder buf = new StringBuilder();
  Iterator<A> i = strings.iterator();
  if (i.hasNext()) {
    buf.append(i.next());
    while (i.hasNext())
      buf.append(glue).append(i.next());
  }
  return buf.toString();
}

public static String join(String glue, String... strings) {
  return join(glue, Arrays.asList(strings));
}

static <A> String join(Iterable<A> strings) {
  return join("", strings);
}

static <A> String join(Iterable<A> strings, String glue) {
  return join(glue, strings);
}

public static String join(String[] strings) {
  return join("", strings);
}


static String join(String glue, Pair p) {
  return p == null ? "" : str(p.a) + glue + str(p.b);
}

static String str(Object o) {
  return o == null ? "null" : o.toString();
}

static String str(char[] c) {
  return new String(c);
}
static List<String> getClassNames(Collection l) {
  List<String> out = new ArrayList();
  if (l != null) for (Object o : l)
    out.add(o == null ? null : getClassName(o));
  return out;
}


static AutoCloseable tempInterceptPrintIfNotIntercepted(F1<String, Boolean> f) {
  return print_byThread().get() == null ? tempInterceptPrint(f) : null;
}
static HashMap<String, List<Method>> callMC_cache = new HashMap();
static String callMC_key;
static Method callMC_value;

// varargs assignment fixer for a single string array argument
static Object callMC(String method, String[] arg) {
  return callMC(method, new Object[] {arg});
}

static Object callMC(String method, Object... args) { try {
  Method me;
  if (callMC_cache == null) callMC_cache = new HashMap(); // initializer time workaround
  synchronized(callMC_cache) {
    me = method == callMC_key ? callMC_value : null;
  }
  if (me != null) try {
    return invokeMethod(me, null, args);
  } catch (IllegalArgumentException e) {
    throw new RuntimeException("Can't call " + me + " with arguments " + classNames(args), e);
  }

  List<Method> m;
  synchronized(callMC_cache) {
    m = callMC_cache.get(method);
  }
  if (m == null) {
    if (callMC_cache.isEmpty()) {
      callMC_makeCache();
      m = callMC_cache.get(method);
    }
    if (m == null) throw fail("Method named " + method + " not found in main");
  }
  int n = m.size();
  if (n == 1) {
    me = m.get(0);
    synchronized(callMC_cache) {
      callMC_key = method;
      callMC_value = me;
    }
    try {
      return invokeMethod(me, null, args);
    } catch (IllegalArgumentException e) {
      throw new RuntimeException("Can't call " + me + " with arguments " + classNames(args), e);
    }
  }
  for (int i = 0; i < n; i++) {
    me = m.get(i);
    if (call_checkArgs(me, args, false))
      return invokeMethod(me, null, args);
  }
  throw fail("No method called " + method + " with arguments (" + joinWithComma(getClasses(args)) + ") found in main");
} catch (Exception __e) { throw rethrow(__e); } }

static void callMC_makeCache() {
  synchronized(callMC_cache) {
    callMC_cache.clear();
    Class _c = (Class) mc(), c = _c;
    while (c != null) {
      for (Method m : c.getDeclaredMethods())
        if ((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0) {
          makeAccessible(m);
          multiMapPut(callMC_cache, m.getName(), m);
        }
      c = c.getSuperclass();
    }
  }
}
static <A> List<A> synchroList() {
  return Collections.synchronizedList(new ArrayList<A>());
}

static <A> List<A> synchroList(List<A> l) {
  return Collections.synchronizedList(l);
}

static <A, B> Pair<A, B> pair(A a, B b) {
  return new Pair(a, b);
}

static <A> Pair<A, A> pair(A a) {
  return new Pair(a, a);
}
static <A> A assertNotNull(A a) {
  assertTrue(a != null);
  return a;
}

static <A> A assertNotNull(String msg, A a) {
  assertTrue(msg, a != null);
  return a;
}
static Map synchroHashMap() {
  return Collections.synchronizedMap(new HashMap());
}

static boolean endsWithLetterOrDigit(String s) {
  return s != null && s.length() > 0 && Character.isLetterOrDigit(s.charAt(s.length()-1));
}
static void ping_okInCleanUp() {

  if (ping_pauseAll || ping_anyActions)
    ping_impl(true);

}
// this syntax should be removed...
static Object getThreadLocal(Object o, String name) {
  ThreadLocal t =  (ThreadLocal) (getOpt(o, name));
  return t != null ? t.get() : null;
}

static <A> A getThreadLocal(ThreadLocal<A> tl) {
  return tl == null ? null : tl.get();
}

static <A> A getThreadLocal(ThreadLocal<A> tl, A defaultValue) {
  return or(getThreadLocal(tl), defaultValue);
}
static ThreadLocal<Object> print_byThread_dontCreate() {
  return print_byThread;
}
static boolean isFalse(Object o) {
  return eq(false, o);
}
static String fixNewLines(String s) {
  int i = indexOf(s, '\r');
  if (i < 0) return s;
  int l = s.length();
  StringBuilder out = new StringBuilder(l);
  out.append(s, 0, i);
  for (; i < l; i++) {
    char c = s.charAt(i);
    if (c != '\r')
      out.append(c);
    else {
      out.append('\n');
      if (i+1 < l && s.charAt(i+1) == '\n') ++i;
    }
  }
  return out.toString();
}
static void print_append(Appendable buf, String s, int max) { try {
  synchronized(buf) {
    buf.append(s);
    if (buf instanceof StringBuffer)
      rotateStringBuffer(((StringBuffer) buf), max);
    else if (buf instanceof StringBuilder)
      rotateStringBuilder(((StringBuilder) buf), max);
  }
} catch (Exception __e) { throw rethrow(__e); } }
static Object vmBus_wrapArgs(Object... args) {
  return empty(args) ? null
    : l(args) == 1 ? args[0]
    : args;
}
static void pcallFAll(Collection l, Object... args) {
  if (l != null) for (Object f : cloneList(l)) pcallF(f, args);
}

static void pcallFAll(Iterator it, Object... args) {
  while (it.hasNext()) pcallF(it.next(), args);
}
static Set vm_busListeners_live_cache;
static Set vm_busListeners_live() { if (vm_busListeners_live_cache == null) vm_busListeners_live_cache = vm_busListeners_live_load(); return vm_busListeners_live_cache; }

static Set vm_busListeners_live_load() {
  return vm_generalIdentityHashSet("busListeners");
}
static Map<String, Set> vm_busListenersByMessage_live_cache;
static Map<String, Set> vm_busListenersByMessage_live() { if (vm_busListenersByMessage_live_cache == null) vm_busListenersByMessage_live_cache = vm_busListenersByMessage_live_load(); return vm_busListenersByMessage_live_cache; }

static Map<String, Set> vm_busListenersByMessage_live_load() {
  return vm_generalHashMap("busListenersByMessage");
}
static <A> A[] makeArray(Class<A> type, int n) {
  return (A[]) Array.newInstance(type, n);
}

static Object first(Object list) {
  return first((Iterable) list);
}


static <A> A first(List<A> list) {
  return empty(list) ? null : list.get(0);
}

static <A> A first(A[] bla) {
  return bla == null || bla.length == 0 ? null : bla[0];
}



static <A> A first(Iterator<A> i) {
  return i == null || !i.hasNext() ? null : i.next();
}

static <A> A first(Iterable<A> i) {
  if (i == null) return null;
  Iterator<A> it = i.iterator();
  return it.hasNext() ? it.next() : null;
}

static Character first(String s) { return empty(s) ? null : s.charAt(0); }
static Character first(CharSequence s) { return empty(s) ? null : s.charAt(0); }


static <A, B> A first(Pair<A, B> p) {
  return p == null ? null : p.a;
}




static Byte first(byte[] l) { 
  return empty(l) ? null : l[0];
}




static ThreadLocal<Object> print_byThread() {
  synchronized(print_byThread_lock) {
    if (print_byThread == null)
      print_byThread = new ThreadLocal();
  }
  return print_byThread;
}
// f can return false to suppress regular printing
// call print_raw within f to actually print something
static AutoCloseable tempInterceptPrint(F1<String, Boolean> f) {
  return tempSetThreadLocal(print_byThread(), f);
}
static List<Class> getClasses(Object[] array) {
  List<Class> l = emptyList(l(array));
  for (Object o : array) l.add(_getClass(o));
  return l;
}
static <A, B> void multiMapPut(Map<A, List<B>> map, A a, B b) {
  List<B> l = map.get(a);
  if (l == null)
    map.put(a, l = new ArrayList());
  l.add(b);
}


static void assertTrue(Object o) {
  if (!(eq(o, true) /*|| isTrue(pcallF(o))*/))
    throw fail(str(o));
}
  
static boolean assertTrue(String msg, boolean b) {
  if (!b)
    throw fail(msg);
  return b;
}

static boolean assertTrue(boolean b) {
  if (!b)
    throw fail("oops");
  return b;
}



//sbool ping_actions_shareable = true;
static volatile boolean ping_pauseAll = false;
static int ping_sleep = 100; // poll pauseAll flag every 100
static volatile boolean ping_anyActions = false;
static Map<Thread, Object> ping_actions = newWeakHashMap();
static ThreadLocal<Boolean> ping_isCleanUpThread = new ThreadLocal();

// always returns true
static boolean ping() {
  if (ping_pauseAll || ping_anyActions) ping_impl(true /* XXX */);
  //ifndef LeanMode ping_impl(); endifndef
  return true;
}

// returns true when it slept
static boolean ping_impl(boolean okInCleanUp) { try {
  if (ping_pauseAll && !isAWTThread()) {
    do
      Thread.sleep(ping_sleep);
    while (ping_pauseAll);
    return true;
  }
  
  if (ping_anyActions) { // don't allow sharing ping_actions
    if (!okInCleanUp && !isTrue(ping_isCleanUpThread.get()))
      failIfUnlicensed();
    Object action = null;
    synchronized(ping_actions) {
      if (!ping_actions.isEmpty()) {
        action = ping_actions.get(currentThread());
        if (action instanceof Runnable)
          ping_actions.remove(currentThread());
        if (ping_actions.isEmpty()) ping_anyActions = false;
      }
    }
    
    if (action instanceof Runnable)
      ((Runnable) action).run();
    else if (eq(action, "cancelled"))
      throw fail("Thread cancelled.");
  }

  return false;
} catch (Exception __e) { throw rethrow(__e); } }

static Object getOpt(Object o, String field) {
  return getOpt_cached(o, field);
}

static Object getOpt(String field, Object o) {
  return getOpt_cached(o, field);
}

static Object getOpt_raw(Object o, String field) { try {
  Field f = getOpt_findField(o.getClass(), field);
  if (f == null) return null;
  makeAccessible(f);
  return f.get(o);
} catch (Exception __e) { throw rethrow(__e); } }

// access of static fields is not yet optimized
static Object getOpt(Class c, String field) { try {
  if (c == null) return null;
  Field f = getOpt_findStaticField(c, field);
  if (f == null) return null;
  makeAccessible(f);
  return f.get(null);
} catch (Exception __e) { throw rethrow(__e); } }

static Field getOpt_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}

static <A> A or(A a, A b) {
  return a != null ? a : b;
}
static boolean eq(Object a, Object b) {
  return a == b || a != null && b != null && a.equals(b);
}


static <A> int indexOf(List<A> l, A a, int startIndex) {
  if (l == null) return -1;
  int n = l(l);
  for (int i = startIndex; i < n; i++)
    if (eq(l.get(i), a))
      return i;
  return -1;
}

static <A> int indexOf(List<A> l, int startIndex, A a) {
  return indexOf(l, a, startIndex);
}

static <A> int indexOf(List<A> l, A a) {
  if (l == null) return -1;
  return l.indexOf(a);
}

static int indexOf(String a, String b) {
  return a == null || b == null ? -1 : a.indexOf(b);
}

static int indexOf(String a, String b, int i) {
  return a == null || b == null ? -1 : a.indexOf(b, i);
}

static int indexOf(String a, char b) {
  return a == null ? -1 : a.indexOf(b);
}

static int indexOf(String a, int i, char b) {
  return indexOf(a, b, i);
}

static int indexOf(String a, char b, int i) {
  return a == null ? -1 : a.indexOf(b, i);
}

static int indexOf(String a, int i, String b) {
  return a == null || b == null ? -1 : a.indexOf(b, i);
}

static <A> int indexOf(A[] x, A a) {
  int n = l(x);
  for (int i = 0; i < n; i++)
    if (eq(x[i], a))
      return i;
  return -1;
}
static void rotateStringBuffer(StringBuffer buf, int max) { try {
  if (buf == null) return;
  synchronized(buf) {
    if (buf.length() <= max) return;
    
    try {
      int newLength = max/2;
      int ofs = buf.length()-newLength;
      String newString = buf.substring(ofs);
      buf.setLength(0);
      buf.append("[...] ").append(newString);
    } catch (Exception e) {
      buf.setLength(0);
    }
    buf.trimToSize();
  }
} catch (Exception __e) { throw rethrow(__e); } }
static void rotateStringBuilder(StringBuilder buf, int max) { try {
  if (buf == null) return;
  synchronized(buf) {
    if (buf.length() <= max) return;
    
    try {
      int newLength = max/2;
      int ofs = buf.length()-newLength;
      String newString = buf.substring(ofs);
      buf.setLength(0);
      buf.append("[...] ").append(newString);
    } catch (Exception e) {
      buf.setLength(0);
    }
    buf.trimToSize();
  }
} catch (Exception __e) { throw rethrow(__e); } }
static boolean empty(Collection c) { return c == null || c.isEmpty(); }
static boolean empty(Iterable c) { return c == null || !c.iterator().hasNext(); }
static boolean empty(CharSequence s) { return s == null || s.length() == 0; }
static boolean empty(Map map) { return map == null || map.isEmpty(); }
static boolean empty(Object[] o) { return o == null || o.length == 0; }
static boolean empty(Object o) {
  if (o instanceof Collection) return empty((Collection) o);
  if (o instanceof String) return empty((String) o);
  if (o instanceof Map) return empty((Map) o);
  if (o instanceof Object[]) return empty((Object[]) o);
  if (o instanceof byte[]) return empty((byte[]) o);
  if (o == null) return true;
  throw fail("unknown type for 'empty': " + getType(o));
}

static boolean empty(Iterator i) { return i == null || !i.hasNext(); }

static boolean empty(double[] a) { return a == null || a.length == 0; }
static boolean empty(float[] a) { return a == null || a.length == 0; }
static boolean empty(int[] a) { return a == null || a.length == 0; }
static boolean empty(long[] a) { return a == null || a.length == 0; }
static boolean empty(byte[] a) { return a == null || a.length == 0; }
static boolean empty(short[] a) { return a == null || a.length == 0; }





static boolean empty(File f) { return getFileSize(f) == 0; }


static boolean empty(IntRange r) { return r == null || r.empty(); }





static <A> ArrayList<A> cloneList(Iterable<A> l) {
  return l instanceof Collection ? cloneList((Collection) l) : asList(l);
}

static <A> ArrayList<A> cloneList(Collection<A> l) {
  if (l == null) return new ArrayList();
  synchronized(collectionMutex(l)) {
    return new ArrayList<A>(l);
  }
}
static Object pcallF(Object f, Object... args) {
  return pcallFunction(f, args);
}




static <A, B> B pcallF(F1<A, B> f, A a) { try {
  return f == null ? null : f.get(a);
} catch (Throwable __e) { return null; } }



static <A> void pcallF(VF1<A> f, A a) {
  try {
    if (f != null) f.get(a);
  } catch (Throwable __e) { _handleException(__e); }
}

static Set vm_generalIdentityHashSet(Object name) {
  synchronized(get(javax(), "generalMap")) {
    Set set =  (Set) (vm_generalMap_get(name));
    if (set == null)
      vm_generalMap_put(name, set = syncIdentityHashSet());
    return set;
  }
}

static Map vm_generalHashMap(Object name) {
  synchronized(get(javax(), "generalMap")) {
    Map m =  (Map) (vm_generalMap_get(name));
    if (m == null)
      vm_generalMap_put(name, m = syncHashMap());
    return m;
  }
}



static <A> AutoCloseable tempSetThreadLocal(final ThreadLocal<A> tl, A a) {
  if (tl == null) return null;
  final A prev = setThreadLocal(tl, a);
  return new AutoCloseable() { public String toString() { return "tl.set(prev);"; } public void close() throws Exception { tl.set(prev); }};
}
static ArrayList emptyList() {
  return new ArrayList();
  //ret Collections.emptyList();
}

static ArrayList emptyList(int capacity) {
  return new ArrayList(max(0, capacity));
}

// Try to match capacity
static ArrayList emptyList(Iterable l) {
  return l instanceof Collection ? emptyList(((Collection) l).size()) : emptyList();
}

static ArrayList emptyList(Object[] l) {
  return emptyList(l(l));
}

// get correct type at once
static <A> ArrayList<A> emptyList(Class<A> c) {
  return new ArrayList();
}
static Class<?> _getClass(String name) {
  try {
    return Class.forName(name);
  } catch (ClassNotFoundException e) {
    return null; // could optimize this
  }
}

static Class _getClass(Object o) {
  return o == null ? null
    : o instanceof Class ? (Class) o : o.getClass();
}

static Class _getClass(Object realm, String name) {
  try {
    return getClass(realm).getClassLoader().loadClass(classNameToVM(name));
  } catch (ClassNotFoundException e) {
    return null; // could optimize this
  }
}
static <A, B> Map<A, B> newWeakHashMap() {
  return _registerWeakMap(synchroMap(new WeakHashMap()));
}
// TODO: test if android complains about this
static boolean isAWTThread() {
  if (isAndroid()) return false;
  if (isHeadless()) return false;
  return isAWTThread_awt();
}

static boolean isAWTThread_awt() {
  return SwingUtilities.isEventDispatchThread();
}
static boolean isTrue(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  if (o == null) return false;
  if (o instanceof ThreadLocal) // TODO: remove this
    return isTrue(((ThreadLocal) o).get());
  throw fail(getClassName(o));
}
static void failIfUnlicensed() {
  assertTrue("license off", licensed());
}
static Thread currentThread() {
  return Thread.currentThread();
}
//static final Map<Class, HashMap<S, Field>> getOpt_cache = newDangerousWeakHashMap(f getOpt_special_init);

static class getOpt_Map extends WeakHashMap {
  getOpt_Map() {
    if (getOpt_special == null) getOpt_special = new HashMap();
    clear();
  }
  
  public void clear() {
    super.clear();
    //print("getOpt clear");
    put(Class.class, getOpt_special);
    put(String.class, getOpt_special);
  }
}

static final Map<Class, HashMap<String, Field>> getOpt_cache = _registerDangerousWeakMap(synchroMap(new getOpt_Map()));
//static final Map<Class, HashMap<S, Field>> getOpt_cache = _registerWeakMap(synchroMap(new getOpt_Map));
static HashMap getOpt_special; // just a marker

/*static void getOpt_special_init(Map map) {
  map.put(Class.class, getOpt_special);
  map.put(S.class, getOpt_special);
}*/

static Object getOpt_cached(Object o, String field) { try {
  if (o == null) return null;

  Class c = o.getClass();
  HashMap<String, Field> map;
  synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  
  if (map == getOpt_special) {
    if (o instanceof Class)
      return getOpt((Class) o, field);
    /*if (o instanceof S)
      ret getOpt(getBot((S) o), field);*/
    if (o instanceof Map)
      return ((Map) o).get(field);
  }
    
  Field f = map.get(field);
  if (f != null) return f.get(o);
  
  return null;
} catch (Exception __e) { throw rethrow(__e); } }

// used internally - we are in synchronized block
static HashMap<String, Field> getOpt_makeCache(Class c) {
  HashMap<String, Field> map;
  if (isSubtypeOf(c, Map.class))
    map = getOpt_special;
  else {
    map = new HashMap();
    if (!reflection_classesNotToScan().contains(c.getName())) {
      Class _c = c;
      do {
        for (Field f : _c.getDeclaredFields()) {
          makeAccessible(f);
          String name = f.getName();
          if (!map.containsKey(name))
            map.put(name, f);
        }
        _c = _c.getSuperclass();
      } while (_c != null);
    }
  }
  if (getOpt_cache != null) getOpt_cache.put(c, map);
  return map;
}
static Field getOpt_findField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}
static String getType(Object o) {
  return getClassName(o);
}
static long getFileSize(String path) {
  return path == null ? 0 : new File(path).length();
}

static long getFileSize(File f) {
  return f == null ? 0 : f.length();
}
// unclear semantics as to whether return null on null

static <A> ArrayList<A> asList(A[] a) {
  return a == null ? new ArrayList<A>() : new ArrayList<A>(Arrays.asList(a));
}

static ArrayList<Integer> asList(int[] a) {
  if (a == null) return null;
  ArrayList<Integer> l = emptyList(a.length);
  for (int i : a) l.add(i);
  return l;
}

static ArrayList<Long> asList(long[] a) {
  if (a == null) return null;
  ArrayList<Long> l = emptyList(a.length);
  for (long i : a) l.add(i);
  return l;
}

static ArrayList<Float> asList(float[] a) {
  if (a == null) return null;
  ArrayList<Float> l = emptyList(a.length);
  for (float i : a) l.add(i);
  return l;
}

static ArrayList<Double> asList(double[] a) {
  if (a == null) return null;
  ArrayList<Double> l = emptyList(a.length);
  for (double i : a) l.add(i);
  return l;
}

static <A> ArrayList<A> asList(Iterable<A> s) {
  if (s instanceof ArrayList) return (ArrayList) s;
  ArrayList l = new ArrayList();
  if (s != null)
    for (A a : s)
      l.add(a);
  return l;
}



static <A> ArrayList<A> asList(Enumeration<A> e) {
  ArrayList l = new ArrayList();
  if (e != null)
    while (e.hasMoreElements())
      l.add(e.nextElement());
  return l;
}


static Object collectionMutex(List l) {
  return l;
}

static Object collectionMutex(Object o) {
  if (o instanceof List) return o;
  
  String c = className(o);
  if (eq(c, "java.util.TreeMap$KeySet"))
    c = className(o = getOpt(o, "m"));
  else if (eq(c, "java.util.HashMap$KeySet"))
    c = className(o = get_raw(o, "this$0"));

  
  
  if (eqOneOf(c, "java.util.TreeMap$AscendingSubMap", "java.util.TreeMap$DescendingSubMap"))
    c = className(o = get_raw(o, "m"));
    
  
    
  return o;
}
static Object pcallFunction(Object f, Object... args) {
  try { return callFunction(f, args); } catch (Throwable __e) { _handleException(__e); }
  return null;
}
static volatile PersistableThrowable _handleException_lastException;
static List _handleException_onException = synchroList(ll("printStackTrace2"));

static void _handleException(Throwable e) {
  _handleException_lastException = persistableThrowable(e);
  
  Throwable e2 = innerException(e);
  if (e2.getClass() == RuntimeException.class && eq(e2.getMessage(), "Thread cancelled.") || e2 instanceof InterruptedException)
    return;
    
  for (Object f : cloneList(_handleException_onException)) try {
    callF(f, e);
  } catch (Throwable e3) {
    try {
      printStackTrace2(e3); // not using pcall here - it could lead to endless loops
    } catch (Throwable e4) {
      System.out.println(getStackTrace(e3));
      System.out.println(getStackTrace(e4));
    }
  }
}

static <A> A get(List<A> l, int idx) {
  return l != null && idx >= 0 && idx < l(l) ? l.get(idx) : null;
}

// seems to conflict with other signatures
/*static <A, B> B get(Map<A, B> map, A key) {
  ret map != null ? map.get(key) : null;
}*/

static <A> A get(A[] l, int idx) {
  return idx >= 0 && idx < l(l) ? l[idx] : null;
}

// default to false
static boolean get(boolean[] l, int idx) {
  return idx >= 0 && idx < l(l) ? l[idx] : false;
}

// get purpose 2: access a field by reflection or a map

static Object get(Object o, String field) {
  try {
    if (o == null) return null;
    if (o instanceof Class) return get((Class) o, field);
    
    if (o instanceof Map)
      return ((Map) o).get(field);
      
    Field f = getOpt_findField(o.getClass(), field);
    if (f != null) {
      makeAccessible(f);
      return f.get(o);
    }
      
    
  } catch (Exception e) {
    throw asRuntimeException(e);
  }
  throw new RuntimeException("Field '" + field + "' not found in " + o.getClass().getName());
}

static Object get_raw(String field, Object o) {
  return get_raw(o, field);
}

static Object get_raw(Object o, String field) { try {
  if (o == null) return null;
  Field f = get_findField(o.getClass(), field);
  makeAccessible(f);
  return f.get(o);
} catch (Exception __e) { throw rethrow(__e); } }

static Object get(Class c, String field) {
  try {
    Field f = get_findStaticField(c, field);
    makeAccessible(f);
    return f.get(null);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

static Field get_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Static field '" + field + "' not found in " + c.getName());
}

static Field get_findField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Field '" + field + "' not found in " + c.getName());
}

static Object get(String field, Object o) {
  return get(o, field);
}
static Object vm_generalMap_get(Object key) {
  return vm_generalMap().get(key);
}
static Object vm_generalMap_put(Object key, Object value) {
  return mapPutOrRemove(vm_generalMap(), key, value);
}
static <A> Set<A> syncIdentityHashSet() {
  return (Set) synchronizedSet(identityHashSet());
}
static Map syncHashMap() {
  return synchroHashMap();
}


static Throwable printStackTrace2(Throwable e) {
  // we go to system.out now - system.err is nonsense
  print(getStackTrace2(e));
  return e;
}

static void printStackTrace2() {
  printStackTrace2(new Throwable());
}

static void printStackTrace2(String msg) {
  printStackTrace2(new Throwable(msg));
}

static <A> A setThreadLocal(ThreadLocal<A> tl, A value) {
  if (tl == null) return null;
  A old = tl.get();
  tl.set(value);
  return old;
}
static int max(int a, int b) { return Math.max(a, b); }
static int max(int a, int b, int c) { return max(max(a, b), c); }
static long max(int a, long b) { return Math.max((long) a, b); }
static long max(long a, long b) { return Math.max(a, b); }
static double max(int a, double b) { return Math.max((double) a, b); }
static float max(float a, float b) { return Math.max(a, b); }
static double max(double a, double b) { return Math.max(a, b); }

static int max(Collection<Integer> c) {
  int x = Integer.MIN_VALUE;
  for (int i : c) x = max(x, i);
  return x;
}

static double max(double[] c) {
  if (c.length == 0) return Double.MIN_VALUE;
  double x = c[0];
  for (int i = 1; i < c.length; i++) x = Math.max(x, c[i]);
  return x;
}

static float max(float[] c) {
  if (c.length == 0) return Float.MAX_VALUE;
  float x = c[0];
  for (int i = 1; i < c.length; i++) x = Math.max(x, c[i]);
  return x;
}

static byte max(byte[] c) {
  byte x = -128;
  for (byte d : c) if (d > x) x = d;
  return x;
}

static short max(short[] c) {
  short x = -0x8000;
  for (short d : c) if (d > x) x = d;
  return x;
}

static int max(int[] c) {
  int x = Integer.MIN_VALUE;
  for (int d : c) if (d > x) x = d;
  return x;
}
static Class<?> getClass(String name) {
  try {
    return Class.forName(name);
  } catch (ClassNotFoundException e) {
    return null;
  }
}

static Class getClass(Object o) {
  return o instanceof Class ? (Class) o : o.getClass();
}

static Class getClass(Object realm, String name) { try {
  try {
    return getClass(realm).getClassLoader().loadClass(classNameToVM(name));
  } catch (ClassNotFoundException e) {
    return null;
  }
} catch (Exception __e) { throw rethrow(__e); } }
static String classNameToVM(String name) {
  return name.replace(".", "$");
}
static List _registerWeakMap_preList;

static <A> A _registerWeakMap(A map) {
  if (javax() == null) {
    // We're in class init
    if (_registerWeakMap_preList == null) _registerWeakMap_preList = synchroList();
    _registerWeakMap_preList.add(map);
    return map;
  }
  
  try {
    call(javax(), "_registerWeakMap", map);
  } catch (Throwable e) {
    printException(e);
    print("Upgrade JavaX!!");
  }
  return map;
}

static void _onLoad_registerWeakMap() {
  assertNotNull(javax());
  if (_registerWeakMap_preList == null) return;
  for (Object o : _registerWeakMap_preList)
    _registerWeakMap(o);
  _registerWeakMap_preList = null;
}
static int isAndroid_flag;

static boolean isAndroid() {
  if (isAndroid_flag == 0)
    isAndroid_flag = System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0 ? 1 : -1;
  return isAndroid_flag > 0;
}

static Boolean isHeadless_cache;

static boolean isHeadless() {
  if (isHeadless_cache != null) return isHeadless_cache;
  if (isAndroid()) return isHeadless_cache = true;
  if (GraphicsEnvironment.isHeadless()) return isHeadless_cache = true;
  
  // Also check if AWT actually works.
  // If DISPLAY variable is set but no X server up, this will notice.
  
  try {
    SwingUtilities.isEventDispatchThread();
    return isHeadless_cache = false;
  } catch (Throwable e) { return isHeadless_cache = true; }
}
static volatile boolean licensed_yes = true;

static boolean licensed() {
  if (!licensed_yes) return false;
  ping_okInCleanUp();
  return true;
}

static void licensed_off() {
  licensed_yes = false;
}
static void clear(Collection c) {
  if (c != null) c.clear();
}

static void clear(Map map) {
  if (map != null) map.clear();
}
static <A, B> void put(Map<A, B> map, A a, B b) {
  if (map != null) map.put(a, b);
}

static <A> void put(List<A> l, int i, A a) {
  if (l != null && i >= 0 && i < l(l)) l.set(i, a);
}
static <A, B> B mapGet2(Map<A, B> map, A a) {
  return map == null ? null : map.get(a);
}

static <A, B> B mapGet2(A a, Map<A, B> map) {
  return map == null ? null : map.get(a);
}
static boolean isSubtypeOf(Class a, Class b) {
  return b.isAssignableFrom(a); // << always hated that method, let's replace it!
}
static Set<String> reflection_classesNotToScan_value = litset(
  "jdk.internal.loader.URLClassPath"
);

static Set<String> reflection_classesNotToScan() {
  return reflection_classesNotToScan_value;
}
static String className(Object o) {
  return getClassName(o);
}
static boolean eqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return true; return false;
}
static Object callFunction(Object f, Object... args) {
  return callF(f, args);
}
static <A> List<A> ll(A... a) {
  ArrayList l = new ArrayList(a.length);
  if (a != null) for (A x : a) l.add(x);
  return l;
}
static PersistableThrowable persistableThrowable(Throwable e) {
  return e == null ? null : new PersistableThrowable(e);
}
static Throwable innerException(Throwable e) {
  return getInnerException(e);
}
static String getStackTrace(Throwable throwable) {
  lastException(throwable);
  return getStackTrace_noRecord(throwable);
}

static String getStackTrace_noRecord(Throwable throwable) {
  StringWriter writer = new StringWriter();
  throwable.printStackTrace(new PrintWriter(writer));
  return hideCredentials(writer.toString());
}

static String getStackTrace() {
  return getStackTrace_noRecord(new Throwable());
}
static Map vm_generalMap_map;

static Map vm_generalMap() {
  if (vm_generalMap_map == null)
    vm_generalMap_map = (Map) get(javax(), "generalMap");
  return vm_generalMap_map;
}
static <A, B> B mapPutOrRemove(Map<A, B> map, A key, B value) {
  if (map != null && key != null)
    if (value != null) return map.put(key, value);
    else return map.remove(key);
  return null;
}
static <A> Set<A> synchronizedSet() {
  return synchroHashSet();
}

static <A> Set<A> synchronizedSet(Set<A> set) {
  return Collections.synchronizedSet(set);
}
static <A> Set<A> identityHashSet() {
  return Collections.newSetFromMap(new IdentityHashMap());
}


static String getStackTrace2(Throwable e) {
  return hideCredentials(getStackTrace(unwrapTrivialExceptionWraps(e)) + replacePrefix("java.lang.RuntimeException: ", "FAIL: ",
    hideCredentials(str(innerException2(e)))) + "\n");
}
static <A extends Throwable> A printException(A e) {
  printStackTrace(e);
  return e;
}
static <A> HashSet<A> litset(A... items) {
  return lithashset(items);
}
static Throwable getInnerException(Throwable e) {
  if (e == null) return null;
  while (e.getCause() != null)
    e = e.getCause();
  return e;
}

static Throwable getInnerException(Runnable r) {
  return getInnerException(getException(r));
}
// PersistableThrowable doesn't hold GC-disturbing class references in backtrace
static volatile PersistableThrowable lastException_lastException;

static PersistableThrowable lastException() {
  return lastException_lastException;
}

static void lastException(Throwable e) {
  lastException_lastException = persistableThrowable(e);
}
static String hideCredentials(URL url) { return url == null ? null : hideCredentials(str(url)); }

static String hideCredentials(String url) {
  try {
    if (startsWithOneOf(url, "http://", "https://") && isAGIBlueDomain(hostNameFromURL(url))) return url;
  } catch (Throwable e) {
    print("HideCredentials", e);
  }
  return url.replaceAll("([&?])(_pass|key|cookie)=[^&\\s\"]*", "$1$2=<hidden>");
}

static String hideCredentials(Object o) {
  return hideCredentials(str(o));
}
static <A> Set<A> synchroHashSet() {
  return Collections.synchronizedSet(new HashSet<A>());
}



static Throwable unwrapTrivialExceptionWraps(Throwable e) {
  if (e == null) return e;
  while (e.getClass() == RuntimeException.class
    && e.getCause() != null && eq(e.getMessage(), str(e.getCause())))
    e = e.getCause();
  return e;
}
static String replacePrefix(String prefix, String replacement, String s) {
  if (!startsWith(s, prefix)) return s;
  return replacement + substring(s, l(prefix));
}
static Throwable innerException2(Throwable e) {
  if (e == null) return null;
  while (empty(e.getMessage()) && e.getCause() != null)
    e = e.getCause();
  return e;
}
static <A extends Throwable> A printStackTrace(A e) {
  // we go to system.out now - system.err is nonsense
  print(getStackTrace(e));
  return e;
}

static void printStackTrace() {
  printStackTrace(new Throwable());
}

static void printStackTrace(String msg) {
  printStackTrace(new Throwable(msg));
}

static void printStackTrace(String msg, Throwable e) {
  printStackTrace(new Throwable(msg, e));
}
static <A> HashSet<A> lithashset(A... items) {
  HashSet<A> set = new HashSet();
  for (A a : items) set.add(a);
  return set;
}
static Throwable getException(Runnable r) {
  try {
    callF(r);
    return null;
  } catch (Throwable e) {
    return e;
  }
}
static boolean startsWithOneOf(String s, String... l) {
  for (String x : l) if (startsWith(s, x)) return true; return false;
}

static boolean startsWithOneOf(String s, Matches m, String... l) {
  for (String x : l) if (startsWith(s, x, m)) return true; return false;
}
static boolean isAGIBlueDomain(String domain) {
  return domainIsUnder(domain, theAGIBlueDomain());
}
static String hostNameFromURL(String url) { try {
  return new URL(url).getHost();
} catch (Exception __e) { throw rethrow(__e); } }


static boolean startsWith(String a, String b) {
  return a != null && a.startsWith(unnull(b));
}

static boolean startsWith(String a, char c) {
  return nemptyString(a) && a.charAt(0) == c;
}


  static boolean startsWith(String a, String b, Matches m) {
    if (!startsWith(a, b)) return false;
    m.m = new String[] {substring(a, strL(b))};
    return true;
  }


static boolean startsWith(List a, List b) {
  if (a == null || listL(b) > listL(a)) return false;
  for (int i = 0; i < listL(b); i++)
    if (neq(a.get(i), b.get(i)))
      return false;
  return true;
}


static String substring(String s, int x) {
  return substring(s, x, strL(s));
}

static String substring(String s, int x, int y) {
  if (s == null) return null;
  if (x < 0) x = 0;
  int n = s.length();
  if (y < x) y = x;
  if (y > n) y = n;
  if (x >= y) return "";
  return s.substring(x, y);
}


static String substring(String s, IntRange r) {
  return r == null ? null : substring(s, r.start, r.end);
}


// convenience method for quickly dropping a prefix
static String substring(String s, CharSequence l) {
  return substring(s, l(l));
}
static boolean domainIsUnder(String domain, String mainDomain) {
  return eqic(domain, mainDomain) || ewic(domain, "." + mainDomain);
}
static String theAGIBlueDomain() {
  return "agi.blue";
}


static String unnull(String s) {
  return s == null ? "" : s;
}

static <A> Collection<A> unnull(Collection<A> l) {
  return l == null ? emptyList() : l;
}

static <A> List<A> unnull(List<A> l) { return l == null ? emptyList() : l; }
static int[] unnull(int[] l) { return l == null ? emptyIntArray() : l; }
static char[] unnull(char[] l) { return l == null ? emptyCharArray() : l; }
static double[] unnull(double[] l) { return l == null ? emptyDoubleArray() : l; }

static <A, B> Map<A, B> unnull(Map<A, B> l) {
  return l == null ? emptyMap() : l;
}

static <A> Iterable<A> unnull(Iterable<A> i) {
  return i == null ? emptyList() : i;
}

static <A> A[] unnull(A[] a) {
  return a == null ? (A[]) emptyObjectArray() : a;
}

static BitSet unnull(BitSet b) {
  return b == null ? new BitSet() : b;
}



//ifclass Symbol



static <A, B> Pair<A, B> unnull(Pair<A, B> p) {
  return p != null ? p : new Pair(null, null);
}


static long unnull(Long l) { return l == null ? 0L : l; }
static boolean nemptyString(String s) {
  return s != null && s.length() > 0;
}
static int strL(String s) {
  return s == null ? 0 : s.length();
}
static int listL(Collection l) {
  return l == null ? 0 : l.size();
}
static boolean neq(Object a, Object b) {
  return !eq(a, b);
}
static boolean eqic(String a, String b) {
  
  
    if ((a == null) != (b == null)) return false;
    if (a == null) return true;
    return a.equalsIgnoreCase(b);
  
}



static boolean eqic(char a, char b) {
  if (a == b) return true;
  
    char u1 = Character.toUpperCase(a);
    char u2 = Character.toUpperCase(b);
    if (u1 == u2) return true;
  
  return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}
static boolean ewic(String a, String b) {
  return endsWithIgnoreCase(a, b);
}


static boolean ewic(String a, String b, Matches m) {
  return endsWithIgnoreCase(a, b, m);
}



static int[] emptyIntArray_a = new int[0];
static int[] emptyIntArray() { return emptyIntArray_a; }
static char[] emptyCharArray = new char[0];
static char[] emptyCharArray() { return emptyCharArray; }
static double[] emptyDoubleArray = new double[0];
static double[] emptyDoubleArray() { return emptyDoubleArray; }
static Map emptyMap() {
  return new HashMap();
}
static Object[] emptyObjectArray_a = new Object[0];
static Object[] emptyObjectArray() { return emptyObjectArray_a; }
static String asString(Object o) {
  return o == null ? null : o.toString();
}
static boolean endsWithIgnoreCase(String a, String b) {
  int la = l(a), lb = l(b);
  return la >= lb && regionMatchesIC(a, la-lb, b, 0, lb);
}


static boolean endsWithIgnoreCase(String a, String b, Matches m) {
  if (!endsWithIgnoreCase(a, b)) return false;
  m.m = new String[] { substring(a, 0, l(a)-l(b)) };
  return true;
}



static boolean regionMatchesIC(String a, int offsetA, String b, int offsetB, int len) {
  
  
    return a != null && a.regionMatches(true, offsetA, b, offsetB, len);
  
}


// immutable, has strong refs
final static class _MethodCache {
  final Class c;
  final HashMap<String, List<Method>> cache = new HashMap();
  
  _MethodCache(Class c) {
  this.c = c; _init(); }
  
  void _init() {
    Class _c = c;
    while (_c != null) {
      for (Method m : _c.getDeclaredMethods())
        if (!isAbstract(m) && !reflection_isForbiddenMethod(m))
          multiMapPut(cache, m.getName(), makeAccessible(m));
      _c = _c.getSuperclass();
    }
    
    // add default methods - this might lead to a duplication
    // because the overridden method is also added, but it's not
    // a problem except for minimal performance loss.
    for (Class intf : allInterfacesImplementedBy(c))
      for (Method m : intf.getDeclaredMethods())
        if (m.isDefault() && !reflection_isForbiddenMethod(m))
          multiMapPut(cache, m.getName(), makeAccessible(m));

    
  }
  
  // Returns only matching methods
  Method findMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
    
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
  
  Method findStaticMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (isStaticMethod(me) && call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
}static abstract class VF1<A> implements IVF1<A> {
  public abstract void get(A a);
}static class Matches {
  String[] m;
  
  Matches() {}
  Matches(String... m) {
  this.m = m;}
  
  String get(int i) { return i < m.length ? m[i] : null; }
  String unq(int i) { return unquote(get(i)); }
  
  String tlc(int i) { return unq(i).toLowerCase(); }
  boolean bool(int i) { return "true".equals(unq(i)); }
  String rest() { return m[m.length-1]; } // for matchStart
  int psi(int i) { return Integer.parseInt(unq(i)); }
  
  public String toString() { return "Matches(" + joinWithComma(quoteAll(asList(m))) + ")"; }
  
  public int hashCode() { return _hashCode(toList(m)); }
  public boolean equals(Object o) { return o instanceof Matches && arraysEqual(m, ((Matches) o).m); }
}
static class PersistableThrowable {
  String className;
  String msg;
  String stacktrace;
  
  PersistableThrowable() {}
  PersistableThrowable(Throwable e) {
    if (e == null)
      className = "Crazy Null Error";
    else {
      className = getClassName(e).replace('/', '.');
      msg = e.getMessage();
      stacktrace = getStackTrace_noRecord(e);
    }
  }
  
  public String toString() {
    return nempty(msg) ? className + ": " + msg : className;
  }
}// not changed yet: allow cookie to be read right after being set


















static boolean NanoHTTPD_debug = false;

static abstract class NanoHTTPD implements AutoCloseable {

    boolean decodePercentInURI = false; // I set this to false, makes more sense to me
    boolean noQueryStringParameter = true;

    static ThreadLocal<IHTTPSession> currentSession = new ThreadLocal<IHTTPSession>();
    
    static List<IHTTPSession> badClients = synchroList();

    /**
     * Pluggable strategy for asynchronously executing requests.
     */
    public interface AsyncRunner {

        void closeAll();

        void closed(ClientHandler clientHandler);

        void exec(ClientHandler code);
    }

    /**
     * The runnable that will be used for every new client connection.
     */
    public class ClientHandler implements Runnable {

        private final InputStream inputStream;

        final Socket acceptSocket;

        private ClientHandler(InputStream inputStream, Socket acceptSocket) {
            this.inputStream = inputStream;
            this.acceptSocket = acceptSocket;
        }

        public void close() {
            safeClose(this.inputStream);
            safeClose(this.acceptSocket);
        }

        @Override
        public void run() {
            OutputStream outputStream = null;
            try {
                //outputStream = wrapStuff("SocketOutputStream", this.acceptSocket.getOutputStream(), this.acceptSocket);
                outputStream = this.acceptSocket.getOutputStream();
                TempFileManager tempFileManager = NanoHTTPD.this.tempFileManagerFactory.create();
                HTTPSession session = new HTTPSession(tempFileManager, this.inputStream, outputStream, this.acceptSocket.getInetAddress());
                if (NanoHTTPD_debug) print("NanoHTTPD: Have session");
                while (!this.acceptSocket.isClosed() && !session.badClient) {
                    session.execute();
                }
            } catch (Exception e) {
                // When the socket is closed by the client,
                // we throw our own SocketException
                // to break the "keep alive" loop above. If
                // the exception was anything other
                // than the expected SocketException OR a
                // SocketTimeoutException, print the
                // stacktrace
                print("NanoHTTPD: Error " + e);

                if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage())) && !(e instanceof SocketTimeoutException)) {
                    NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);
                }
            } finally {
                safeClose(outputStream);
                safeClose(this.inputStream);
                safeClose(this.acceptSocket);
                NanoHTTPD.this.asyncRunner.closed(this);
                if (NanoHTTPD_debug) print("NanoHTTPD: finally branch done");
            }
        }
    }

    public static class Cookie {

        public static String getHTTPTime(int days) {
            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
            calendar.add(Calendar.DAY_OF_MONTH, days);
            return dateFormat.format(calendar.getTime());
        }

        final String n, v, e;
        String domain, path;

        public Cookie(String name, String value) {
            this(name, value, 30);
        }

        public Cookie(String name, String value, int numDays) {
            this.n = name;
            this.v = value;
            this.e = getHTTPTime(numDays);
        }

        public Cookie(String name, String value, String expires) {
            this.n = name;
            this.v = value;
            this.e = expires;
        }

        public String getHTTPHeader() {
          StringBuilder buf = new StringBuilder();
            buf.append(n).append("=").append(v).append("; expires=").append(e);
          if (nempty(domain)) buf.append("; Domain=").append(domain);
          if (nempty(path)) buf.append("; Path=").append(path);
          return buf.toString();
        }
    }

    /**
     * Provides rudimentary support for cookies. Doesn't support 'path',
     * 'secure' nor 'httpOnly'. Feel free to improve it and/or add unsupported
     * features.
     * 
     * @author LordFokas
     */
    public class CookieHandler implements Iterable<String> {

        private final HashMap<String, String> cookies = new HashMap<String, String>();

        private final ArrayList<Cookie> queue = new ArrayList<Cookie>();

        public CookieHandler(Map<String, String> httpHeaders) {
            String raw = httpHeaders.get("cookie");
            if (raw != null) {
                String[] tokens = raw.split(";");
                for (String token : tokens) {
                    String[] data = token.trim().split("=");
                    if (data.length == 2) {
                        this.cookies.put(data[0], data[1]);
                    }
                }
            }
        }

        /**
         * Set a cookie with an expiration date from a month ago, effectively
         * deleting it on the client side.
         * 
         * @param name
         *            The cookie name.
         */
        public void delete(String name) {
            set(name, "-delete-", -30);
        }

        @Override
        public Iterator<String> iterator() {
            return this.cookies.keySet().iterator();
        }

        /**
         * Read a cookie from the HTTP Headers.
         * 
         * @param name
         *            The cookie's name.
         * @return The cookie's value if it exists, null otherwise.
         */
        public String read(String name) {
            return this.cookies.get(name);
        }

        public void set(Cookie cookie) {
            this.queue.add(cookie);
            //cookies.put(cookie.n, cookie.v); // CHANGED
        }

        /**
         * Sets a cookie.
         * 
         * @param name
         *            The cookie's name.
         * @param value
         *            The cookie's value.
         * @param expires
         *            How many days until the cookie expires.
         */
        public Cookie set(String name, String value, int expires) {
            return addAndReturn(this.queue, new Cookie(name, value, Cookie.getHTTPTime(expires)));
        }

        /**
         * Internally used by the webserver to add all queued cookies into the
         * Response's HTTP Headers.
         * 
         * @param response
         *            The Response object to which headers the queued cookies
         *            will be added.
         */
        public void unloadQueue(Response response) {
            for (Cookie cookie : this.queue) {
                response.addHeader("Set-Cookie", cookie.getHTTPHeader());
            }
        }
    }

    /**
     * Default threading strategy for NanoHTTPD.
     * <p/>
     * <p>
     * By default, the server spawns a new Thread for every incoming request.
     * These are set to <i>daemon</i> status, and named according to the request
     * number. The name is useful when profiling the application.
     * </p>
     */
    public static class DefaultAsyncRunner implements AsyncRunner {

        private long requestCount;

        private final List<ClientHandler> running = Collections.synchronizedList(new ArrayList<NanoHTTPD.ClientHandler>());

        /**
         * @return a list with currently running clients.
         */
        public List<ClientHandler> getRunning() {
            return running;
        }

        @Override
        public void closeAll() {
            // copy of the list for concurrency
            for (ClientHandler clientHandler : new ArrayList<ClientHandler>(this.running)) {
                clientHandler.close();
            }
        }

        @Override
        public void closed(ClientHandler clientHandler) {
            this.running.remove(clientHandler);
        }

        @Override
        public void exec(ClientHandler clientHandler) {
            ++this.requestCount;
            Thread t = new Thread(clientHandler);
            //t.setDaemon(true);
            String clientIP = "?";
            try { clientIP = clientHandler.acceptSocket.getInetAddress().getHostAddress().toString(); } catch (Throwable __e) { _handleException(__e); }
            t.setName("NanoHttpd serving request #" + this.requestCount + " to " + clientIP);
            this.running.add(clientHandler);
            t.start();
        }
    }

    /**
     * Default strategy for creating and cleaning up temporary files.
     * <p/>
     * <p>
     * By default, files are created by <code>File.createTempFile()</code> in
     * the directory specified.
     * </p>
     */
    public static class DefaultTempFile implements TempFile {

        private final File file;

        private final OutputStream fstream;

        public DefaultTempFile(String tempdir) throws IOException {
            this.file = File.createTempFile("NanoHTTPD-", "", new File(tempdir));
            this.fstream = new FileOutputStream(this.file);
            System.err.println("Temp file created: " + file);
        }

        @Override
        public void delete() throws Exception {
            safeClose(this.fstream);
            System.err.println("Temp file deleted: " + file);
            if (!this.file.delete()) {
                throw new Exception("could not delete temporary file");
            }
        }

        @Override
        public String getName() {
            return this.file.getAbsolutePath();
        }

        @Override
        public OutputStream open() throws Exception {
            return this.fstream;
        }
    }

    /**
     * Default strategy for creating and cleaning up temporary files.
     * <p/>
     * <p>
     * This class stores its files in the standard location (that is, wherever
     * <code>java.io.tmpdir</code> points to). Files are added to an internal
     * list, and deleted when no longer needed (that is, when
     * <code>clear()</code> is invoked at the end of processing a request).
     * </p>
     */
    public static class DefaultTempFileManager implements TempFileManager {

        private final String tmpdir;

        private final List<TempFile> tempFiles;

        public DefaultTempFileManager() {
            this.tmpdir = tempDir().getPath(); // use JavaX, dude
              // System.getProperty("java.io.tmpdir");
            this.tempFiles = new ArrayList<TempFile>();
        }

        @Override
        public void clear() {
            for (TempFile file : this.tempFiles) {
                try {
                    file.delete();
                } catch (Exception ignored) {
                    NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored);
                }
            }
            this.tempFiles.clear();
        }

        @Override
        public TempFile createTempFile() throws Exception {
            DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);
            this.tempFiles.add(tempFile);
            return tempFile;
        }
    }

    /**
     * Default strategy for creating and cleaning up temporary files.
     */
    private class DefaultTempFileManagerFactory implements TempFileManagerFactory {

        @Override
        public TempFileManager create() {
            return new DefaultTempFileManager();
        }
    }

    private static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)";

    private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE);

    private static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)";

    private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE);

    private static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]";

    private static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX);

    class HTTPSession implements IHTTPSession {
    
        boolean badClient = false;
        
        long opened = sysNow();

        public static final int BUFSIZE = 8192;

        private final TempFileManager tempFileManager;

        private final OutputStream outputStream;

        private final PushbackInputStream inputStream;

        private int splitbyte;

        private int rlen;

        private String uri;

        private Method method;

        private Map<String, String> parms;

        private Map<String, String> headers;

        Map<String, String> files = new HashMap();
        
        private CookieHandler cookies;

        private String queryParameterString;

        private String remoteIp;

        private String protocolVersion;

        public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
            this.tempFileManager = tempFileManager;
            this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);
            this.outputStream = outputStream;
        }

        public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
            this.tempFileManager = tempFileManager;
            this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);
            this.outputStream = outputStream;
            this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString();
            this.headers = new LinkedHashMap<String, String>();
        }
        
        public void badClient(boolean b) { badClient = b; }

        /**
         * Decodes the sent headers and loads the data into Key/value pairs
         */
        private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers, Map<String, String> files) throws ResponseException {
            try {
                // Read the request line
                String inLine = in.readLine();
                if (inLine == null) {
                    return;
                }

                StringTokenizer st = new StringTokenizer(inLine);
                if (!st.hasMoreTokens()) {
                    throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
                }

                pre.put("method", st.nextToken());

                if (!st.hasMoreTokens()) {
                    throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
                }

                String uri = st.nextToken();

                // Decode parameters from the URI
                int qmi = uri.indexOf('?');
                if (qmi >= 0) {
                    String query = uri.substring(qmi + 1);
                    files.put("query", query);
                    decodeParms(query, parms);
                    uri = uri.substring(0, qmi);
                }
                
                if (decodePercentInURI)
                  uri = decodePercent(uri);

                // If there's another token, its protocol version,
                // followed by HTTP headers.
                // NOTE: this now forces header names lower case since they are
                // case insensitive and vary by client.
                if (st.hasMoreTokens()) {
                    protocolVersion = st.nextToken();
                } else {
                    protocolVersion = "HTTP/1.1";
                    NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange. Assuming HTTP/1.1.");
                }
                String line = in.readLine();
                while (line != null && line.trim().length() > 0) {
                    int p = line.indexOf(':');
                    if (p >= 0) {
                        headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());
                    }
                    line = in.readLine();
                }

                pre.put("uri", uri);
            } catch (IOException ioe) {
                throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
            }
        }

        /**
         * Decodes the Multipart Body data and put it into Key/Value pairs.
         */
        private void decodeMultipartFormData(String boundary, ByteBuffer fbuf, Map<String, String> parms, Map<String, String> files) throws ResponseException {
            try {
                int[] boundary_idxs = getBoundaryPositions(fbuf, boundary.getBytes());
                if (boundary_idxs.length < 2) {
                    throw new ResponseException(
                    Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings.");
                }

                final int MAX_HEADER_SIZE = 1024;
                byte[] part_header_buff = new byte[MAX_HEADER_SIZE];
                for (int bi = 0; bi < boundary_idxs.length - 1; bi++) {
                    fbuf.position(boundary_idxs[bi]);
                    int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE;
                    fbuf.get(part_header_buff, 0, len);
                    ByteArrayInputStream bais = new ByteArrayInputStream(part_header_buff, 0, len);
                    BufferedReader in = new BufferedReader(new InputStreamReader(bais, Charset.forName("US-ASCII")));

                    // First line is boundary string
                    String mpline = in.readLine();
                    if (!mpline.contains(boundary)) {
                        throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary.");
                    }

                    String part_name = null, file_name = null, content_type = null;
                    // Parse the reset of the header lines
                    mpline = in.readLine();
                    while (mpline != null && mpline.trim().length() > 0) {
                        Matcher matcher = CONTENT_DISPOSITION_PATTERN.matcher(mpline);
                        if (matcher.matches()) {
                            String attributeString = matcher.group(2);
                            matcher = CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString);
                            while (matcher.find()) {
                                String key = matcher.group(1);
                                if (key.equalsIgnoreCase("name")) {
                                    part_name = matcher.group(2);
                                } else if (key.equalsIgnoreCase("filename")) {
                                    file_name = matcher.group(2);
                                }
                            }
                        }
                        matcher = CONTENT_TYPE_PATTERN.matcher(mpline);
                        if (matcher.matches()) {
                            content_type = matcher.group(2).trim();
                        }
                        mpline = in.readLine();
                    }

                    // Read the part data
                    int part_header_len = len - (int) in.skip(MAX_HEADER_SIZE);
                    if (part_header_len >= len - 4) {
                        throw new ResponseException(Status.INTERNAL_ERROR, "Multipart header size exceeds MAX_HEADER_SIZE.");
                    }
                    int part_data_start = boundary_idxs[bi] + part_header_len;
                    int part_data_end = boundary_idxs[bi + 1] - 4;

                    fbuf.position(part_data_start);
                    if (content_type == null) {
                        // Read the part into a string
                        byte[] data_bytes = new byte[part_data_end - part_data_start];
                        fbuf.get(data_bytes);
                        parms.put(part_name, new String(data_bytes));
                    } else {
                        // Read it into a file
                        String path = saveTmpFile(fbuf, part_data_start, part_data_end - part_data_start);
                        if (!files.containsKey(part_name)) {
                            files.put(part_name, path);
                        } else {
                            int count = 2;
                            while (files.containsKey(part_name + count)) {
                                count++;
                            }
                            files.put(part_name + count, path);
                        }
                        parms.put(part_name, file_name);
                    }
                }
            } catch (ResponseException re) {
                throw re;
            } catch (Exception e) {
                throw new ResponseException(Status.INTERNAL_ERROR, e.toString());
            }
        }

        /**
         * Decodes parameters in percent-encoded URI-format ( e.g.
         * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given
         * Map. NOTE: this doesn't support multiple identical keys due to the
         * simplicity of Map.
         */
        private void decodeParms(String parms, Map<String, String> p) {
            if (parms == null) {
                this.queryParameterString = "";
                return;
            }

            this.queryParameterString = parms;
            StringTokenizer st = new StringTokenizer(parms, "&");
            while (st.hasMoreTokens()) {
                String e = st.nextToken();
                int sep = e.indexOf('=');
                if (sep >= 0) {
                    p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1)));
                } else {
                    p.put(decodePercent(e).trim(), "");
                }
            }
        }

        @Override
        public void execute() throws IOException {
            Response r = null;
            try {
                // Read the first 8192 bytes.
                // The full header should fit in here.
                // Apache's default header limit is 8KB.
                // Do NOT assume that a single read will get the entire header
                // at once!
                byte[] buf = new byte[HTTPSession.BUFSIZE];
                this.splitbyte = 0;
                this.rlen = 0;

                int read = -1;
                try {
                    read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);
                } catch (Exception e) {
                    safeClose(this.inputStream);
                    safeClose(this.outputStream);
                    throw new SocketException("NanoHttpd Shutdown");
                }
                if (read == -1) {
                    // socket was been closed
                    safeClose(this.inputStream);
                    safeClose(this.outputStream);
                    throw new SocketException("NanoHttpd Shutdown");
                }
                while (read > 0) {
                    this.rlen += read;
                    this.splitbyte = findHeaderEnd(buf, this.rlen);
                    if (this.splitbyte > 0)
                        break;             
                        
                    if (rlen >= buf.length) {
                      if (NanoHTTPD_debug)
                        printWithIndent("HEADER> ", new String(buf));
                      throw fail("Header too big (" + rlen + " bytes)");
                    }
                    read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);
                }

                if (this.splitbyte < this.rlen) {
                    this.inputStream.unread(buf, this.splitbyte, this.rlen - this.splitbyte);
                }

                this.parms = new HashMap<String, String>();
                if (null == this.headers) {
                    this.headers = new HashMap<String, String>();
                } else {
                    this.headers.clear();
                }

                if (null != this.remoteIp) {
                    this.headers.put("remote-addr", this.remoteIp);
                    this.headers.put("http-client-ip", this.remoteIp);
                }

                // Create a BufferedReader for parsing the header.
                BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen)));

                // Decode the header into parms and header java properties
                Map<String, String> pre = new LinkedHashMap<String, String>();
                decodeHeader(hin, pre, this.parms, this.headers, this.files);

                this.method = Method.lookup(pre.get("method"));
                if (this.method == null) {
                    throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
                }

                this.uri = pre.get("uri");

                this.cookies = new CookieHandler(this.headers);

                String connection = this.headers.get("connection");
                boolean keepAlive = protocolVersion.equals("HTTP/1.1") && (connection == null || !connection.matches("(?i).*close.*"));

                // Ok, now do the serve()
                r = serve(this);
                
                if (badClient) return;
                
                if (r == null) {
                    throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
                } else {
                    String acceptEncoding = this.headers.get("accept-encoding");
                    this.cookies.unloadQueue(r);
                    r.setRequestMethod(this.method);
                    r.setGzipEncoding(useGzipWhenAccepted(r) && acceptEncoding != null && acceptEncoding.contains("gzip"));
                    r.setKeepAlive(keepAlive);
                    r.send(this.outputStream);
                }
                if (!keepAlive || "close".equalsIgnoreCase(r.getHeader("connection"))) {
                    throw new SocketException("NanoHttpd Shutdown");
                }
            } catch (SocketException e) {
                // throw it out to close socket object (finalAccept)
                throw e;
            } catch (SocketTimeoutException ste) {
                // treat socket timeouts the same way we treat socket exceptions
                // i.e. close the stream & finalAccept object by throwing the
                // exception up the call stack.
                throw ste;
            } catch (IOException ioe) {
                Response resp = newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
                resp.send(this.outputStream);
                safeClose(this.outputStream);
            } catch (ResponseException re) {
                Response resp = newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
                resp.send(this.outputStream);
                safeClose(this.outputStream);
            } finally {
              if (badClient)
                badClients.add(this);
              else
                safeClose(r);
              this.tempFileManager.clear();
            }
        }

        /**
         * Find byte index separating header from body. It must be the last byte
         * of the first two sequential new lines.
         */
        private int findHeaderEnd(final byte[] buf, int rlen) {
            int splitbyte = 0;
            while (splitbyte + 3 < rlen) {
                if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
                    return splitbyte + 4;
                }
                splitbyte++;
            }
            return 0;
        }

        /**
         * Find the byte positions where multipart boundaries start. This reads
         * a large block at a time and uses a temporary buffer to optimize
         * (memory mapped) file access.
         */
        private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {
            int[] res = new int[0];
            if (b.remaining() < boundary.length) {
                return res;
            }

            int search_window_pos = 0;
            byte[] search_window = new byte[4 * 1024 + boundary.length];

            int first_fill = (b.remaining() < search_window.length) ? b.remaining() : search_window.length;
            b.get(search_window, 0, first_fill);
            int new_bytes = first_fill - boundary.length;

            do {
                // Search the search_window
                for (int j = 0; j < new_bytes; j++) {
                    for (int i = 0; i < boundary.length; i++) {
                        if (search_window[j + i] != boundary[i])
                            break;
                        if (i == boundary.length - 1) {
                            // Match found, add it to results
                            int[] new_res = new int[res.length + 1];
                            System.arraycopy(res, 0, new_res, 0, res.length);
                            new_res[res.length] = search_window_pos + j;
                            res = new_res;
                        }
                    }
                }
                search_window_pos += new_bytes;

                // Copy the end of the buffer to the start
                System.arraycopy(search_window, search_window.length - boundary.length, search_window, 0, boundary.length);

                // Refill search_window
                new_bytes = search_window.length - boundary.length;
                new_bytes = (b.remaining() < new_bytes) ? b.remaining() : new_bytes;
                b.get(search_window, boundary.length, new_bytes);
            } while (new_bytes > 0);
            return res;
        }

        @Override
        public CookieHandler getCookies() {
            return this.cookies;
        }

        @Override
        public final Map<String, String> getHeaders() {
            return this.headers;
        }

        @Override
        public final Map<String, String> getFiles() {
            return this.files;
        }

        @Override
        public final InputStream getInputStream() {
            return this.inputStream;
        }

        @Override
        public final Method getMethod() {
            return this.method;
        }

        @Override
        public final Map<String, String> getParms() {
            return this.parms;
        }

        @Override
        public String getQueryParameterString() {
            return this.queryParameterString;
        }

        private RandomAccessFile getTmpBucket() {
            try {
                TempFile tempFile = this.tempFileManager.createTempFile();
                return new RandomAccessFile(tempFile.getName(), "rw");
            } catch (Exception e) {
                throw new Error(e); // we won't recover, so throw an error
            }
        }

        @Override
        public final String getUri() {
            return this.uri;
        }

        @Override
        public void parseBody() throws IOException, ResponseException {
            final int REQUEST_BUFFER_LEN = 512;
            final int MEMORY_STORE_LIMIT = 1024;
            RandomAccessFile randomAccessFile = null;
            try {
                long size;
                if (this.headers.containsKey("content-length")) {
                    size = Integer.parseInt(this.headers.get("content-length"));
                } else if (this.splitbyte < this.rlen) {
                    size = this.rlen - this.splitbyte;
                } else {
                    size = 0;
                }

                ByteArrayOutputStream baos = null;
                DataOutput request_data_output = null;

                // Store the request in memory or a file, depending on size
                if (size < MEMORY_STORE_LIMIT) {
                    baos = new ByteArrayOutputStream();
                    request_data_output = new DataOutputStream(baos);
                } else {
                    randomAccessFile = getTmpBucket();
                    request_data_output = randomAccessFile;
                }

                // Read all the body and write it to request_data_output
                byte[] buf = new byte[REQUEST_BUFFER_LEN];
                while (this.rlen >= 0 && size > 0) {
                    this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, REQUEST_BUFFER_LEN));
                    size -= this.rlen;
                    if (this.rlen > 0) {
                        request_data_output.write(buf, 0, this.rlen);
                    }
                }

                ByteBuffer fbuf = null;
                if (baos != null) {
                    fbuf = ByteBuffer.wrap(baos.toByteArray(), 0, baos.size());
                } else {
                    fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length());
                    randomAccessFile.seek(0);
                }

                // If the method is POST, there may be parameters
                // in data section, too, read it:
                if (Method.POST.equals(this.method)) {
                    String contentType = "";
                    String contentTypeHeader = this.headers.get("content-type");

                    StringTokenizer st = null;
                    if (contentTypeHeader != null) {
                        st = new StringTokenizer(contentTypeHeader, ",; ");
                        if (st.hasMoreTokens()) {
                            contentType = st.nextToken();
                        }
                    }

                    if ("multipart/form-data".equalsIgnoreCase(contentType)) {
                        // Handle multipart/form-data
                        if (!st.hasMoreTokens()) {
                            throw new ResponseException(Status.BAD_REQUEST,
                                    "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
                        }

                        String boundaryStartString = "boundary=";
                        int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length();
                        String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());
                        if (boundary.startsWith("\"") && boundary.endsWith("\"")) {
                            boundary = boundary.substring(1, boundary.length() - 1);
                        }

                        decodeMultipartFormData(boundary, fbuf, this.parms, files);
                    } else {
                        byte[] postBytes = new byte[fbuf.remaining()];
                        if (NanoHTTPD_debug)
                          print("NanoHTTPD: Handling POST data (" + l(postBytes) + " bytes)");
                        fbuf.get(postBytes);
                        String postLine = new String(postBytes).trim();
                        // Handle application/x-www-form-urlencoded
                        if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
                            decodeParms(postLine, this.parms);
                        } else if (postLine.length() != 0) {
                            // Special case for raw POST data => create a
                            // special files entry "postData" with raw content
                            // data
                            files.put("postData", postLine);
                        }
                    }
                } else if (Method.PUT.equals(this.method)) {
                    files.put("content", saveTmpFile(fbuf, 0, fbuf.limit()));
                }
            } finally {
                safeClose(randomAccessFile);
            }
        }

        /**
         * Retrieves the content of a sent file and saves it to a temporary
         * file. The full path to the saved file is returned.
         */
        private String saveTmpFile(ByteBuffer b, int offset, int len) {
            String path = "";
            if (len > 0) {
                FileOutputStream fileOutputStream = null;
                try {
                    TempFile tempFile = this.tempFileManager.createTempFile();
                    ByteBuffer src = b.duplicate();
                    fileOutputStream = new FileOutputStream(tempFile.getName());
                    FileChannel dest = fileOutputStream.getChannel();
                    src.position(offset).limit(offset + len);
                    dest.write(src.slice());
                    path = tempFile.getName();
                } catch (Exception e) { // Catch exception if any
                    throw new Error(e); // we won't recover, so throw an error
                } finally {
                    safeClose(fileOutputStream);
                }
            }
            return path;
        }
        
        public String remoteIp() { return remoteIp; }
    } // end of HTTPSession

    /**
     * Handles one session, i.e. parses the HTTP request and returns the
     * response.
     */
    public interface IHTTPSession {
    
        void badClient(boolean b);

        void execute() throws IOException;

        CookieHandler getCookies();

        Map<String, String> getHeaders();
        
        Map<String, String> getFiles();

        InputStream getInputStream();

        Method getMethod();

        Map<String, String> getParms();

        String getQueryParameterString();

        /**
         * @return the path part of the URL.
         */
        String getUri();

        /**
         * Adds the files in the request body to the files map.
         * 
         * @param files
         *            map to modify
         */
        void parseBody() throws IOException, ResponseException;
        
        String remoteIp();
    }

    /**
     * HTTP Request methods, with the ability to decode a <code>String</code>
     * back to its enum value.
     */
    public enum Method {
        GET,
        PUT,
        POST,
        DELETE,
        HEAD,
        OPTIONS,
        TRACE,
        CONNECT,
        PATCH;

        static Method lookup(String method) {
            for (Method m : Method.values()) {
                if (m.toString().equalsIgnoreCase(method)) {
                    return m;
                }
            }
            return null;
        }
    }

    /**
     * HTTP response. Return one of these from serve().
     */
    public static class Response implements Closeable {


        /**
         * Output stream that will automatically send every write to the wrapped
         * OutputStream according to chunked transfer:
         * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
         */
        private static class ChunkedOutputStream extends FilterOutputStream {

            public ChunkedOutputStream(OutputStream out) {
                super(out);
            }

            @Override
            public void write(int b) throws IOException {
                byte[] data = {
                    (byte) b
                };
                write(data, 0, 1);
            }

            @Override
            public void write(byte[] b) throws IOException {
                write(b, 0, b.length);
            }

            @Override
            public void write(byte[] b, int off, int len) throws IOException {
                if (len == 0)
                    return;
                out.write(String.format("%x\r\n", len).getBytes());
                out.write(b, off, len);
                out.write("\r\n".getBytes());
            }

            public void finish() throws IOException {
                out.write("0\r\n\r\n".getBytes());
            }

        }

        /**
         * HTTP status code after processing, e.g. "200 OK", Status.OK
         */
        private IStatus status;

        /**
         * MIME type of content, e.g. "text/html"
         */
        private String mimeType;

        /**
         * Data of the response, may be null.
         */
        private InputStream data;

        private long contentLength;

        /**
         * Headers for the HTTP response. Use addHeader() to add lines.
         */
        //private final Map<String, String> header = new HashMap<String, String>();
        final Map<String, String> header = lithashmap("X-Powered-By", "JavaX");

        /**
         * The request method that spawned this response.
         */
        private Method requestMethod;

        /**
         * Use chunkedTransfer
         */
        private boolean chunkedTransfer = false;

        private boolean encodeAsGzip = false;

        private boolean keepAlive = false;

        /**
         * Creates a fixed length response if totalBytes>=0, otherwise chunked.
         */
        protected Response(IStatus status, String mimeType, InputStream data, long totalBytes) {
            this.status = status;
            this.mimeType = mimeType;
            if (data == null) {
                this.data = new ByteArrayInputStream(new byte[0]);
                this.contentLength = 0L;
            } else {
                this.data = data;
                this.contentLength = totalBytes;
            }
            this.chunkedTransfer = this.contentLength < 0;
            keepAlive = true;
        }

        @Override
        public void close() throws IOException {
            if (this.data != null) {
                this.data.close();
            }
        }

        /**
         * Adds given line to the header.
         */
        public void addHeader(String name, String value) {
            this.header.put(name, value);
        }

        public InputStream getData() {
            return this.data;
        }

        public String getHeader(String name) {
            for (String headerName : header.keySet()) {
                if (headerName.equalsIgnoreCase(name)) {
                    return header.get(headerName);
                }
            }
            return null;
        }

        public String getMimeType() {
            return this.mimeType;
        }

        public Method getRequestMethod() {
            return this.requestMethod;
        }

        public IStatus getStatus() {
            return this.status;
        }

        public void setGzipEncoding(boolean encodeAsGzip) {
            this.encodeAsGzip = encodeAsGzip;
        }

        public void setKeepAlive(boolean useKeepAlive) {
            this.keepAlive = useKeepAlive;
        }

        private boolean headerAlreadySent(Map<String, String> header, String name) {
            boolean alreadySent = false;
            for (String headerName : header.keySet()) {
                alreadySent |= headerName.equalsIgnoreCase(name);
            }
            return alreadySent;
        }

        /**
         * Sends given response to the socket.
         */
        protected void send(OutputStream outputStream) {
            String mime = this.mimeType;
            SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
            gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));

            try {
                if (this.status == null) {
                    throw new Error("sendResponse(): Status can't be null.");
                }
                PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8")), false);
                pw.print("HTTP/1.1 " + this.status.getDescription() + " \r\n");

                if (mime != null) {
                    pw.print("Content-Type: " + mime + "\r\n");
                }

                if (this.header == null || this.header.get("Date") == null) {
                    pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
                }

                if (this.header != null) {
                    for (String key : this.header.keySet()) {
                        String value = this.header.get(key);
                        pw.print(key + ": " + value + "\r\n");
                    }
                }

                if (!headerAlreadySent(header, "connection")) {
                    pw.print("Connection: " + (this.keepAlive ? "keep-alive" : "close") + "\r\n");
                }

                if (headerAlreadySent(this.header, "content-length")) {
                    encodeAsGzip = false;
                }

                if (encodeAsGzip) {
                    pw.print("Content-Encoding: gzip\r\n");
                    setChunkedTransfer(true);
                }

                long pending = this.data != null ? this.contentLength : 0;
                if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
                    pw.print("Transfer-Encoding: chunked\r\n");
                } else if (!encodeAsGzip) {
                    pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, this.header, pending);
                }
                pw.print("\r\n");
                pw.flush();
                sendBodyWithCorrectTransferAndEncoding(outputStream, pending);
                outputStream.flush();
                safeClose(this.data);
            } catch (IOException ioe) {
                NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);
            }
        }

        private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException {
            if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
                ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream);
                sendBodyWithCorrectEncoding(chunkedOutputStream, -1);
                chunkedOutputStream.finish();
            } else {
                sendBodyWithCorrectEncoding(outputStream, pending);
            }
        }

        private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {
            if (encodeAsGzip) {
                GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
                sendBody(gzipOutputStream, -1);
                gzipOutputStream.finish();
            } else {
                sendBody(outputStream, pending);
            }
        }

        /**
         * Sends the body to the specified OutputStream. The pending parameter
         * limits the maximum amounts of bytes sent unless it is -1, in which
         * case everything is sent.
         * 
         * @param outputStream
         *            the OutputStream to send data to
         * @param pending
         *            -1 to send everything, otherwise sets a max limit to the
         *            number of bytes sent
         * @throws IOException
         *             if something goes wrong while sending the data.
         */
        private void sendBody(OutputStream outputStream, long pending) throws IOException {
            long BUFFER_SIZE = 16 * 1024;
            byte[] buff = new byte[(int) BUFFER_SIZE];
            boolean sendEverything = pending == -1;
            while (pending > 0 || sendEverything) {
                long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE);
                int read = this.data.read(buff, 0, (int) bytesToRead);
                if (read <= 0) {
                    break;
                }
                outputStream.write(buff, 0, read);
                if (!sendEverything) {
                    pending -= read;
                }
            }
        }

        protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header, long size) {
            for (String headerName : header.keySet()) {
                if (headerName.equalsIgnoreCase("content-length")) {
                    try {
                        return Long.parseLong(header.get(headerName));
                    } catch (NumberFormatException ex) {
                        return size;
                    }
                }
            }

            pw.print("Content-Length: " + size + "\r\n");
            return size;
        }

        public void setChunkedTransfer(boolean chunkedTransfer) {
            this.chunkedTransfer = chunkedTransfer;
        }

        public void setData(InputStream data) {
            this.data = data;
        }

        public void setMimeType(String mimeType) {
            this.mimeType = mimeType;
        }

        public void setRequestMethod(Method requestMethod) {
            this.requestMethod = requestMethod;
        }

        public void setStatus(IStatus status) {
            this.status = status;
        }
    }

    public static final class ResponseException extends Exception {

        private static final long serialVersionUID = 6569838532917408380L;

        private final Status status;

        public ResponseException(Status status, String message) {
            super(message);
            this.status = status;
        }

        public ResponseException(Status status, String message, Exception e) {
            super(message, e);
            this.status = status;
        }

        public Status getStatus() {
            return this.status;
        }
    }

    /**
     * The runnable that will be used for the main listening thread.
     */
    public class ServerRunnable implements Runnable {

        private final int timeout;

        private IOException bindException;

        private boolean hasBinded = false;

        private ServerRunnable(int timeout) {
            this.timeout = timeout;
        }

        @Override
        public void run() {
            try {
                myServerSocket.bind(hostname != null ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
                hasBinded = true;
            } catch (IOException e) {
                print("Was trying to bind to port: " + myPort + (hostname == null ? "" : " on " + hostname));
                this.bindException = e;
                return;
            }
            do {
                try {
                    final Socket finalAccept = NanoHTTPD.this.myServerSocket.accept();
                    if (NanoHTTPD_debug) print("NanoHTTPD: New socket.");
                    if (this.timeout > 0) {
                        finalAccept.setSoTimeout(this.timeout);
                    }
                    final InputStream inputStream = wrapStuff("SocketInputStream", finalAccept.getInputStream(), finalAccept, NanoHTTPD.this.myServerSocket);
                    if (NanoHTTPD_debug) print("NanoHTTPD: Have input stream.");
                    NanoHTTPD.this.asyncRunner.exec(createClientHandler(finalAccept, inputStream));
                } catch (IOException e) {
                    NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);
                }
            } while (!NanoHTTPD.this.myServerSocket.isClosed());
        }
    }

    /**
     * A temp file.
     * <p/>
     * <p>
     * Temp files are responsible for managing the actual temporary storage and
     * cleaning themselves up when no longer needed.
     * </p>
     */
    public interface TempFile {

        void delete() throws Exception;

        String getName();

        OutputStream open() throws Exception;
    }

    /**
     * Temp file manager.
     * <p/>
     * <p>
     * Temp file managers are created 1-to-1 with incoming requests, to create
     * and cleanup temporary files created as a result of handling the request.
     * </p>
     */
    public interface TempFileManager {

        void clear();

        TempFile createTempFile() throws Exception;
    }

    /**
     * Factory to create temp file managers.
     */
    public interface TempFileManagerFactory {

        TempFileManager create();
    }

    /**
     * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
     * This is required as the Keep-Alive HTTP connections would otherwise block
     * the socket reading thread forever (or as long the browser is open).
     */
    public static int SOCKET_READ_TIMEOUT =
      // 5000;
      24*3600*1000; // for WebSockets!

    /**
     * Common MIME type for dynamic content: plain text
     */
    public static final String MIME_PLAINTEXT = "text/plain; charset=utf-8";

    /**
     * Common MIME type for dynamic content: html
     */
    public static final String MIME_HTML = "text/html; charset=utf-8";

    /**
     * Pseudo-Parameter to use to store the actual query string in the
     * parameters map for later re-processing.
     */
    private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";

    /**
     * logger to log to.
     */
    private static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName());

    /**
     * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an
     * array of loaded KeyManagers. These objects must properly
     * loaded/initialized by the caller.
     */
    public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException {
        SSLServerSocketFactory res = null;
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(loadedKeyStore);
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null);
            res = ctx.getServerSocketFactory();
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }
        return res;
    }

    /**
     * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a
     * loaded KeyManagerFactory. These objects must properly loaded/initialized
     * by the caller.
     */
    public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException {
        SSLServerSocketFactory res = null;
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(loadedKeyStore);
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
            res = ctx.getServerSocketFactory();
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }
        return res;
    }

    /**
     * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your
     * certificate and passphrase
     */
    public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException {
        SSLServerSocketFactory res = null;
        try {
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);
            keystore.load(keystoreStream, passphrase);
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keystore);
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keystore, passphrase);
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
            res = ctx.getServerSocketFactory();
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }
        return res;
    }

    private static final void safeClose(Object closeable) {
        try {
            if (closeable != null) {
                if (closeable instanceof Closeable) {
                    ((Closeable) closeable).close();
                } else if (closeable instanceof Socket) {
                    ((Socket) closeable).close();
                } else if (closeable instanceof ServerSocket) {
                    ((ServerSocket) closeable).close();
                } else {
                    throw new IllegalArgumentException("Unknown object to close");
                }
            }
        } catch (IOException e) {
            NanoHTTPD.LOG.log(Level.SEVERE, "Could not close", e);
        }
    }

    private final String hostname;

    private final int myPort;

    private ServerSocket myServerSocket;

    private SSLServerSocketFactory sslServerSocketFactory;

    private Thread myThread;

    /**
     * Pluggable strategy for asynchronously executing requests.
     */
    protected AsyncRunner asyncRunner;

    /**
     * Pluggable strategy for creating and cleaning up temporary files.
     */
    private TempFileManagerFactory tempFileManagerFactory;

    /**
     * Constructs an HTTP server on given port.
     */
    public NanoHTTPD(int port) {
        this(null, port);
    }

    // -------------------------------------------------------------------------------
    // //
    //
    // Threading Strategy.
    //
    // -------------------------------------------------------------------------------
    // //

    /**
     * Constructs an HTTP server on given hostname and port.
     */
    public NanoHTTPD(String hostname, int port) {
        this.hostname = hostname;
        this.myPort = port;
        setTempFileManagerFactory(new DefaultTempFileManagerFactory());
        setAsyncRunner(new DefaultAsyncRunner());
    }

    /**
     * Forcibly closes all connections that are open.
     */
    public synchronized void closeAllConnections() {
        stop();
    }

    /**
     * create a instance of the client handler, subclasses can return a subclass
     * of the ClientHandler.
     * 
     * @param finalAccept
     *            the socket the cleint is connected to
     * @param inputStream
     *            the input stream
     * @return the client handler
     */
    protected ClientHandler createClientHandler(final Socket finalAccept, final InputStream inputStream) {
        return new ClientHandler(inputStream, finalAccept);
    }

    /**
     * Instantiate the server runnable, can be overwritten by subclasses to
     * provide a subclass of the ServerRunnable.
     * 
     * @param timeout
     *            the socet timeout to use.
     * @return the server runnable.
     */
    protected ServerRunnable createServerRunnable(final int timeout) {
        return new ServerRunnable(timeout);
    }

    /**
     * Decode parameters from a URL, handing the case where a single parameter
     * name might have been supplied several times, by return lists of values.
     * In general these lists will contain a single element.
     * 
     * @param parms
     *            original <b>NanoHTTPD</b> parameters values, as passed to the
     *            <code>serve()</code> method.
     * @return a map of <code>String</code> (parameter name) to
     *         <code>List&lt;String&gt;</code> (a list of the values supplied).
     */
    protected Map<String, List<String>> decodeParameters(Map<String, String> parms) {
        return this.decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER));
    }

    // -------------------------------------------------------------------------------
    // //

    /**
     * Decode parameters from a URL, handing the case where a single parameter
     * name might have been supplied several times, by return lists of values.
     * In general these lists will contain a single element.
     * 
     * @param queryString
     *            a query string pulled from the URL.
     * @return a map of <code>String</code> (parameter name) to
     *         <code>List&lt;String&gt;</code> (a list of the values supplied).
     */
    protected Map<String, List<String>> decodeParameters(String queryString) {
        Map<String, List<String>> parms = new HashMap<String, List<String>>();
        if (queryString != null) {
            StringTokenizer st = new StringTokenizer(queryString, "&");
            while (st.hasMoreTokens()) {
                String e = st.nextToken();
                int sep = e.indexOf('=');
                String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();
                if (!parms.containsKey(propertyName)) {
                    parms.put(propertyName, new ArrayList<String>());
                }
                String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null;
                // XXX Stefan - allow raw parameters - took the if out:
                /* if (propertyValue != null) */ {
                    parms.get(propertyName).add(propertyValue);
                }
            }
        }
        return parms;
    }

    /**
     * Decode percent encoded <code>String</code> values.
     * 
     * @param str
     *            the percent encoded <code>String</code>
     * @return expanded form of the input, for example "foo%20bar" becomes
     *         "foo bar"
     */
    protected String decodePercent(String str) {
        String decoded = null;
        try {
            decoded = URLDecoder.decode(str, "UTF8");
        } catch (UnsupportedEncodingException ignored) {
            NanoHTTPD.LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored);
        }
        return decoded;
    }

    /**
     * @return true if the gzip compression should be used if the client
     *         accespts it. Default this option is on for text content and off
     *         for everything else.
     */
    protected boolean useGzipWhenAccepted(Response r) {
        return r.getMimeType() != null && r.getMimeType().toLowerCase().contains("text/");
    }

    public final int getListeningPort() {
        return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort();
    }

    public final boolean isAlive() {
        return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive();
    }

    public void join() throws InterruptedException {
      myThread.join();
    }

    /**
     * Call before start() to serve over HTTPS instead of HTTP
     */
    public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) {
        this.sslServerSocketFactory = sslServerSocketFactory;
    }

    /**
     * Create a response with unknown length (using HTTP 1.1 chunking).
     */
    public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) {
        return new Response(status, mimeType, data, -1);
    }

    /**
     * Create a response with known length.
     */
    public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {
        return new Response(status, mimeType, data, totalBytes);
    }

    /**
     * Create a text response with known length.
     */
    public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {
        if (txt == null) {
            return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);
        } else {
            byte[] bytes;
            try {
                bytes = txt.getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e);
                bytes = new byte[0];
            }
            return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(bytes), bytes.length);
        }
    }

    /**
     * Create a text response with known length.
     */
    public static Response newFixedLengthResponse(String msg) {
        return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg);
    }

    /**
     * Override this to customize the server.
     * <p/>
     * <p/>
     * (By default, this returns a 404 "Not Found" plain text error response.)
     * 
     * @param session
     *            The HTTP session
     * @return HTTP response, see class Response for details
     */
    public Response serve(IHTTPSession session) {
        currentSession.set(session);
        Method method = session.getMethod();
        if (Method.PUT.equals(method) || Method.POST.equals(method)) {
            try {
                session.parseBody();
            } catch (IOException ioe) {
                return newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
            } catch (ResponseException re) {
                return newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
            }
        }

        Map<String, String> parms = session.getParms();
        if (!noQueryStringParameter)
          parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString());
        return serve(session.getUri(), method, session.getHeaders(), parms, session.getFiles());
    }

    /**
     * Override this to customize the server.
     * <p/>
     * <p/>
     * (By default, this returns a 404 "Not Found" plain text error response.)
     * 
     * @param uri
     *            Percent-decoded URI without parameters, for example
     *            "/index.cgi"
     * @param method
     *            "GET", "POST" etc.
     * @param parms
     *            Parsed, percent decoded parameters from URI and, in case of
     *            POST, data.
     * @param headers
     *            Header entries, percent decoded
     * @return HTTP response, see class Response for details
     */
    @Deprecated
    public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) {
        return newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");
    }

    /**
     * Pluggable strategy for asynchronously executing requests.
     * 
     * @param asyncRunner
     *            new strategy for handling threads.
     */
    public void setAsyncRunner(AsyncRunner asyncRunner) {
        this.asyncRunner = asyncRunner;
    }

    /**
     * Pluggable strategy for creating and cleaning up temporary files.
     * 
     * @param tempFileManagerFactory
     *            new strategy for handling temp files.
     */
    public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {
        this.tempFileManagerFactory = tempFileManagerFactory;
    }

    /**
     * Start the server.
     * 
     * @throws IOException
     *             if the socket is in use.
     */
    public void start() throws IOException {
        start(NanoHTTPD.SOCKET_READ_TIMEOUT);
    }

    /**
     * Start the server.
     * 
     * @param timeout
     *            timeout to use for socket connections.
     * @throws IOException
     *             if the socket is in use.
     */
    public void start(final int timeout) throws IOException {
        boolean ssl = this.sslServerSocketFactory != null;
        if (ssl) {
            SSLServerSocket ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();
            ss.setNeedClientAuth(false);
            this.myServerSocket = ss;
        } else {
            this.myServerSocket = new ServerSocket();
        }
        this.myServerSocket.setReuseAddress(true);

        ServerRunnable serverRunnable = createServerRunnable(timeout);
        this.myThread = new Thread(serverRunnable);
        //this.myThread.setDaemon(true);
        this.myThread.setName("NanoHttpd Main Listener");
        this.myThread.start();
        while (!serverRunnable.hasBinded && serverRunnable.bindException == null) {
            try {
                Thread.sleep(10L);
            } catch (Throwable e) {
                // on android this may not be allowed, that's why we
                // catch throwable the wait should be very short because we are
                // just waiting for the bind of the socket
            }
        }
        if (serverRunnable.bindException != null) {
            throw serverRunnable.bindException;
        }
        
      System.out.println("HTTP" + (ssl ? "S" : "") + " server started (listening on port " + getListeningPort() + "!)");
      printMyIPs();
    }

    /**
     * Stop the server.
     */
    public void stop() {
        try {
            safeClose(this.myServerSocket);
            this.asyncRunner.closeAll();
            if (this.myThread != null) {
                this.myThread.join();
            }
        } catch (Exception e) {
            NanoHTTPD.LOG.log(Level.SEVERE, "Could not stop all connections", e);
        }
    }

    public final boolean wasStarted() {
        return this.myServerSocket != null && this.myThread != null;
    }

  
static void printMyIPs() {
String ip;
  try {
      Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
      while (interfaces.hasMoreElements()) {
          NetworkInterface iface = interfaces.nextElement();
          // filters out 127.0.0.1 and inactive interfaces
          if (iface.isLoopback() || !iface.isUp())
              continue;

          Enumeration<InetAddress> addresses = iface.getInetAddresses();
          while(addresses.hasMoreElements()) {
              InetAddress addr = addresses.nextElement();
              ip = addr.getHostAddress();
if (ip.startsWith("127.")) continue;
boolean local = addr.isSiteLocalAddress() || ip.startsWith("fe");
              System.out.println(iface.getDisplayName() + " " + ip + " " + (local ? "(private address)" : "(public address)"));
          }
      }
  } catch (Throwable e) {
      e.printStackTrace();
  }
}
static interface IStatus {
    String getDescription();
    int getRequestStatus();
  }
  
  /**
   * Some HTTP response status codes
   */
  static enum Status implements IStatus {
      SWITCH_PROTOCOL(101, "Switching Protocols"),
      OK(200, "OK"),
      CREATED(201, "Created"),
      ACCEPTED(202, "Accepted"),
      NO_CONTENT(204, "No Content"),
      PARTIAL_CONTENT(206, "Partial Content"),
      REDIRECT(301, "Moved Permanently"),
      NOT_MODIFIED(304, "Not Modified"),
      BAD_REQUEST(400, "Bad Request"),
      UNAUTHORIZED(401, "Unauthorized"),
      FORBIDDEN(403, "Forbidden"),
      NOT_FOUND(404, "Not Found"),
      METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
      REQUEST_TIMEOUT(408, "Request Timeout"),
      RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
      INTERNAL_ERROR(500, "Internal Server Error"),
      UNSUPPORTED_HTTP_VERSION(505, "HTTP Version Not Supported");
  
      private final int requestStatus;
  
      private final String description;
  
      Status(int requestStatus, String description) {
          this.requestStatus = requestStatus;
          this.description = description;
      }
  
      @Override
      public String getDescription() {
          return "" + this.requestStatus + " " + this.description;
      }
  
      @Override
      public int getRequestStatus() {
          return this.requestStatus;
      }
  }
  
  int getPort() { return myPort; }
  
  public void close() { stop(); }
}
static abstract class F1<A, B> {
  abstract B get(A a);
}static class Pair<A, B> implements Comparable<Pair<A, B>> {
  A a;
  B b;

  Pair() {}
  Pair(A a, B b) {
  this.b = b;
  this.a = a;}
  
  public int hashCode() {
    return hashCodeFor(a) + 2*hashCodeFor(b);
  }
  
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Pair)) return false;
    Pair t = (Pair) o;
    return eq(a, t.a) && eq(b, t.b);
  }
  
  public String toString() {
    return "<" + a + ", " + b + ">";
  }
  
  public int compareTo(Pair<A, B> p) {
    if (p == null) return 1;
    int i = ((Comparable<A>) a).compareTo(p.a);
    if (i != 0) return i;
    return ((Comparable<B>) b).compareTo(p.b);
  }
}

static interface IVF1<A> {
  void get(A a);
}

static boolean isAbstract(Class c) {
  return (c.getModifiers() & Modifier.ABSTRACT) != 0;
}

static boolean isAbstract(Method m) {
  return (m.getModifiers() & Modifier.ABSTRACT) != 0;
}
static boolean reflection_isForbiddenMethod(Method m) {
  return m.getDeclaringClass() == Object.class
    && eqOneOf(m.getName(), "finalize", "clone", "registerNatives");
}
static Set<Class> allInterfacesImplementedBy(Class c) {
  if (c == null) return null;
  HashSet<Class> set = new HashSet();
  allInterfacesImplementedBy_find(c, set);
  return set;
}

static void allInterfacesImplementedBy_find(Class c, Set<Class> set) {
  if (c.isInterface() && !set.add(c)) return;
  do {
    for (Class intf : c.getInterfaces())
      allInterfacesImplementedBy_find(intf, set);
  } while ((c = c.getSuperclass()) != null);
}
static Method findMethod(Object o, String method, Object... args) {
  return findMethod_cached(o, method, args);
}

static boolean findMethod_checkArgs(Method m, Object[] args, boolean debug) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length) {
    if (debug)
      System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++)
    if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
      if (debug)
        System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
      return false;
    }
  return true;
}
static Method findStaticMethod(Class c, String method, Object... args) {
  Class _c = c;
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (!m.getName().equals(method))
        continue;

      if ((m.getModifiers() & Modifier.STATIC) == 0 || !findStaticMethod_checkArgs(m, args))
        continue;

      return m;
    }
    c = c.getSuperclass();
  }
  return null;
}

static boolean findStaticMethod_checkArgs(Method m, Object[] args) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length)
    return false;
  for (int i = 0; i < types.length; i++)
    if (!(args[i] == null || isInstanceX(types[i], args[i])))
      return false;
  return true;
}

static String unquote(String s) {
  if (s == null) return null;
  if (startsWith(s, '[')) {
    int i = 1;
    while (i < s.length() && s.charAt(i) == '=') ++i;
    if (i < s.length() && s.charAt(i) == '[') {
      String m = s.substring(1, i);
      if (s.endsWith("]" + m + "]"))
        return s.substring(i+1, s.length()-i-1);
    }
  }
  
  if (s.length() > 1) {
    char c = s.charAt(0);
    if (c == '\"' || c == '\'') {
      int l = endsWith(s, c) ? s.length()-1 : s.length();
      StringBuilder sb = new StringBuilder(l-1);
  
      for (int i = 1; i < l; i++) {
        char ch = s.charAt(i);
        if (ch == '\\') {
          char nextChar = (i == l - 1) ? '\\' : s.charAt(i + 1);
          // Octal escape?
          if (nextChar >= '0' && nextChar <= '7') {
              String code = "" + nextChar;
              i++;
              if ((i < l - 1) && s.charAt(i + 1) >= '0'
                      && s.charAt(i + 1) <= '7') {
                  code += s.charAt(i + 1);
                  i++;
                  if ((i < l - 1) && s.charAt(i + 1) >= '0'
                          && s.charAt(i + 1) <= '7') {
                      code += s.charAt(i + 1);
                      i++;
                  }
              }
              sb.append((char) Integer.parseInt(code, 8));
              continue;
          }
          switch (nextChar) {
          case '\"': ch = '\"'; break;
          case '\\': ch = '\\'; break;
          case 'b': ch = '\b'; break;
          case 'f': ch = '\f'; break;
          case 'n': ch = '\n'; break;
          case 'r': ch = '\r'; break;
          case 't': ch = '\t'; break;
          case '\'': ch = '\''; break;
          // Hex Unicode: u????
          case 'u':
              if (i >= l - 5) {
                  ch = 'u';
                  break;
              }
              int code = Integer.parseInt(
                      "" + s.charAt(i + 2) + s.charAt(i + 3)
                         + s.charAt(i + 4) + s.charAt(i + 5), 16);
              sb.append(Character.toChars(code));
              i += 5;
              continue;
          default:
            ch = nextChar; // added by Stefan
          }
          i++;
        }
        sb.append(ch);
      }
      return sb.toString();
    }
  }
    
  return s; // not quoted - return original
}
static List<String> quoteAll(Collection<String> l) {
  List<String> x = new ArrayList();
  for (String s : l)
    x.add(quote(s));
  return x;
}
static int _hashCode(Object a) {
  return a == null ? 0 : a.hashCode();
}
static <A> ArrayList<A> toList(A[] a) { return asList(a); }
static ArrayList<Integer> toList(int[] a) { return asList(a); }
static <A> ArrayList<A> toList(Set<A> s) { return asList(s); }
static <A> ArrayList<A> toList(Iterable<A> s) { return asList(s); }
static boolean arraysEqual(Object[] a, Object[] b) {
  if (a.length != b.length) return false;
  for (int i = 0; i < a.length; i++)
    if (neq(a[i], b[i])) return false;
  return true;
}
static boolean nempty(Collection c) {
  return !empty(c);
}

static boolean nempty(CharSequence s) {
  return !empty(s);
}

static boolean nempty(Object[] o) { return !empty(o); }
static boolean nempty(byte[] o) { return !empty(o); }
static boolean nempty(int[] o) { return !empty(o); }

static boolean nempty(Map m) {
  return !empty(m);
}

static boolean nempty(Iterator i) {
  return i != null && i.hasNext();
}

static boolean nempty(Object o) { return !empty(o); }


static boolean nempty(IntRange r) { return !empty(r); }





static <A> A set(A o, String field, Object value) {
  if (o == null) return null;
  if (o instanceof Class) set((Class) o, field, value);
  else try {
    Field f = set_findField(o.getClass(), field);
    makeAccessible(f);
    smartSet(f, o, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
  return o;
}

static void set(Class c, String field, Object value) {
  if (c == null) return;
  try {
    Field f = set_findStaticField(c, field);
    makeAccessible(f);
    smartSet(f, null, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
  
static Field set_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Static field '" + field + "' not found in " + c.getName());
}

static Field set_findField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Field '" + field + "' not found in " + c.getName());
}
static <A> Iterator<A> iterator(Iterable<A> c) {
  return c == null ? emptyIterator() : c.iterator();
}
static <B, A extends B> A addAndReturn(Collection<B> c, A a) {
  if (c != null) c.add(a);
  return a;
}
  static File tempDir() {
    return makeTempDir();
  }
static File createTempFile() {
  return createTempFile("tmp", null);
}

static File createTempFile(String prefix, String suffix) { try {
  prefix = nohup_sanitize(prefix);
  suffix = nohup_sanitize(suffix);
  if (shouldKeepTempFiles())
    return mkdirsForFile(javaxCachesDir("Temp/" + prefix + "-" + randomID() + "-" + suffix));
  // File.createTempFile needs at least 3 prefix characters, so we
  // fíll them up for you
  File f = File.createTempFile(takeFirst(10, pad(prefix, 3, '-')), suffix);
  f.deleteOnExit();
  return f;
} catch (Exception __e) { throw rethrow(__e); } }
static long sysNow() {
  ping();
  return System.nanoTime()/1000000;
}
static <A> A printWithIndent(A o) {
  return printIndent(o);
}



static <A> A printWithIndent(String indent, A o) {
  return printIndent(indent, o);
}



static void printWithIndent(int indent, Object o) {
  printIndent(indent, o);
}
static HashMap lithashmap(Object... x) {
  return litmap(x);
}
// name currently not used, we only call a method called "_wrap" in
// the main class with the to-wrapped object as first argument and
// a list of other parameters as second argument.
static <A> A wrapStuff(String name, A object, Object... args) {
  //ret or(object, (A) callOpt(mc(), "wrap" + name,
  //  toObjectArray(concatLists(litlist((O) object), toList(args)))));
  return or((A) callOpt(mc(), "_wrap", object, asList(args)), object);
}
static void printMyIPs() {
String ip;
  try {
      Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
      while (interfaces.hasMoreElements()) {
          NetworkInterface iface = interfaces.nextElement();
          // filters out 127.0.0.1 and inactive interfaces
          if (iface.isLoopback() || !iface.isUp())
              continue;

          Enumeration<InetAddress> addresses = iface.getInetAddresses();
          while(addresses.hasMoreElements()) {
              InetAddress addr = addresses.nextElement();
              ip = addr.getHostAddress();
if (ip.startsWith("127.")) continue;
boolean local = addr.isSiteLocalAddress() || ip.startsWith("fe");
              System.out.println(iface.getDisplayName() + " " + ip + " " + (local ? "(private address)" : "(public address)"));
          }
      }
  } catch (Throwable e) {
      e.printStackTrace();
  }
}
static int hashCodeFor(Object a) {
  return a == null ? 0 : a.hashCode();
}


static Method findMethod_cached(Object o, String method, Object... args) { try {
  if (o == null) return null;
  if (o instanceof Class) {
    _MethodCache cache = callOpt_getCache(((Class) o));
    List<Method> methods = cache.cache.get(method);
    if (methods != null) for (Method m : methods)
      if (isStaticMethod(m) && findMethod_checkArgs(m, args, false))
        return m;
    return null;
  } else {
    _MethodCache cache = callOpt_getCache(o.getClass());
    List<Method> methods = cache.cache.get(method);
    if (methods != null) for (Method m : methods)
      if (findMethod_checkArgs(m, args, false))
        return m;
    return null;
  }
} catch (Exception __e) { throw rethrow(__e); } }

static boolean endsWith(String a, String b) {
  return a != null && a.endsWith(b);
}

static boolean endsWith(String a, char c) {
  return nempty(a) && lastChar(a) == c;
}


  static boolean endsWith(String a, String b, Matches m) {
    if (!endsWith(a, b)) return false;
    m.m = new String[] {dropLast(l(b), a)};
    return true;
  }


static String quote(Object o) {
  if (o == null) return "null";
  return quote(str(o));
}

static String quote(String s) {
  if (s == null) return "null";
  StringBuilder out = new StringBuilder((int) (l(s)*1.5+2));
  quote_impl(s, out);
  return out.toString();
}
  
static void quote_impl(String s, StringBuilder out) {
  out.append('"');
  int l = s.length();
  for (int i = 0; i < l; i++) {
    char c = s.charAt(i);
    if (c == '\\' || c == '"')
      out.append('\\').append(c);
    else if (c == '\r')
      out.append("\\r");
    else if (c == '\n')
      out.append("\\n");
    else if (c == '\t')
      out.append("\\t");
    else if (c == '\0')
      out.append("\\0");
    else
      out.append(c);
  }
  out.append('"');
}
static void smartSet(Field f, Object o, Object value) throws Exception {
  try {
    f.set(o, value);
  } catch (Exception e) {
    Class type = f.getType();
    
    // take care of common case (long to int)
    if (type == int.class && value instanceof Long)
      value = ((Long) value).intValue();
    
    if (type == LinkedHashMap.class && value instanceof Map)
      { f.set(o, asLinkedHashMap((Map) value)); return; }
    
    
    throw e;
  }
}
static Iterator emptyIterator() {
  return Collections.emptyIterator();
}
  static File makeTempDir() {
    return (File) call(getJavaX(), "TempDirMaker_make");
  }
static String nohup_sanitize(String s) {
  return empty(s) ? s : takeFirst(50, s.replaceAll("[^.a-zA-Z0-9\\-_]", ""));
}

static  IF0<Boolean> shouldKeepTempFiles;
static boolean shouldKeepTempFiles() { return shouldKeepTempFiles != null ? shouldKeepTempFiles.get() : shouldKeepTempFiles_base(); }
final static boolean shouldKeepTempFiles_fallback(IF0<Boolean> _f) { return _f != null ? _f.get() : shouldKeepTempFiles_base(); }
static boolean shouldKeepTempFiles_base() {
  return false;
}
public static File mkdirsForFile(File file) {
  File dir = file.getParentFile();
  if (dir != null) { // is null if file is in current dir
    dir.mkdirs();
    if (!dir.isDirectory())
      if (dir.isFile()) throw fail("Please delete the file " + f2s(dir) + " - it is supposed to be a directory!");
      else throw fail("Unknown IO exception during mkdirs of " + f2s(file));
  }
  return file;
}

public static String mkdirsForFile(String path) {
  mkdirsForFile(new File(path));
  return path;
}
static File javaxCachesDir_dir; // can be set to work on different base dir

static File javaxCachesDir() {
  return javaxCachesDir_dir != null ? javaxCachesDir_dir : new File(userHome(), "JavaX-Caches");
}

static File javaxCachesDir(String sub) {
  return newFile(javaxCachesDir(), sub);
}
static int randomID_defaultLength = 12;

static String randomID(int length) {
  return makeRandomID(length);
}

static String randomID(Random r, int length) {
  return makeRandomID(r, length);
}

static String randomID() {
  return randomID(randomID_defaultLength);
}

static String randomID(Random r) {
  return randomID(r, randomID_defaultLength);
}
static <A> List<A> takeFirst(List<A> l, int n) {
  return l(l) <= n ? l : newSubListOrSame(l, 0, n);
}

static <A> List<A> takeFirst(int n, List<A> l) {
  return takeFirst(l, n);
}

static String takeFirst(int n, String s) { return substring(s, 0, n); }
static String takeFirst(String s, int n) { return substring(s, 0, n); }

static CharSequence takeFirst(int n, CharSequence s) { return subCharSequence(s, 0, n); }

static <A> List<A> takeFirst(int n, Iterable<A> i) {
  if (i == null) return null;
  List l = new ArrayList();
  Iterator<A> it = i.iterator();
  for (int _repeat_0 = 0; _repeat_0 < n; _repeat_0++)  { if (it.hasNext()) l.add(it.next()); else break; }
  return l;
}

static int[] takeFirst(int n, int[] a) {
  return takeFirstOfIntArray(n, a);
}
static String pad(Object s, int l) {
  return pad(s, l, ' ');
}

static String pad(Object s, int l, char c) {
  String _s = str(s);
  if (lengthOfString(_s) >= l) return _s;
  return rep(c, l-lengthOfString(_s)) + _s;
}
static <A> A printIndent(A o) {
  print(indentx(str(o)));
  return o;
}

static <A> A printIndent(String indent, A o) {
  print(indentx(indent, str(o)));
  return o;
}

static void printIndent(int indent, Object o) {
  print(indentx(indent, str(o)));
}
static HashMap litmap(Object... x) {
  HashMap map = new HashMap();
  litmap_impl(map, x);
  return map;
}

static void litmap_impl(Map map, Object... x) {
  if (x != null) for (int i = 0; i < x.length-1; i += 2)
    if (x[i+1] != null)
      map.put(x[i], x[i+1]);
}
static Object callOpt(Object o) {
  return callF(o);
}

static <A> A callOpt(Object o, String method, Object... args) {
  return (A) callOpt_withVarargs(o, method, args);
}


static char lastChar(String s) {
  return empty(s) ? '\0' : s.charAt(l(s)-1);
}
static <A> A[] dropLast(A[] a) { return dropLast(a, 1); }
static <A> A[] dropLast(A[] a, int n) {
  if (a == null) return null;
  n = Math.min(n, a.length);
  A[] b = arrayOfSameType(a, a.length-n);
  System.arraycopy(a, 0, b, 0, b.length);
  return b;
}

static <A> List<A> dropLast(List<A> l) {
  return subList(l, 0, l(l)-1);
}

static <A> List<A> dropLast(int n, List<A> l) {
  return subList(l, 0, l(l)-n);
}

static <A> List<A> dropLast(Iterable<A> l) {
  return dropLast(asList(l));
}

static String dropLast(String s) {
  return substring(s, 0, l(s)-1);
}

static String dropLast(String s, int n) {
  return substring(s, 0, l(s)-n);
}

static String dropLast(int n, String s) {
  return dropLast(s, n);
}

static <A, B> LinkedHashMap<A, B> asLinkedHashMap(Map<A, B> map) {
  if (map instanceof LinkedHashMap) return (LinkedHashMap) map;
  LinkedHashMap<A, B> m = new LinkedHashMap();
  if (map != null) synchronized(collectionMutex(map)) {
    m.putAll(map);
  }
  return m;
}
static String f2s(File f) {
  return f == null ? null : f.getAbsolutePath();
}

static String f2s(String s) { return f2s(newFile(s)); }


 static String f2s(java.nio.file.Path p) {
  return p == null ? null : f2s(p.toFile());
}

static String _userHome;
static String userHome() {
  if (_userHome == null)
    return actualUserHome();
  return _userHome;
}

static File userHome(String path) {
  return new File(userDir(), path);
}
static File newFile(File base, String... names) {
  for (String name : names) base = new File(base, name);
  return base;
}

static File newFile(String name) {
  return name == null ? null : new File(name);
}

static File newFile(String base, String... names) {
  return newFile(newFile(base), names);
}
static String makeRandomID(int length) {
  return makeRandomID(length, defaultRandomGenerator());
}

static String makeRandomID(int length, Random random) {
  char[] id = new char[length];
  for (int i = 0; i < id.length; i++)
    id[i] = (char) ((int) 'a' + random.nextInt(26));
  return new String(id);
}

static String makeRandomID(Random r, int length) {
  return makeRandomID(length, r);
}
static <A> List<A> newSubListOrSame(List<A> l, int startIndex) {
  return newSubListOrSame(l, startIndex, l(l));
}

static <A> List<A> newSubListOrSame(List<A> l, int startIndex, int endIndex) {
  if (l == null) return null;
  int n = l(l);
  startIndex = max(0, startIndex);
  endIndex = min(n, endIndex);
  if (startIndex >= endIndex) return ll();
  if (startIndex == 0 && endIndex == n) return l;
  return cloneList(l.subList(startIndex, endIndex));
}


static <A> List<A> newSubListOrSame(List<A> l, IntRange r) {
  return newSubListOrSame(l, r.start, r.end);
}

static CharSequence subCharSequence(CharSequence s, int x) {
  return subCharSequence(s, x, s == null ? 0 : s.length());
}

static CharSequence subCharSequence(CharSequence s, int x, int y) {
  if (s == null) return null;
  if (x < 0) x = 0;
  if (x >= s.length()) return "";
  if (y < x) y = x;
  if (y > s.length()) y = s.length();
  return s.subSequence(x, y);
}
static int[] takeFirstOfIntArray(int[] b, int n) {
  return subIntArray(b, 0, n);
}

static int[] takeFirstOfIntArray(int n, int[] b) {
  return takeFirstOfIntArray(b, n);
}
static int lengthOfString(String s) {
  return s == null ? 0 : s.length();
}
static String rep(int n, char c) {
  return repeat(c, n);
}

static String rep(char c, int n) {
  return repeat(c, n);
}

static <A> List<A> rep(A a, int n) {
  return repeat(a, n);
}

static <A> List<A> rep(int n, A a) {
  return repeat(n, a);
}

static String indentx(String s) {
  return indentx(indent_default, s);
}

static String indentx(int n, String s) {
  return dropSuffix(repeat(' ', n), indent(n, s));
}

static String indentx(String indent, String s) {
  return dropSuffix(indent, indent(indent, s));
}
static Object callOpt_withVarargs(Object o, String method, Object... args) { try {
  if (o == null) return null;
  
  if (o instanceof Class) {
    Class c = (Class) o;
    _MethodCache cache = callOpt_getCache(c);
    
    Method me = cache.findMethod(method, args);
    if (me == null) {
      // TODO: varargs
      return null;
    }
    if ((me.getModifiers() & Modifier.STATIC) == 0)
      return null;
    return invokeMethod(me, null, args);
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

    Method me = cache.findMethod(method, args);
    if (me != null)
      return invokeMethod(me, o, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(method);
    if (methods != null) methodSearch: for (Method m : methods) {
      { if (!(m.isVarArgs())) continue; }
      Object[] newArgs = massageArgsForVarArgsCall(m, args);
      if (newArgs != null)
        return invokeMethod(m, o, newArgs);
    }
    
    return null;
  }
} catch (Exception __e) { throw rethrow(__e); } }


static <A> A[] arrayOfSameType(A[] a, int n) {
  return newObjectArrayOfSameType(a, n);
}
static <A> List<A> subList(List<A> l, int startIndex) {
  return subList(l, startIndex, l(l));
}

static <A> List<A> subList(int startIndex, int endIndex, List<A> l) {
  return subList(l, startIndex, endIndex);
}

static <A> List<A> subList(List<A> l, int startIndex, int endIndex) {
  if (l == null) return null;
  int n = l(l);
  startIndex = Math.max(0, startIndex);
  endIndex = Math.min(n, endIndex);
  if (startIndex >= endIndex) return ll();
  if (startIndex == 0 && endIndex == n) return l;
  return l.subList(startIndex, endIndex);
}


static <A> List<A> subList(List<A> l, IntRange r) {
  return subList(l, r.start, r.end);
}

static String actualUserHome_value;
static String actualUserHome() {
  if (actualUserHome_value == null) {
    if (isAndroid())
      actualUserHome_value = "/storage/emulated/0/";
    else
      actualUserHome_value = System.getProperty("user.home");
  }
  return actualUserHome_value;
}

static File actualUserHome(String sub) {
  return newFile(new File(actualUserHome()), sub);
}
static File userDir() {
  return new File(userHome());
}

static File userDir(String path) {
  return new File(userHome(), path);
}
static Random defaultRandomGenerator() {
  return ThreadLocalRandom.current();
}
static int min(int a, int b) {
  return Math.min(a, b);
}

static long min(long a, long b) {
  return Math.min(a, b);
}

static float min(float a, float b) { return Math.min(a, b); }
static float min(float a, float b, float c) { return min(min(a, b), c); }

static double min(double a, double b) {
  return Math.min(a, b);
}

static double min(double[] c) {
  double x = Double.MAX_VALUE;
  for (double d : c) x = Math.min(x, d);
  return x;
}

static float min(float[] c) {
  float x = Float.MAX_VALUE;
  for (float d : c) x = Math.min(x, d);
  return x;
}

static byte min(byte[] c) {
  byte x = 127;
  for (byte d : c) if (d < x) x = d;
  return x;
}

static short min(short[] c) {
  short x = 0x7FFF;
  for (short d : c) if (d < x) x = d;
  return x;
}

static int min(int[] c) {
  int x = Integer.MAX_VALUE;
  for (int d : c) if (d < x) x = d;
  return x;
}
static int[] subIntArray(int[] b, int start) {
  return subIntArray(b, start, l(b));
}
  
static int[] subIntArray(int[] b, int start, int end) {
  start = max(start, 0); end = min(end, l(b));
  if (start == 0 && end == l(b)) return b;
  if (start >= end) return new int[0];
  int[] x = new int[end-start];
  System.arraycopy(b, start, x, 0, end-start);
  return x;
}

static int[] subIntArray(int[] a, IntRange r) {
  return r == null ? null : subIntArray(a, r.start, r.end);
}
static String repeat(char c, int n) {
  n = Math.max(n, 0);
  char[] chars = new char[n];
  for (int i = 0; i < n; i++)
    chars[i] = c;
  return new String(chars);
}

static <A> List<A> repeat(A a, int n) {
  n = Math.max(n, 0);
  List<A> l = new ArrayList(n);
  for (int i = 0; i < n; i++)
    l.add(a);
  return l;
}

static <A> List<A> repeat(int n, A a) {
  return repeat(a, n);
}
static String dropSuffix(String suffix, String s) {
  return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}
static int indent_default = 2;

static String indent(int indent) {
  return repeat(' ', indent);
}

static String indent(int indent, String s) {
  return indent(repeat(' ', indent), s);
}

static String indent(String indent, String s) {
  return indent + s.replace("\n", "\n" + indent);
}

static String indent(String s) {
  return indent(indent_default, s);
}

static List<String> indent(String indent, List<String> lines) {
  List<String> l = new ArrayList();
  if (lines != null) for (String s : lines)
    l.add(indent + s);
  return l;
}


static <A> A[] newObjectArrayOfSameType(A[] a, int n) {
  return (A[]) Array.newInstance(a.getClass().getComponentType(), n);
}


static interface IF0<A> {
  A get();
}// it's unclear whether the end is inclusive or exclusive
// (usually exclusive I guess)
static class IntRange {
  int start, end;
  
  IntRange() {}
  IntRange(int start, int end) {
  this.end = end;
  this.start = start;}
  IntRange(IntRange r) { start = r.start; end = r.end; }
  
  public boolean equals(Object o) { return stdEq2(this, o); }
public int hashCode() { return stdHash2(this); }
  
  final int length() { return end-start; }
  final boolean empty() { return start >= end; }
  final boolean isEmpty() { return start >= end; }
  
  static String _fieldOrder = "start end";
  
  public String toString() { return "[" + start + ";" + end + "]"; }
}

static boolean stdEq2(Object a, Object b) {
  if (a == null) return b == null;
  if (b == null) return false;
  if (a.getClass() != b.getClass()) return false;
  for (String field : allFields(a))
    if (neq(getOpt(a, field), getOpt(b, field)))
      return false;
  return true;
}
static int stdHash2(Object a) {
  if (a == null) return 0;
  return stdHash(a, toStringArray(allFields(a)));
}


static Set<String> allFields(Object o) {
  TreeSet<String> fields = new TreeSet();
  Class _c = _getClass(o);
  do {
    for (Field f : _c.getDeclaredFields())
      fields.add(f.getName());
    _c = _c.getSuperclass();
  } while (_c != null);
  return fields;
}
static int stdHash(Object a, String... fields) {
  if (a == null) return 0;
  int hash = getClassName(a).hashCode();
  for (String field : fields)
    hash = boostHashCombine(hash, hashCode(getOpt(a, field)));
  return hash;
}
static String[] toStringArray(Collection<String> c) {
  String[] a = new String[l(c)];
  Iterator<String> it = c.iterator();
  for (int i = 0; i < l(a); i++)
    a[i] = it.next();
  return a;
}

static String[] toStringArray(Object o) {
  if (o instanceof String[])
    return (String[]) o;
  else if (o instanceof Collection)
    return toStringArray((Collection<String>) o);
  else
    throw fail("Not a collection or array: " + getClassName(o));
}



static int boostHashCombine(int a, int b) {
  return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
static int hashCode(Object a) {
  return a == null ? 0 : a.hashCode();
}

}