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 java.util.function.*;
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 java.awt.geom.*;
import javax.imageio.*;
import java.math.*;
import java.time.Duration;
import java.text.SimpleDateFormat;
import java.text.*;
import java.util.TimeZone;
import static x30_pkg.x30_util.DynamicObject;
import java.text.NumberFormat;
import java.awt.geom.*;

class main {

static class TestPhantomCleanUp {
   final public TestPhantomCleanUp setWithGC(boolean withGC){ return withGC(withGC); }
public TestPhantomCleanUp withGC(boolean withGC) { this.withGC = withGC; return this; }  final public boolean getWithGC(){ return withGC(); }
public boolean withGC() { return withGC; }
 boolean withGC = false;
   final public TestPhantomCleanUp setMaxWait(double maxWait){ return maxWait(maxWait); }
public TestPhantomCleanUp maxWait(double maxWait) { this.maxWait = maxWait; return this; }  final public double getMaxWait(){ return maxWait(); }
public double maxWait() { return maxWait; }
 double maxWait = 60.0; // how many seconds
  
   final public boolean getReferenceWasCleared(){ return referenceWasCleared(); }
public boolean referenceWasCleared() { return referenceWasCleared; }
 boolean referenceWasCleared = false;
   final public Duration getDelayBeforeClearance(){ return delayBeforeClearance(); }
public Duration delayBeforeClearance() { return delayBeforeClearance; }
 Duration delayBeforeClearance;
  
  // The object to be referenced & cleared by the GC.
  // Don't keep any references to it, of course!
  transient  IF0<Object> createReferent;
Object createReferent() { return createReferent != null ? createReferent.get() : createReferent_base(); }
final Object createReferent_fallback(IF0<Object> _f) { return _f != null ? _f.get() : createReferent_base(); }
Object createReferent_base() {
    return new String("copy of a string");
  }

  public void run() { try {
     RunnablesReferenceQueue queue = new RunnablesReferenceQueue(); try {
    
    class MyPhantomRef<A> extends PhantomReference<A> implements Runnable {
      Flag flag = new Flag();
    
      MyPhantomRef(A a, RunnablesReferenceQueue queue) {
        super(a, queue.get());
      }
    
      public void run() { try {
        printWithMS("Behold the phantom!");
        flag.raise();
      } catch (Exception __e) { throw rethrow(__e); } }
    }
    
    var ref = new MyPhantomRef<Object>(createReferent(), queue);
    printWithMS("Made the object.");
    
    if (withGC) {
      print("GC.");
      timedGC();
    }
    long time = sysNow();
    printWithMS("Now waiting...");
    if (ref.flag.waitUntilUp(toMS_int(maxWait))) {
      delayBeforeClearance = msToDuration(sysNow()-time);
      referenceWasCleared = true;
      printWithMS("Cool! Reference was cleared after "
        + renderElapsedSecondsPleasantly(delayBeforeClearance)
        + " (" + withOrWithout(withGC) + " GC)");
    } else
      printWithMS("Ouch. Reference not cleared even after " + maxWait + " s");
  } finally { _close(queue); }} catch (Exception __e) { throw rethrow(__e); } }
  
  static void allTests() {
    testSmallObject();
    testHumungousObject();
  }
  
  static void testSmallObject() {
    for (var gc : falseTrue()) {
      printAfterNL("Small object test!");
      new TestPhantomCleanUp().withGC(gc).run();
    }
  }
  
  static void testHumungousObject() {
    for (var gc : falseTrue()) {
      printAfterNL("Humungous object test!");
      
      var tpcu = new TestPhantomCleanUp().withGC(gc);
      
      // pretty likely to be humungous
      // Def. humungous: an array bigger than (G1 region size/2)
      // Check "jinfo -flags $pid" to see the region size
      tpcu.createReferent = () -> new byte[8*oneMegabyte_int()];
      
      tpcu.run();
    }
  }
}
static void printWithMS(String s) {
  printWithMSTime(s);
}


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 volatile StringBuffer local_log = new StringBuffer(); // not redirected




static boolean printAlsoToSystemOut = true;

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(combinePrintParameters(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) {
  
  try {
    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;
  } catch (Throwable e) {
    System.out.println(getStackTrace(e));
  }
  

  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);
  
  
  if (printAlsoToSystemOut)
    System.out.print(s);
  
  
  vmBus_send("printed", mc(), s);
  
}

static void print_autoRotate() {
  
}


static int timedGC_minPrint = 5000; // print notice to console when GC takes longer than these many ms

static void timedGC() {
  long time = sysNow(), start = now();
  gc_notTooOften();
  done2_systemErr_minPrint = timedGC_minPrint;
  done2_systemErr(time, "GC " + hmsWithColons(start));
}


static long sysNow() {
  ping();
  return System.nanoTime()/1000000;
}


static int toMS_int(double seconds) {
  return toInt_checked((long) (seconds*1000));
}


static Duration msToDuration(double ms) {
  return Duration.ofNanos(lround(msToNanos(ms)));
}


static String renderElapsedSecondsPleasantly(double seconds) {
  if (seconds >= 1) return formatDouble(seconds, 3) + " s";
  double ms = seconds*1000;
  if (ms >= 1) return formatDouble(ms, 3) + " ms";
  double us = ms*1000;
  if (us >= 1) return formatDouble(us, 3) + " µs";
  double ns = us*1000;
  return formatDouble(ns, 3) + " ns";
}

static String renderElapsedSecondsPleasantly(Duration d) {
  return d == null ? null : renderElapsedSecondsPleasantly(nanosToSeconds(d.toNanos()));
}


static String withOrWithout(boolean b) { return b ? "with" : "without"; }


static void _close(AutoCloseable c) {
  if (c != null) try {
    c.close();
  } catch (Throwable e) {
    // Some classes stupidly throw an exception on double-closing
    if (c instanceof javax.imageio.stream.ImageOutputStream)
      return;
    else throw rethrow(e);
  }
}


static List<Boolean> falseTrue_cache;
static List<Boolean> falseTrue() { if (falseTrue_cache == null) falseTrue_cache = falseTrue_load(); return falseTrue_cache;}

static List<Boolean> falseTrue_load() {
  return ll(false, true);
}


static <A> A printAfterNL(A o) {
  return nlPrint(o);
}



static <A> A printAfterNL(String prefix, A o) {
  return nlPrint(prefix, o);
}


static int oneMegabyte_int() {
  return 1024*1024;
}




static AutoCloseable tempInterceptPrintIfNotIntercepted(F1<String, Boolean> f) {
  return print_byThread().get() == null ? tempInterceptPrint(f) : null;
}


static void printWithMSTime(String s) {
  print(hmsWithColonsAndMS() + ": " + s);
}


static void _handleError(Error e) {
  //call(javax(), '_handleError, e);
}


static String combinePrintParameters(String s, Object o) {
  return (endsWithLetterOrDigit(s) ? s + ": " : s) + o;
}


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 Map<Class, ArrayList<Method>> callF_cache = newDangerousWeakHashMap();



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



  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, B> B callF(IF1<A, B> f, A a) {
    return f == null ? null : f.get(a);
  }


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






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


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

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


static Object callF(Runnable r) { { if (r != null) r.run(); } return null; }


static Object callF(Object f, Object... args) {
  
    
  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) {
    
    
    if (f instanceof String)
      throw fail("Legacy call: " + f);
    
    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 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 String getStackTrace(String msg) {
  return getStackTrace_noRecord(new Throwable(msg));
}


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 void vmBus_send(String msg, Object... args) {
  Object arg = vmBus_wrapArgs(args);
  pcallFAll_minimalExceptionHandling(vm_busListeners_live(), msg, arg);
  pcallFAll_minimalExceptionHandling(vm_busListenersByMessage_live().get(msg), msg, arg);
}

static void vmBus_send(String msg) {
  vmBus_send(msg, (Object) null);
}


static Class mc() {
  return main.class;
}


static long now_virtualTime;
static long now() {
  return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis();
}



static volatile long gc_notTooOften_last;
static volatile long gc_notTooOften_interval = 1000;

static void gc_notTooOften() {
  gc_notTooOften_last = fixTimestamp(gc_notTooOften_last);
  if (now() >= gc_notTooOften_last + gc_notTooOften_interval) {
    gc();
    gc_notTooOften_last = now();
  }
}


static int done2_systemErr_minPrint = 10;

static long done2_systemErr(long startTime, String desc) {
  long time = sysNow()-startTime;
  if (time >= done2_systemErr_minPrint)
    System.err.println(desc + " [" + time + " ms]");
  return time;
}

static long done2_systemErr(String desc, long startTime) {
  return done2_systemErr(startTime, desc);
}

static long done2_systemErr(long startTime) {
  return done2_systemErr(startTime, "");
}




static String hmsWithColons() {
  return hmsWithColons(now());
}

static String hmsWithColons(long time) {
  return new SimpleDateFormat("HH:mm:ss").format(time);
}





// legacy mode





//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();

// ignore pingSource if not PingV3
static boolean ping(PingSource pingSource) { return ping(); }

// always returns true
static boolean ping() {
  //ifdef useNewPing
  newPing();
  //endifdef
  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 int toInt_checked(long l) {
  if (l != (int) l) throw fail("Too large for int: " + l);
  return (int) l;
}


static long lround(double d) {
  return Math.round(d);
}

static long lround(Number n) {
  return lround(toDouble(n));
}


static double msToNanos(double ms) {
  return ms*1e6;
}


static String formatDouble(double d, int digits) {
  String format = digits <= 0 ? "0" : "0." + rep(digits, '#');
  return decimalFormatEnglish(format, d);
}

static String formatDouble(double d) {
  return str(d);
}




static double nanosToSeconds(double nanos) {
  return nanoSecondsToSeconds(nanos);
}


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 <A> A nlPrint(A o) {
  return nlPrint("", o);
}

static <A> A nlPrint(String prefix, A o) {
  return print("\n" + unnull(prefix), o);
}




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 String hmsWithColonsAndMS() {
  return hmsWithColonsAndMS(now());
}

static String hmsWithColonsAndMS(long time) {
  return simpleDateFormat_local("HH:mm:ss:SSSS").format(time);
}



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


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);
}


// a little kludge for stuff like eq(symbol, "$X")
static boolean eq(Symbol a, String b) {
  return eq(str(a), b);
}



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 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(IMultiMap mm) { return mm == null ? 0 : mm.size(); }















  static int l(AppendableChain a) { return a == null ? 0 : a.size; }









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(Object... objects) { throw new Fail(objects); }


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 != l(args)) {
    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;
}


// 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> 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 <A> int indexOf(Iterable<A> l, A a) {
  if (l == null) return -1;
  int i = 0;
  for (A x : l) {
    if (eq(x, a))
      return i;
    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 Object vmBus_wrapArgs(Object... args) {
  return empty(args) ? null
    : l(args) == 1 ? args[0]
    : args;
}


static void pcallFAll_minimalExceptionHandling(Collection l, Object... args) {
  if (l != null) for  (Object f : cloneList(l)) { ping(); pcallF_minimalExceptionHandling(f, args); }
}

static void pcallFAll_minimalExceptionHandling(Iterator it, Object... args) {
  while  (it.hasNext()) { ping(); pcallF_minimalExceptionHandling(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 long fixTimestamp(long timestamp) {
  return timestamp > now() ? 0 : timestamp;
}


static volatile long gc_memoryUsedAfterGC;
static volatile long gc_lastStart;
static volatile long gc_duration;
static Lock gc_lock = lock();

static void gc() {
  callOpt(javax(), "cleanWeakMaps");
  {
    Lock __0 = gc_lock; lock(__0); try {
    gc_lastStart = sysNow();
    System.gc();
    gc_duration = sysNow()-gc_lastStart;
    gc_memoryUsedAfterGC = usedMemory();
  } finally { unlock(__0); } }
  vmBus_send("gcDone");
}


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


static void newPing() {
  var tl = newPing_actionTL();
  Runnable action = tl == null ? null : tl.get();
  { if (action != null) action.run(); }
}


// 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 boolean isTrue(Boolean b) {
  return b != null && b.booleanValue();
}


static void failIfUnlicensed() {
  assertTrue("license off", licensed());
}


static Thread currentThread() {
  return Thread.currentThread();
}


static double toDouble(Object o) {
  if (o instanceof Number)
    return ((Number) o).doubleValue();
  if (o instanceof BigInteger)
    return ((BigInteger) o).doubleValue();
  if (o instanceof String)
    return parseDouble((String) o);
  if (o == null) return 0.0;
  throw fail(o);
}


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 decimalFormatEnglish(String format, double d) {
  return decimalFormatEnglish(format).format(d);
}

static java.text.DecimalFormat decimalFormatEnglish(String format) {
  return new java.text.DecimalFormat(format, new java.text.DecimalFormatSymbols(Locale.ENGLISH));
}


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

static String str(char[] c) {
  return new String(c);
}

static String str(char[] c, int offset, int count) {
  return new String(c, offset, count);
}


static double nanoSecondsToSeconds(double nanos) {
  return nanos*1e-9;
}


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;
}


static Pt unnull(Pt p) {
  return p == null ? new Pt() : p;
}


//ifclass Symbol

static Symbol unnull(Symbol s) {
  return s == null ? emptySymbol() : s;
}
//endif



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


static int unnull(Integer i) { return i == null ? 0 : i; }
static long unnull(Long l) { return l == null ? 0L : l; }
static double unnull(Double l) { return l == null ? 0.0 : l; }




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 SimpleDateFormat simpleDateFormat_local(String format) {
  SimpleDateFormat sdf = new SimpleDateFormat(format);
  sdf.setTimeZone(localTimeZone());
  return sdf;
}


//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 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 Map<String, Field> getOpt_getFieldMap(Object o) {
  Class c = _getClass(o);
  HashMap<String, Field> map = getOpt_cache.get(c);
  if (map == null)
    map = getOpt_makeCache(c);
  return map;
}

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

  Map<String, Field> map = getOpt_getFieldMap(o);

  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);
  
    if (o instanceof DynamicObject)
      return syncMapGet2(((DynamicObject) o).fieldValues, field);
  
  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 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 <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 List<String> classNames(Collection l) {
  return getClassNames(l);
}

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


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 PersistableThrowable persistableThrowable(Throwable e) {
  return e == null ? null : new PersistableThrowable(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 empty(url) ? null : new URL(url).getHost();
} 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(BitSet bs) { return bs == null || bs.isEmpty(); }


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(IMultiMap mm) { return mm == null || mm.size() == 0; }


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










static boolean empty(Rect r) { return !(r != null && r.w != 0 && r.h != 0); }



static boolean empty(Chain c) { return c == null; }



static boolean empty(AppendableChain c) { return c == null; }



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_minimalExceptionHandling(Object f, Object... args) {
  try {
    return callFunction(f, args);
  } catch (Throwable e) {
    System.out.println(getStackTrace(e));
    _storeException(e);
  }
  return null;
}


static Set vm_generalIdentityHashSet(Object name) {
  synchronized(vm_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(vm_generalMap()) {
    Map m =  (Map) (vm_generalMap_get(name));
    if (m == null)
      vm_generalMap_put(name, m = syncHashMap());
    return m;
  }
}



static void lock(Lock lock) { try {
  ping();
  if (lock == null) return;
  try {
    vmBus_send("locking", lock, "thread" , currentThread());
    lock.lockInterruptibly();
    vmBus_send("locked", lock, "thread" , currentThread());
  } catch (InterruptedException e) {
    Object reason = vm_threadInterruptionReasonsMap().get(currentThread());
    print("Locking interrupted! Reason: " + strOr(reason, "Unknown"));
    printStackTrace(e);
    rethrow(e);
  }
  // NO call to ping here! Make sure lock is always released.
} catch (Exception __e) { throw rethrow(__e); } }

static void lock(Lock lock, String msg) {
  print("Locking: " + msg);
  lock(lock);
}

static void lock(Lock lock, String msg, long timeout) {
  print("Locking: " + msg);
  lockOrFail(lock, timeout);
}

static ReentrantLock lock() {
  return fairLock();
}


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

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


static Class javax() {
  return getJavaX();
}


static long usedMemory() {
  return Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
}


static void unlock(Lock lock, String msg) {
  if (lock == null) return;
  lock.unlock();
  vmBus_send("unlocked", lock, "thread" , currentThread());
  print("Unlocked: " + msg); // print afterwards to make sure the lock is always unlocked
}

static void unlock(Lock lock) {
  if (lock == null) return;
  lock.unlock();
  vmBus_send("unlocked", lock, "thread" , currentThread());
}


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 x30_pkg.x30_util.BetterThreadLocal<Runnable> newPing_actionTL;

static x30_pkg.x30_util.BetterThreadLocal<Runnable> newPing_actionTL() {
  if (newPing_actionTL == null)
    newPing_actionTL = vm_generalMap_getOrCreate("newPing_actionTL",
      () -> {
        Runnable value =  (Runnable) (callF_gen(vm_generalMap_get("newPing_valueForNewThread")));
        var tl = new x30_pkg.x30_util.BetterThreadLocal<Runnable>();
        tl.set(value);
        return tl;
      });
  return newPing_actionTL;
}



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 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;
}


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 double parseDouble(String s) {
  return empty(s) ? 0.0 : Double.parseDouble(s);
}


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 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 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 Symbol emptySymbol_value;

static Symbol emptySymbol() {
  if (emptySymbol_value == null) emptySymbol_value = symbol("");
  return emptySymbol_value;
}




static <A> A setThreadLocal(ThreadLocal<A> tl, A value) {
  if (tl == null) return null;
  A old = tl.get();
  tl.set(value);
  return old;
}


static TimeZone localTimeZone() {
  return getTimeZone(standardTimeZone());
  // TimeZone.getDefault()?
}


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 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 classLoaderForObject(realm).loadClass(classNameToVM(name));
  } catch (ClassNotFoundException e) {
    return null; // could optimize this
  }
}


static <A, B> B syncMapGet2(Map<A, B> map, A a) {
  if (map == null) return null;
  synchronized(collectionMutex(map)) {
    return map.get(a);
  }
}

static <A, B> B syncMapGet2(A a, Map<A, B> map) {
  return syncMapGet2(map, a);
}


static boolean isSubtypeOf(Class a, Class b) {
  return a != null && b != null && 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 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 synchroList(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 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 <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 synchronizedMap(new HashMap());
}



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));
}

public static String join(String glue, Object... 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 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;
}


// 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 ArrayList<Short> asList(short[] a) {
  if (a == null) return null;
  ArrayList<Short> l = emptyList(a.length);
  for (short i : a) l.add(i);
  return l;
}

static <A> ArrayList<A> asList(Iterator<A> it) {
  ArrayList l = new ArrayList();
  if (it != null)
    while (it.hasNext())
      l.add(it.next());
  return l;  
}

// disambiguation
static <A> ArrayList<A> asList(IterableIterator<A> s) {
  return asList((Iterator) s);
}

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 <A> ArrayList<A> asList(ReverseChain<A> c) {
  return c == null ? emptyList() : c.toList();
}



static <A> List<A> asList(Pair<A, A> p) {
  return p == null ? null : ll(p.a, p.b);
}



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;
    if (m != null) 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 boolean domainIsUnder(String domain, String mainDomain) {
  return eqic(domain, mainDomain) || ewic(domain, "." + mainDomain);
}


static String theAGIBlueDomain() {
  return "agi.blue";
}


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();
}


// TODO: JDK 17!! ?? No! Yes? Yes!!

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

static Object collectionMutex(Object o) {
  

  if (o instanceof List) return o;
  
  // TODO: actually use our own maps so we can get the mutex properly
  
  String c = className(o);
  
    
  
    
  
  return o;
}


static Object callFunction(Object f, Object... args) {
  return callF(f, args);
}


static Throwable _storeException_value;

static void _storeException(Throwable e) {
  _storeException_value = e;
}


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 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 Map<Thread, Object> vm_threadInterruptionReasonsMap() {
  return vm_generalWeakSubMap("Thread interruption reasons");
}


static String strOr(Object o, String ifNull) {
  return o == null ? ifNull : str(o);
}


static <A extends Throwable> A printStackTrace(A e) {
  // we go to system.out now - system.err is nonsense
  if (e != null) 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 void lockOrFail(Lock lock, long timeout) { try {
  ping();
  vmBus_send("locking", lock, "thread" , currentThread());
  if (!lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
    String s = "Couldn't acquire lock after " + timeout + " ms.";
    if (lock instanceof ReentrantLock) {
      ReentrantLock l =  (ReentrantLock) lock;
      s += " Hold count: " + l.getHoldCount() + ", owner: " + call(l, "getOwner");
    }
    throw fail(s);
  }
  vmBus_send("locked", lock, "thread" , currentThread());
  ping();
} catch (Exception __e) { throw rethrow(__e); } }


static ReentrantLock fairLock() {
  return new ReentrantLock(true);
}


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 Class __javax;

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

static void __setJavaX(Class j) {
  __javax = j;
  _onJavaXSet();
}


static <A extends Throwable> A printException(A e) {
  printStackTrace(e);
  return e;
}


static <A> A vm_generalMap_getOrCreate(Object key, F0<A> create) {
  return vm_generalMap_getOrCreate(key, f0ToIF0(create));
}

static <A> A vm_generalMap_getOrCreate(Object key, IF0<A> create) {
  Map generalMap = vm_generalMap();
  if (generalMap == null) return null; // must be x30 init
  
  synchronized(generalMap) { // should switch to locks here
    A a =  (A) (vm_generalMap_get(key));
    if (a == null)
      vm_generalMap_put(key, a = create == null ? null : create.get());
    return a;
  }
}




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



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



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



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


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






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


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

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

static Object callF_gen(Runnable r) { { if (r != null) r.run(); } return null; }

static Object callF_gen(Object f, Object... args) {
  return callF(f, args);
}


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 <A extends Comparable<A>> A max(A a, A b) {
  return cmp(a, b) >= 0 ? a : b;
}




static WeakHasherMap<Symbol, Boolean> symbol_map = new WeakHasherMap(new Hasher<Symbol>() {
  public int hashCode(Symbol symbol) { return symbol.text.hashCode(); }
  public boolean equals(Symbol a, Symbol b) {
    if (a == null) return b == null;
    return b != null && eq(a.text, b.text);
  }
});



static Symbol symbol(String s) {
  
  
  if (s == null) return null;
  synchronized(symbol_map) {
    // TODO: avoid object creation by passing the string to findKey
    Symbol symbol = new Symbol(s, true);
    Symbol existingSymbol = symbol_map.findKey(symbol);
    if (existingSymbol == null)
      symbol_map.put(existingSymbol = symbol, true);
    
      
    return existingSymbol;
  }
  
}

static Symbol symbol(CharSequence s) {
  if (s == null) return null;
  
  
  if (s instanceof Symbol) return (Symbol) s;
  if (s instanceof String) return symbol((String) s);
  return symbol(str(s));
  
}

static Symbol symbol(Object o) {
  return symbol((CharSequence) o);
}






static TimeZone getTimeZone(String name) {
  return TimeZone.getTimeZone(name);
}


static String standardTimeZone_name = "Europe/Berlin";

static String standardTimeZone() {
  return standardTimeZone_name;
}


static ClassLoader classLoaderForObject(Object o) {
  if (o instanceof ClassLoader) return ((ClassLoader) o);
  if (o == null) return null;
  return _getClass(o).getClassLoader();
}


// Note: This is actually broken. Inner classes must stay with a $ separator
static String classNameToVM(String name) {
  return name.replace(".", "$");
}


static <A> HashSet<A> litset(A... items) {
  return lithashset(items);
}


static <A> String joinWithComma(Collection<A> c) {
  return join(", ", c);
}

static String joinWithComma(Object... 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<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 <A, B> void multiMapPut(MultiMap<A, B> mm, A key, B value) {
  if (mm != null && key != null && value != null) mm.put(key, value);
}



static Object call_withVarargs(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);
    
    Method me = cache.findStaticMethod(methodName, args);
    if (me != null)
      return invokeMethod(me, null, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(methodName);
    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() + "." + methodName + "(" + joinWithComma(classNames(args)) + ") not found");
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

    Method me = cache.findMethod(methodName, args);
    if (me != null)
      return invokeMethod(me, o, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(methodName);
    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() + "." + methodName + "(" + joinWithComma(classNames(args)) + ") not found");
  }
} catch (Exception __e) { throw rethrow(__e); } }


static Map synchronizedMap() {
  return synchroMap();
}

static <A, B> Map<A, B> synchronizedMap(Map<A, B> map) {
  return synchroMap(map);
}



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, B> Pair<A, B> first(Map<A, B> map) {
  return mapEntryToPair(first(entrySet(map)));
}

static <A, B> Pair<A, B> first(MultiMap<A, B> mm) {
  if (mm == null) return null;
  var e = first(mm.data.entrySet());
  if (e == null) return null;
  return pair(e.getKey(), first(e.getValue()));
}


static <A> A first(IterableIterator<A> i) {
  return first((Iterator<A>) i);
}


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 <A> A first(A[] l, IF1<A, Boolean> pred) {
  return firstThat(l, pred);
}

static <A> A first(Iterable<A> l, IF1<A, Boolean> pred) {
  return firstThat(l, pred);
}

static <A> A first(IF1<A, Boolean> pred, Iterable<A> l) {
  return firstThat(pred, l);
}


static <A> A first(AppendableChain<A> a) {
  return a == null ? null : a.element;
}




static boolean nemptyString(String s) {
  return s != null && s.length() > 0;
}


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);
}



// convenience method for quickly dropping a prefix
static String substring(String s, CharSequence l) {
  return substring(s, lCharSequence(l));
}


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(Symbol a, Symbol b) {
  return eq(a, b);
}

static boolean eqic(Symbol a, String b) {
  return eqic(asString(a), 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 String className(Object o) {
  return getClassName(o);
}


// get purpose 1: access a list/array/map (safer version of x.get(y))

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);
    }
      
    
      if (o instanceof DynamicObject)
        return getOptDynOnly(((DynamicObject) o), field);
    
  } 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 boolean get(BitSet bs, int idx) {
  return bs != null && bs.get(idx);
}


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 Map vm_generalWeakSubMap(Object name) {
  synchronized(vm_generalMap()) {
    Map map =  (Map) (vm_generalMap_get(name));
    if (map == null)
      vm_generalMap_put(name, map = newWeakMap());
    return map;
  }
}



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); } }

// no longer synchronizes! (see #1102990)
static _MethodCache callOpt_getCache(Class c) {
  _MethodCache cache = callOpt_cache.get(c);
  if (cache == null)
    callOpt_cache.put(c, cache = new _MethodCache(c));
  return cache;
}


static Object[] massageArgsForVarArgsCall(Executable m, Object[] args) {
  Class<?>[] types = m.getParameterTypes();
  int n = types.length-1, nArgs = l(args);
  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);
  
  // TODO: optimize
  
  int nVarArgs = nArgs-n;
  Object varArgs = Array.newInstance(varArgType, nVarArgs);
  for (int i = 0; i < nVarArgs; i++)
    Array.set(varArgs, i, args[n+i]);
    
  newArgs[n] = varArgs;
  return newArgs;
}


static void _onJavaXSet() {}


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


static int cmp(Number a, Number b) {
  return a == null ? b == null ? 0 : -1 : cmp(a.doubleValue(), b.doubleValue());
}

static int cmp(double a, double b) {
  return a < b ? -1 : a == b ? 0 : 1;
}

static int cmp(int a, int b) {
  return a < b ? -1 : a == b ? 0 : 1;
}

static int cmp(long a, long b) {
  return a < b ? -1 : a == b ? 0 : 1;
}

static int cmp(Object a, Object b) {
  if (a == null) return b == null ? 0 : -1;
  if (b == null) return 1;
  return ((Comparable) a).compareTo(b);
}




static <A> HashSet<A> lithashset(A... items) {
  HashSet<A> set = new HashSet();
  for (A a : items) set.add(a);
  return set;
}


static boolean isStaticMethod(Method m) {
  return methodIsStatic(m);
}


static <A, B> Pair<A, B> mapEntryToPair(Map.Entry<A, B> e) {
  return e == null ? null : pair(e.getKey(), e.getValue());
}


static <A, B> Set<Map.Entry<A,B>> entrySet(Map<A, B> map) {
  return _entrySet(map);
}


static <A> A firstThat(Iterable<A> l, IF1<A, Boolean> pred) {
  for (A a : unnullForIteration(l))
    if (pred.get(a))
      return a;
  return null;
}

static <A> A firstThat(A[] l, IF1<A, Boolean> pred) {
  for (A a : unnullForIteration(l))
    if (pred.get(a))
      return a;
  return null;
}

static <A> A firstThat(IF1<A, Boolean> pred, Iterable<A> l) {
  return firstThat(l, pred);
}

static <A> A firstThat(IF1<A, Boolean> pred, A[] l) {
  return firstThat(l, pred);
}



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


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;
  if (m != null)
    m.m = new String[] { substring(a, 0, l(a)-l(b)) };
  return true;
}



static Object getOptDynOnly(DynamicObject o, String field) {
  if (o == null || o.fieldValues == null) return null;
  return o.fieldValues.get(field);
}


static <A> Set<A> synchroHashSet() {
  return synchronizedSet(new HashSet<A>());
}



static <A, B> Map<A, B> newWeakMap() {
  return newWeakHashMap();
}


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, int destPos, int n) { arraycopy(src, srcPos, src, destPos, n); }
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int n) {
  if (n != 0)
    System.arraycopy(src, srcPos, dest, destPos, n);
}




static boolean methodIsStatic(Method m) {
  return (m.getModifiers() & Modifier.STATIC) != 0;
}


static <A, B> Set<Map.Entry<A,B>> _entrySet(Map<A, B> map) {
  return map == null ? Collections.EMPTY_SET : map.entrySet();
}


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

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

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

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

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

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

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


static Pt unnullForIteration(Pt p) {
  return p == null ? new Pt() : p;
}


//ifclass Symbol

static Symbol unnullForIteration(Symbol s) {
  return s == null ? emptySymbol() : s;
}
//endif



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


static long unnullForIteration(Long l) { return l == null ? 0L : l; }


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




static <A> List<A> immutableEmptyList() {
  return Collections.emptyList();
}


static short[] emptyShortArray = new short[0];
static short[] emptyShortArray() { return emptyShortArray; }


static <A, B> Map<A, B> immutableEmptyMap() {
  return Collections.emptyMap();
}




static abstract class VF1<A> implements IVF1<A> {
  public abstract void get(A a);
}
// immutable, has strong refs
// Do not run in a synchronized block - it goes wrong in the presence
// of elaborate classloaders (like in Gazelle BEA)
// see #1102990 and #1102991

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;
    java.lang.Module myModule = getClass().getModule();
    boolean anyHiddenClasses = false;
    
    while (_c != null) {
      boolean exported = classIsExportedTo(_c, myModule);
       
      
      if (!exported)
        anyHiddenClasses = true;
      else
        for (Method m : _c.getDeclaredMethods())
          if ((anyHiddenClasses || !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.
    // If any classes in the hierarchy were inaccessible, we add
    // all interface methods (see test_callForbiddenMethodByReflection for a test)
    
    for (Class intf : allInterfacesImplementedBy(c))
      for (Method m : intf.getDeclaredMethods())
        if ((anyHiddenClasses || 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); } }
  
  //Cl<Method> allMethods() { ret allValues(cache); }
}
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); }
}

// for the version with MasterSymbol (used WAY back in "Smart Bot"!) see #1010608

static class Symbol implements CharSequence {
  String text;
  
  Symbol() {}
  Symbol(String text, boolean dummy) {
  this.text = text;} // weird signature to prevent accidental calling
  
  public int hashCode() { return _hashCode(text); }
  public String toString() { return text; }
  public boolean equals(Object o) {
    return this == o;
  }

  // implementation of CharSequence methods
  
  public int length() { return text.length(); }
  public char charAt(int index) { return text.charAt(index); }
  public CharSequence subSequence(int start, int end) {
    return text.substring(start, end);
  }
}
static abstract class F0<A> {
  abstract A get();
}
// Note: Costs one thread while it exists
static class RunnablesReferenceQueue implements AutoCloseable {
  ReferenceQueue queue = new ReferenceQueue();
  volatile Thread thread;
  Flag closed = new Flag();
  
   final public RunnablesReferenceQueue setTimeout(int timeout){ return timeout(timeout); }
public RunnablesReferenceQueue timeout(int timeout) { this.timeout = timeout; return this; }  final public int getTimeout(){ return timeout(); }
public int timeout() { return timeout; }
 int timeout = 60000; // just for safety
  
  RunnablesReferenceQueue() {
    thread = startThread("RunnablesReferenceQueue", () -> _run());
  }
  
  void _run() { try {
    try {
      while (ping() && !closed.get()) {
        Reference<?> ref = queue.remove(timeout);
        if (ref != null) {
          if (ref instanceof Runnable)
            pcallF((Runnable) ref);
          else
            warn("RunnablesReferenceQueue: Reference not runnable - "
              + className(ref));
        }
      }
    } catch (InterruptedException e) {
    } finally {
      thread = null;
    }
  } catch (Exception __e) { throw rethrow(__e); } }
  
  public void close() { try {
    closed.raise();
    interruptThread(thread);
  } catch (Exception __e) { throw rethrow(__e); } }
  
  final ReferenceQueue get(){ return queue(); }
ReferenceQueue queue() { return queue; }
}
static abstract class F1<A, B> {
  abstract B get(A a);
}
// you still need to implement hasNext() and next()
static abstract class IterableIterator<A> implements Iterator<A>, Iterable<A> {
  public Iterator<A> iterator() {
    return this;
  }
  
  public void remove() {
    unsupportedOperation();
  }
}
/** this class is fully thread-safe */
static class Flag implements Runnable {
  private boolean up = false;

  /** returns true if flag was down before (i.e. flag was actually raised right now) */
  public synchronized boolean raise() {
    if (!up) {
      up = true;
      notifyAll();
      return true;
    } else
      return false;
  }

  public synchronized void waitUntilUp() { try {
    while (!up) {
      //try {
        wait();
      /*} catch (InterruptedException e) {
        e.printStackTrace();
      }*/
    }
  } catch (Exception __e) { throw rethrow(__e); } }

  public boolean waitUntilUp(double timeout) {
    if (timeout == infinity()) {
      waitUntilUp();
      return isUp();
    } else
      return waitUntilUp(toMS(timeout));
  }
  
  public synchronized boolean waitUntilUp(long timeout) { try {
    if (!up) {
      //try {
        wait(timeout);
      /*} catch (InterruptedException e) {
        e.printStackTrace();
      }*/
    }
    return isUp();
  } catch (Exception __e) { throw rethrow(__e); } }

  public synchronized boolean isUp() {
    return up;
  }
  
  boolean get() { return isUp(); }

  public String toString() {
    return isUp() ? "up" : "down";
  }

  // currently does a semi-active wait with latency = 50 ms
  public void waitForThisOr(Flag otherFlag) { try {
    while (!isUp() && !otherFlag.isUp())
      Thread.sleep(50);
  } catch (Exception __e) { throw rethrow(__e); } }
  
  public void run() { raise(); }
}
// In the newest pinging system (with flag PingV3), a ping source
// is the object that "allows" some code to run.
// When that code calls ping(), the ping source's action (if defined)
// is triggered.

// This allows randomly interrupting code execution, for example.

static class PingSource {
  // returns true if it slept
   final public PingSource setAction(IF0<Boolean> action){ return action(action); }
public PingSource action(IF0<Boolean> action) { this.action = action; return this; }  final public IF0<Boolean> getAction(){ return action(); }
public IF0<Boolean> action() { return action; }
volatile IF0<Boolean> action;
  
  // optional description of this ping source
  String text;
  
  // optional thread pool that this ping source likes to run in
  ThreadPool threadPool;
  
  PingSource() {}
  PingSource(ThreadPool threadPool) {
  this.threadPool = threadPool;}
  PingSource(ThreadPool threadPool, String text) {
  this.text = text;
  this.threadPool = threadPool;}
  PingSource(IF0<Boolean> action) {
  this.action = action;}

  // returns true if it slept
  final boolean get() {
    var a = action;
    return a != null && a.get();
  }
  
  final void ping() { 
    var a = action;
    if (a != null) a.get();
  }
  
  void cancel() {
    action = new Cancelled();
  }
  
  class Cancelled implements IF0<Boolean> {
    public Boolean get() { throw new PingSourceCancelledException(PingSource.this); }
  }
  
  class Encapsulated implements Runnable , IFieldsToList{
  Runnable r;
  Encapsulated() {}
  Encapsulated(Runnable r) {
  this.r = r;}public Object[] _fieldsToList() { return new Object[] {r}; }

    public void run() { try {
      //System.out.println("Encapsulated running: " + r);
      try {
        pingSource_tl().set(PingSource.this);
        //System.out.println("Ping source set");
        ping();
        r.run();
        //System.out.println("Done running");
      } finally {
        //System.out.println("Finally");
        pingSource_tl().set(null);
      }
    } catch (Exception __e) { throw rethrow(__e); } }
    
    public String toString() { return PingSource.this + ": " + r; }
  }
  
  void dO(Runnable r) {
    if (r == null) return;
    threadPool.acquireThreadOrQueue(new Encapsulated(r));
  }
  
  public String toString() { String t = text; return nempty(t) ? t : super.toString(); }
  
  ISleeper_v2 sleeper() { return threadPool.sleeper(); }
}
static class MultiMap<A,B> implements IMultiMap<A, B> {
  Map<A, List<B>> data = new HashMap<A, List<B>>();
  int fullSize;
  
  MultiMap() {}
  MultiMap(boolean useTreeMap) { if (useTreeMap) data = new TreeMap(); }
  MultiMap(MultiMap<A, B> map) { putAll(map); }
  MultiMap(Map<A, List<B>> data) {
  this.data = data;}

  void put(A key, B value) { synchronized(data) {
    List<B> list = data.get(key);
    if (list == null)
      data.put(key, list = _makeEmptyList());
    list.add(value);
    ++fullSize;
  }}

  void add(A key, B value) { put(key, value); }

  void addAll(A key, Collection<B> values) { putAll(key, values); }
  
  void addAllIfNotThere(A key, Collection<B> values) { synchronized(data) {
    for (B value : values)
      setPut(key, value);
  }}
  
  void setPut(A key, B value) { synchronized(data) {
    if (!containsPair(key, value))
      put(key, value);
  }}
  
  boolean containsPair(A key, B value) { synchronized(data) {
    return get(key).contains(value);
  }}
  
  void putAll(Collection<A> keys, B value) { synchronized(data) {
    for (A key : unnullForIteration(keys))
      put(key, value);
  }}

  void putAll(A key, Collection<B> values) { synchronized(data) {
    if (nempty(values)) getActual(key).addAll(values);
  }}

  void putAll(Iterable<Pair<A, B>> pairs) { synchronized(data) {
    for (Pair<A, B> p : unnullForIteration(pairs))
      put(p.a, p.b);
  }}
  
  void removeAll(A key, Collection<B> values) { synchronized(data) {
    for (B value : values)
      remove(key, value);
  }}
  
  public List<B> get(A key) { synchronized(data) {
    List<B> list = data.get(key);
    return list == null ? Collections.<B> emptyList() : list;
  }}
  
  List<B> getOpt(A key) { synchronized(data) {
    return data.get(key);
  }}

  List<B> getAndClear(A key) { synchronized(data) {
    List<B> l = cloneList(data.get(key));
    remove(key);
    return l;
  }}
  
  // returns actual mutable live list
  // creates the list if not there
  List<B> getActual(A key) { synchronized(data) {
    List<B> list = data.get(key);
    if (list == null)
      data.put(key, list = _makeEmptyList());
    return list;
  }}
 
  void clean(A key) { synchronized(data) {
    List<B> list = data.get(key);
    if (list != null && list.isEmpty()) {
      fullSize -= l(list);
      data.remove(key);
    }
  }}

  final public Set<A> keys(){ return keySet(); }
public Set<A> keySet() { synchronized(data) {
    return data.keySet();
  }}

  void remove(A key) { synchronized(data) {
    fullSize -= l(this.getOpt(key));
    data.remove(key);
  }}
  
  final void remove(Pair<A, B> p){ removePair(p); }
void removePair(Pair<A, B> p) {
    if (p != null) remove(p.a, p.b);
  }

  void remove(A key, B value) { synchronized(data) {
    List<B> list = data.get(key);
    if (list != null) {
      if (list.remove(value))
        fullSize--;
      if (list.isEmpty())
        data.remove(key);
    }
  }}

  void clear() { synchronized(data) {
    data.clear();
  }}

  boolean containsKey(A key) { synchronized(data) {
    return data.containsKey(key);
  }}

  B getFirst(A key) { synchronized(data) {
    List<B> list = get(key);
    return list.isEmpty() ? null : list.get(0);
  }}
  
  void addAll(MultiMap<A, B> map) { putAll(map); }
  
  void putAll(MultiMap<A, B> map) { synchronized(data) {
    for (A key : map.keySet())
      putAll(key, map.get(key));
  }}
  
  void putAll(Map<A, B> map) { synchronized(data) {
    if (map != null) for (Map.Entry<A, B> e : map.entrySet())
      put(e.getKey(), e.getValue());
  }}
  
  final public int keyCount(){ return keysSize(); }
public int keysSize() { synchronized(data) { return l(data); }}
  
  // full size - note: expensive operation
  final public int fullSize(){ return size(); }
public int size() { synchronized(data) {
    return fullSize;
  }}
  
  // expensive operation
  List<A> reverseGet(B b) { synchronized(data) {
    List<A> l = new ArrayList();
    for (A key : data.keySet())
      if (data.get(key).contains(b))
        l.add(key);
    return l;
  }}
  
  Map<A, List<B>> asMap() { synchronized(data) {
    return cloneMap(data);
  }}
  
  boolean isEmpty() { synchronized(data) { return data.isEmpty(); }}
  
  // override in subclasses
  List<B> _makeEmptyList() {
    return new ArrayList();
  }
  
  // returns live lists
  Collection<List<B>> allLists() {
    synchronized(data) {
      return new ArrayList(data.values());
    }
  }
  Collection<List<B>> values() { return allLists(); }
  
  List<B> allValues() {
    return concatLists(data.values());
  }
  
  Object mutex() { return data; }
  
  public String toString() { return "mm" + str(data); }
}
public static interface IF0<A> {
  A get();
}
static interface Hasher<A> {
  int hashCode(A a);
  boolean equals(A a, A b);
}

static interface IF1<A, B> {
  B get(A a);
}
/*
 * @(#)WeakHashMap.java 1.5 98/09/30
 *
 * Copyright 1998 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */
 
// From https://github.com/mernst/plume-lib/blob/df0bfafc3c16848d88f4ea0ef3c8bf3367ae085e/java/src/plume/WeakHasherMap.java

static final class WeakHasherMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {

    private Hasher hasher = null;
    /*@Pure*/
    private boolean keyEquals(Object k1, Object k2) {
  return (hasher==null ? k1.equals(k2)
           : hasher.equals(k1, k2));
    }
    /*@Pure*/
    private int keyHashCode(Object k1) {
  return (hasher==null ? k1.hashCode()
           : hasher.hashCode(k1));
    }

    // The WeakKey class can't be static because it depends on the hasher.
    // That in turn means that its methods can't be static.
    // However, I need to be able to call the methods such as create() that
    // were static in the original version of this code.
    // This finesses that.

    private /*@Nullable*/ WeakKey WeakKeyCreate(K k) {
  if (k == null) return null;
  else return new WeakKey(k);
    }
    private /*@Nullable*/ WeakKey WeakKeyCreate(K k, ReferenceQueue<? super K> q) {
  if (k == null) return null;
  else return new WeakKey(k, q);
    }

    // Cannot be a static class: uses keyHashCode() and keyEquals()
    private final class WeakKey extends WeakReference<K> {
  private int hash; /* Hashcode of key, stored here since the key
           may be tossed by the GC */

  private WeakKey(K k) {
      super(k);
      hash = keyHashCode(k);
  }

  private /*@Nullable*/ WeakKey create(K k) {
      if (k == null) return null;
      else return new WeakKey(k);
  }

  private WeakKey(K k, ReferenceQueue<? super K> q) {
      super(k, q);
      hash = keyHashCode(k);
  }

  private /*@Nullable*/ WeakKey create(K k, ReferenceQueue<? super K> q) {
      if (k == null) return null;
      else return new WeakKey(k, q);
  }

        /* A WeakKey is equal to another WeakKey iff they both refer to objects
     that are, in turn, equal according to their own equals methods */
  /*@Pure*/
  @Override
  public boolean equals(/*@Nullable*/ Object o) {
            if (o == null) return false; // never happens
      if (this == o) return true;
            // This test is illegal because WeakKey is a generic type,
            // so use the getClass hack below instead.
      // if (!(o instanceof WeakKey)) return false;
            if (!(o.getClass().equals(WeakKey.class))) return false;
      Object t = this.get();
            @SuppressWarnings("unchecked")
      Object u = ((WeakKey)o).get();
      if ((t == null) || (u == null)) return false;
      if (t == u) return true;
      return keyEquals(t, u);
  }

  /*@Pure*/
  @Override
  public int hashCode() {
      return hash;
  }

    }


    /* Hash table mapping WeakKeys to values */
    private HashMap<WeakKey,V> hash;

    /* Reference queue for cleared WeakKeys */
    private ReferenceQueue<? super K> queue = new ReferenceQueue<K>();


    /* Remove all invalidated entries from the map, that is, remove all entries
       whose keys have been discarded.  This method should be invoked once by
       each public mutator in this class.  We don't invoke this method in
       public accessors because that can lead to surprising
       ConcurrentModificationExceptions. */
    @SuppressWarnings("unchecked")
    private void processQueue() {
  WeakKey wk;
  while ((wk = (WeakKey)queue.poll()) != null) { // unchecked cast
      hash.remove(wk);
  }
    }


    /* -- Constructors -- */

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the given
     * initial capacity and the given load factor.
     *
     * @param  initialCapacity  the initial capacity of the
     *                          <code>WeakHashMap</code>
     *
     * @param  loadFactor       the load factor of the <code>WeakHashMap</code>
     *
     * @throws IllegalArgumentException  If the initial capacity is less than
     *                                   zero, or if the load factor is
     *                                   nonpositive
     */
    public WeakHasherMap(int initialCapacity, float loadFactor) {
  hash = new HashMap<WeakKey,V>(initialCapacity, loadFactor);
    }

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the given
     * initial capacity and the default load factor, which is
     * <code>0.75</code>.
     *
     * @param  initialCapacity  the initial capacity of the
     *                          <code>WeakHashMap</code>
     *
     * @throws IllegalArgumentException  If the initial capacity is less than
     *                                   zero
     */
    public WeakHasherMap(int initialCapacity) {
  hash = new HashMap<WeakKey,V>(initialCapacity);
    }

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the default
     * capacity and the default load factor, which is <code>0.75</code>.
     */
    public WeakHasherMap() {
  hash = new HashMap<WeakKey,V>();
    }

    /**
     * Constructs a new, empty <code>WeakHashMap</code> with the default
     * capacity and the default load factor, which is <code>0.75</code>.
     * The <code>WeakHashMap</code> uses the specified hasher for hashing
     * keys and comparing them for equality.
     * @param h the Hasher to use when hashing values for this map
     */
    public WeakHasherMap(Hasher h) {
  hash = new HashMap<WeakKey,V>();
  hasher = h;
    }


    /* -- Simple queries -- */

    /**
     * Returns the number of key-value mappings in this map.
     * <strong>Note:</strong> <em>In contrast to most implementations of the
     * <code>Map</code> interface, the time required by this operation is
     * linear in the size of the map.</em>
     */
    /*@Pure*/
    @Override
    public int size() {
  return entrySet().size();
    }

    /**
     * Returns <code>true</code> if this map contains no key-value mappings.
     */
    /*@Pure*/
    @Override
    public boolean isEmpty() {
  return entrySet().isEmpty();
    }

    /**
     * Returns <code>true</code> if this map contains a mapping for the
     * specified key.
     *
     * @param   key   the key whose presence in this map is to be tested
     */
    /*@Pure*/
    @Override
    public boolean containsKey(Object key) {
        @SuppressWarnings("unchecked")
        K kkey = (K) key;
  return hash.containsKey(WeakKeyCreate(kkey));
    }


    /* -- Lookup and modification operations -- */

    /**
     * Returns the value to which this map maps the specified <code>key</code>.
     * If this map does not contain a value for this key, then return
     * <code>null</code>.
     *
     * @param  key  the key whose associated value, if any, is to be returned
     */
    /*@Pure*/
    @Override
    public /*@Nullable*/ V get(Object key) {  // type of argument is Object, not K
        @SuppressWarnings("unchecked")
        K kkey = (K) key;
  return hash.get(WeakKeyCreate(kkey));
    }

    /**
     * Updates this map so that the given <code>key</code> maps to the given
     * <code>value</code>.  If the map previously contained a mapping for
     * <code>key</code> then that mapping is replaced and the previous value is
     * returned.
     *
     * @param  key    the key that is to be mapped to the given
     *                <code>value</code>
     * @param  value  the value to which the given <code>key</code> is to be
     *                mapped
     *
     * @return  the previous value to which this key was mapped, or
     *          <code>null</code> if if there was no mapping for the key
     */
    @Override
    public V put(K key, V value) {
  processQueue();
  return hash.put(WeakKeyCreate(key, queue), value);
    }

    /**
     * Removes the mapping for the given <code>key</code> from this map, if
     * present.
     *
     * @param  key  the key whose mapping is to be removed
     *
     * @return  the value to which this key was mapped, or <code>null</code> if
     *          there was no mapping for the key
     */
    @Override
    public V remove(Object key) { // type of argument is Object, not K
  processQueue();
        @SuppressWarnings("unchecked")
        K kkey = (K) key;
  return hash.remove(WeakKeyCreate(kkey));
    }

    /**
     * Removes all mappings from this map.
     */
    @Override
    public void clear() {
  processQueue();
  hash.clear();
    }


    /* -- Views -- */


    /* Internal class for entries */
    // This can't be static, again because of dependence on hasher.
    @SuppressWarnings("TypeParameterShadowing")
    private final class Entry<K,V> implements Map.Entry<K,V> {
  private Map.Entry<WeakKey,V> ent;
  private K key;  /* Strong reference to key, so that the GC
           will leave it alone as long as this Entry
           exists */

  Entry(Map.Entry<WeakKey,V> ent, K key) {
      this.ent = ent;
      this.key = key;
  }

  /*@Pure*/
  @Override
  public K getKey() {
      return key;
  }

  /*@Pure*/
  @Override
  public V getValue() {
      return ent.getValue();
  }

  @Override
  public V setValue(V value) {
      return ent.setValue(value);
  }

        /*@Pure*/
        private boolean keyvalEquals(K o1, K o2) {
      return (o1 == null) ? (o2 == null) : keyEquals(o1, o2);
  }

        /*@Pure*/
        private boolean valEquals(V o1, V o2) {
      return (o1 == null) ? (o2 == null) : o1.equals(o2);
  }

        /*@Pure*/
        @SuppressWarnings("NonOverridingEquals")
        public boolean equals(Map.Entry<K,V> e /* Object o*/) {
            // if (! (o instanceof Map.Entry)) return false;
            // Map.Entry<K,V> e = (Map.Entry<K,V>)o;
      return (keyvalEquals(key, e.getKey())
        && valEquals(getValue(), e.getValue()));
  }

  /*@Pure*/
  @Override
  public int hashCode() {
      V v;
      return (((key == null) ? 0 : keyHashCode(key))
        ^ (((v = getValue()) == null) ? 0 : v.hashCode()));
  }

    }


    /* Internal class for entry sets */
    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
  Set<Map.Entry<WeakKey,V>> hashEntrySet = hash.entrySet();

  @Override
  public Iterator<Map.Entry<K,V>> iterator() {

      return new Iterator<Map.Entry<K,V>>() {
    Iterator<Map.Entry<WeakKey,V>> hashIterator = hashEntrySet.iterator();
    Map.Entry<K,V> next = null;

    @Override
    public boolean hasNext() {
        while (hashIterator.hasNext()) {
      Map.Entry<WeakKey,V> ent = hashIterator.next();
      WeakKey wk = ent.getKey();
      K k = null;
      if ((wk != null) && ((k = wk.get()) == null)) {
          /* Weak key has been cleared by GC */
          continue;
      }
      next = new Entry<K,V>(ent, k);
      return true;
        }
        return false;
    }

    @Override
    public Map.Entry<K,V> next() {
        if ((next == null) && !hasNext())
      throw new NoSuchElementException();
        Map.Entry<K,V> e = next;
        next = null;
        return e;
    }

    @Override
    public void remove() {
        hashIterator.remove();
    }

      };
  }

  /*@Pure*/
  @Override
  public boolean isEmpty() {
      return !(iterator().hasNext());
  }

  /*@Pure*/
  @Override
  public int size() {
      int j = 0;
      for (Iterator<Map.Entry<K,V>> i = iterator(); i.hasNext(); i.next()) j++;
      return j;
  }

  @Override
  public boolean remove(Object o) {
      processQueue();
      if (!(o instanceof Map.Entry<?,?>)) return false;
            @SuppressWarnings("unchecked")
      Map.Entry<K,V> e = (Map.Entry<K,V>)o; // unchecked cast
      Object ev = e.getValue();
      WeakKey wk = WeakKeyCreate(e.getKey());
      Object hv = hash.get(wk);
      if ((hv == null)
    ? ((ev == null) && hash.containsKey(wk)) : hv.equals(ev)) {
    hash.remove(wk);
    return true;
      }
      return false;
  }

  /*@Pure*/
  @Override
  public int hashCode() {
      int h = 0;
      for (Iterator<Map.Entry<WeakKey,V>> i = hashEntrySet.iterator(); i.hasNext(); ) {
    Map.Entry<WeakKey,V> ent = i.next();
    WeakKey wk = ent.getKey();
    Object v;
    if (wk == null) continue;
    h += (wk.hashCode()
          ^ (((v = ent.getValue()) == null) ? 0 : v.hashCode()));
      }
      return h;
  }

    }


    private /*@Nullable*/ Set<Map.Entry<K,V>> entrySet = null;

    /**
     * Returns a <code>Set</code> view of the mappings in this map.
     */
    /*@SideEffectFree*/
    @Override
    public Set<Map.Entry<K,V>> entrySet() {
  if (entrySet == null) entrySet = new EntrySet();
  return entrySet;
    }

    // find matching key
    K findKey(Object key) {
      processQueue();
      K kkey = (K) key;
      // TODO: use replacement for HashMap to avoid reflection
      WeakKey wkey = WeakKeyCreate(kkey);
      WeakKey found = hashMap_findKey(hash, wkey);
      return found == null ? null : found.get();
    }
}
static class PersistableThrowable extends DynamicObject {
  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;
  }
  
  RuntimeException asRuntimeException() {
    return new Fail(this);
  }
}
static interface IVF1<A> {
  void get(A a);
}
static class Pair<A, B> implements Comparable<Pair<A, B>> {
   final public Pair<A, B> setA(A a){ return a(a); }
public Pair<A, B> a(A a) { this.a = a; return this; }  final public A getA(){ return a(); }
public A a() { return a; }
 A a;
   final public Pair<A, B> setB(B b){ return b(b); }
public Pair<A, B> b(B b) { this.b = b; return this; }  final public B getB(){ return b(); }
public B b() { return b; }
 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 class Fail extends RuntimeException implements IFieldsToList{
  Object[] objects;
  Fail() {}
  Fail(Object... objects) {
  this.objects = objects;}public Object[] _fieldsToList() { return new Object[] {objects}; }

  Fail(Throwable cause, Object... objects) {
    super(cause);
  this.objects = objects;
  }
  
  public String toString() { return joinNemptiesWithColon("Fail", commaCombine(getCause(), objects)); }
}


static class PingSourceCancelledException extends RuntimeException implements IFieldsToList{
  PingSource pingSource;
  PingSourceCancelledException() {}
  PingSourceCancelledException(PingSource pingSource) {
  this.pingSource = pingSource;}
  public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + pingSource + ")"; }public Object[] _fieldsToList() { return new Object[] {pingSource}; }

}
static interface IFieldsToList {
  Object[] _fieldsToList();
}
static interface ISleeper_v2 {
  Sleeping doLater(Timestamp targetTime, Runnable r);
  
  public default Sleeping doAfter(double seconds, Runnable r) {
    return doLater(tsNow().plusSeconds(seconds), r);
  }
}
// The idea is to leave max as the actual number of cores the system
// has (numberOfCores()), and in case of being fully booked, raise an
// alert (customerMustWaitAlert) which can be handled by a strategy
// object (different reactions are possible).
// If nothing is done in such an event, clients are processed serially
// (no guarantees of order), split up among the available threads.

/* SYNChronisation order:
      1. PooledThread
      2. ThreadPool */

static class ThreadPool implements AutoCloseable {
  int max = numberOfCores();
  List<PooledThread> all = new ArrayList();
  Set<PooledThread> used = new HashSet();
  Set<PooledThread> free = new HashSet();
  boolean verbose, retired;
  
  // our own ping surce so we can start threads & keep them running
  class InternalPingSource extends PingSource {}
  InternalPingSource internalPingSource = new InternalPingSource();
  
  MultiSleeper sleeper = new MultiSleeper();
  
  ThreadPool() {}
  ThreadPool(int max) {
  this.max = max;}
  
  synchronized int maxSize() { return max; }
  synchronized int total() { return l(used)+l(free); }
  
  transient Set<Runnable> onCustomerMustWaitAlert;
public ThreadPool onCustomerMustWaitAlert(Runnable r) { onCustomerMustWaitAlert = createOrAddToSyncLinkedHashSet(onCustomerMustWaitAlert, r); return this; }
public ThreadPool removeCustomerMustWaitAlertListener(Runnable r) { main.remove(onCustomerMustWaitAlert, r); return this; }
public void customerMustWaitAlert() {  if (onCustomerMustWaitAlert != null) for (var listener : onCustomerMustWaitAlert) pcallF_typed(listener); }
  
  void fireCustomerMustWaitAlert() {
    vmBus_send("customerMustWaitAlert", this, currentThread());
    customerMustWaitAlert();
  }

  // DOESN'T WAIT. adds action to a thread's queue if nothing is
  // available immediately.
  PooledThread acquireThreadOrQueue(Runnable action) {
    if (action == null) return null;
    PooledThread t;
    synchronized(this) {
      if (_hasFreeAfterCreating()) {
        t = _firstFreeThread();
        markUsed(t);
      } else
        t = _anyThread();
    }
    
    t.addWork(action); // will move it from free to used
    return t;
  }
  
  // run in synchronized block
  boolean _hasFreeAfterCreating() {
    checkNotRetired();
    if (nempty(free)) return true;
    if (total() < max) {
      PooledThread t = newThread();
      all.add(t);
      free.add(t);
      return true;
    }
    return false;
  }
  
  // WAITS until thread is available
  PooledThread acquireThreadOrWait(Runnable action) { try {
    if (action == null) return null;
    PooledThread t;
    while (true) {
      synchronized(this) {
        if (_hasFreeAfterCreating()) {
          t = _firstFreeThread();
          break;
        } else
          _waitWaitWait();
      }
    }
    t.addWork(action);
    return t;
  } catch (Exception __e) { throw rethrow(__e); } }
  
  PooledThread _firstFreeThread() {
    return first(free);
  }
  
  PooledThread _anyThread() {
    return random(used);
  }
  
  class PooledThread extends Thread {
    PooledThread(String name) { super(name); }
    
    AppendableChain<Runnable> q;
    
    synchronized Runnable _grabWorkOrSleep() { try {
      Runnable r = first(q);
      if (r == null) {
        markFree(this);
        if (verbose) print("Thread sleeps");
        synchronized(this) { wait(); }
        if (verbose) print("Thread woke up");
        return null;
      }
      q = popFirst(q);
      return r;
    } catch (Exception __e) { throw rethrow(__e); } }
    
    public void run() { try {
      pingSource_tl().set(internalPingSource);
      while  (!retired()) { ping(); 
        Runnable r = _grabWorkOrSleep();
        if (verbose) print(this + " work: " + r);
        if (r != null)
          try {
            if (verbose) print(this + " running: " + r);
            r.run();
            pingSource_tl().set(internalPingSource);
            if (verbose) print(this + " done");
          } catch (Throwable e) {
            pingSource_tl().set(internalPingSource);
            if (verbose) print(this + " error");
            printStackTrace(e);
          } finally {
            pingSource_tl().set(internalPingSource);          
            if (verbose) print("ThreadPool finally");
          }
      }
    } catch (Exception __e) { throw rethrow(__e); } }
    
    synchronized boolean isEmpty() { return empty(q); }
    
    // append to q (do later)
    void addWork(Runnable r) {
      if (verbose) print("Added work to " + this + ": " + r);
      synchronized(this) {
        q = chainPlus(q, r);
        notifyAll();
      }
    }  
  }
  
  PooledThread newThread() {
    PooledThread t = new PooledThread("Thread Pool Inhabitant " + n2(total()+1));
    t.start();
    return t;
  }
  
  synchronized void markFree(PooledThread t) {
    used.remove(t);
    free.add(t);
    notifyAll();
  }
  
  synchronized void markUsed(PooledThread t) {
    free.remove(t);
    used.add(t);
  }
  
  synchronized public String toString() {
    return retired()
      ? "Retired ThreadPool"
      : "ThreadPool " + roundBracket(commaCombine(
        n2(used) + " used out of " + n2(total()),
        max <= total() ? null : "could grow to " + n2(max)));
  }
  
  synchronized boolean retired() { return retired; }
  synchronized void retire() {
    if (verbose) print("ThreadPool Retiring");
    retired = true;
    for (var thread : free) syncNotifyAll(thread); // wake it up so it exits
  }
  void checkNotRetired() {   
    if (retired()) throw fail("retired");
  }
 
  // We could do a soft-close here (stop the idle threads, let running threads finish, then end those too, stop accepting new orders)
  // or a hard close (interrupt all threads, stop accepting new orders)
  synchronized public void close() { try {
    retire();
  } catch (Exception __e) { throw rethrow(__e); } }
  
  // run in synchronized block
  void _waitWaitWait() { try {
    do {
      fireCustomerMustWaitAlert();
      wait();
      checkNotRetired();
    } while (empty(free));
  } catch (Exception __e) { throw rethrow(__e); } }
  
  void dO(String text, Runnable r) {
    if (r == null) return;
    new PingSource(this, text).dO(r);
  }
  
  ISleeper_v2 sleeper() { return sleeper; }
}
interface IMultiMap<A, B> {
  public Set<A> keySet();
  public Collection<B> get(A a);
  public int size();
  public int keyCount();
}


abstract static class Sleeping implements AutoCloseable , IFieldsToList{
  Timestamp targetTime;
  Runnable action;
  Sleeping() {}
  Sleeping(Timestamp targetTime, Runnable action) {
  this.action = action;
  this.targetTime = targetTime;}
  public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + targetTime + ", " + action + ")"; }public Object[] _fieldsToList() { return new Object[] {targetTime, action}; }

  long remainingMS() { return targetTime.minus(tsNow()); }
}
// AppendableChain has one "smart" head element (with size counter
// and pointer to the chain's last element), all the other nodes are
// maximally simple (MinimalChain).
// This allows O(1) front insertion, front removal and back insertion
// (not removal at the back though) which is fine for what I need this
// for (event queues).
//
// Stefan Reich, Oct 21

static class AppendableChain<A> extends MinimalChain<A> implements Iterable<A> {
  MinimalChain<A> last; // pointer to last element in chain (which may be us)
  int size; // total length of chain

  AppendableChain() {} // only used internally
  AppendableChain(A element) {
  this.element = element; size = 1; last = this; }
  
  // intermediate constructor called by itemPlusChain()
  AppendableChain(A element, AppendableChain<A> next) {
  this.next = next;
  this.element = element;
    if (next == null) return;
    
    MinimalChain<A> b = new MinimalChain();
    b.element = next.element;
    b.next = next.next;
    this.next = b;
    last = next.last;
    size = next.size+1;
  }
  
  public String toString() { return str(toList()); }
  
  // append at the end
  boolean add(A a) {
    MinimalChain newLast = new MinimalChain(a);
    last.next = newLast;
    last = newLast;
    ++size;
    return true;
  }
  
  // drop first element
  AppendableChain<A> popFirst() {
    if (next == null) return null;
    element = next.element;
    if (last == next) last = this;
    next = next.next;
    --size;
    return this;
  }
  
  ArrayList<A> toList() {
    ArrayList<A> l = emptyList(size);
    MinimalChain<A> c = this;
    while (c != null) {
      l.add(c.element);
      c = c.next;
    }
    return l;
  }
  
  //public Iterator<A> iterator() { ret toList().iterator(); }
  
  class ACIt extends IterableIterator  < A > {
    MinimalChain<A> c = AppendableChain.this;
    
    public boolean hasNext() {
      return c != null;
    }
    
    public A next() {
      var a = c.element;
      c = c.next;
      return a;
    }
  }
  
  public IterableIterator<A> iterator() {
    return new ACIt();
  }
}
static class MultiSleeper extends RestartableCountdown implements ISleeper_v2 {
  TreeMultiMap<Timestamp, Runnable> entries = new TreeMultiMap();
  
  void check() {
    var time = nextWakeUpTime();
    var action = firstValue(entries);
    setTargetTime(time == null ? 0 : time.sysTime(), new Runnable() {  public void run() { try { 
      List<Runnable> toCall;
      synchronized(MultiSleeper.this) {
        toCall = entries.get(time);
        entries.remove(time);
      }
      check();
      pcallFAll(toCall);
    
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "List<Runnable> toCall;\r\n      synchronized(MultiSleeper.this) {\r\n        toCa..."; }});
  }
  
  synchronized void removeEntry(Timestamp targetTime, Runnable action) {
    entries.remove(targetTime, action);
  }
  
  // API
  
  synchronized Timestamp nextWakeUpTime() {
    return firstKey(entries);
  }
  
  public synchronized Sleeping doLater(Timestamp targetTime, Runnable r) {
    if (r == null || targetTime == null) return null;
    targetTime = max(targetTime, tsNow());
    entries.put(targetTime, r);
    check();
    return new Sleeping(targetTime, r) {
      public void close() { try {
        removeEntry(targetTime, r);
      } catch (Exception __e) { throw rethrow(__e); } }
    };
  }
}
static class Timestamp implements Comparable<Timestamp> , IFieldsToList{
  long date;
  Timestamp(long date) {
  this.date = date;}

public boolean equals(Object o) {
if (!(o instanceof Timestamp)) return false;
    Timestamp __1 =  (Timestamp) o;
    return date == __1.date;
}

  public int hashCode() {
    int h = 2059094262;
    h = boostHashCombine(h, _hashCode(date));
    return h;
  }
  public Object[] _fieldsToList() { return new Object[] {date}; }

  Timestamp() { date = now(); }
  
  long unixDate() { return date; }
  
  public String toString() { return formatLocalDateWithSeconds(date); }
  
  // Hmm. Should Timestamp(0) be equal to null? Question, questions...
  public int compareTo(Timestamp t) {
    return t == null ? 1 : cmp(date, t.date);
  }
  
  Timestamp plus(Seconds seconds) {
    return plus(seconds == null ? null : seconds.getDouble());
  }
  
  final Timestamp plusSeconds(double seconds){ return plus(seconds); }
Timestamp plus(double seconds) {
    return new Timestamp(date+toMS(seconds));
  }
  
  // returns milliseconds
  long minus(Timestamp ts) {
    return unixDate()-ts.unixDate();
  }
  
  long sysTime() {
    return clockTimeToSystemTime(date);
  }
  
  Duration minusAsDuration(Timestamp ts) {
    return Duration.ofMillis(minus(ts));
  }
}


static class MinimalChain<A> implements Iterable<A> {
  A element;
  MinimalChain<A> next;

  MinimalChain() {}
  MinimalChain(A element) {
  this.element = element;}
  MinimalChain(A element, MinimalChain<A> next) {
  this.next = next;
  this.element = element;}
  
  public String toString() { return str(toList()); }
  
  ArrayList<A> toList() {
    ArrayList<A> l = new ArrayList();
    MinimalChain<A> c = this;
    while (c != null) {
      l.add(c.element);
      c = c.next;
    }
    return l;
  }
  
  void setElement(A a) { element = a; }
  void setNext(MinimalChain<A> next) { this.next = next; }
  
  // TODO: optimize
  public Iterator<A> iterator() { return toList().iterator(); }
  
  A get() { return element; }
}
static class RestartableCountdown implements AutoCloseable {
  java.util.Timer timer;
  long targetTime; // in sys time

  
  long /*firings,*/ totalSleepTime; // stats
  
  
  synchronized void setTargetTime(long targetTime, Runnable action) {
    if (targetTime <= 0)
      stop();
    else if (targetTime != this.targetTime) {
      start(targetTime-sysNow(), action);
      this.targetTime = targetTime;
    }
  }
  
  // stops the countdown and restarts it
  synchronized void start(long delayMS, Object action) {
    stop();
    if (delayMS <= 0)
      { startThread(new Runnable() {  public void run() { try {  callF(action); 
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "callF(action);"; }}); }
    else {
      
        totalSleepTime += delayMS;
      
      timer = doLater_daemon(delayMS, action);
      targetTime = sysNow()+delayMS;
    }
  }
  
  void start(double delaySeconds, Object action) {
    start(toMS(delaySeconds), action);
  }
  
  synchronized void stop() {
    cancelTimer(timer);
    timer = null;
    targetTime = 0;
  }
  
  public void close() { stop(); }
}
static class Seconds implements Comparable<Seconds> , IFieldsToList{
  double seconds;
  Seconds() {}
  Seconds(double seconds) {
  this.seconds = seconds;}

public boolean equals(Object o) {
if (!(o instanceof Seconds)) return false;
    Seconds __1 =  (Seconds) o;
    return seconds == __1.seconds;
}

  public int hashCode() {
    int h = -660217249;
    h = boostHashCombine(h, _hashCode(seconds));
    return h;
  }
  public Object[] _fieldsToList() { return new Object[] {seconds}; }

  final double get(){ return seconds(); }
final double getDouble(){ return seconds(); }
double seconds() { return seconds; }
  
  public String toString() { return formatDouble(seconds, 3) + " s"; }
  
  public int compareTo(Seconds s) {
    return cmp(seconds, s.seconds);
  }
  
  Seconds div(double x) { return new Seconds(get()/x); }
  Seconds minus(Seconds x) { return new Seconds(get()-x.get()); }
}
static class TreeMultiMap<A, B> extends MultiMap<A, B> {
  TreeMultiMap() { super(true); }
  TreeMultiMap(MultiMap<A, B> map) { this(); putAll(map); }
}


static Class<?> getClass(String name) {
  return _getClass(name);
}

static Class getClass(Object o) {
  return _getClass(o);
}

static Class getClass(Object realm, String name) {
  return _getClass(realm, name);
}


static boolean classIsExportedTo(Class c, java.lang.Module destModule) {
  if (c == null || destModule == null) return false;
  
  java.lang.Module srcModule = c.getModule();
  String packageName = c.getPackageName();
  return srcModule.isExported(packageName, destModule);
}


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(Object o) {
  return allInterfacesImplementedBy(_getClass(o));
}

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(String[] l) {
  return quoteAll(asList(l));
}

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 Thread startThread(Object runnable) {
  return startThread(defaultThreadName(), runnable);
}

static Thread startThread(String name, Runnable runnable) {
  runnable = wrapAsActivity(runnable);
  return startThread(newThread(runnable, name));
}

static Thread startThread(String name, Object runnable) {
  runnable = wrapAsActivity(runnable);
  return startThread(newThread(toRunnable(runnable), name));
}

static Thread startThread(Thread t) {
  
  _registerThread(t);
  
  t.start();
  return t;
}


static Class _run(String progID, String... args) {
  Class main = hotwire(progID);
  callMain(main, args);
  return main;
}


static Object pcallF(Object f, Object... args) {
  return pcallFunction(f, args);
}


static <A> A pcallF(F0<A> f) {
  try { return f == null ? null : f.get(); } catch (Throwable __e) { printStackTrace(__e); } return null;
}



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



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


static Object pcallF(Runnable r) {
  try { { if (r != null) r.run(); } } catch (Throwable __e) { printStackTrace(__e); } return null;
}

static <A> A pcallF(IF0<A> f) {
  try { return f == null ? null : f.get(); } catch (Throwable __e) { printStackTrace(__e); } return null;
}

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



static boolean warn_on = true;
static ThreadLocal<List<String>> warn_warnings = new ThreadLocal();

static void warn(String s) {
  if (warn_on)
    print("Warning: " + s);
}

static void warn(String s, List<String> warnings) {
  warn(s);
  if (warnings != null)
    warnings.add(s);
  addToCollection(warn_warnings.get(), s);
}


static boolean interruptThread_verbose = false;

static void interruptThread(Thread t) {
  if (t == null) return;
  if (interruptThread_verbose) print("Interrupting thread " + t);
  
  // note reason in global map
  
  vm_threadInterruptionReasonsMap().put(t, getStackTrace());
  
  t.interrupt();
  URLConnection c =  (URLConnection) (vm_generalSubMap("URLConnection per thread").get(t));
  if (c != null) { try {
    print("Closing URLConnection of interrupted thread.");
    call(c, "disconnect");
  } catch (Throwable __e) { printStackTrace(__e); }}
}


static <A> Iterator<A> iterator(Iterable<A> c) {
  return c == null ? emptyIterator() : c.iterator();
}


static UnsupportedOperationException unsupportedOperation() {
  throw new UnsupportedOperationException();
}


static double infinity() {
  return positiveInfinity();
}


static long toMS(double seconds) {
  return (long) (seconds*1000);
}




static BetterThreadLocal<PingSource> pingSource_tl_var = new BetterThreadLocal<PingSource>() {
  @Override
  public PingSource initialValue() {
    return ping_v3_pingSourceMaker().get(); 
  }
};

static BetterThreadLocal<PingSource> pingSource_tl() {
  return pingSource_tl_var;
}


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(BitSet bs) { return !empty(bs); }

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

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


static boolean nempty(IMultiMap mm) { return mm != null && mm.size() != 0; }



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









static boolean nempty(Rect r) { return r != null && r.w != 0 && r.h != 0; }







static <A, B> Map<A, B> putAll(Map<A, B> a, Map<? extends A,? extends B> b) {
  if (a != null && b != null) a.putAll(b);
  return a;
}


static <A, B> MultiMap<A, B> putAll(MultiMap<A, B> a, Map<? extends A,? extends B> b) {
  if (a != null) a.putAll((Map) b);
  return a;
}


static <A, B> Map<A, B> putAll(Map<A, B> a, Object... b) {
  if (a != null)
    litmap_impl(a, b);
  return a;
}



static <A> void remove(List<A> l, int i) {
  if (l != null && i >= 0 && i < l(l))
    l.remove(i);
}

static <A> void remove(Collection<A> l, A a) {
  if (l != null) l.remove(a);
}

static <A, B> B remove(Map<A, B> map, Object a) {
  return map == null ? null : map.remove(a);
}

static void remove(BitSet bs, int i) {
  bs.clear(i);
}


static <A> A getAndClear(IVar<A> v) {
  A a = v.get();
  v.set(null);
  return a;
}


static <A, B> Set<A> keys(Map<A, B> map) {
  return map == null ? new HashSet() : map.keySet();
}

// convenience shortcut for keys_gen
static Set keys(Object map) {
  return keys((Map) map);
}




  static <A, B> Set<A> keys(IMultiMap<A, B> mm) {
    return mm.keySet();
  }





static <A, B> Set<A> keySet(Map<A, B> map) {
  return map == null ? new HashSet() : map.keySet();
}

static Set keySet(Object map) {
  return keys((Map) map);
}




  static <A, B> Set<A> keySet(MultiMap<A, B> mm) {
    return mm.keySet();
  }





static <A, B> int keysSize(MultiMap<A, B> mm) {
  return lKeys(mm);
}


static <A> A reverseGet(List<A> l, int idx) {
  if (l == null || idx < 0) return null;
  int n = l(l);
  return idx < n ? l.get(n-1-idx) : null;
}


static <A, B> Map<A, B> cloneMap(Map<A, B> map) {
  if (map == null) return new HashMap();
  // assume mutex is equal to map
  synchronized(map) {
    return map instanceof TreeMap ? new TreeMap((TreeMap) map) // copies comparator
      : map instanceof LinkedHashMap ? new LinkedHashMap(map)
      : new HashMap(map);
  }
}

static <A, B> List<B> cloneMap(Iterable<A> l, IF1<A, B> f) {
  List x = emptyList(l);
  if (l != null) for (A o : cloneList(l))
    x.add(f.get(o));
  return x;
}


static <A, B> Collection<B> values(Map<A, B> map) {
  return map == null ? emptyList() : map.values();
}

// convenience shortcut for values_gen
static Collection values(Object map) {
  return values((Map) map);
}


static <A, B> Collection<B> values(MultiMap<A, B> mm) {
  return mm == null ? emptyList() : concatLists(values(mm.data));
}





static <A, B, C extends Collection<B>> List<B> allValues(Map<A, C> map) {
  List<B> out = new ArrayList();
  for (var l : values(map))
    addAll(out, l);
  return out;
}


static <A> List<A> concatLists(Iterable<A>... lists) {
  List<A> l = new ArrayList();
  if (lists != null) for (Iterable<A> list : lists)
    addAll(l, list);
  return l;
}

static <A> List<A> concatLists(Collection<? extends Iterable<A>> lists) {
  List<A> l = new ArrayList();
  if (lists != null) for (Iterable<A> list : lists)
    addAll(l, list);
  return l;
}



static Method hashMap_findKey_method;

static <A, B> A hashMap_findKey(HashMap<A, B> map, Object key) { try {
  if (hashMap_findKey_method == null)
    hashMap_findKey_method = findMethodNamed(HashMap.class, "getNode");
  Map.Entry<A, B> entry = (Map.Entry) hashMap_findKey_method.invoke(map, hashMap_internalHash(key), key);
  // java.util.Map.Entry<A, B> entry = (java.util.Map.Entry) call(hash, 'getNode, hashMap_internalHash(key), wkey);
  return entry == null ? null : entry.getKey();
} catch (Exception __e) { throw rethrow(__e); } }


static String a(String noun) {
  if (eq(noun, "")) return "?";
  return ("aeiou".indexOf(noun.charAt(0)) >= 0 ? "an " : "a ") + noun;
}

static String a(String contents, Object... params) {
  return hfulltag("a", contents, params);
}



static String b(Object contents, Object... params) {
  return tag("b", contents, params);
}


static int hashCodeFor(Object a) {
  return a == null ? 0 : a.hashCode();
}


static String joinNemptiesWithColon(String... strings) {
  return joinNempties(": ", strings);
}

static String joinNemptiesWithColon(Collection<String> strings) {
  return joinNempties(": ", strings);
}


static String commaCombine(Object... l) {
  return joinNemptiesWithComma(flattenCollectionsAndArrays(ll(l)));
}


static String shortClassName_dropNumberPrefix(Object o) {
  return dropNumberPrefix(shortClassName(o));
}


static java.util.Timer doLater(long delay, final Object r) {
  ping();
  final java.util.Timer timer = new java.util.Timer();
  timer.schedule(timerTask(r, timer), delay);
  return vmBus_timerStarted(timer);
}

static java.util.Timer doLater(double delaySeconds, final Object r) {
  return doLater(toMS(delaySeconds), r);
}


static Timestamp tsNow() {
  return new Timestamp();
}


static volatile int numberOfCores_value;

static int numberOfCores() {
  if (numberOfCores_value == 0)
    numberOfCores_value = Runtime.getRuntime().availableProcessors();
  return numberOfCores_value;
}


static <A> Set<A> createOrAddToSyncLinkedHashSet(Set<A> set, A a) {
  if (set == null) set = syncLinkedHashSet();
  set.add(a);
  return set;
}



static <A> A pcallF_typed(F0<A> f) {
  try { return f == null ? null : f.get(); } catch (Throwable __e) { printStackTrace(__e); } return null;
}



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



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


static <A> void pcallF_typed(IVF1<A> f, A a) {
  try {
    { if (f != null) f.get(a); }
  } catch (Throwable __e) { printStackTrace(__e); }
}

static <A, B> void pcallF_typed(IVF2<A, B> f, A a, B b) {
  try {
    { if (f != null) f.get(a, b); }
  } catch (Throwable __e) { printStackTrace(__e); }
}

static Object pcallF_typed(Runnable r) {
  try { { if (r != null) r.run(); } } catch (Throwable __e) { printStackTrace(__e); } return null;
}

static <A> A pcallF_typed(IF0<A> f) {
  try { return f == null ? null : f.get(); } catch (Throwable __e) { printStackTrace(__e); } return null;
}

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



// runnable = Runnable or String (method name)
static Thread newThread(Object runnable) {
  return new BetterThread(_topLevelErrorHandling(toRunnable(runnable)));
}

static Thread newThread(Object runnable, String name) {
  if (name == null) name = defaultThreadName();
  return new BetterThread(_topLevelErrorHandling(toRunnable(runnable)), name);
}

static Thread newThread(String name, Object runnable) {
  return newThread(runnable, name);
}


static int random(int n) { return random(n, defaultRandomGenerator()); }
static int random(int n, Random r) {
  return random(r, n);
}

static int random(Random r, int n) {
  return n <= 0 ? 0 : getRandomizer(r).nextInt(n);
}

static double random(double max) {
  return random()*max;
}

static double random() {
  return defaultRandomGenerator().nextInt(100001)/100000.0;
}

static double random(double min, double max) {
  return min+random()*(max-min);
}

// min <= value < max
static int random(int min, int max) {
  return min+random(max-min);
}

static int random(int min, int max, Random r) {
  return random(r, min, max);
}

static int random(Random r, int min, int max) {
  return min+random(r, max-min);
}

static <A> A random(List<A> l) {
  return oneOf(l);
}

static <A> A random(Collection<A> c) {
  if (c instanceof List) return random((List<A>) c);
  int i = random(l(c));
  return collectionGet(c, i);
}



static <A, B> Pair<A, B> random(Map<A, B> map) {
  return entryToPair(random(entries(map)));
}


static <A> A popFirst(List<A> l) {
  if (empty(l)) return null;
  A a = first(l);
  l.remove(0);
  return a;
}

static <A> A popFirst(Collection<A> l) {
  if (empty(l)) return null;
  A a = first(l);
  l.remove(a);
  return a;
}

static <A, B> Pair<A, B> popFirst(Map<A, B> map) {
  if (map == null) return null;
  var it = map.entrySet().iterator();
  if (!it.hasNext()) return null;
  var p = mapEntryToPair(it.next());
  it.remove();
  return p;
}

static <A> List<A> popFirst(int n, List<A> l) {
  List<A> part = cloneSubList(l, 0, n);
  removeSubList(l, 0, n);
  return part;
}

static <A> AppendableChain<A> popFirst(AppendableChain<A> a) {
  return a == null ? null : a.popFirst();
}


// Yes the nomenclature is a bit illogical

static <A> Chain<A> chainPlus(Chain<A> chain, A a) {
  return new Chain<A>(a, chain);
}

static <A> Chain<A> chainPlus(Chain<A> chain, A... l) {
  for (A a : unnullForIteration(l))
    chain = chainPlus(chain, a);
  return chain;
}

static <A> ReverseChain<A> chainPlus(ReverseChain<A> chain, A a) {
  return new ReverseChain<A>(chain, a);
}

static <A> ReverseChain<A> chainPlus(ReverseChain<A> chain, A... l) {
  for (A a : unnullForIteration(l))
    chain = chainPlus(chain, a);
  return chain;
}


static <A> AppendableChain<A> chainPlus(AppendableChain<A> chain, A a) {
  if (chain == null) return new AppendableChain<A>(a);
  chain.add(a);
  return chain;
}

static <A> AppendableChain<A> chainPlus(AppendableChain<A> chain, A... l) {
  for (A a : unnullForIteration(l))
    chain = chainPlus(chain, a);
  return chain;
}




static String n2(long l) { return formatWithThousands(l); }
static String n2(AtomicLong l) { return n2(l.get()); }
static String n2(Collection l) { return n2(l(l)); }
static String n2(Map map) { return n2(l(map)); }

static String n2(double l, String singular) {
  return empty(singular) ? str(l) : n2(l, singular, singular + "s");
}

static String n2(double l, String singular, String plural) {
  if (fraction(l) == 0)
    return n2((long) l, singular, plural);
  else
    return l + " " + plural;
}

static String n2(long l, String singular, String plural) {
  return n_fancy2(l, singular, plural);
}

static String n2(long l, String singular) {
  return empty(singular) ? n2(l) : n_fancy2(l, singular, singular + "s");
}

static String n2(Collection l, String singular) {
  return n2(l(l), singular);
}

static String n2(Collection l, String singular, String plural) {
  return n_fancy2(l, singular, plural);
}

static String n2(Map m, String singular, String plural) {
  return n_fancy2(m, singular, plural);
}

static String n2(Map m, String singular) {
  return n2(l(m), singular);
}

static String n2(long[] a, String singular) { return n2(l(a), singular); }

static String n2(Object[] a, String singular) { return n2(l(a), singular); }
static String n2(Object[] a, String singular, String plural) { return n_fancy2(a, singular, plural); }




  static String n2(IMultiMap mm, String singular) { return n2(mm, singular, singular + "s"); }
static String n2(IMultiMap mm, String singular, String plural) {
    return n_fancy2(l(mm), singular, plural);
  }



static String roundBracket(String s) {
  return "(" + s + ")";
}

static String roundBracket(Object s) {
  return roundBracket(str(s));
}


static void syncNotifyAll(Object o) {
  if (o != null) synchronized(o) { o.notifyAll(); }
}


static <A, B> B firstValue(Map<A, B> map) {
  return first(values(map));
}




static <A, B> B firstValue(MultiMap<A, B> map) {
  return map == null ? null : first(firstValue(map.data));
}



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 <A, B> A firstKey(Map<A, B> map) {
  return first(keys(map));
}


static <A, B> A firstKey(IMultiMap<A, B> map) {
  return map == null ? null : first(map.keySet());
}



static int boostHashCombine(int a, int b) {
  return a ^ (b + 0x9e3779b9 + (a << 6) + (a >>> 2));
  
  // OLD (changed) 2022/3/10: ret a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}




static String formatLocalDateWithSeconds(long time) {
  return localDateWithSeconds(time);
}



static String formatLocalDateWithSeconds() {
  return localDateWithSeconds();
}


static BigInteger plus(BigInteger a, BigInteger b) {
  return a.add(b);
}

static BigInteger plus(BigInteger a, long b) {
  return a.add(bigint(b));
}

static long plus(long a, long b) { return a+b; }
static int plus(int a, int b) { return a+b; }
static float plus(float a, float b) { return a+b; }
static double plus(double a, double b) { return a+b; }


static long clockTimeToSystemTime(long now) {
  return now == 0 ? 0 : now + clockToSysTimeDiff();
}


static <A> List<A> minus(Collection<A> a, Object... b) {
  Set set = asSet(b);
  List l = new ArrayList();
  for (Object s : unnull(a))
    if (!set.contains(s))
      l.add(s);
  return l;
}

static BigInteger minus(BigInteger a, BigInteger b) {
  return a.subtract(b);
}


static Complex minus(Complex c) {
  return c == null ? null : complex(-c.re(), -c.im());
}



  static int minus(int a, int b) {
    return a-b;
  }


  static double minus(double a, double b) {
    return a-b;
  }




static java.util.Timer doLater_daemon(long delay, final Object r) {
  final java.util.Timer timer = new java.util.Timer(true);
  timer.schedule(timerTask(r, timer), delay);
  return timer;
}

static java.util.Timer doLater_daemon(double delaySeconds, final Object r) {
  return doLater_daemon(toMS(delaySeconds), r);
}



static void cancelTimer(javax.swing.Timer timer) {
  if (timer != null) timer.stop();
}


static void cancelTimer(java.util.Timer timer) {
  if (timer != null) timer.cancel();
}

static void cancelTimer(Object o) {
  if (o instanceof java.util.Timer) cancelTimer((java.util.Timer) o);
  
  else if (o instanceof javax.swing.Timer) cancelTimer((javax.swing.Timer) o);
  
  else if (o instanceof AutoCloseable) { try { ((AutoCloseable) o).close(); } catch (Throwable __e) { printStackTrace(__e); }}
}


static int seconds() {
  return seconds(Calendar.getInstance());
}

static int seconds(Calendar c) {
  return c.get(Calendar.SECOND);
}




static boolean eqOneOf(Object o, Object... l) {
  if (l != null) for (Object x : l) if (eq(o, x)) return true; return false;
}


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 String defaultThreadName_name;

static String defaultThreadName() {
  if (defaultThreadName_name == null)
    defaultThreadName_name = "A thread by " + programID();
  return defaultThreadName_name;
}


static Runnable wrapAsActivity(Object r) {
  if (r == null) return null;
  Runnable r2 = toRunnable(r);


  Object mod = dm_current_generic();
  if (mod == null) return r2;
  return new Runnable() {  public void run() { try { 
    AutoCloseable c =  (AutoCloseable) (rcall("enter", mod));
     AutoCloseable __1 = c; try {
    r2.run();
  
} finally { _close(__1); }} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "AutoCloseable c =  (AutoCloseable) (rcall enter(mod));\r\n    temp c;\r\n    r2.r..."; }};

}


static Runnable toRunnable(final Object o) {
  if (o == null) return null;
  if (o instanceof Runnable) return (Runnable) o;
  
  if (o instanceof String) throw fail("callF_legacy");
  
  return new Runnable() {  public void run() { try {  callF(o) ;
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "callF(o)"; }};
}


static Map<Thread, Boolean> _registerThread_threads;
static Object _onRegisterThread; // voidfunc(Thread)

static Thread _registerThread(Thread t) {
  if (_registerThread_threads == null)
    _registerThread_threads = newWeakHashMap();
  _registerThread_threads.put(t, true);
  vm_generalWeakSubMap("thread2mc").put(t, weakRef(mc()));
  callF(_onRegisterThread, t);
  return t;
}

static void _registerThread() {
  _registerThread(Thread.currentThread());
}


// custom mainClass only works with hotwire_here
static Class<?> hotwire(String src) { return hotwire(src, __1 -> mainClassNameForClassLoader(__1)); }
static Class<?> hotwire(String src, IF1<ClassLoader, String> calculateMainClass) {
  assertFalse(_inCore());
  Class j = getJavaX();
  if (isAndroid()) {
    synchronized(j) { // hopefully this goes well...
      List<File> libraries = new ArrayList<File>();
      File srcDir = (File) call(j, "transpileMain", src, libraries);
      if (srcDir == null)
        throw fail("transpileMain returned null (src=" + quote(src) + ")");
    
      Object androidContext = get(j, "androidContext");
      return (Class) call(j, "loadx2android", srcDir, src);
    }
  } else {
    
    
    Class c =  (Class) (call(j, "hotwire", src));
    hotwire_copyOver(c);
    return c;
    
  }
}


static <A> A callMain(A c, String... args) {
  callOpt(c, "main", new Object[] {args});
  return c;
}

static void callMain() {
  callMain(mc());
}


static Object pcallFunction(Object f, Object... args) {
  try { return callFunction(f, args); } catch (Throwable __e) { printStackTrace(__e); }
  return null;
}


static <A> boolean addToCollection(Collection<A> c, A a) {
  return c != null && c.add(a);
}


static Map vm_generalSubMap(Object name) {
  synchronized(vm_generalMap()) {
    Map map =  (Map) (vm_generalMap_get(name));
    if (map == null)
      vm_generalMap_put(name, map = synchroMap());
    return map;
  }
}



static Iterator emptyIterator() {
  return Collections.emptyIterator();
}


static double positiveInfinity() {
  return Double.POSITIVE_INFINITY;
}


static IF0<PingSource> ping_v3_pingSourceMaker_cache;
static IF0<PingSource> ping_v3_pingSourceMaker() { if (ping_v3_pingSourceMaker_cache == null) ping_v3_pingSourceMaker_cache = ping_v3_pingSourceMaker_load(); return ping_v3_pingSourceMaker_cache;}

static IF0<PingSource> ping_v3_pingSourceMaker_load() {
  return or((IF0) vm_generalMap_get("ping_v3_pingSourceMaker"), () -> null);
}



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 <A, B> int lKeys(MultiMap<A, B> mm) {
  return mm == null ? 0 : mm.keysSize();
}


static <A, B extends A> void addAll(Collection<A> c, Iterable<B> b) {
  if (c != null && b != null) for (A a : b) c.add(a);
}

static <A, B extends A> boolean addAll(Collection<A> c, Collection<B> b) {
  return c != null && b != null && c.addAll(b);
}

static <A, B extends A> boolean addAll(Collection<A> c, B... b) {
  return c != null && b != null && c.addAll(Arrays.asList(b));
}



static <A, B> Map<A, B> addAll(Map<A, B> a, Map<? extends A,? extends B> b) {
  if (a != null && b != null) a.putAll(b);
  return a;
}

static <A extends Container> A addAll(A c, Collection<? extends Component> components) {
  return addComponents(c, components);
}

static <A extends Container> A addAll(A c, Component... components) {
  return addComponents(c, components);
}


// This is a bit rough... finds static and non-static methods.

static Method findMethodNamed(Object obj, String method) {
  if (obj == null) return null;
  if (obj instanceof Class)
    return findMethodNamed((Class) obj, method);
  return findMethodNamed(obj.getClass(), method);
}

static Method findMethodNamed(Class c, String method) {
  while (c != null) {
    for (Method m : c.getDeclaredMethods())
      if (m.getName().equals(method)) {
        makeAccessible(m);
        return m;
      }
    c = c.getSuperclass();
  }
  return null;
}


static int hashMap_internalHash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


static String hfulltag(String tag) {
  return hfulltag(tag, "");
}

static String hfulltag(String tag, Object contents, Object... params) {
  return hopeningTag(tag, params) + str(contents) + "</" + tag + ">";
}


static String tag(String tag) {
  return htag(tag);
}

static String tag(String tag, Object contents, Object... params) {
  return htag(tag, str(contents), params);
}

static String tag(String tag, StringBuilder contents, Object... params) {
  return htag(tag, contents, params);
}

static String tag(String tag, StringBuffer contents, Object... params) {
  return htag(tag, contents, params);
}


static String joinNempties(String sep, Object... strings) {
  return joinStrings(sep, strings);
}

static String joinNempties(String sep, Iterable strings) {
  return joinStrings(sep, strings);
}


static String joinNemptiesWithComma(Object... strings) {
  return joinNempties(", ", strings);
}

static String joinNemptiesWithComma(Iterable strings) {
  return joinNempties(", ", strings);
}


static List flattenCollectionsAndArrays(Iterable a) {
  List l = new ArrayList();
  for (Object x : a)
    if (x instanceof Collection)
      l.addAll(flattenCollectionsAndArrays((Collection) x));
    else if (x instanceof Object[])
      l.addAll(flattenCollectionsAndArrays(asList((Object[]) x)));
    else
      l.add(x);
  return l;
}


static String dropNumberPrefix(String s) {
  return dropFirst(s, indexOfNonDigit(s));
}


static String shortClassName(Object o) {
  if (o == null) return null;
  Class c = o instanceof Class ? (Class) o : o.getClass();
  String name = c.getName();
  return shortenClassName(name);
}


static TimerTask timerTask(final Object r, final java.util.Timer timer) {
  return new TimerTask() {
    public void run() {
      
      if (!licensed())
        timer.cancel();
      else
        pcallF(r);
    }
  };
}


static <A> A vmBus_timerStarted(A timer) {
  vmBus_send("timerStarted", timer, costCenter());
  return timer;
}


static <A> Set<A> syncLinkedHashSet() {
  return synchroLinkedHashSet();
}


static Runnable _topLevelErrorHandling(Runnable r) {


  if (r == null) return null;
  
  // maybe we don't want this anymore. just dm_current_generic()
  Object info = _threadInfo();
  Object mod = dm_current_generic();
  Runnable r2 = r;
  
  if (info != null || mod == null)
    r2 = new Runnable() {  public void run() { try { 
       AutoCloseable __1 =  (AutoCloseable) (rcall("enter", mod)); try {
      _threadInheritInfo(info);
      r.run();
    
} finally { _close(__1); }} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "temp (AutoCloseable) rcall enter(mod);\r\n      _threadInheritInfo(info);\r\n    ..."; }};
  r2 = rPcall(r2);
  return r2;

}


static Random defaultRandomGenerator() {
  { Random r = customRandomizerForThisThread(); if (r != null) return r; }
  return ThreadLocalRandom.current();
}


static Random getRandomizer(Random r) {
  return r != null ? r : defaultRandomGenerator();
}


static <A> A oneOf(List<A> l) {
  if (empty(l)) return null;
  int n = l.size();
  return n == 1 ? first(l) : l.get(defaultRandomizer().nextInt(n));
}

static char oneOf(String s) {
  return empty(s) ? '?' : s.charAt(random(l(s)));
}

static String oneOf(String... l) {
  return oneOf(asList(l));
}


static <A> A collectionGet(Collection<A> c, int idx) {
  if (c == null || idx < 0 || idx >= l(c)) return null;
  if (c instanceof List) return listGet((List<A>) c, idx);
  Iterator<A> it = c.iterator();
  for (int i = 0; i < idx; i++) if (it.hasNext()) it.next(); else return null;
  return it.hasNext() ? it.next() : null;
}


static <A, B> Pair<A, B> entryToPair(Map.Entry<A, B> e) {
  return mapEntryToPair(e);
}


static <A, B> Set<Map.Entry<A,B>> entries(Map<A, B> map) {
  return _entrySet(map);
}


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

static <A> List<A> cloneSubList(List<A> l, int startIndex) {
  return newSubList(l, startIndex);
}


static void removeSubList(List l, int from, int to) {
  if (l != null) subList(l, from, to).clear();
}

static void removeSubList(List l, int from) {
  if (l != null) subList(l, from).clear();
}


static String formatWithThousands(long l) {
  return formatWithThousandsSeparator(l);
}


static double fraction(double d) {
  return d % 1;
}


static String n_fancy2(long l, String singular, String plural) {
  return formatWithThousandsSeparator(l) + " " + trim(l == 1 ? singular : plural);
}

static String n_fancy2(Collection l, String singular, String plural) {
  return n_fancy2(l(l), singular, plural);
}

static String n_fancy2(Map m, String singular, String plural) {
  return n_fancy2(l(m), singular, plural);
}

static String n_fancy2(Object[] a, String singular, String plural) {
  return n_fancy2(l(a), singular, plural);
}






static String localDateWithSeconds(long time) {
  SimpleDateFormat format = simpleDateFormat_local("yyyy/MM/dd HH:mm:ss");
  return format.format(time);
}

static String localDateWithSeconds() {
  return localDateWithSeconds(now());
}


static BigInteger bigint(String s) {
  return new BigInteger(s);
}

static BigInteger bigint(long l) {
  return BigInteger.valueOf(l);
}


static long clockToSysTimeDiff() {
  return sysNow()-now();
}


static Set asSet(Object[] array) {
  HashSet set = new HashSet();
  for (Object o : array)
    if (o != null)
      set.add(o);
  return set;
}

static Set<String> asSet(String[] array) {
  TreeSet<String> set = new TreeSet();
  for (String o : array)
    if (o != null)
      set.add(o);
  return set;
}

static <A> Set<A> asSet(Iterable<A> l) {
  if (l instanceof Set) return (Set) l;
  HashSet<A> set = new HashSet();
  for (A o : unnull(l))
    if (o != null)
      set.add(o);
  return set;
}




static Complex complex(double re, double im) {
  return new Complex(re, im);
}

static Complex complex(double re) {
  return new Complex(re, 0.0);
}

static Complex complex(double[] reIm) {
  if (empty(reIm)) return null;
  if (l(reIm) != 2) throw fail("Need 2 doubles to make complex number");
  return complex(reIm[0], reIm[1]);
}




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 String programID() {
  return getProgramID();
}

static String programID(Object o) {
  return getProgramID(o);
}


static Object dm_current_generic() {
  return getWeakRef(dm_current_generic_tl().get());
}


static Object rcall(String method, Object o, Object... args) {
  return call_withVarargs(o, method, args);
}


static <A> WeakReference<A> weakRef(A a) {
  return newWeakReference(a);
}


static String mainClassNameForClassLoader(ClassLoader cl) {
  return or((String) callOpt(cl, "mainClassName"), "main");
}


static void assertFalse(Object o) {
  if (!(eq(o, false) /*|| isFalse(pcallF(o))*/))
    throw fail(str(o));
}
  
static boolean assertFalse(boolean b) {
  if (b) throw fail("oops");
  return b;
}

static boolean assertFalse(String msg, boolean b) {
  if (b) throw fail(msg);
  return b;
}



static boolean _inCore() {
  return false;
}


static List hotwire_copyOver_after = synchroList();

static void hotwire_copyOver(Class c) {
  // TODO: make a mechanism for making such "inheritable" fields
  for (String field : ll("print_log", "print_silent", "androidContext", "_userHome"))
    setOptIfNotNull(c, field, getOpt(mc(), field));
    
  
  
  setOptIfNotNull(c, "mainBot" , getMainBot());
  setOpt(c, "creator_class" , new WeakReference(mc()));
  pcallFAll(hotwire_copyOver_after, c);
}


static <A extends Container> A addComponents(A c, Collection<? extends Component> components) {
  if (nempty(components)) { swing(() -> { 
    for (Component comp : components)
      if (comp != null)
        c.add(comp);
    revalidate(c);
  }); }
  return c;
}

static <A extends Container> A addComponents(A c, Component... components) {
  return addComponents(c, asList(components));
}


static String hopeningTag(String tag, Map params) {
  return hopeningTag(tag, mapToParams(params));
}

static String hopeningTag(String tag, Object... params) {
  StringBuilder buf = new StringBuilder();
  buf.append("<" + tag);
  params = unrollParams(params);
  for (int i = 0; i < l(params); i += 2) {
    String name = (String) get(params, i);
    Object val = get(params, i+1);
    if (nempty(name) && val != null) {
      if (eqOneOf(val, html_valueLessParam(), true))
        buf.append(" " + name);
      else {
        String s = str(val);
        if (!empty(s))
          buf.append(" " + name + "=" + htmlQuote(s));
      }
    }
  }
  buf.append(">");
  return str(buf);
}


static String htag(String tag) {
  return htag(tag, "");
}

static String htag(String tag, Object contents, Object... params) {
  String openingTag = hopeningTag(tag, params);
  String s = str(contents);
  if (empty(s) && neqic(tag, "script"))
    return dropLast(openingTag) + "/>";
  return openingTag + s + "</" + tag + ">";
}


static String joinStrings(String sep, Object... strings) {
  return joinStrings(sep, Arrays.asList(strings));
}

static String joinStrings(String sep, Iterable strings) {
  StringBuilder buf = new StringBuilder();
  for (Object o : unnull(strings)) { 
    String s = strOrNull(o);
    if (nempty(s)) {
      if (nempty(buf)) buf.append(sep);
      buf.append(s);
    }
  }
  return str(buf);
}


static String[] dropFirst(int n, String[] a) {
  return drop(n, a);
}

static String[] dropFirst(String[] a) {
  return drop(1, a);
}

static Object[] dropFirst(Object[] a) {
  return drop(1, a);
}

static <A> List<A> dropFirst(List<A> l) {
  return dropFirst(1, l);
}

static <A> List<A> dropFirst(int n, Iterable<A> i) { return dropFirst(n, toList(i)); }
static <A> List<A> dropFirst(Iterable<A> i) { return dropFirst(toList(i)); }

static <A> List<A> dropFirst(int n, List<A> l) {
  return n <= 0 ? l : new ArrayList(l.subList(Math.min(n, l.size()), l.size()));
}

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

static String dropFirst(int n, String s) { return substring(s, n); }
static String dropFirst(String s, int n) { return substring(s, n); }
static String dropFirst(String s) { return substring(s, 1); }


static <A> Chain<A> dropFirst(Chain<A> c) {
  return c == null ? null : c.next;
}



static int indexOfNonDigit(String s) {
  int n = l(s);
  for (int i = 0; i < n; i++)
    if (!isDigit(s.charAt(i)))
      return i;
  return -1;
}


static String shortenClassName(String name) {
  if (name == null) return null;
  int i = lastIndexOf(name, "$");
  if (i < 0) i = lastIndexOf(name, ".");
  return i < 0 ? name : substring(name, i+1);
}


static Object costCenter() { return mc(); }


// BREAKING CHANGE!
// Also NOTE: Iterators of these sync-wrapped collections
// after generally NOT thread-safe!
// TODO: change that?
static <A> Set<A> synchroLinkedHashSet() {
  
  
  return Collections.synchronizedSet(new CompactLinkedHashSet());
  
}


static List<VF1<Map>> _threadInfo_makers = synchroList();

static Object _threadInfo() {
  if (empty(_threadInfo_makers)) return null;
  HashMap map = new HashMap();
  pcallFAll(_threadInfo_makers, map);
  return map;
}


static List<VF1<Map>> _threadInheritInfo_retrievers = synchroList();

static void _threadInheritInfo(Object info) {
  if (info == null) return;
  pcallFAll(_threadInheritInfo_retrievers, (Map) info);
}


static Runnable rPcall(Runnable r) {
  return r == null ? null : () -> { try { r.run(); } catch (Throwable __e) { printStackTrace(__e); } };
}


static Random customRandomizerForThisThread() {
  return customRandomizerForThisThread_tl().get();
}


static Random defaultRandomizer() {
  return defaultRandomGenerator();
}


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


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

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


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

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

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 String formatWithThousandsSeparator(long l) {
  return NumberFormat.getInstance(new Locale("en_US")).format(l);
}


static String trim(String s) { return s == null ? null : s.trim(); }
static String trim(StringBuilder buf) { return buf.toString().trim(); }
static String trim(StringBuffer buf) { return buf.toString().trim(); }




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



static String programID;
static String getProgramID() {
  return nempty(programID) ? formatSnippetIDOpt(programID) : "?";
}


// TODO: ask JavaX instead
static String getProgramID(Class c) {
  String id = (String) getOpt(c, "programID");
  if (nempty(id))
    return formatSnippetID(id);
  return "?";
}


static String getProgramID(Object o) {
  return getProgramID(getMainClass(o));
}


static <A> A getWeakRef(Reference<A> ref) {
  return ref == null ? null : ref.get();
}


static x30_pkg.x30_util.BetterThreadLocal<WeakReference> dm_current_generic_tl;

static x30_pkg.x30_util.BetterThreadLocal<WeakReference> dm_current_generic_tl() {
  if (dm_current_generic_tl == null)
    dm_current_generic_tl = vm_generalMap_getOrCreate("currentModule", () -> new x30_pkg.x30_util.BetterThreadLocal());
  return dm_current_generic_tl;
}


static <A> WeakReference<A> newWeakReference(A a) {
  return a == null ? null : new WeakReference(a);
}


static void setOptIfNotNull(Object o, String field, Object value) {
  if (value != null) setOpt(o, field, value);
}


static Object mainBot;

static Object getMainBot() {
  return mainBot;
}


static Field setOpt_findField(Class c, String field) {
  HashMap<String, Field> map;
  synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  return map.get(field);
}

static void setOpt(Object o, String field, Object value) { try {
  if (o == null) return;
  
  
  
  Class c = o.getClass();
  HashMap<String, Field> map;
  
  if (getOpt_cache == null)
    map = getOpt_makeCache(c); // in class init
  else synchronized(getOpt_cache) {
    map = getOpt_cache.get(c);
    if (map == null)
      map = getOpt_makeCache(c);
  }
  
  if (map == getOpt_special) {
    if (o instanceof Class) {
      setOpt((Class) o, field, value);
      return;
    }
    
    // It's probably a subclass of Map. Use raw method. TODO: huh?
    setOpt_raw(o, field, value);
    return;
  }
  
  Field f = map.get(field);
  
  if (f != null)
    { smartSet(f, o, value); return; } // possible improvement: skip setAccessible
  
    if (o instanceof DynamicObject)
      { setDyn(((DynamicObject) o), field, value); return; }
  
  if (o instanceof IMeta)
    setDyn(((IMeta) o), field, value);
} catch (Exception __e) { throw rethrow(__e); } }

static void setOpt(Class c, String field, Object value) {
  if (c == null) return;
  try {
    Field f = setOpt_findStaticField(c, field); // TODO: optimize
    if (f != null)
      smartSet(f, null, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
  
static Field setOpt_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) {
        makeAccessible(f);
        return f;
      }
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}


static Object swing(Object f) {
  return swingAndWait(f);
}

static void swing(Runnable f) {
  swingAndWait(f);
}

static <A> A swing(F0<A> f) {
  return (A) swingAndWait(f);
}

static <A> A swing(IF0<A> f) {
  return (A) swingAndWait(f);
}


static <A extends Component> A revalidate(final A c) {
  if (c == null || !c.isShowing()) return c;
  { swing(() -> { 
    // magic combo to actually relayout and repaint
    c.revalidate();
    c.repaint();
  }); }
  return c;
}

static void revalidate(JFrame f) { revalidate((Component) f); }
static void revalidate(JInternalFrame f) { revalidate((Component) f); }


static Object[] mapToParams(Map map) {
  return mapToObjectArray(map);
}


static Object[] unrollParams(Object[] params) {
  if (l(params) == 1 && params[0] instanceof Map)
    return mapToParams((Map) params[0]);
  return params;
}


static Object html_valueLessParam_cache;
static Object html_valueLessParam() { if (html_valueLessParam_cache == null) html_valueLessParam_cache = html_valueLessParam_load(); return html_valueLessParam_cache;}

static Object html_valueLessParam_load() {
  return new Object();
}


static String htmlQuote(String s) {
  return "\"" + htmlencode_forParams(s) + "\"";
}


static boolean neqic(String a, String b) {
  return !eqic(a, b);
}

static boolean neqic(char a, char b) {
  return !eqic(a, b);
}


static String strOrNull(Object o) {
  return o == null ? null : str(o);
}


static String[] drop(int n, String[] a) {
  n = Math.min(n, a.length);
  String[] b = new String[a.length-n];
  System.arraycopy(a, n, b, 0, b.length);
  return b;
}

static Object[] drop(int n, Object[] a) {
  n = Math.min(n, a.length);
  Object[] b = new Object[a.length-n];
  System.arraycopy(a, n, b, 0, b.length);
  return b;
}


static boolean isDigit(char c) {
  return Character.isDigit(c);
}


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

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

// starts searching from i-1
static <A> int lastIndexOf(List<A> l, int i, A a) {
  if (l == null) return -1;
  for (i = min(l(l), i)-1; i >= 0; i--)
    if (eq(l.get(i), a))
      return i;
  return -1;
}

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


static ThreadLocal<Random> customRandomizerForThisThread_tl = new ThreadLocal();

static ThreadLocal<Random> customRandomizerForThisThread_tl() {
  return customRandomizerForThisThread_tl;
}




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


static String formatSnippetIDOpt(String s) {
  return isSnippetID(s) ? formatSnippetID(s) : s;
}


static String formatSnippetID(String id) {
  return "#" + parseSnippetID(id);
}

static String formatSnippetID(long id) {
  return "#" + id;
}


static Class getMainClass() {
  return mc();
}

static Class getMainClass(Object o) { try {
  if (o == null) return null;
  if (o instanceof Class && eq(((Class) o).getName(), "x30")) return (Class) o;
  ClassLoader cl = (o instanceof Class ? (Class) o : o.getClass()).getClassLoader();
  if (cl == null) return null;
  String name = mainClassNameForClassLoader(cl);
  return loadClassFromClassLoader_orNull(cl, name);
} catch (Exception __e) { throw rethrow(__e); } }


// TODO: optimize (use getOpt_cache)
static void setOpt_raw(Object o, String field, Object value) { try {
  if (o == null) return;
  if (o instanceof Class) setOpt_raw((Class) o, field, value);
  else {
    Field f = setOpt_raw_findField(o.getClass(), field);
    if (f != null) {
      makeAccessible(f);
      smartSet(f, o, value);
    }
  }
} catch (Exception __e) { throw rethrow(__e); } }

static void setOpt_raw(Class c, String field, Object value) { try {
  if (c == null) return;
  Field f = setOpt_raw_findStaticField(c, field);
  if (f != null) {
    makeAccessible(f);
    smartSet(f, null, value);
  }
} catch (Exception __e) { throw rethrow(__e); } }
  
static Field setOpt_raw_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 Field setOpt_raw_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 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)
      { f.set(o, ((Long) value).intValue()); return; }
      
    if (type == boolean.class && value instanceof String)
      { f.set(o, isTrueOrYes(((String) value))); return; }
    
    if (type == LinkedHashMap.class && value instanceof Map)
      { f.set(o, asLinkedHashMap((Map) value)); return; }
    
    
    throw e;
  }
}


static <A extends DynamicObject> A setDyn(A o, String key, Object value) {
  setDynObjectValue(o, key, value);
  return o;
}

static void setDyn(IMeta o, String key, Object value) {
  metaMapPut(o, key, value);
}


static void swingAndWait(Runnable r) { try {
  if (isAWTThread())
    r.run();
  else
    EventQueue.invokeAndWait(addThreadInfoToRunnable(r));
} catch (Exception __e) { throw rethrow(__e); } }

static Object swingAndWait(final Object f) {
  if (isAWTThread())
    return callF(f);
  else {
    final Var result = new Var();
    swingAndWait(new Runnable() {  public void run() { try { 
      result.set(callF(f));
    
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "result.set(callF(f));"; }});
    return result.get();
  }
}


static Object[] mapToObjectArray(Map map) {
  List l = new ArrayList();
  for (Object o : keys(map)) {
    l.add(o);
    l.add(map.get(o));
  }
  return toObjectArray(l);
}

static Object[] mapToObjectArray(Object f, Collection l) {
  int n = l(l);
  Object[] array = new Object[n];
  if (n != 0) {
    Iterator it = iterator(l);
    for (int i = 0; i < n; i++)
      array[i] = callF(f, it.next());
  }
  return array;
}

static Object[] mapToObjectArray(Object f, Object[] l) {
  int n = l(l);
  Object[] array = new Object[n];
  for (int i = 0; i < n; i++)
    array[i] = callF(f, l[i]);
  return array;
}

static <A> Object[] mapToObjectArray(Collection<A> l, IF1<A, Object> f) {
  return mapToObjectArray(f, l);
}

static <A> Object[] mapToObjectArray(A[] l, IF1<A, Object> f) {
  return mapToObjectArray(f, l);
}

static <A> Object[] mapToObjectArray(IF1<A, Object> f, A[] l) {
  int n = l(l);
  Object[] array = new Object[n];
  for (int i = 0; i < n; i++)
    array[i] = f.get(l[i]);
  return array;
}

static <A> Object[] mapToObjectArray(IF1<A, Object> f, Collection<A> l) {
  int n = l(l);
  Object[] array = new Object[n];
  if (n != 0) {
    Iterator it = iterator(l);
    for (int i = 0; i < n; i++)
      array[i] = callF(f, it.next());
  }
  return array;
}


// this should be on by default now I think, but it may break
// legacy code...
static ThreadLocal<Boolean> htmlencode_forParams_useV2 = new ThreadLocal();

static String htmlencode_forParams(String s) {
  if (s == null) return "";
  if (isTrue(htmlencode_forParams_useV2.get()))
    return htmlencode_forParams_v2(s);
    
  StringBuilder out = new StringBuilder(Math.max(16, s.length()));
  for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      if (c > 127 || c == '"' || c == '<' || c == '>') {
          out.append("&#");
          out.append((int) c);
          out.append(';');
      } else
          out.append(c);
  }
  return out.toString();
}


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;
}




  public static boolean isSnippetID(String s) {
    try {
      parseSnippetID(s);
      return true;
    } catch (RuntimeException e) {
      return false;
    }
  }


public static long parseSnippetID(String snippetID) {
  long id = Long.parseLong(shortenSnippetID(snippetID));
  if (id == 0) throw fail("0 is not a snippet ID");
  return id;
}


static Class loadClassFromClassLoader_orNull(ClassLoader cl, String name) {
  try {
    return cl == null ? null : cl.loadClass(name);
  } catch (ClassNotFoundException e) {
    return null;
  }
}


static boolean isTrueOrYes(Object o) {
  return isTrueOpt(o) || o instanceof String && (eqicOneOf(((String) o), "1", "t", "true") || isYes(((String) o)));
}


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 void setDynObjectValue(DynamicObject o, String field, Object value) {
  dynamicObject_setRawFieldValue(o, field, value);
}


static void metaMapPut(IMeta o, Object key, Object value) {
  { if (o != null) o.metaPut(key, value); }
}

static void metaMapPut(Object o, Object key, Object value) {
  var meta = initIMeta(o);
  { if (meta != null) meta.metaPut(key, value); }
}


static Runnable addThreadInfoToRunnable(final Object r) {
  final Object info = _threadInfo();
  return info == null ? asRunnable(r) : new Runnable() {  public void run() { try {  _inheritThreadInfo(info); callF(r); 
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "_inheritThreadInfo(info); callF(r);"; }};
}


// binary legacy signature
static Object[] toObjectArray(Collection c) {
  return toObjectArray((Iterable) c);
}

static Object[] toObjectArray(Iterable c) {
  List l = asList(c);
  return l.toArray(new Object[l.size()]);
}



static String htmlencode_forParams_v2(String s) {
  if (s == null) return "";
  StringBuilder out = new StringBuilder(Math.max(16, s.length()));
  for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      if (c > 127 || c == '"' || c == '<' || c == '>' || c == '&') {
          out.append("&#");
          out.append((int) c);
          out.append(';');
      } else
          out.append(c);
  }
  return out.toString();
}




static String shortenSnippetID(String snippetID) {
  if (snippetID.startsWith("#"))
    snippetID = snippetID.substring(1);
  String httpBlaBla = "http://tinybrain.de/";
  if (snippetID.startsWith(httpBlaBla))
    snippetID = snippetID.substring(httpBlaBla.length());
  return "" + parseLong(snippetID);
}


static boolean isTrueOpt(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  return false;
}

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


static boolean eqicOneOf(String s, String... l) {
  for (String x : l) if (eqic(s, x)) return true; return false;
}


static List<String> isYes_yesses = litlist("y", "yes", "yeah", "y", "yup", "yo", "corect", "sure", "ok", "afirmative"); // << collapsed words, so "corect" means "correct"

static boolean isYes(String s) {
  return isYes_yesses.contains(collapseWord(toLowerCase(firstWord2(s))));
}


static void dynamicObject_setRawFieldValue(DynamicObject o, Object key, Object value) {
  if (o == null) return;
  
  // double sync, but should be OK here because of locking order o > o.fieldValues
  synchronized(o) {
    o.fieldValues = syncMapPut2_createLinkedHashMap((LinkedHashMap) o.fieldValues, key, value);
  }
}


static IMeta initIMeta(Object o) {
  if (o == null) return null;
  if (o instanceof IMeta) return ((IMeta) o);
  if (o instanceof JComponent) return initMetaOfJComponent((JComponent) o);
  
  // This is not really used. Try to use BufferedImageWithMeta instead
  if (o instanceof BufferedImage) return optCast(IMeta.class, ((BufferedImage) o).getProperty("meta"));
  return null;
}


static Runnable asRunnable(Object o) {
  return toRunnable(o);
}




static void _inheritThreadInfo(Object info) {
  _threadInheritInfo(info);
}




static long parseLong(String s) {
  if (empty(s)) return 0;
  return Long.parseLong(dropSuffix("L", s));
}

static long parseLong(Object s) {
  return Long.parseLong((String) s);
}


static <A> ArrayList<A> litlist(A... a) {
  ArrayList l = new ArrayList(a.length);
  for (A x : a) l.add(x);
  return l;
}


static String collapseWord(String s) {
  if (s == null) return "";
  StringBuilder buf = new StringBuilder();
  for (int i = 0; i < l(s); i++)
    if (i == 0 || !charactersEqualIC(s.charAt(i), s.charAt(i-1)))
      buf.append(s.charAt(i));
  return buf.toString();
}


static List<String> toLowerCase(List<String> strings) {
  List<String> x = new ArrayList();
  for (String s : strings)
    x.add(s.toLowerCase());
  return x;
}

static String[] toLowerCase(String[] strings) {
  String[] x = new String[l(strings)];
  for (int i = 0; i < l(strings); i++)
    x[i] = strings[i].toLowerCase();
  return x;
}

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


static String firstWord2(String s) {
  s = xltrim(s);
  if (empty(s)) return "";
  if (isLetterOrDigit(first(s)))
    return takeCharsWhile(__33 -> isLetterOrDigit(__33), s);
  else return "" + first(s);
}


static <A, B> LinkedHashMap<A, B> syncMapPut2_createLinkedHashMap(LinkedHashMap<A, B> map, A key, B value) {
  if (key != null)
    if (value != null) {
      if (map == null) map = new LinkedHashMap();
      synchronized(collectionMutex(map)) { map.put(key, value); }
    } else if (map != null) synchronized(collectionMutex(map)) { map.remove(key); }
  return map;
}


static IMeta initMetaOfJComponent(JComponent c) {
  if (c == null) return null;
  IMeta meta =  (IMeta) (c.getClientProperty(IMeta.class));
  if (meta == null)
    c.putClientProperty(IMeta.class, meta = new Meta());
  
  return meta;
}


static <A> A optCast(Class<A> c, Object o) {
  return isInstance(c, o) ? (A) o : null;
}




static String dropSuffix(String suffix, String s) {
  return nempty(suffix) && endsWith(s, suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}


static boolean charactersEqualIC(char c1, char c2) {
  if (c1 == c2) return true;
  char u1 = Character.toUpperCase(c1);
  char u2 = Character.toUpperCase(c2);
  if (u1 == u2) return true;
  return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}



static String xltrim(String s) {
  int i = 0, n = l(s);
  while (i < n && contains(" \t\r\n", s.charAt(i)))
    ++i;
  return substr(s, i);
}


static boolean isLetterOrDigit(char c) {
  return Character.isLetterOrDigit(c);
}


// pred: char -> bool
static String takeCharsWhile(String s, Object pred) {
  int i = 0;
  while (i < l(s) && isTrue(callF(pred, s.charAt(i)))) ++i;
  return substring(s, 0, i);
}

static String takeCharsWhile(IF1<Character, Boolean> f, String s) {
  return takeCharsWhile(s, f);
}


static boolean isInstance(Class type, Object arg) {
  return type.isInstance(arg);
}




static boolean contains(Collection c, Object o) {
  return c != null && c.contains(o);
}

static boolean contains(Iterable it, Object a) {
  if (it != null)
    for (Object o : it)
      if (eq(a, o))
        return true;
  return false;
}

static boolean contains(Object[] x, Object o) {
  if (x != null)
    for (Object a : x)
      if (eq(a, o))
        return true;
  return false;
}

static boolean contains(String s, char c) {
  return s != null && s.indexOf(c) >= 0;
}

static boolean contains(String s, String b) {
  return s != null && s.indexOf(b) >= 0;
}

static boolean contains(BitSet bs, int i) {
  return bs != null && bs.get(i);
}




static boolean contains(Rect r, Pt p) { return rectContains(r, p); }



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

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




static boolean rectContains(int x1, int y1, int w, int h, Pt p) {
  return p.x >= x1 && p.y >= y1 && p.x < x1+w && p.y < y1+h;
}

static boolean rectContains(Rect a, Rect b) {
  return b.x >= a.x && b.y >= a.y && b.x2() <= a.x2() && b.y2() <= a.y2();
}

static boolean rectContains(Rect a, Rectangle b) {
  return rectContains(a, toRect(b));
}

static boolean rectContains(Rect a, int x, int y) {
  return a != null && a.contains(x, y);
}

static boolean rectContains(Rect a, Pt p) {
  return a != null && p != null && a.contains(p);
}






static Rect toRect(Rectangle r) {
  return r == null ? null : new Rect(r);
}

static Rect toRect(RectangularShape r) {
  return r == null ? null : toRect(r.getBounds());
}



static Rect toRect(Rect r) { return r; }




static int iround(double d) {
  return (int) Math.round(d);
}


static int iround(Number n) {
  return iround(toDouble(n));
}





static class Chain<A> implements Iterable<A> {
  A element;
  Chain<A> next;
  int size;
  
  Chain() {}
  Chain(A element) {
  this.element = element; size = 1; }
  Chain(A element, Chain<A> next) {
  this.next = next;
  this.element = element;
    size = next != null ? next.size+1 : 1;
  }
  
  public String toString() { return str(toList()); }
  
  ArrayList<A> toList() {
    ArrayList<A> l = emptyList(size);
    Chain<A> c = this;
    while (c != null) {
      l.add(c.element);
      c = c.next;
    }
    return l;
  }
  
  // TODO: optimize
  public Iterator<A> iterator() { return toList().iterator(); }
}
// Meta - a "minimal" approach to adding meta-level to Java objects

static class Meta implements IMeta {
  
// Meta - a "minimal" approach to adding meta-level to Java objects
// (implementing the interface IMeta)

// We allocate one extra field for each Java object to make it
// reasoning-compatible (reasoning-compatible = extensible with
// fields of any name at runtime).
//
// We couldn't go for 0 extra fields (meta values must be linked
// directly from the object) and there are no half fields in
// Java... so there you go.
//
// Also, if you don't use any meta data, you are probably not
// reasoning about anything. The point of reasoning in JavaX is
// to attach information to objects directly used in the program.

// Possible information contained in the meta field:
//   Origin, destination, security level, sender, cost center,
//   purpose, list of reifications, ...

// So here it is. THE FIELD YOU HAVE BEEN WAITING FOR!

// [We also have IMeta to retrofit foreign classes (rare but
// probably useful).]

//////////////////////
// The "meta" field //
//////////////////////

// Generic meta value of any kind, but the typical case is it's a
// Map with extra field values for the object etc.
// "meta" is volatile to avoid synchronization; but you can also synchronize on
// _tempMetaMutex() which is usually the object itself. Collections
// and maps are exempt from using the collections's monitor as the meta
// mutex because their monitor tends to be held for long operations
// (e.g. cloneList). For those we use a substantially more complex
// algorithm using a weakMap. Probably overkill. I may reconsider.

volatile Object meta;

// The meta field is not transient, thus by default it will be
// persisted like anything else unless you customize your object
// to suppress or modulate this.

// ...and the interface methods

public void _setMeta(Object meta) { this.meta = meta; }
public Object _getMeta() { return meta; }

// MOST functions are implemented in IMeta (default implementations)

// Scaffolding convenience functions

final boolean scaffolding(){ return scaffoldingEnabled(); }
boolean scaffoldingEnabled() { return main.scaffoldingEnabled(this); }
boolean scaffoldingEnabled(Object o) { return main.scaffoldingEnabled(o); }
}
static class Rect implements IFieldsToList{
  static final String _fieldOrder = "x y w h";
  int x;
  int y;
  int w;
  int h;
  Rect() {}
  Rect(int x, int y, int w, int h) {
  this.h = h;
  this.w = w;
  this.y = y;
  this.x = x;}

public boolean equals(Object o) {
if (!(o instanceof Rect)) return false;
    Rect __1 =  (Rect) o;
    return x == __1.x && y == __1.y && w == __1.w && h == __1.h;
}

  public int hashCode() {
    int h = 2543108;
    h = boostHashCombine(h, _hashCode(x));
    h = boostHashCombine(h, _hashCode(y));
    h = boostHashCombine(h, _hashCode(w));
    h = boostHashCombine(h, _hashCode(h));
    return h;
  }
  public Object[] _fieldsToList() { return new Object[] {x, y, w, h}; }

  Rect(Rectangle r) {
    x = r.x;
    y = r.y;
    w = r.width;
    h = r.height;
  }
  
  Rect(Pt p, int w, int h) {
  this.h = h;
  this.w = w; x = p.x; y = p.y; }
  Rect(Rect r) { x = r.x; y = r.y; w = r.w; h = r.h; }
  
  final Rectangle getRectangle() {
    return new Rectangle(x, y, w, h);
  }
  
  public String toString() {
    return x + "," + y + " / " + w + "," + h;
  }
  
  final int x1() { return x; }
  final int y1() { return y; }
  final int x2() { return x + w; }
  final int y2() { return y + h; }
  
  final boolean contains(Pt p) {
    return contains(p.x, p.y);
  }
  
  final boolean contains(int _x, int _y) {
    return _x >= x && _y >= y && _x < x+w && _y < y+h;
  }
  
  final boolean contains(Rectangle r) {
    return rectContains(this, r);
  }
  
  final boolean empty() { return w <= 0 || h <= 0; }
  
  final int getWidth() { return w; }
  final int getHeight() { return h; }
  
  final int area() { return w*h; }
}
static class Pt implements Comparable<Pt>, IDoublePt {
  int x, y;
  
  Pt() {}
  Pt(Point p) {
    x = p.x;
    y = p.y;
  }
  Pt(int x, int y) {
  this.y = y;
  this.x = x;}
  
  Point getPoint() {
    return new Point(x, y);
  }
  
  public boolean equals(Object o) {
    return o instanceof Pt && x == ((Pt) o).x && y == ((Pt) o).y;
  }
  
  public int hashCode() {
    return boostHashCombine(x, y);
  }
  
  // compare in scan order
  public int compareTo(Pt p) {
    if (y != p.y) return cmp(y, p.y);
    return cmp(x, p.x);
  }
  
  public String toString() {
    return x + ", " + y;
  }
  
  double length() { return sqrt(x*x+y*y); }
  
  public Pt minus(Pt p) { return ptMinus(this, p); }
  
  public double x_double() { return x; }
  public double y_double() { return y; }
}
static class Var<A> implements IVar<A>, ISetter<A> {
  Var() {}
  Var(A v) {
  this.v = v;}

  
  
  A v; // you can access this directly if you use one thread
  
  public synchronized void set(A a) {
    if (v != a) {
      v = a;
      notifyAll();
    }
  }
  
  public synchronized A get() { return v; }
  public synchronized boolean has() { return v != null; }
  public void clear() { set(null); }

public String toString() { return str(this.get()); }
}
static interface IMeta {
  // see class "Meta" for the bla bla
  
  public void _setMeta(Object meta);
  public Object _getMeta();
  default public IAutoCloseableF0 _tempMetaMutex() {
    return new IAutoCloseableF0() {
      public Object get() { return IMeta.this; }
      public void close() {}
    };
  }
  
  // actually query another object
  default public Object getMeta(Object obj, Object key){ return metaGet(obj, key); }
default public Object metaGet(Object obj, Object key) {
    // call global function
    return metaMapGet(obj, key);
  }
  
  default public Object metaGet(String key, Object obj) {
    // call global function
    return metaMapGet(obj, key);
  }
  
  default public Object getMeta(Object key){ return metaGet(key); }
default public Object metaGet(Object key) {
    if (key == null) return null;
    Object meta = _getMeta();
    if (meta instanceof Map) return ((Map) meta).get(key);
    return null;
  }
  
  default public void metaSet(IMeta obj, Object key, Object value){ metaPut(obj, key, value); }
default public void metaPut(IMeta obj, Object key, Object value) {
    // call global function
    metaMapPut(obj, key, value);
  }
  
  default public void metaSet(Object key, Object value){ metaPut(key, value); }
default public void metaPut(Object key, Object value) {
    if (key == null) return;
    Map map = convertObjectMetaToMap(this);
    syncMapPutOrRemove(map, key, value);
  }
}
// a variant of thread where you can get the Runnable target later.
// Also notes its existence on the VM bus.
// We should use this exclusively instead of Thread.

static class BetterThread extends Thread {
  Runnable target;
  
  BetterThread(Runnable target) {
  this.target = target; _created(); }
  BetterThread(Runnable target, String name) { super(name);
  this.target = target; _created(); }
  
  void _created() { vmBus_send("threadCreated", this); }
  
  public void run() { try {
    try {
      vmBus_send("threadStarted", this);
      if (target != null) target.run();
    } finally {
      vmBus_send("threadEnded", this); 
    }
  } catch (Exception __e) { throw rethrow(__e); } }
  
  Runnable getTarget() { return target; }
}
static class Complex implements IFieldsToList{
  static final String _fieldOrder = "re im";
  double re;
  double im;
  Complex() {}
  Complex(double re, double im) {
  this.im = im;
  this.re = re;}

public boolean equals(Object o) {
if (!(o instanceof Complex)) return false;
    Complex __1 =  (Complex) o;
    return re == __1.re && im == __1.im;
}

  public int hashCode() {
    int h = -1679819632;
    h = boostHashCombine(h, _hashCode(re));
    h = boostHashCombine(h, _hashCode(im));
    return h;
  }
  public Object[] _fieldsToList() { return new Object[] {re, im}; }

  double abs() { return sqrt(re*re+im*im); }
  
  double re() { return re; }
  double im() { return im; }
  
  final double angle(){ return phase(); }
double phase() { return Math.atan2(im, re); }
  double fracAngle() { return fracNonNeg(angle()/twoPi()); } // angle as 0 to 1
  
  public String toString() {
    if (im != 0)
      return re == 0 ? im + "i" : re + plusPrefixUnlessMinus(str(im)) + "i";
    else
      return str(re);
  }
}
// Note: This does have the values problem (complicated values can cause memory leaks)
static class BetterThreadLocal<A> {
  Map<Thread, A> map = newWeakHashMap();
  
  BetterThreadLocal() {}
  BetterThreadLocal(A value) { set(value); }
  
  boolean isSet() { return map.containsKey(currentThread()); }
  
  A get() {
    if (map.containsKey(currentThread()))
      return map.get(currentThread());
    A value = initialValue();
    set(value);
    return value;
  }
  
  A get(Thread thread) {
    return thread == null ? null : map.get(thread);
  }
  
  void set(A a) {
    map.put(currentThread(), a);
  }
  
  public A initialValue() { return null; }
}
static class ReverseChain<A> implements Iterable<A> {
  A element;
  ReverseChain<A> prev;
  int size;
  
  ReverseChain() {}
  ReverseChain(ReverseChain<A> prev, A element) {
  this.element = element;
  this.prev = prev;
    if (prev == null) size = 1;
    else {
      prev.check();
      size = prev.size+1;
    }
  }
  
  void check() {
    if (size < 1) throw fail("You called the ReverseChain default constructor. Don't do that");
  }
  
  public String toString() {
    return str(toList());
  }
  
  ArrayList<A> toList() {
    check();
    ArrayList<A> l = emptyList(size);
    for (int i = 0; i < size; i++) l.add(null);
    int i = size;
    ReverseChain<A> c = this;
    while (c != null) {
      l.set(--i, c.element);
      c = c.prev;
    }
    return l;
  }
  
  public Iterator<A> iterator() { return toList().iterator(); }
}
// -has fast nextElement() and prevElement()
// -design allows for more functions like reordering the list
// -Saves up to 34% in space over LinkedHashSet
//    (e.g. 22% for a set of 1,000 Ints)
static class CompactLinkedHashSet<A> extends AbstractSet<A> {
  UnsynchronizedCompactHashSet<Entry<A>> entries = new UnsynchronizedCompactHashSet();
  Entry<A> head, tail;
  
  static class Entry<A> {
    A value;
    Entry<A> prev, next;
    
    public int hashCode() {
      return _hashCode(value);
    }
    
    // "magic" equals function for CompactHashSet lookup without temp object
    public boolean equals(Object o) {
      return o == this || eq(value, o);
    }
  }
  
  public boolean add(A a) {
    if (entries.contains(a)) return false;
    Entry<A> n = new Entry();
    n.value = a;
    n.prev = tail;
    if (tail != null) tail.next = n;
    tail = n;
    if (head == null) head = n;
    entries.add(n);
    return true;
  }
  
  public boolean remove(Object a) {
    return remove(entries.find(a));
  }
  
  public boolean remove(Entry<A> node) {
    if (node == null) return false;
    if (node.next != null) node.next.prev = node.prev; else tail = node.prev;
    if (node.prev != null) node.prev.next = node.next; else head = node.next;
    entries.remove(node);
    return true;
  }
  
  public int size() { return entries.size(); }
  
  public IterableIterator<A> iterator() {
    return new IterableIterator<A>() {
      Entry<A> entry = head, prev = null;
      public boolean hasNext() { return entry != null; }
      public A next() {
        A a = entry.value;
        prev = entry;
        entry = entry.next;
        return a;
      }
      
      // untested
      public void remove() {
        if (prev == null) throw new IllegalStateException();
        CompactLinkedHashSet.this.remove(prev);
        prev = null;
      }
    };
  }
  
  public void clear() {
    entries.clear();
    head = tail = null;
  }
  
  public boolean contains(Object a) {
    return entries.contains(a);
  }
  
  public A find(Object o) {
    Entry<A> e = entries.find(o);
    return e == null ? null : e.value;
  }
  
  public A prevElement(A a) {
    Entry<A> e = entries.find(a);
    if (e == null || e.prev == null) return null;
    return e.prev.value;
  }
  
  public A nextElement(A a) {
    Entry<A> e = entries.find(a);
    if (e == null || e.next == null) return null;
    return e.next.value;
  }
  
  public A first() { return head == null ? null : head.value; }
  public A last() { return tail == null ? null : tail.value; }
  
  boolean removeIfSame(Object o) {
    A value = find(o);
    if (value == o) {
      remove(value);
      return true;
    }
    return false;
  }
}
static interface IVF2<A, B> {
  void get(A a, B b);
}
static interface IVar<A> extends IF0<A> {
  void set(A a);
  A get();
  
  // reified type of value (if available)
  default Class<A> getType() { return null; }
  
  
  default boolean has() { return get() != null; }
  default void clear() { set(null); }
  
}


static interface ISetter<A> {
 void set(A a);
}

static interface IAutoCloseableF0<A> extends IF0<A>, AutoCloseable {}
/*
 * #!
 * Ontopia Engine
 * #-
 * Copyright (C) 2001 - 2013 The Ontopia Project
 * #-
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * !#
 */

// modified by Stefan Reich

// Implements the Set interface more compactly than
// java.util.HashSet by using a closed hashtable.

// Note: equals is always called on the _stored_ object, not the one
// passed as an argument to find(), contains() etc.
// (In case you want to put special magic in your equals() function)

static class UnsynchronizedCompactHashSet<A> extends java.util.AbstractSet<A> {
  protected final static int INITIAL_SIZE = 3;
  public final static double LOAD_FACTOR = 0.75;

  protected final static Object nullObject = new Object();
  protected final static Object deletedObject = new Object();
  protected int elements;
  protected int freecells;
  protected A[] objects;
  
  protected int modCount;
  
  
  UnsynchronizedCompactHashSet() {
    this(INITIAL_SIZE);
  }

  UnsynchronizedCompactHashSet(int size) {
    // NOTE: If array size is 0, we get a
    // "java.lang.ArithmeticException: / by zero" in add(Object).
    objects = (A[]) new Object[(size==0 ? 1 : size)];
    elements = 0;
    freecells = objects.length;
    
      modCount = 0;
    
  }

  UnsynchronizedCompactHashSet(Collection<A> c) {
    this(c.size());
    addAll(c);
  }

  @Override
  public Iterator<A> iterator() {
    return new CompactHashIterator<A>();
  }

  @Override
  public int size() {
    return elements;
  }

  @Override
  public boolean isEmpty() {
    return elements == 0;
  }

  @Override
  public boolean contains(Object o) {
    return find(o) != null;
  }
  
  A find(Object o) {
    if (o == null) o = nullObject;
    
    int hash = o.hashCode();
    int index = (hash & 0x7FFFFFFF) % objects.length;
    int offset = 1;

    // search for the object (continue while !null and !this object)
    while(objects[index] != null &&
          !(objects[index].hashCode() == hash &&
            objects[index].equals(o))) {
      index = ((index + offset) & 0x7FFFFFFF) % objects.length;
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }

    return objects[index];
  }
  
  boolean removeIfSame(Object o) {
    A value = find(o);
    if (value == o) {
      remove(value);
      return true;
    }
    return false;
  }

  @Override
  public boolean add(Object o) {
    if (o == null) o = nullObject;

    int hash = o.hashCode();
    int index = (hash & 0x7FFFFFFF) % objects.length;
    int offset = 1;
    int deletedix = -1;
    
    // search for the object (continue while !null and !this object)
    while(objects[index] != null &&
          !(objects[index].hashCode() == hash &&
            objects[index].equals(o))) {

      // if there's a deleted object here we can put this object here,
      // provided it's not in here somewhere else already
      if (objects[index] == deletedObject)
        deletedix = index;
      
      index = ((index + offset) & 0x7FFFFFFF) % objects.length;
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }
    
    if (objects[index] == null) { // wasn't present already
      if (deletedix != -1) // reusing a deleted cell
        index = deletedix;
      else
        freecells--;

      
        modCount++;
      
      elements++;

      // here we face a problem regarding generics:
      // add(A o) is not possible because of the null Object. We cant do 'new A()' or '(A) new Object()'
      // so adding an empty object is a problem here
      // If (! o instanceof A) : This will cause a class cast exception
      // If (o instanceof A) : This will work fine

      objects[index] = (A) o;
      
      // do we need to rehash?
      if (1 - (freecells / (double) objects.length) > LOAD_FACTOR)
        rehash();
      return true;
    } else // was there already 
      return false;
  }
  
  @Override
  public boolean remove(Object o) {
    if (o == null) o = nullObject;
    
    int hash = o.hashCode();
    int index = (hash & 0x7FFFFFFF) % objects.length;
    int offset = 1;
    
    // search for the object (continue while !null and !this object)
    while(objects[index] != null &&
          !(objects[index].hashCode() == hash &&
            objects[index].equals(o))) {
      index = ((index + offset) & 0x7FFFFFFF) % objects.length;
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }

    // we found the right position, now do the removal
    if (objects[index] != null) {
      // we found the object

      // same problem here as with add
      objects[index] = (A) deletedObject;
      
        modCount++;
      
      elements--;
      return true;
    } else
      // we did not find the object
      return false;
  }
  
  @Override
  public void clear() {
    elements = 0;
    for (int ix = 0; ix < objects.length; ix++)
      objects[ix] = null;
    freecells = objects.length;
    
      modCount++;
    
  }

  @Override
  public Object[] toArray() {
    Object[] result = new Object[elements];
    Object[] objects = this.objects;
    int pos = 0;
    for (int i = 0; i < objects.length; i++)
      if (objects[i] != null && objects[i] != deletedObject) {
        if (objects[i] == nullObject)
          result[pos++] = null;
        else
          result[pos++] = objects[i];
      }
    // unchecked because it should only contain A
    return result;
  }

  // not sure if this needs to have generics
  @Override
  public <T> T[] toArray(T[] a) {
    int size = elements;
    if (a.length < size)
      a = (T[])java.lang.reflect.Array.newInstance(
                                 a.getClass().getComponentType(), size);
    A[] objects = this.objects;
    int pos = 0;
    for (int i = 0; i < objects.length; i++)
      if (objects[i] != null && objects[i] != deletedObject) {
        if (objects[i] == nullObject)
          a[pos++] = null;
        else
          a[pos++] = (T) objects[i];
      }
    return a;
  }
  
  protected void rehash() {
    int garbagecells = objects.length - (elements + freecells);
    if (garbagecells / (double) objects.length > 0.05)
      // rehash with same size
      rehash(objects.length);
    else
      // rehash with increased capacity
      rehash(objects.length*2 + 1);
  }
  
  protected void rehash(int newCapacity) {
    int oldCapacity = objects.length;
    @SuppressWarnings("unchecked")
    A[] newObjects = (A[]) new Object[newCapacity];

    for (int ix = 0; ix < oldCapacity; ix++) {
      Object o = objects[ix];
      if (o == null || o == deletedObject)
        continue;
      
      int hash = o.hashCode();
      int index = (hash & 0x7FFFFFFF) % newCapacity;
      int offset = 1;

      // search for the object
      while(newObjects[index] != null) { // no need to test for duplicates
        index = ((index + offset) & 0x7FFFFFFF) % newCapacity;
        offset = offset*2 + 1;

        if (offset == -1)
          offset = 2;
      }

      newObjects[index] = (A) o;
    }

    objects = newObjects;
    freecells = objects.length - elements;
  }
  
  private class CompactHashIterator<T> implements Iterator<T> {
    private int index;
    private int lastReturned = -1;

    
      private int expectedModCount;
    

    @SuppressWarnings("empty-statement")
    public CompactHashIterator() {
        for (index = 0; index < objects.length &&
                        (objects[index] == null ||
                        objects[index] == deletedObject); index++)
          ;
        
          expectedModCount = modCount;
        
    }

    @Override
    public boolean hasNext() {
        return index < objects.length;
    }

    @SuppressWarnings("empty-statement")
    @Override
    public T next() {
        /*if (modCount != expectedModCount)
          throw new ConcurrentModificationException();*/
        int length = objects.length;
        if (index >= length) {
          lastReturned = -2;
          throw new NoSuchElementException();
        }
  
        lastReturned = index;
        for (index += 1; index < length &&
                         (objects[index] == null ||
                          objects[index] == deletedObject); index++)
          ;
        if (objects[lastReturned] == nullObject)
          return null;
        else
          return (T) objects[lastReturned];
    }

    @Override
    public void remove() {
        
          if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        
        if (lastReturned == -1 || lastReturned == -2)
          throw new IllegalStateException();
        // delete object
        if (objects[lastReturned] != null && objects[lastReturned] != deletedObject) {
          objects[lastReturned] = (A) deletedObject;
          elements--;
          
            modCount++;
            expectedModCount = modCount; // this is expected; we made the change
          
        }
    }
  }
  
  int capacity() { return objects.length; }
  
  // returns true if there was a shrink
  boolean shrinkToFactor(double factor) {
    if (factor > LOAD_FACTOR)
      throw fail("Shrink factor must be equal to or smaller than load factor: " + factor + " / " + LOAD_FACTOR);
    int newCapacity = max(INITIAL_SIZE, iround(size()/factor));
    if (newCapacity >= capacity()) return false;
    rehash(newCapacity);
    return true;
  }
}
interface IDoublePt {
  public double x_double();
  public double y_double();
}


static double sqrt(double x) {
  return Math.sqrt(x);
}


static Pt ptMinus(Pt a, Pt b) {
  if (b == null) return a;
  return new Pt(a.x-b.x, a.y-b.y);
}


static Object metaGet(IMeta o, Object key) {
  return metaMapGet(o, key);
}

static Object metaGet(Object o, Object key) {
  return metaMapGet(o, key);
}

static Object metaGet(String key, IMeta o) {
  return metaMapGet(o, key);
}

static Object metaGet(String key, Object o) {
  return metaMapGet(o, key);
}


static Object metaMapGet(IMeta o, Object key) {
  return o == null ? null : o.metaGet(key); // We now let the object itself do it (overridable!)
}

static Object metaMapGet(Object o, Object key) {
  return metaMapGet(toIMeta(o), key);
}


static void metaPut(IMeta o, Object key, Object value) {
  metaMapPut(o, key, value);
}



static void metaPut(Object o, Object key, Object value) {
  metaMapPut(o, key, value);
}


static Map convertObjectMetaToMap(IMeta o) { return convertObjectMetaToMap(o, () -> makeObjectMetaMap()); }
static Map convertObjectMetaToMap(IMeta o, IF0<Map> createEmptyMap) {
  if (o == null) return null;
  
  // The following shortcut depends on the assumption that a meta field never reverts
  // to null when it was a map
  
    Object meta = o._getMeta();
    if (meta instanceof Map) return ((Map) meta);
  
  
  // non-shortcut path (create meta)
   var mutex = tempMetaMutex(o); try {
  var actualMutex = mutex.get();
  synchronized(actualMutex) {
    meta = o._getMeta();
    if (meta instanceof Map) return ((Map) meta);
    Map map = createEmptyMap.get();
    if (meta != null) map.put("previousMeta" , meta);
    o._setMeta(map);
    return map;
  }
} finally { _close(mutex); }}


static <A, B> void syncMapPutOrRemove(Map<A, B> map, A key, B value) {
  syncMapPut2(map, key, value);
}


static double fracNonNeg(double d) {
  return frac_nonNeg(d);
}


static double twoPi() {
  return Math.PI*2;
}


static String plusPrefixUnlessMinus(String s) {
  return startsWith(s, "-") ? s : "+" + s;
}


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 void set(BitSet bs, int idx) {
  { if (bs != null) bs.set(idx); }
}


static String find(String pattern, String text) {
  Matcher matcher = Pattern.compile(pattern).matcher(text);
  if (matcher.find())
    return matcher.group(1);
  return null;
}

static <A> A find(Collection<A> c, Object... data) {
  for (A x : c)
    if (checkFields(x, data))
      return x;
  return null;
}




static IMeta toIMeta(Object o) {
  return initIMeta(o);
}


static Map makeObjectMetaMap() {
  //ret synchroLinkedHashMap();
  return new CompactHashMap();
}


static IAutoCloseableF0 tempMetaMutex(IMeta o) {
  return o == null ? null : o._tempMetaMutex();
}


static <A, B> void syncMapPut2(Map<A, B> map, A key, B value) {
  if (map != null && key != null) synchronized(collectionMutex(map)) {
    if (value != null) map.put(key, value);
    else map.remove(key);
  }
}


static double frac_nonNeg(double d) {
  return mod(d, 1);
}


static boolean checkFields(Object x, Object... data) {
  for (int i = 0; i < l(data); i += 2)
    if (neq(getOpt(x, (String) data[i]), data[i+1]))
      return false;
  return true;
}




// better modulo that gives positive numbers always
static int mod(int n, int m) {
  return (n % m + m) % m;
}

static long mod(long n, long m) {
  return (n % m + m) % m;
}

static BigInteger mod(BigInteger n, int m) {
  return n.mod(bigint(m));
}

static double mod(double n, double m) {
  return (n % m + m) % m;
}





// size:
// 64 bytes for 0 to 1 elements
// 96 bytes for 2 to 4 elements

/*
 * #!
 * Ontopia Engine
 * #-
 * Copyright (C) 2001 - 2013 The Ontopia Project
 * #-
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * !#
 */

static class CompactHashMap<K, V> extends CompactAbstractMap<K, V> {
  final static int INITIAL_SIZE = 3;
  final static double LOAD_FACTOR = 0.6;

  // This object is used to represent a null KEY (null value are kept as is)
  final static Object nullObject = new Object();
  
  /**
   * When a key is deleted this object is put into the hashtable in
   * its place, so that other entries with the same key (collisions)
   * further down the hashtable are not lost after we delete an object
   * in the collision chain.
   */
  final static Object deletedObject = new Object();
  int elements;
  int freecells;
  Object[] table; // key, value, key, value, ...
  //int modCount;

  CompactHashMap() {
    this(INITIAL_SIZE);
  }

  CompactHashMap(int size) {
    table = new Object[(size==0 ? 1 : size)*2];
    elements = 0;
    freecells = tableSize();
    //modCount = 0;
  }
  
  // TODO: allocate smarter
  CompactHashMap(Map<K, V> map) {
    this(0);
    if (map != null) putAll(map);
  }

  // ===== MAP IMPLEMENTATION =============================================

  /**
   * Returns the number of key/value mappings in this map.
   */
  public synchronized int size() {
    return elements;
  }
  
  /**
   * Returns <tt>true</tt> if this map contains no mappings.
   */
  public synchronized boolean isEmpty() {
    return elements == 0;
  }

  /**
   * Removes all key/value mappings in the map.
   */
  public synchronized void clear() {
    elements = 0;
    for (int ix = 0; ix < tableSize(); ix++) {
      key(ix, null);
      value(ix, null);
    }
    freecells = tableSize();
    //modCount++;
  }
  
  /**
   * Returns <tt>true</tt> if this map contains the specified key.
   */
  public synchronized boolean containsKey(Object k) {
    return key(findKeyIndex(k)) != null;
  }
  
  /**
   * Returns <tt>true</tt> if this map contains the specified value.
   */
  public synchronized boolean containsValue(Object v) {
    if (v == null)
      v = (V)nullObject;

    for (int ix = 0; ix < tableSize(); ix++)
      if (value(ix) != null && value(ix).equals(v))
        return true;

    return false;
  }

  /**
   * Returns a read-only set view of the map's keys.
   */
  public synchronized Set<Entry<K, V>> entrySet() {
    return new EntrySet();
  }

  /**
   * Removes the mapping with key k, if there is one, and returns its
   * value, if there is one, and null if there is none.
   */
  public synchronized V remove(Object k) {
    int index = findKeyIndex(k);

    // we found the right position, now do the removal
    if (key(index) != null) {
      // we found the object

      // same problem here as with put
      V v = value(index);
      key(index, deletedObject);
      value(index, deletedObject);
      //modCount++;
      elements--;
      return v;
    } else
      // we did not find the key
      return null;
  }

  /**
   * Adds the specified mapping to this map, returning the old value for
   * the mapping, if there was one.
   */
  public synchronized V put(K k, V v) {
    if (k == null)
      k = (K)nullObject;

    int hash = k.hashCode();
    int index = (hash & 0x7FFFFFFF) % tableSize();
    int offset = 1;
    int deletedix = -1;
    
    // search for the key (continue while !null and !this key)
    while(key(index) != null &&
          !(key(index).hashCode() == hash &&
            key(index).equals(k))) {

      // if there's a deleted mapping here we can put this mapping here,
      // provided it's not in here somewhere else already
      if (key(index) == deletedObject)
        deletedix = index;
      
      index = ((index + offset) & 0x7FFFFFFF) % tableSize();
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }
    
    if (key(index) == null) { // wasn't present already
      if (deletedix != -1) // reusing a deleted cell
        index = deletedix;
      else
        freecells--;

      //modCount++;
      elements++;

      key(index, k);
      value(index, v);
      
      // rehash with increased capacity
      if (1 - (freecells / (double) tableSize()) > LOAD_FACTOR)
        rehash(tableSize()*2 + 1);
      return null;
    } else { // was there already
      //modCount++;
      V oldv = value(index);
      value(index, v);
      return oldv;
    }
  }

  /**
   * INTERNAL: Rehashes the hashmap to a bigger size.
   */
  void rehash(int newCapacity) {
    int oldCapacity = tableSize();
    Object[] newTable = new Object[newCapacity*2];

    for (int ix = 0; ix < oldCapacity; ix++) {
      Object k = key(ix);
      if (k == null || k == deletedObject)
        continue;
      
      int hash = k.hashCode();
      int index = (hash & 0x7FFFFFFF) % newCapacity;
      int offset = 1;

      // search for the key
      while(newTable[index*2] != null) { // no need to test for duplicates
        index = ((index + offset) & 0x7FFFFFFF) % newCapacity;
        offset = offset*2 + 1;

        if (offset == -1)
          offset = 2;
      }

      newTable[index*2] = k;
      newTable[index*2+1] = value(ix);
    }

    table = newTable;
    freecells = tableSize() - elements;
  }

  /**
   * Returns the value for the key k, if there is one, and null if
   * there is none.
   */
  public synchronized V get(Object k) {
    return value(findKeyIndex(k));
  }

  /**
   * Returns a virtual read-only collection containing all the values
   * in the map.
   */
  public synchronized Collection<V> values() {
    return new ValueCollection();
  }

  /**
   * Returns a virtual read-only set of all the keys in the map.
   */
  public synchronized Set<K> keySet() {
    return new KeySet();
  }

  // --- Internal utilities

  final int findKeyIndex(Object k) {
    if (k == null)
      k = nullObject;

    int hash = k.hashCode();
    int index = (hash & 0x7FFFFFFF) % tableSize();
    int offset = 1;

    // search for the key (continue while !null and !this key)
    while(key(index) != null &&
          !(key(index).hashCode() == hash &&
            key(index).equals(k))) {
      index = ((index + offset) & 0x7FFFFFFF) % tableSize();
      offset = offset*2 + 1;

      if (offset == -1)
        offset = 2;
    }
    return index;
  }
  
  // --- Key set

  class KeySet extends AbstractSet<K> {
    public int size() { synchronized(CompactHashMap.this) {
      return elements;
    }}

    public boolean contains(Object k) { synchronized(CompactHashMap.this) {
      return containsKey(k);
    }}

    public Iterator<K> iterator() { synchronized(CompactHashMap.this) {
      return new KeyIterator();
    }}
  }

  class KeyIterator<K> implements Iterator<K> {
    private int ix;
    
    private KeyIterator() {
      synchronized(CompactHashMap.this) {
        // walk up to first value, so that hasNext() and next() return
        // correct results
        for (; ix < tableSize(); ix++)
          if (value(ix) != null && key(ix) != deletedObject)
            break;
      }
    }

    public boolean hasNext() { synchronized(CompactHashMap.this) {
      return ix < tableSize();
    }}

    public void remove() {
      throw new UnsupportedOperationException("Collection is read-only");
    }

    public K next() { synchronized(CompactHashMap.this) {
      if (ix >= tableSize())
        throw new NoSuchElementException();
      K key = (K) key(ix++);
      
      // walk up to next value
      for (; ix < tableSize(); ix++)
        if (key(ix) != null && key(ix) != deletedObject)
          break;
      
      // ix now either points to next key, or outside array (if no next)
      return key;
    }}
  }
  
  // --- Entry set
  
  class EntrySet extends AbstractSet<Map.Entry<K, V>> {
    public int size() { synchronized(CompactHashMap.this) {
      return elements;
    }}

    public boolean contains(Object o) { synchronized(CompactHashMap.this) {
      if (o instanceof Map.Entry) {
        Object key = ((Map.Entry) o).getKey();
        if (!containsKey((Map.Entry) o)) return false;
        return eq(((Map.Entry) o).getValue(), get(key));
      }
      return false;
    }}

    public Iterator<Map.Entry<K, V>> iterator() {
      return new EntryIterator();
    }
  }

  class EntryIterator implements Iterator<Map.Entry<K, V>> {
    private int ix;
    
    private EntryIterator() {
      synchronized(CompactHashMap.this) {
        // walk up to first value, so that hasNext() and next() return
        // correct results
        for (; ix < tableSize(); ix++)
          if (value(ix) != null && key(ix) != deletedObject)
            break;
      }
    }

    public boolean hasNext() { synchronized(CompactHashMap.this) {
      return ix < tableSize();
    }}

    public void remove() {
      throw new UnsupportedOperationException("Collection is read-only");
    }

    public Map.Entry<K, V> next() { synchronized(CompactHashMap.this) {
      if (ix >= tableSize())
        throw new NoSuchElementException();
      K key = key(ix);
      V val = value(ix);
      ++ix;
      
      // walk up to next value
      for (; ix < tableSize(); ix++)
        if (key(ix) != null && key(ix) != deletedObject)
          break;
      
      // ix now either points to next key, or outside array (if no next)
      return simpleMapEntry(key, val);
    }}
  }
  
  // --- Value collection

  class ValueCollection<V> extends AbstractCollection<V> {
    public int size() { synchronized(CompactHashMap.this) {
      return elements;
    }}

    public Iterator<V> iterator() {
      return new ValueIterator();
    }

    public boolean contains(Object v) {
      return containsValue(v);
    }
  }

  class ValueIterator<V> implements Iterator<V> {
    private int ix;
    
    private ValueIterator() {
      synchronized(CompactHashMap.this) {
        // walk up to first value, so that hasNext() and next() return
        // correct results
        for (; ix < table.length/2; ix++)
          if (value(ix) != null && value(ix) != deletedObject)
            break;
      }
    }

    public boolean hasNext() { synchronized(CompactHashMap.this) {
      return ix < tableSize();
    }}

    public void remove() {
      throw new UnsupportedOperationException("Collection is read-only");
    }

    public V next() { synchronized(CompactHashMap.this) {
      if (ix >= tableSize())
        throw new NoSuchElementException();
      V value = (V) value(ix++);
      
      // walk up to next value
      for (; ix < tableSize(); ix++)
        if (value(ix) != null && value(ix) != deletedObject)
          break;
      
      // ix now either points to next value, or outside array (if no next)
      return value;
    }}
  }
  
  K key(int i) { return (K) table[i*2]; }
  void key(int i, Object key) { table[i*2] = key; }
  V value(int i) { return (V) table[i*2+1]; }
  void value(int i, Object value) { table[i*2+1] = value; }
  
  int tableSize() { return table.length/2; }
}


abstract static class CompactAbstractMap<K, V> implements Map<K, V> {
  public int size() {
      return entrySet().size();
  }

  public boolean isEmpty() {
      return size() == 0;
  }

  public boolean containsValue(Object value) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (value == null) {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getValue() == null)
                  return true;
          }
      } else {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (value.equals(e.getValue()))
                  return true;
          }
      }
      return false;
  }

  public boolean containsKey(Object key) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (key == null) {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getKey() == null)
                  return true;
          }
      } else {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (key.equals(e.getKey()))
                  return true;
          }
      }
      return false;
  }

  public V get(Object key) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (key == null) {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getKey() == null)
                  return e.getValue();
          }
      } else {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (key.equals(e.getKey()))
                  return e.getValue();
          }
      }
      return null;
  }

  public V put(K key, V value) {
      throw new UnsupportedOperationException();
  }

  public V remove(Object key) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      Entry<K, V> correctEntry = null;
      if (key == null) {
          while (correctEntry == null && i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getKey() == null)
                  correctEntry = e;
          }
      } else {
          while (correctEntry == null && i.hasNext()) {
              Entry<K, V> e = i.next();
              if (key.equals(e.getKey()))
                  correctEntry = e;
          }
      }

      V oldValue = null;
      if (correctEntry != null) {
          oldValue = correctEntry.getValue();
          i.remove();
      }
      return oldValue;
  }

  public void putAll(Map<? extends K, ? extends V> m) {
      for (Entry<? extends K, ? extends V> e : m.entrySet())
          put(e.getKey(), e.getValue());
  }

  public void clear() {
      entrySet().clear();
  }

  public Set<K> keySet() {
      return new AbstractSet<K>() {
          public Iterator<K> iterator() {
              return new Iterator<K>() {
                  private Iterator<Entry<K, V>> i = entrySet().iterator();

                  public boolean hasNext() {
                      return i.hasNext();
                  }

                  public K next() {
                      return i.next().getKey();
                  }

                  public void remove() {
                      i.remove();
                  }
              };
          }

          public int size() {
              return CompactAbstractMap.this.size();
          }

          public boolean isEmpty() {
              return CompactAbstractMap.this.isEmpty();
          }

          public void clear() {
              CompactAbstractMap.this.clear();
          }

          public boolean contains(Object k) {
              return CompactAbstractMap.this.containsKey(k);
          }
      };
  }

  public Collection<V> values() {
      return new AbstractCollection<V>() {
          public Iterator<V> iterator() {
              return new Iterator<V>() {
                  private Iterator<Entry<K, V>> i = entrySet().iterator();

                  public boolean hasNext() {
                      return i.hasNext();
                  }

                  public V next() {
                      return i.next().getValue();
                  }

                  public void remove() {
                      i.remove();
                  }
              };
          }

          public int size() {
              return CompactAbstractMap.this.size();
          }

          public boolean isEmpty() {
              return CompactAbstractMap.this.isEmpty();
          }

          public void clear() {
              CompactAbstractMap.this.clear();
          }

          public boolean contains(Object v) {
              return CompactAbstractMap.this.containsValue(v);
          }
      };
  }

  public abstract Set<Entry<K, V>> entrySet();

  public boolean equals(Object o) {
      if (o == this)
          return true;

      if (!(o instanceof Map))
          return false;
      Map<?, ?> m = (Map<?, ?>) o;
      if (m.size() != size())
          return false;

      try {
          for (Entry<K, V> e : entrySet()) {
              K key = e.getKey();
              V value = e.getValue();
              if (value == null) {
                  if (!(m.get(key) == null && m.containsKey(key)))
                      return false;
              } else {
                  if (!value.equals(m.get(key)))
                      return false;
              }
          }
      } catch (ClassCastException unused) {
          return false;
      } catch (NullPointerException unused) {
          return false;
      }

      return true;
  }

  public int hashCode() {
      int h = 0;
      for (Entry<K, V> entry : entrySet())
          h += entry.hashCode();
      return h;
  }

  public String toString() {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (!i.hasNext())
          return "{}";

      StringBuilder sb = new StringBuilder();
      sb.append('{');
      for (; ; ) {
          Entry<K, V> e = i.next();
          K key = e.getKey();
          V value = e.getValue();
          sb.append(key == this ? "(this Map)" : key);
          sb.append('=');
          sb.append(value == this ? "(this Map)" : value);
          if (!i.hasNext())
              return sb.append('}').toString();
          sb.append(',').append(' ');
      }
  }

  protected Object clone() throws CloneNotSupportedException {
      CompactAbstractMap<?, ?> result = (CompactAbstractMap<?, ?>) super.clone();
      return result;
  }

  public static class SimpleEntry<K, V>
          implements Entry<K, V>, java.io.Serializable {
      @java.io.Serial
      private static final long serialVersionUID = -8499721149061103585L;

      @SuppressWarnings("serial")
      private final K key;
      @SuppressWarnings("serial")
      private V value;

      public SimpleEntry(K key, V value) {
          this.key = key;
          this.value = value;
      }

      public SimpleEntry(Entry<? extends K, ? extends V> entry) {
          this.key = entry.getKey();
          this.value = entry.getValue();
      }

      public K getKey() {
          return key;
      }

      public V getValue() {
          return value;
      }

      public V setValue(V value) {
          V oldValue = this.value;
          this.value = value;
          return oldValue;
      }

      public boolean equals(Object o) {
          if (!(o instanceof Map.Entry))
              return false;
          Entry<?, ?> e = (Entry<?, ?>) o;
          return eq(key, e.getKey()) && eq(value, e.getValue());
      }

      public int hashCode() {
          return (key == null ? 0 : key.hashCode()) ^
                  (value == null ? 0 : value.hashCode());
      }

      public String toString() {
          return key + "=" + value;
      }

  }

  public static class SimpleImmutableEntry<K, V>
          implements Entry<K, V>, java.io.Serializable {
      @java.io.Serial
      private static final long serialVersionUID = 7138329143949025153L;

      @SuppressWarnings("serial")
      private final K key;
      @SuppressWarnings("serial")
      private final V value;

      public SimpleImmutableEntry(K key, V value) {
          this.key = key;
          this.value = value;
      }

      public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
          this.key = entry.getKey();
          this.value = entry.getValue();
      }

      public K getKey() {
          return key;
      }

      public V getValue() {
          return value;
      }

      public V setValue(V value) {
          throw new UnsupportedOperationException();
      }

      public boolean equals(Object o) {
          if (!(o instanceof Map.Entry))
              return false;
          Entry<?, ?> e = (Entry<?, ?>) o;
          return eq(key, e.getKey()) && eq(value, e.getValue());
      }

      public int hashCode() {
          return (key == null ? 0 : key.hashCode()) ^
                  (value == null ? 0 : value.hashCode());
      }

      public String toString() {
          return key + "=" + value;
      }
  }
}


static boolean scaffoldingEnabled(Object o) {
  return metaGet(o, "scaffolding") != null;
}


static <A> Value<A> value(A a) {
  return new Value<A>(a);
}


static <A, B> boolean containsKey(Map<A, B> map, A key) {
  return map != null && map.containsKey(key);
}


static <A, B> Map.Entry<A, B> simpleMapEntry(A key, B value) {
  return new Map.Entry<A, B>() {
    public A getKey() { return key; }
    public B getValue() { return value; }
    public B setValue(B newValue) { throw unimplemented(); }
  };
}




static RuntimeException unimplemented() {
  throw fail("TODO");
}

static RuntimeException unimplemented(String msg) {
  throw fail("TODO: " + msg);
}

static RuntimeException unimplemented(Object obj) {
  throw fail("TODO: implement method in " + className(obj));
}




static class Value<A> implements IF0<A> , IFieldsToList{
  A value;
  Value() {}
  Value(A value) {
  this.value = value;}

public boolean equals(Object o) {
if (!(o instanceof Value)) return false;
    Value __1 =  (Value) o;
    return eq(value, __1.value);
}

  public int hashCode() {
    int h = 82420049;
    h = boostHashCombine(h, _hashCode(value));
    return h;
  }
  public Object[] _fieldsToList() { return new Object[] {value}; }

  public A get() { return value; }
  
  public String toString() { return str(get()); }
}

}