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.nio.charset.Charset;
import java.text.SimpleDateFormat;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import java.text.NumberFormat;
import java.awt.geom.*;
import static x30_pkg.x30_util.DynamicObject;

class main {

static String makeSFSynonym(String oldName, String newName) {
  if (eq(oldName, newName))
    throw fail("Can't make a synonym of itself");
    
  stdFunctions_clearCache();
  String snippetID = stdFunctions_cached().get(oldName);
  if (snippetID == null) {
    snippetID = stdFunctions_cached().get(newName);
    if (snippetID == null)
      throw fail("Standard function " + oldName + " not found");
    String temp = oldName; oldName = newName; newName = temp;
  }
  
    List<String> tok = javaTok(loadSnippet(snippetID));
  List<List<String>> funcs = findFullFunctionDefs(tok, true);
  List<String> out = new ArrayList();
  for (List<String> tokF : funcs) {
    int i = indexOfAny(tokF, 0, "(", "{");
    if (i < 0) continue;
    String fname = get(tokF, i-2);
    if (!eq(fname, oldName)) continue;
    List<String> args = tok_parseArgsDeclList(tokF);
    jreplace(tokF = cloneList(tokF), oldName, newName);
    List<String> start = cloneList(subList(tokF, 0, i));
    jreplace(start, "synchronized", "");
    boolean isVoid = containsOneOf(start, "void", "svoid");
    out.add(join(start) + "(" + joinWithComma(trimAll(map(__19 -> tok_dropFinal(__19), args))) + ") {\n"
      + "  " + (isVoid ? "" : "ret ") + oldName + "(" + joinWithComma(lmap(__20 -> tok_nameOfParam(__20), args)) + ");\n"
      + "}");
  }
  if (empty(out))
    throw fail("No functions found");
  print();
  String src = joinWithEmptyLines(out);
  printIndent(src);

  if (isStandardFunction(newName)) {
    String snippetID2 = sfSnippet(newName);
    String result = editSnippet(snippetID2, src);
    return print("Standard function " + newName + " exists, edited " + snippetID2 + ": " + result);
  }
  
  String newSnippetID = createSnippet(src, newName + " - synonym of " + oldName, snippetType_JavaXInclude());
  checkMarkAnimation_bottomLeft(addStdFunction(newSnippetID, true), 2);
  stdFunctions_clearCache();
  return "Synonym made!";
}
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 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 Map<String, String> stdFunctions_cached_map; // name -> snippet ID
static Lock stdFunctions_cached_lock = lock();

static Map<String, String> stdFunctions_cached() {
  Lock __0 = stdFunctions_cached_lock; lock(__0); try {
  if (stdFunctions_cached_map == null)
    stdFunctions_cached_map = stdFunctions_uncached();
  return stdFunctions_cached_map;
} finally { unlock(__0); } }

static synchronized void stdFunctions_clearCache() {
  stdFunctions_cached_map = null;
}


// TODO: extended multi-line strings

static int javaTok_n, javaTok_elements;
static boolean javaTok_opt = false;

static List<String> javaTok(String s) {
  ++javaTok_n;
  ArrayList<String> tok = new ArrayList();
  int l = s == null ? 0 : s.length();
  
  int i = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
        // scan for whitespace
        while (j < l) {
          c = s.charAt(j);
          d = j+1 >= l ? '\0' : s.charAt(j+1);
          if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
            ++j;
          else if (c == '/' && d == '*') {
            do ++j; while (j < l && !regionMatches(s, j, "*/"));
            j = Math.min(j+2, l);
          } else if (c == '/' && d == '/') {
            do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
          } else
            break;
        }
        
        tok.add(javaTok_substringN(s, i, j));
        i = j;
        if (i >= l) break;
        c = s.charAt(i);
        d = i+1 >= l ? '\0' : s.charAt(i+1);
    
        // scan for non-whitespace
        
        // Special JavaX syntax: 'identifier
        if (c == '\'' && Character.isJavaIdentifierStart(d) && i+2 < l && "'\\".indexOf(s.charAt(i+2)) < 0) {
          j += 2;
          while (j < l && Character.isJavaIdentifierPart(s.charAt(j)))
            ++j;
        } else if (c == '\'' || c == '"') {
          char opener = c;
          ++j;
          while (j < l) {
            int c2 = s.charAt(j);
            if (c2 == opener || c2 == '\n' && opener == '\'') { // allow multi-line strings, but not for '
              ++j;
              break;
            } else if (c2 == '\\' && j+1 < l)
              j += 2;
            else
              ++j;
          }
        } else if (Character.isJavaIdentifierStart(c))
          do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || s.charAt(j) == '\'')); // for stuff like "don't"
        else if (Character.isDigit(c)) {
          do ++j; while (j < l && Character.isDigit(s.charAt(j)));
          if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L
        } else if (c == '[' && d == '[') {
          do ++j; while (j < l && !regionMatches(s, j, "]]"));
          j = Math.min(j+2, l);
        } else if (c == '[' && d == '=' && i+2 < l && s.charAt(i+2) == '[') {
          do ++j; while (j+2 < l && !regionMatches(s, j, "]=]"));
          j = Math.min(j+3, l);
        } else
          ++j;
      
    tok.add(javaTok_substringC(s, i, j));
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  javaTok_elements += tok.size();
  return tok;
}

static List<String> javaTok(List<String> tok) {
  return javaTokWithExisting(join(tok), tok);
}


static boolean preferCached = false;
static boolean loadSnippet_debug = false;
static ThreadLocal<Boolean> loadSnippet_silent = new ThreadLocal();
static ThreadLocal<Boolean> loadSnippet_publicOnly = new ThreadLocal();
static int loadSnippet_timeout = 30000;


static String loadSnippet(Snippet s) {
  return loadSnippet(s.id);
}


static String loadSnippet(String snippetID) { try {
  if (snippetID == null) return null;
  return loadSnippet(parseSnippetID(snippetID), preferCached);
} catch (Exception __e) { throw rethrow(__e); } }

static String loadSnippet(String snippetID, boolean preferCached) throws IOException {
  return loadSnippet(parseSnippetID(snippetID), preferCached);
}

static  IF1<Long, String> loadSnippet;
static String loadSnippet(long snippetID) { return loadSnippet != null ? loadSnippet.get(snippetID) : loadSnippet_base(snippetID); }
final static String loadSnippet_fallback(IF1<Long, String> _f, long snippetID) { return _f != null ? _f.get(snippetID) : loadSnippet_base(snippetID); }
static String loadSnippet_base(long snippetID) { try {
  return loadSnippet(snippetID, preferCached);
} catch (Exception __e) { throw rethrow(__e); } }

static String loadSnippet(long snippetID, boolean preferCached) throws IOException {
  if (isLocalSnippetID(snippetID))
    return loadLocalSnippet(snippetID);
    
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadSnippet(fsI(snippetID));
  
  
  return loadSnippet_noResourceLoader(snippetID, preferCached);
}

static String loadSnippet_noResourceLoader(long snippetID, boolean preferCached) throws IOException {
  String text;
  
  // boss bot (old concept)
  /*text = getSnippetFromBossBot(snippetID);
  if (text != null) return text;*/
  
  initSnippetCache();
  text = DiskSnippetCache_get(snippetID);
  
  if (preferCached && text != null)
    return text;
  
  try {
    if (loadSnippet_debug && text != null) System.err.println("md5: " + md5(text));
    String url = tb_mainServer() + "/getraw.php?id=" + snippetID + "&utf8=1";
    if (nempty(text)) url += "&md5=" + md5(text);
    if (!isTrue(loadSnippet_publicOnly.get()))
      url += standardCredentials();
    
    String text2 = loadSnippet_loadFromServer(url);
    
    boolean same = eq(text2, "==*#*==");
    if (loadSnippet_debug) print("loadSnippet: same=" + same);
    if (!same) text = text2;
  } catch (RuntimeException e) {
    e.printStackTrace();
    throw new IOException("Snippet #" + snippetID + " not found or not public");
  }

  try {
    initSnippetCache();
    DiskSnippetCache_put(snippetID, text);
  } catch (IOException e) {
    System.err.println("Minor warning: Couldn't save snippet to cache ("  + DiskSnippetCache_getDir() + ")");
  }

  return text;
}

static File DiskSnippetCache_dir;

public static void initDiskSnippetCache(File dir) {
  DiskSnippetCache_dir = dir;
  dir.mkdirs();
}

public static synchronized String DiskSnippetCache_get(long snippetID) throws IOException {
  return loadTextFile(DiskSnippetCache_getFile(snippetID).getPath(), null);
}

private static File DiskSnippetCache_getFile(long snippetID) {
  return new File(DiskSnippetCache_dir, "" + snippetID);
}

public static synchronized void DiskSnippetCache_put(long snippetID, String snippet) throws IOException {
  saveTextFile(DiskSnippetCache_getFile(snippetID).getPath(), snippet);
}

public static File DiskSnippetCache_getDir() {
  return DiskSnippetCache_dir;
}

public static void initSnippetCache() {
  if (DiskSnippetCache_dir == null)
    initDiskSnippetCache(getGlobalCache());
}

static String loadSnippet_loadFromServer(String url) {
  Integer oldTimeout = setThreadLocal(loadPage_forcedTimeout_byThread, loadSnippet_timeout);
  try {
    return isTrue(loadSnippet_silent.get()) ? loadPageSilently(url) : loadPage(url);
  } finally {
    loadPage_forcedTimeout_byThread.set(oldTimeout);
  }
}



static Set<String> findFullFunctionDefs_keywords = new HashSet(splitAtSpace("static svoid ssvoid ssynchronized sbool sS sO sL"));

// returns actual CNC
static List<List<String>> findFullFunctionDefs(List<String> tok, boolean topLevelOnly) {
  int n = l(tok);
  List<List<String>> functions = new ArrayList();
  for (int i = 1; i < n; i += 2) {
    String t = tok.get(i);
    if (topLevelOnly && eq(t, "{")) i = findEndOfBlock(tok, i)-1;
    else if (findFullFunctionDefs_keywords.contains(t)) {
      
      int j = i+2;
      while (j < n && !eqOneOf(tok.get(j), ";", "=", "(", "{"))
        j += 2;
      if ((eqGet(tok, j, "(") || eq(t, "svoid") && eqGet(tok, j, "{"))
        && isIdentifier(tok.get(j-2))
        && !contains(subList(tok, i, j), "new")) {
        int k = smartIndexOf(tok, "{", j);
        if (k < l(tok)) {
          k = findEndOfBlock(tok, k)+1;
          functions.add(subList(tok, i-1, k));
          i = k-2;
        }
      }
    }
  }
  return functions;
}

static List<List<String>> findFullFunctionDefs(String s, boolean topLevelOnly) {
  return findFullFunctionDefs(javaTok(s), topLevelOnly);
}


static <A> int indexOfAny(List<A> l, int i, A... x) {
  while (i < l(l))
    if (eqOneOf(l.get(i), x)) return i; else ++i;
  return -1;
}

static <A> int indexOfAny(List<A> l, Collection<A> x) {
  return indexOfAny(l, 0, x);
}

static <A> int indexOfAny(List<A> l, int i, Collection<A> x) {
  if (nempty(x))
    while (i < l(l))
      if (x.contains(l.get(i))) return i; else ++i;
  return -1;
}

static int indexOfAny(String s, int i, String chars) {
  for (; i < l(s); i++)
    if (chars.indexOf(s.charAt(i)) >= 0)
      return i;
  return -1;
}

static int indexOfAny(String s, String chars) { return indexOfAny(s, 0, chars); }


// 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 List<String> tok_parseArgsDeclList(List<String> tok) {
  return tok_parseArgsList(tok, getBracketMapIncludingAngleBrackets(tok));
}




static String jreplace(String s, String in, String out) {
  return jreplace(s, in, out, null);
}

static String jreplace(String s, String in, String out, Object condition) {
  List<String> tok = javaTok(s);
  return jreplace(tok, in, out, condition) ? join(tok) : s;
}

// leaves tok properly tokenized
// returns true iff anything was replaced
static boolean jreplace(List<String> tok, String in, String out) {
  return jreplace(tok, in, out, false, true, null);
}

static boolean jreplace(List<String> tok, String in, String out, Object condition) {
  return jreplace(tok, in, out, false, true, condition);
}

static boolean jreplace(List<String> tok, String in, String out, IF2<List<String>, Integer, Boolean> condition) {
  return jreplace(tok, in, out, (Object) condition);
}

static boolean jreplace(List<String> tok, String in, String out, boolean ignoreCase, boolean reTok, Object condition) {
  String[] toks = javaTokForJFind_array(in);
  int lTokin = toks.length*2+1;

  boolean anyChange = false;
  int i = -1;
  for (int n = 0; n < 10000; n++) { // TODO: don't need this check anymore
    i = findCodeTokens(tok, i+1, ignoreCase, toks, condition);
    if (i < 0)
      return anyChange;
    List<String> subList = tok.subList(i-1, i+lTokin-1); // N to N
    String expansion = jreplaceExpandRefs(out, subList);
    int end = i+lTokin-2;
    
    clearAllTokens(tok, i, end); // C to C
    tok.set(i, expansion);
    if (reTok) // would this ever be false??
      reTok(tok, i, end);
    i = end;
    anyChange = true;
  }
  throw fail("woot? 10000! " + quote(in) + " => " + quote(out));
}

static boolean jreplace_debug = false;


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




// unclear semantics when l is a special set (e.g. ciSet)

static <A> boolean containsOneOf(Collection<A> l, A... x) {
  if (l instanceof Set) {
    if (x != null)
      for (A a : x)
        if (l.contains(a))
          return true;
  } else {
    for (A a : unnull(l))
      if (eqOneOf(a, x))
        return true;
  }
  return false;
}

static <A> boolean containsOneOf(Collection<A> l, Set<A> set) {
  if (set == null) return false;
  for (A a : unnull(l))
    if (set.contains(a))
      return true;
  return false;
}

static boolean containsOneOf(String s, String... x) {
  for (String o : x)
    if (contains(s, o)) return true;
  return false;
}


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 <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<String> trimAll(Collection<String> l) {
  List<String> l2 = new ArrayList();
  if (l != null) for (String s : l)
    l2.add(trim(s));
  return l2;
}


static List map(Iterable l, Object f) { return map(f, l); }

static List map(Object f, Iterable l) {
  List x = emptyList(l);
  if (l != null) for  (Object o : l)
    { ping(); x.add(callF(f, o)); }
  return x;
}


  static <A, B> List<B> map(Iterable<A> l, F1<A, B> f) { return map(f, l); }

  static <A, B> List<B> map(F1<A, B> f, Iterable<A> l) {
    List x = emptyList(l);
    if (l != null) for  (A o : l)
      { ping(); x.add(callF(f, o)); }
    return x;
  }


static <A, B> List<B> map(IF1<A, B> f, Iterable<A> l) { return map(l, f); }
static <A, B> List<B> map(Iterable<A> l, IF1<A, B> f) {
  List x = emptyList(l);
  if (l != null) for  (A o : l)
    { ping(); x.add(f.get(o)); }
  return x;
}
  
static <A, B> List<B> map(IF1<A, B> f, A[] l) { return map(l, f); }
static <A, B> List<B> map(A[] l, IF1<A, B> f) {
  List x = emptyList(l);
  if (l != null) for  (A o : l)
    { ping(); x.add(f.get(o)); }
  return x;
}
  
static List map(Object f, Object[] l) { return map(f, asList(l)); }
static List map(Object[] l, Object f) { return map(f, l); }

static List map(Object f, Map map) {
  return map(map, f);
}

// map: func(key, value) -> list element
static List map(Map map, Object f) {
  List x = new ArrayList();
  if (map != null) for  (Object _e : map.entrySet()) { ping(); 
    Map.Entry e = (Map.Entry) _e;
    x.add(callF(f, e.getKey(), e.getValue()));
  }
  return x;
}

static <A, B, C> List<C> map(Map<A, B> map, IF2<A, B, C> f) {
  return map(map, (Object) f);
}

// new magic alias for mapLL - does it conflict?

static <A, B> List<A> map(IF1<A, B> f, A data1, A... moreData) {
  List x = emptyList(l(moreData)+1);
  x.add(f.get(data1));
  if (moreData != null) for  (A o : moreData)
    { ping(); x.add(f.get(o)); }
  return x;
}


static String tok_dropFinal(String s) {
  return jreplace(s, "final", "");
}


static <A, B> List<B> lmap(IF1<A, B> f, Iterable<A> l) {
  return lambdaMap(f, l);
}



static <A, B> List<B> lmap(IF1<A, B> f, A[] l) {
  return lambdaMap(f, l);
}


static String tok_nameOfParam(String param) {
  List<String> tok = javaTok(param);
  tok = takeFirst(tok, smartLastIndexOf(tok, "default"));
  return lastIdentifier(tok);
}


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(MultiMap mm) { return mm == null || mm.isEmpty(); }


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










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







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 String joinWithEmptyLines(Iterable<String> l) {
  return join("\n\n", map(__21 -> rtrim(__21), l));
}

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


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

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

static void printIndent(int indent, Object o) {
  print(indentx(indent, str(o)));
}


static boolean isStandardFunction(String name) {
  return isIdentifier(name) && stdFunctions_cached().containsKey(name);
}


static String sfSnippet(String function) {
  return stdFunctions_cached().get(function);
}


static String editSnippet(String id, String text) {
  if (isLocalSnippetID(id)) { saveLocalSnippet(id, text); return "Snippet saved"; }
  return editSnippet(id, text,
    standardCredentialsUser(), standardCredentialsPass());
}

static String editSnippet(String id, String text, String user, String pass) {
  if (!isSnippetID(id)) throw fail("Need snippet ID");
  Map query = litmap("id", parseSnippetID(id),
    "text", text,
    "_user", user,
    "_pass", pass,
    "len", lUtf8(text));
  String url = tb_mainServer() + "/tb-int/update_snippet_text.php";
  String page = doPost(query, url);
  if (!page.contains("OK"))
    throw fail("Error editing snippet " + id + ": " + quote(page));
  return page + " - edited " + fsI(id);
}


static Snippet createSnippet(Snippet s) {
  s.id = createSnippet(s.text, s.title, parseSnippetType(s.type));
  return s;
}

static String createSnippet(String text, String title, int type) { return createSnippet(text, title, type, true); }
static String createSnippet(String text, String title, int type, boolean isPublic) {
  return createSnippet(text, title, type, standardCredentialsUser(), standardCredentialsPass(), isPublic);
}

static String createSnippet(String text, String title, int type, String user, String pass) { return createSnippet(text, title, type, user, pass, true); }
static String createSnippet(String text, String title, int type, String user, String pass, boolean isPublic) {
  String addURL = tb_mainServer() + "/tb-int/add_snippet.php";
  Map query = litmap("type", type,
    "high", 1,
    "public", isPublic ? 1 : 0,
    "text", text,
    "name", title,
    "_user", user,
    "_pass", pass);
  String answer = doPost(query, addURL);
  if (!isSnippetID(answer)) throw fail(answer);
  print("Snippet created. " + formatSnippetID(answer));
  return formatSnippetID(answer);
}


static int snippetType_JavaXInclude() {
  return 42;
}


static void checkMarkAnimation_bottomLeft(String text, double seconds) {
  showAnimationInBottomLeftCorner("#1005392", text, seconds);
}


// add latest include
static String addStdFunction() {
  {  AutoCloseable __1 = tempShowLoadingAnimation(); try { 
    return addStdFunction(findLatestInclude().id, true);
  } finally { _close(__1); }}
}

static String addStdFunction(boolean realEdit) {
  return addStdFunction(findLatestInclude().id, realEdit);
}

static String addStdFunction_1006654;

static String addStdFunction(String snippetID, boolean realEdit) {
  {  AutoCloseable __2 = tempShowLoadingAnimation(); try { 
    List<String> names = 
      // OLD findFunctionDefinitions(loadSnippet(snippetID));
      findFunctionDefs(loadSnippet(snippetID));
    printStructure("Functions defined in " + snippetID + ": ", names);
    if (empty(names)) return "No functions found in " + snippetID;
    String name = firstNotStartingWith(names, "_onLoad_");
    if (empty(names)) return "No definable functions found in " + snippetID;
    
    if (addStdFunction_1006654 == null)
      addStdFunction_1006654 = loadSnippet("#1006654");
    Map<String, String> map = parseStdFunctionsList(addStdFunction_1006654);
    String text = loadSnippet("#761");
    map.putAll(parseStdFunctionsList(text));
    String def = map.get(name);
    String result = "";
    if (def == null) {
      print(result = "Defining " + name + ".");
      
      String newText = addToStdFunctionsList(text, name, snippetID);
      print();
      print(unidiff(text, newText));
      if (!realEdit)
        print(result += " Not editing (test mode).");
      else {
        editSnippet("#761", newText);
        triggerTranspilerDirty();
        print(result += " Edited #761. Now " + countLines(newText) + " lines - " + n(l(map)+1, "total function") + "!");
      }
    } else {
      print(result = "Function " + name + " already defined as " + def);
      if (sameSnippetID(def, snippetID))
        print("Exiting.");
      else
        print("Will not redefine as " + snippetID + ".");
    }
    return result;
  } finally { _close(__2); }}
}




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


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

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


static RuntimeException asRuntimeException(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}


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 Map<String, String> stdFunctions_uncached() {
  return stdFunctions_uncached(new HashMap());
}

static Map<String, String> stdFunctions_uncached(Map<String, String> map) {
  parseStdFunctionsList(loadSnippetSilently("#1006654"), map);
  parseStdFunctionsList(loadSnippetSilently("#761"), map);
  return map;
}


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 boolean regionMatches(String a, int offsetA, String b, int offsetB, int len) {
  return a != null && b != null && a.regionMatches(offsetA, b, offsetB, len);
}

static boolean regionMatches(String a, int offsetA, String b) {
  return regionMatches(a, offsetA, b, 0, l(b));
}


static String javaTok_substringN(String s, int i, int j) {
  if (i == j) return "";
  if (j == i+1 && s.charAt(i) == ' ') return " ";
  return s.substring(i, j);
}


static String javaTok_substringC(String s, int i, int j) {
  return s.substring(i, j);
}


static List<String> javaTokWithExisting(String s, List<String> existing) {
  ++javaTok_n;
  int nExisting = javaTok_opt && existing != null ? existing.size() : 0;
  ArrayList<String> tok = existing != null ? new ArrayList(nExisting) : new ArrayList();
  int l = s.length();
  
  int i = 0, n = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
    // scan for whitespace
    while (j < l) {
      c = s.charAt(j);
      d = j+1 >= l ? '\0' : s.charAt(j+1);
      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
        ++j;
      else if (c == '/' && d == '*') {
        do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
        j = Math.min(j+2, l);
      } else if (c == '/' && d == '/') {
        do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
      } else
        break;
    }
    
    if (n < nExisting && javaTokWithExisting_isCopyable(existing.get(n), s, i, j))
      tok.add(existing.get(n));
    else
      tok.add(javaTok_substringN(s, i, j));
    ++n;
    i = j;
    if (i >= l) break;
    c = s.charAt(i);
    d = i+1 >= l ? '\0' : s.charAt(i+1);

    // scan for non-whitespace
    
    // Special JavaX syntax: 'identifier
    if (c == '\'' && Character.isJavaIdentifierStart(d) && i+2 < l && "'\\".indexOf(s.charAt(i+2)) < 0) {
      j += 2;
      while (j < l && Character.isJavaIdentifierPart(s.charAt(j)))
        ++j;
    } else if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        if (s.charAt(j) == opener /*|| s.charAt(j) == '\n'*/) { // allow multi-line strings
          ++j;
          break;
        } else if (s.charAt(j) == '\\' && j+1 < l)
          j += 2;
        else
          ++j;
      }
    } else if (Character.isJavaIdentifierStart(c))
      do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || "'".indexOf(s.charAt(j)) >= 0)); // for stuff like "don't"
    else if (Character.isDigit(c)) {
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
      if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L
    } else if (c == '[' && d == '[') {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (c == '[' && d == '=' && i+2 < l && s.charAt(i+2) == '[') {
      do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]"));
      j = Math.min(j+3, l);
    } else
      ++j;
      
    if (n < nExisting && javaTokWithExisting_isCopyable(existing.get(n), s, i, j))
      tok.add(existing.get(n));
    else
      tok.add(javaTok_substringC(s, i, j));
    ++n;
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  javaTok_elements += tok.size();
  return tok;
}

static boolean javaTokWithExisting_isCopyable(String t, String s, int i, int j) {
  return t.length() == j-i
    && s.regionMatches(i, t, 0, j-i); // << could be left out, but that's brave
}


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 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 boolean isLocalSnippetID(String snippetID) {
  return isSnippetID(snippetID) && isLocalSnippetID(psI(snippetID));
}

static boolean isLocalSnippetID(long snippetID) {
  return snippetID >= 1000 && snippetID <= 9999;
}


static String loadLocalSnippet(String snippetID) {
  return loadLocalSnippet(psI(snippetID));
}

static String loadLocalSnippet(long snippetID) {
  return loadTextFile(localSnippetFile(snippetID));
}


static IResourceLoader vm_getResourceLoader() {
  return proxy(IResourceLoader.class, vm_generalMap_get("_officialResourceLoader"));
}


static String fsI(String id) {
  return formatSnippetID(id);
}

static String fsI(long id) {
  return formatSnippetID(id);
}


static String md5(String text) { try {
  if (text == null) return "-";
  return bytesToHex(md5_impl(toUtf8(text))); // maybe different than the way PHP does it...
} catch (Exception __e) { throw rethrow(__e); } }

static String md5(byte[] data) {
  if (data == null) return "-";
  return bytesToHex(md5_impl(data));
}

static byte[] md5_impl(byte[] data) { try {
  return MessageDigest.getInstance("MD5").digest(data);
} catch (Exception __e) { throw rethrow(__e); } }

static String md5(File file) {
  return md5OfFile(file);
}


static String tb_mainServer_default = "https://code.botcompany.de:9898";
static Object tb_mainServer_override; // func -> S

static String tb_mainServer() {
  if (tb_mainServer_override != null) return (String) callF(tb_mainServer_override);
  return trim(loadTextFile(tb_mainServer_file(),
    tb_mainServer_default));
}

static File tb_mainServer_file() {
  return getProgramFile("#1001638", "mainserver.txt");
}

static boolean tb_mainServer_isDefault() {
  return eq(tb_mainServer(), tb_mainServer_default);
}


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(MultiMap mm) { return mm != null && !mm.isEmpty(); }



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









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







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 String standardCredentials() {
  String user = standardCredentialsUser();
  String pass = standardCredentialsPass();
  if (nempty(user) && nempty(pass))
    return "&_user=" + urlencode(user) + "&_pass=" + urlencode(pass);
  return "";
}


static String loadTextFile(String fileName) {
  return loadTextFile(fileName, null);
}

static String loadTextFile(File f, String defaultContents) { return loadTextFile(f, defaultContents, "UTF-8"); }
static String loadTextFile(File f, String defaultContents, String encoding) { try {
  
  checkFileNotTooBigToRead(f);
  
  if (f == null || !f.exists()) return defaultContents;

  FileInputStream fileInputStream = new FileInputStream(f);
  InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, encoding);
  return loadTextFile(inputStreamReader);
} catch (Exception __e) { throw rethrow(__e); } }

public static String loadTextFile(File fileName) {
  return loadTextFile(fileName, null);
}

static String loadTextFile(String fileName, String defaultContents) {
  return fileName == null ? defaultContents : loadTextFile(newFile(fileName), defaultContents);
}

static String loadTextFile(Reader reader) throws IOException {
  StringBuilder builder = new StringBuilder();
  try {
    char[] buffer = new char[1024];
    int n;
    while (-1 != (n = reader.read(buffer)))
      builder.append(buffer, 0, n);
  } finally {
    reader.close();
  }
  return str(builder);
}


/** writes safely (to temp file, then rename) */
static File saveTextFile(String fileName, String contents) throws IOException {
  CriticalAction action = beginCriticalAction("Saving file " + fileName + " (" + l(contents) + " chars)");
  try {
    File file = new File(fileName);
    mkdirsForFile(file);
    String tempFileName = fileName + "_temp";
    File tempFile = new File(tempFileName);
    if (contents != null) {
      if (tempFile.exists()) try {
        String saveName = tempFileName + ".saved." + now();
        copyFile(tempFile, new File(saveName));
      } catch (Throwable e) { printStackTrace(e); }
      FileOutputStream fileOutputStream = newFileOutputStream(tempFile.getPath());
      OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
      PrintWriter printWriter = new PrintWriter(outputStreamWriter);
      printWriter.print(contents);
      printWriter.close();
    }
    
    if (file.exists() && !file.delete())
      throw new IOException("Can't delete " + fileName);
  
    if (contents != null)
      if (!tempFile.renameTo(file))
        throw new IOException("Can't rename " + tempFile + " to " + file);
        
    
    vmBus_send("wroteFile", file);
    
    return file;
  } finally {
    action.done();
  }
}

static File saveTextFile(File fileName, String contents) { try {
  saveTextFile(fileName.getPath(), contents);
  return fileName;
} catch (Exception __e) { throw rethrow(__e); } }


static File getGlobalCache() {
  File file = new File(javaxCachesDir(), "Binary Snippets");
  file.mkdirs();
  return file;
}



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


static int loadPage_defaultTimeout = 60000;
static ThreadLocal<String> loadPage_charset = new ThreadLocal();
static boolean loadPage_allowGzip = true, loadPage_debug;
static boolean loadPage_anonymous = false; // don't send computer ID
static int loadPage_verboseness = 100000;
static int loadPage_retries = 1; //60; // seconds
static ThreadLocal<Boolean> loadPage_silent = new ThreadLocal();
static volatile int loadPage_forcedTimeout; // ms
static ThreadLocal<Integer> loadPage_forcedTimeout_byThread = new ThreadLocal(); // ms
static ThreadLocal<Map<String, List<String>>> loadPage_responseHeaders = new ThreadLocal();
static ThreadLocal<Map<String, String>> loadPage_extraHeaders = new ThreadLocal();
static ThreadLocal<Long> loadPage_sizeLimit = new ThreadLocal();

public static String loadPageSilently(String url) { try {
  return loadPageSilently(new URL(loadPage_preprocess(url)));
} catch (Exception __e) { throw rethrow(__e); } }

public static String loadPageSilently(URL url) { try {
  if (!networkAllowanceTest(str(url))) throw fail("Not allowed: " + url);
    
  IOException e = null;
  for (int tries = 0; tries < loadPage_retries; tries++)
    try {
      URLConnection con = loadPage_openConnection(url);
      return loadPage(con, url);
    } catch (IOException _e) {
      e = _e;
      if (loadPage_debug)
        print(exceptionToStringShort(e));
      if (tries < loadPage_retries-1) sleepSeconds(1);
    }
  throw e;
} catch (Exception __e) { throw rethrow(__e); } }

static String loadPage_preprocess(String url) {  
  if (url.startsWith("tb/")) // don't think we use this anymore
    url = tb_mainServer() + "/" + url;
  if (url.indexOf("://") < 0)
    url = "http://" + url;
  return url;
}

static String loadPage(String url) { try {
  url = loadPage_preprocess(url);
  if (!isTrue(loadPage_silent.get()))
    printWithTime("Loading: " + hideCredentials(url));
  return loadPageSilently(new URL(url));
} catch (Exception __e) { throw rethrow(__e); } }

static String loadPage(URL url) {
  return loadPage(url.toExternalForm());
}

static String loadPage(URLConnection con, URL url) throws IOException {
  return loadPage(con, url, true);
}

static String loadPage(URLConnection con, URL url, boolean addHeaders) throws IOException {
  Map<String, String> extraHeaders = getAndClearThreadLocal(loadPage_extraHeaders);
  Long limit = optPar(loadPage_sizeLimit);
  if (addHeaders) try {
    if (!loadPage_anonymous)
      setHeaders(con);
    if (loadPage_allowGzip)
      con.setRequestProperty("Accept-Encoding", "gzip");
    con.setRequestProperty("X-No-Cookies", "1");
    for (String key : keys(extraHeaders))
      con.setRequestProperty(key, extraHeaders.get(key));
  } catch (Throwable e) {} // fails if within doPost
  
  
  vm_generalSubMap("URLConnection per thread").put(currentThread(), con);
  
  loadPage_responseHeaders.set(con.getHeaderFields());
  InputStream in = null;
  try {
    in = urlConnection_getInputStream(con);
  //vm_generalSubMap("InputStream per thread").put(currentThread(), in);
  if (loadPage_debug)
    print("Put stream in map: " + currentThread());
    String contentType = con.getContentType();
    if (contentType == null) {
      //printStruct("Headers: ", con.getHeaderFields());
      throw new IOException("Page could not be read: " + hideCredentials(url));
    }
    //print("Content-Type: " + contentType);
    String charset = loadPage_charset == null ? null : loadPage_charset.get();
    if (charset == null) charset = loadPage_guessCharset(contentType);
    
    if ("gzip".equals(con.getContentEncoding())) {
      if (loadPage_debug)
        print("loadPage: Using gzip.");
      in = newGZIPInputStream(in);
    }
    Reader r;
    try {
      r = new InputStreamReader(in, unquote(charset));
    } catch (UnsupportedEncodingException e) {
      print(toHex(utf8(charset)));
      throw e;
    }
    
    boolean silent = isTrue(loadPage_silent.get());
    StringBuilder buf = new StringBuilder();
    int n = 0;
    while (limit == null || n < limit) {
      ping();
      int ch = r.read();
      if (ch < 0)
        break;
      buf.append((char) ch);
      ++n;
      if (!silent && (n % loadPage_verboseness) == 0)
        print("  " + n + " chars read");
    }
    return buf.toString();
  } finally {
    if (loadPage_debug)
      print("loadPage done");
    //vm_generalSubMap("InputStream per thread").remove(currentThread());
    
    vm_generalSubMap("URLConnection per thread").remove(currentThread());
    
    if (in != null) in.close();
  }
}

static String loadPage_guessCharset(String contentType) {
  Matcher m = regexpMatcher("text/[a-z]+;\\s*charset=([^\\s]+)\\s*", contentType);
  String match = m.matches() ? m.group(1) : null;
  if (loadPage_debug)
    print("loadPage: contentType=" + contentType + ", match: " + match);
  /* If Content-Type doesn't match this pre-conception, choose default and hope for the best. */
  //return or(match, "ISO-8859-1");
  return or(match, "UTF-8");
}

static URLConnection loadPage_openConnection(URL url) {
  URLConnection con = openConnection(url);
  int timeout = toInt(loadPage_forcedTimeout_byThread.get());
  if (timeout == 0) timeout = loadPage_forcedTimeout;
  if (timeout != 0)
    setURLConnectionTimeouts(con, loadPage_forcedTimeout);
  else
    setURLConnectionDefaultTimeouts(con, loadPage_defaultTimeout);
  return con;
}


static List<String> splitAtSpace(String s) {
  return empty(s) ? emptyList() : asList(s.split("\\s+"));
}


static int l(Object[] a) { return a == null ? 0 : a.length; }
static int l(boolean[] a) { return a == null ? 0 : a.length; }
static int l(byte[] a) { return a == null ? 0 : a.length; }
static int l(short[] a) { return a == null ? 0 : a.length; }
static int l(long[] a) { return a == null ? 0 : a.length; }
static int l(int[] a) { return a == null ? 0 : a.length; }
static int l(float[] a) { return a == null ? 0 : a.length; }
static int l(double[] a) { return a == null ? 0 : a.length; }
static int l(char[] a) { return a == null ? 0 : a.length; }
static int l(Collection c) { return c == null ? 0 : c.size(); }

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

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



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






















// i must point at the (possibly imaginary) opening bracket ("{")
// index returned is index of closing bracket + 1 (or l(tok))
static int findEndOfBlock(List<String> tok, int i) {
  return tok_findEndOfBlock(tok, i);
}


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


static boolean eqGet(List l, int i, Object o) {
  return eq(get(l, i), o);
}

static <A, B> boolean eqGet(Map<A, B> map, A key, Object o) {
  return eq(mapGet(map, key), o);
}


static boolean isIdentifier(String s) {
  return isJavaIdentifier(s);
}


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




// returns l(s) if not found
static int smartIndexOf(String s, String sub, int i) {
  if (s == null) return 0;
  i = s.indexOf(sub, min(i, l(s)));
  return i >= 0 ? i : l(s);
}

static int smartIndexOf(String s, int i, char c) {
  return smartIndexOf(s, c, i);
}

static int smartIndexOf(String s, char c, int i) {
  if (s == null) return 0;
  i = s.indexOf(c, min(i, l(s)));
  return i >= 0 ? i : l(s);
}

static int smartIndexOf(String s, String sub) {
  return smartIndexOf(s, sub, 0);
}

static int smartIndexOf(String s, char c) {
  return smartIndexOf(s, c, 0);
}

static <A> int smartIndexOf(List<A> l, A sub) {
  return smartIndexOf(l, sub, 0);
}

static <A> int smartIndexOf(List<A> l, int start, A sub) {
  return smartIndexOf(l, sub, start);
}

static <A> int smartIndexOf(List<A> l, A sub, int start) {
  int i = indexOf(l, sub, start);
  return i < 0 ? l(l) : i;
}


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 Field makeAccessible(Field f) {
  try {
    f.setAccessible(true);
  } catch (Throwable e) {
    // Note: The error reporting only works with Java VM option --illegal-access=deny
    
    vmBus_send("makeAccessible_error", e, f);
    
  }
  return f;
}

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

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


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


static List<String> tok_parseArgsList(String s) {
  return tok_parseArgsList(javaTok(s));
}

static List<String> tok_parseArgsList(List<String> tok) {
  return tok_parseArgsList(tok, getBracketMap(tok));
}
  
static List<String> tok_parseArgsList(List<String> tok, Map<Integer, Integer> bracketMap) {
  int i = indexOfAny(tok, 0, "(", "{");
  if (i < 0) return null;
  if (eq(get(tok, i), "{")) return ll(); // e.g. void bla { ... }
  int argStart = (i += 2);
  List<String> args = new ArrayList();
  while (i < l(tok)-2 && neq(get(tok, i), ")")) {
    Integer j = bracketMap.get(i);
    if (j != null) { i = j+2; continue; }
    if (eqGetOneOf(tok, i, ",")) {
      if (i > argStart) args.add(trimJoinSubList(tok, argStart, i));
      argStart = i+2;
    }
    i += 2;
  }
  if (i > argStart) args.add(trimJoinSubList(tok, argStart, i));
  return args;
}


static HashSet<String> getBracketMapIncludingAngleBrackets_opening = lithashset("(", "{", "<");
static HashSet<String> getBracketMapIncludingAngleBrackets_closing = lithashset(")", "}", ">");

static Map<Integer, Integer> getBracketMapIncludingAngleBrackets(List tok) {
  return getBracketMap(tok, getBracketMapIncludingAngleBrackets_opening, getBracketMapIncludingAngleBrackets_closing);
}

static Map<Integer, Integer> getBracketMapIncludingAngleBrackets(List tok, int from, int to) {
  return getBracketMap(tok, getBracketMapIncludingAngleBrackets_opening, getBracketMapIncludingAngleBrackets_closing, from, to);
}






  static Map<String, String[]> javaTokForJFind_array_cache = synchronizedMRUCache(1000);


static String[] javaTokForJFind_array(String s) {
  String[] tok = javaTokForJFind_array_cache.get(s);
  if (tok == null)
    javaTokForJFind_array_cache.put(s, tok = codeTokensAsStringArray(jfind_preprocess(javaTok(s))));
  return tok;
}




// Note: In the transpiler, this version is used: #1025802

static int findCodeTokens(List<String> tok, String... tokens) {
  return findCodeTokens(tok, 1, false, tokens);
}

static int findCodeTokens(List<String> tok, boolean ignoreCase, String... tokens) {
  return findCodeTokens(tok, 1, ignoreCase, tokens);
}

static int findCodeTokens(List<String> tok, int startIdx, boolean ignoreCase, String... tokens) {
  return findCodeTokens(tok, startIdx, ignoreCase, tokens, null);
}

static HashSet<String> findCodeTokens_specials = lithashset("*", "<quoted>", "<id>", "<int>", "\\*");
static int findCodeTokens_bails, findCodeTokens_nonbails;

static interface findCodeTokens_Matcher {
  boolean get(String token);
}

static int findCodeTokens(List<String> tok, int startIdx, boolean ignoreCase, String[] tokens, Object condition) {
  int end = tok.size()-tokens.length*2+2, nTokens = tokens.length;
  int i = startIdx | 1;
  if (i >= end) return -1;
  
  // bail out early if first token not found (works great with IndexedList)
  String firstToken = tokens[0];
  if (!ignoreCase && !findCodeTokens_specials.contains(firstToken)) {
    
    
    // quickly scan for first token
    while (i < end && !firstToken.equals(tok.get(i)))
      i += 2;
  }
  
  findCodeTokens_Matcher[] matchers = new findCodeTokens_Matcher[nTokens];
  for (int j = 0; j < nTokens; j++) {
    String p = tokens[j];
    findCodeTokens_Matcher matcher;
    if (p.equals("*"))
      matcher = t -> true;
    else if (p.equals("<quoted>"))
      matcher = t -> isQuoted(t);
    else if (p.equals("<id>"))
      
      
      matcher = t -> isIdentifier(t);
      
    else if (p.equals("<int>"))
      
      
      matcher = t -> isInteger(t);
      
    else if (p.equals("\\*"))
      matcher = t -> t.equals("*");
    else if (ignoreCase)
      matcher = t -> eqic(p, t);
    else
      matcher = t -> t.equals(p);
    matchers[j] = matcher;
  }
 
  outer: for (; i < end; i += 2) {
    for (int j = 0; j < nTokens; j++)
      if (!matchers[j].get(tok.get(i+j*2)))
        continue outer;

    if (condition == null || checkTokCondition(condition, tok, i-1)) // pass N index
      return i;
  }
  return -1;
}


// "$1" is first code token, "$2" second code token etc.
static String jreplaceExpandRefs(String s, List<String> tokref) {
  if (!contains(s, '$')) return s;
  List<String> tok = javaTok(s);
  for (int i = 1; i < l(tok); i += 2) {
    String t = tok.get(i);
    if (t.startsWith("$") && isInteger(t.substring(1))) {
      String x = tokref.get(-1+parseInt(t.substring(1))*2);
      tok.set(i, x);
    } else if (t.equals("\\")) {
      tok.set(i, "");
      i += 2;
    }
  }
  return join(tok);
}



  static void clearAllTokens(List<String> tok) {
    for (int i = 0; i < tok.size(); i++)
      tok.set(i, "");
  }
  
  static void clearAllTokens(List<String> tok, int i, int j) {
    for (; i < j; i++)
      tok.set(i, "");
  }


static List<String> reTok(List<String> tok) {
  replaceCollection(tok, javaTok(tok));
  return tok;
}

static List<String> reTok(List<String> tok, int i) {
  return reTok(tok, i, i+1);
}

static List<String> reTok(List<String> tok, int i, int j) {
  // extend i to an "N" token
  // and j to "C" (so j-1 is an "N" token)
  i = max(i & ~1, 0);
  j = min(l(tok), j | 1);
  if (i >= j) return tok;
  
  List<String> t = javaTok(joinSubList(tok, i, j));
  replaceListPart(tok, i, j, t);
  
  // fallback to safety
  // reTok(tok);
  
  return tok;
}




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


// 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> List<A> asList(Pair<A, A> p) {
  return p == null ? null : ll(p.a, p.b);
}



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

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

static Object collectionMutex(Object o) {
  

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

  
  
  if (eqOneOf(c, "java.util.TreeMap$AscendingSubMap", "java.util.TreeMap$DescendingSubMap"))
    c = className(o = get_raw(o, "m"));
    
  
    
  
  return o;
}


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








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

// always returns true
static boolean ping() {
  //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 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, B, C> C callF(IF2<A, B, C> f, A a, B b) {
    return f == null ? null : f.get(a, b);
  }



  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 <A, B> List<B> lambdaMap(IF1<A, B> f, Iterable<A> l) {
  return map(l, f);
}

static <A, B> List<B> lambdaMap(IF1<A, B> f, A[] l) {
  return map(l, f);
}


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

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

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

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

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

static <A> List<A> takeFirst(int n, Iterable<A> i) {
  if (i == null) return null;
  return i == null ? null : takeFirst(n, i.iterator());
}

static <A> List<A> takeFirst(int n, IterableIterator<A> i) {
  return takeFirst(n, (Iterator<A>) i);
}

static int[] takeFirst(int n, int[] a) { return takeFirstOfIntArray(n, a); }

static short[] takeFirst(int n, short[] a) { return takeFirstOfShortArray(n, a); }

static byte[] takeFirst(int n, byte[] a) { return takeFirstOfByteArray(n, a); }
static byte[] takeFirst(byte[] a, int n) { return takeFirstOfByteArray(n, a); }

static double[] takeFirst(int n, double[] a) { return takeFirstOfDoubleArray(n, a); }
static double[] takeFirst(double[] a, int n) { return takeFirstOfDoubleArray(n, a); }


static int smartLastIndexOf(String s, char c) {
  if (s == null) return 0;
  int i = s.lastIndexOf(c);
  return i >= 0 ? i : l(s);
}

static <A> int smartLastIndexOf(List<A> l, A sub) {
  int i = lastIndexOf(l, sub);
  return i < 0 ? l(l) : i;
}


static String lastIdentifier(List<String> l) {
  for (int i = l(l)-1; i >= 0; i--) {
    String t = l.get(i);
    if (isIdentifier(t)) return t;
  }
  return null;
}


static String getType(Object o) {
  return getClassName(o);
}


static long getFileSize(String path) {
  return path == null ? 0 : new File(path).length();
}

static long getFileSize(File f) {
  return f == null ? 0 : f.length();
}


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


public static String rtrim(String s) {
  if (s == null) return null;
  int i = s.length();
  while (i > 0 && " \t\r\n".indexOf(s.charAt(i-1)) >= 0)
    --i;
  return i < s.length() ? s.substring(0, i) : s;
}


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

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

static String indentx(String indent, String s) {
  return dropSuffix(indent, indent(indent, s));
}


static void saveLocalSnippet(String snippetID, String text) {
  saveLocalSnippet(psI(snippetID), text);
}

static void saveLocalSnippet(long snippetID, String text) {
  saveTextFile(localSnippetFile(snippetID), text);
}


static String standardCredentialsUser() {
  return trim(loadTextFile(
    oneOfTheFiles(
      javaxSecretDir("tinybrain-username"),
      userDir(".tinybrain/username"))));
}


static String standardCredentialsPass() {
  return trim(loadTextFile(
    oneOfTheFiles(
      javaxSecretDir("tinybrain-userpass"),
      userDir(".tinybrain/userpass"))));
}


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


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 int lUtf8(String s) {
  return l(utf8(s));
}


static ThreadLocal<Boolean> doPost_silently = new ThreadLocal();
static ThreadLocal<Long> doPost_timeout = new ThreadLocal();
static ThreadLocal<Map<String, String>> doPost_extraHeaders = new ThreadLocal();

static String doPost(String url, Map urlParameters) {
  return doPost(urlParameters, url);
}

static String doPost(Map urlParameters, String url) {
  return doPost(makePostData(urlParameters), url);
}

static String doPost(String urlParameters, String url) { try {
  URL _url = new URL(url);
  ping();
  return doPost(urlParameters, _url.openConnection(), _url);
} catch (Exception __e) { throw rethrow(__e); } }

static String doPost(String urlParameters, URLConnection conn, URL url) { try {
  boolean silently = isTrue(optParam(doPost_silently));
  Long timeout = optParam(doPost_timeout);
  Map<String, String> extraHeaders = optPar(doPost_extraHeaders);
  setHeaders(conn);
  for (String key : keys(extraHeaders)) {
    
    conn.setRequestProperty(key, extraHeaders.get(key));
  }

  int l = lUtf8(urlParameters);
  if (!silently)
    print("Sending POST request: " + hideCredentials(url) + " (" + l + " bytes)");
      
  // connect and do POST
  if (timeout != null) setURLConnectionTimeouts(conn, timeout);
  ((HttpURLConnection) conn).setRequestMethod("POST");
  conn.setDoOutput(true);
  conn.setRequestProperty("Content-Length", str(l));

  OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
  writer.write(urlParameters);
  writer.flush();

  String contents = loadPage_utf8(conn, url, false);
  writer.close();
  return contents;
} catch (Exception __e) { throw rethrow(__e); } }


static String parseSnippetType_defs1 = "\r\ndefine('SN_DECLS', 1);\r\ndefine('SN_TESTCASE', 2);\r\ndefine('SN_COMMENT', 3);\r\ndefine('SN_RUNREPORT', 4); # report after running a test case\r\ndefine('SN_SOURCE', 5);  # uploaded source file (Java or other)\r\ndefine('SN_SOLUTION', 6);  # solution tree\r\ndefine('SN_LUA', 7);  # Lua code (solutions have this type too)\r\ndefine('SN_LUA_CHATBOT', 8);  # A Lua chat-bot\r\ndefine('SN_LUA_TESTCASE', 9); # A test case in Lua\r\n#define('SN_LUA_SOLUTION', 10); # A text-to-text solution in Lua (now integrated as plain SN_LUA)\r\ndefine('SN_OBJECT', 11); # An object\r\ndefine('SN_LUA_IMAGE', 12);\r\ndefine('SN_LUA_ANIMATION', 13); # Image plus (optional) animation\r\ndefine('SN_LUA_GUI', 14); # Animation plus keyboard+mouse events\r\ndefine('SN_COMMENT', 15);  # A comment on another snippet\r\ndefine('SN_INOUTEXAMPLE', 16); # A conversion example with input+output\r\ndefine('SN_LUA_SYSTEMTEST', 17); # A free-standing test case (not requiring a solution snippet)\r\ndefine('SN_LUA_SERVICEBOT', 18); # A bot in the group chat\r\ndefine('SN_LUA_SUGGESTER', 19); # A suggester for editing snippets\r\ndefine('SN_RUNLOG', 20);\r\ndefine('SN_JAVA', 21);  # Java source code\r\ndefine('SN_PSEUDOCODE', 22);\r\ndefine('SN_IMAGE', 23); # An image stored as a blob\r\ndefine('SN_T2G_GUICODE', 24); # Janino code for Text2GUI\r\ndefine('SN_JAVA_IR', 25);  # Java image recognition script\r\ndefine('SN_LUA_IR', 26);  # Lua image recognition script\r\ndefine('SN_LUA_COMMENTER', 27);  # Lua snippet commenter script\r\ndefine('SN_LUA_VISUALIZER', 28);  # Lua visualizer script\r\ndefine('SN_LUA_PRODUCER', 29);\r\ndefine('SN_LUA_IMAGETRANSFORMER', 30); # Lua image transformer\r\ndefine('SN_LUA_ID_LEARNER', 31); # Image distinction (learner)\r\ndefine('SN_LUA_ID_EXECUTOR', 32); # Image distinction (executor)\r\ndefine('SN_LUA_LIVE', 33); # Lua Live\r\ndefine('SN_LUA_SNIPPET_TO_SNIPPET', 33);\r\ndefine('SN_JAVAX', 34);  # JavaX source code\r\ndefine('SN_HTML', 35);  # HTML source code\r\n#define('SN_DATA_ZIP', 36); # Data (.zip)\r\ndefine('SN_DATA', 37); # Data (any type)\r\ndefine('SN_JAVAX_TRANSLATOR', 38);  # JavaX translator\r\ndefine('SN_JAVAX_INPUT_TXT_OUTPUT_TXT', 39);  # Certain type of JavaX program\r\ndefine('SN_JAVAX_ANDROID', 40);\r\ndefine('SN_IOIOI', 41);\r\ndefine('SN_JAVAX_INC', 42);\r\ndefine('SN_JAVAX_INCOMPLETE', 43);\r\ndefine('SN_JAVAX_SNIPPETCOMMENTER', 44);\r\ndefine('SN_QUESTION', 45);\r\ndefine('SN_INFORMATION', 46);\r\ndefine('SN_JAVAX_TEST', 47);\r\ndefine('SN_NL', 48);\r\ndefine('SN_BLOGPOST', 49);\r\ndefine('SN_MICROTHEORY', 50);\r\ndefine('SN_SNL', 51);\r\ndefine('SN_USER_SUPPLIED_DIALOG', 52);\r\ndefine('SN_AI_CONCEPTS', 53);\r\ndefine('SN_JAVAX_MODULE', 54);\r\ndefine('SN_JAVAX_DESKTOP', 55);\r\ndefine('SN_AI_DRAWING', 56);\r\n# insert next here\r\n\r\ndefine('SN_NT_SNIPPET', 80); # New Tinybrain snippet\r\ndefine('SN_SPACE', 81); # a space for snippets\r\ndefine('SN_DOCUMENT', 100);  # just a human-readable text\r\ndefine('SN_DOCUMENT_TASK', 101);  # human-readable text - task description\r\ndefine('SN_DIRECTORY', 102); # A directory of snippets\r\ndefine('SN_SNIPPET_TYPE', 103);\r\ndefine('SN_IR_TASK', 104);\r\n";

static String parseSnippetType_defs2 = "\r\n  SN_NT_SNIPPET => 'New Tinybrain snippet',\r\n  SN_MICROTHEORY => 'Microtheory',\r\n  SN_DOCUMENT => 'Document',\r\n  SN_USER_SUPPLIED_DIALOG => 'User-supplied dialog',\r\n  SN_JAVAX => 'JavaX source code',\r\n  SN_JAVAX_INC => 'JavaX fragment (include)',\r\n  SN_JAVAX_DESKTOP => 'JavaX source code (desktop)',\r\n  SN_JAVAX_INCOMPLETE => 'JavaX (incomplete)',\r\n  SN_JAVAX_TRANSLATOR => 'JavaX translator',\r\n  SN_JAVAX_ANDROID => 'JavaX source code (Android)',\r\n  SN_JAVAX_MODULE => 'JavaX module',\r\n  SN_JAVAX_TEST => 'JavaX General Test Case',\r\n  SN_JAVAX_INPUT_TXT_OUTPUT_TXT => 'JavaX (input.txt to output.txt)',\r\n  SN_JAVAX_SNIPPETCOMMENTER => 'JavaX (snippet commenter)',\r\n  SN_IOIOI => 'IOIOI',\r\n  SN_QUESTION => 'Question',\r\n  SN_NL => 'Natural language',\r\n  SN_SPACE => 'Space for snippets',\r\n  SN_DOCUMENT_TASK => 'Task description',\r\n  SN_SNL => 'Simplified Natural Language',\r\n  SN_LUA => 'Lua code',\r\n  SN_LUA_IR => 'Lua code - Image recognition',\r\n  SN_LUA_IMAGE => 'Lua code - Image',\r\n  SN_LUA_VISUALIZER => 'Lua code - Visualizer',\r\n  SN_LUA_IMAGETRANSFORMER => 'Lua code - Image transformer',\r\n  SN_LUA_CHATBOT => 'Lua code - Chat-bot',\r\n  SN_LUA_TESTCASE => 'Lua code - Test case',\r\n  SN_LUA_ANIMATION => 'Lua code - Animation',\r\n  SN_LUA_GUI => 'Lua code - GUI',\r\n  SN_LUA_SYSTEMTEST => 'Lua code - System test',\r\n  SN_LUA_SERVICEBOT => 'Lua code - Service bot',\r\n  SN_LUA_SUGGESTER => 'Lua code - Suggester',\r\n  SN_LUA_COMMENTER => 'Lua code - Snippet commenter',\r\n  SN_LUA_PRODUCER => 'Lua code - Snippet producer',\r\n  SN_LUA_ID_LEARNER => 'Lua code - Image distinction (learner)',\r\n  SN_LUA_ID_EXECUTOR => 'Lua code - Image distinction (executor)',\r\n  SN_LUA_LIVE => 'Lua Live - Lua code with side effects',\r\n  SN_LUA_SNIPPET_TO_SNIPPET => 'Lua code - Snippet-to-snippet',\r\n  SN_T2G_GUICODE => 'Java GUI source (Text2GUI/Janino)',\r\n  SN_JAVA => 'Java source code',\r\n  SN_JAVA_IR => 'Java image recognition code',\r\n  SN_PSEUDOCODE => 'Pseudo code',\r\n  SN_HTML => 'HTML',\r\n  SN_IMAGE => 'Image',\r\n  SN_RUNLOG => 'Run Log',\r\n  SN_INFORMATION => 'Information',\r\n# SN_DATA_ZIP => 'Data (.zip)',\r\n  SN_DATA => 'Data',\r\n  SN_DECLS => 'TB declarations',\r\n  SN_TESTCASE => 'TB test case',\r\n  SN_COMMENT => 'Comment',\r\n  SN_RUNREPORT => 'Run report',\r\n  SN_SOURCE => 'Source file',\r\n  SN_SOLUTION => 'TB solution tree',\r\n  SN_COMMENT => 'Comment',\r\n  SN_INOUTEXAMPLE => 'Input/output example',\r\n  SN_OBJECT => 'Object',\r\n  SN_DIRECTORY => 'Directory',\r\n  SN_SNIPPET_TYPE => 'Snippet type',\r\n  SN_IR_TASK => 'Image recognition task',\r\n  SN_BLOGPOST => 'Blog post',\r\n  SN_AI_CONCEPTS => 'AI Concepts',\r\n  SN_AI_DRAWING => 'AI Drawing'\r\n";

static int parseSnippetType(String type) {
  if (eqic(type, "JavaX source code (Desktop)"))
    return 55;
  throw todo();
}


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

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


static JWindow showAnimationInBottomLeftCorner(final String imageID, final String text, double seconds) {
  JWindow w = showAnim(imageID, text, seconds);
  moveToBottomLeftCorner(w);
  return w;
}



static AutoCloseable tempShowLoadingAnimation() {
  return tempShowLoadingAnimation("Hold on user...");
}

static AutoCloseable tempShowLoadingAnimation(String text) { try {
  
  
  return tempDisposeWindow(showAnimationInTopRightCorner("#1003543", text));
  
} catch (Throwable __e) { return null; } }


static Snippet findLatestInclude() {
  return first(
    snippetsFromHTML(tb_mainServer() + "/tb/snippets.php?type=42&order=created&reverse=1&limit=1&author=1" + standardCredentials()));
}


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 Set<String> findFunctionDefs_keywords = new HashSet(splitAtSpace("static svoid ssvoid ssynchronized sbool sS sO sL"));

static List<String> findFunctionDefs(String s) {
  return findFunctionDefs(javaTok(s));
}

static List<String> findFunctionDefs(List<String> tok) {
  List<String> functions = new ArrayList();
  
  if (tok instanceof IContentsIndexedList2) {
    for (String keyword : findFunctionDefs_keywords) {
      TreeSet<HasIndex> indices = ((IContentsIndexedList2) tok).indicesOf_treeSetOfHasIndex(keyword);
      if (indices != null)
        for (HasIndex i : indices)
          findFunctionDefs_step(tok, i.idx, functions);
    }
  } else {
    int n = l(tok);
    for (int i = 1; i < n; i += 2)
      if (findFunctionDefs_keywords.contains(tok.get(i)))
        findFunctionDefs_step(tok, i, functions);
  }
  return functions;
}

// we found one of the keywords listed above at token i
// now scan forward for a function definition
static void findFunctionDefs_step(List<String> tok, int i, List<String> functions) {
  String t = tok.get(i);
  int j = i+2, n = l(tok);
  while (j < n && !findFunctionsDefs_checkToken(tok.get(j)))
    j += 2;
  if (isIdentifier(tok.get(j-2)) &&
    eqGet(tok, j, "(") || eqGet(tok, j, "{") && eq(t, "svoid"))
    functions.add(tok.get(j-2));
}

static boolean findFunctionsDefs_checkToken(String t) {
  if (t.length() != 1) return false;
  char c = t.charAt(0);
  return c == ';' || c == '=' || c == '(' || c == '{' /*|| c == '#' XXX*/;
}


static <A> A printStructure(String prefix, A o) {
  if (endsWithLetter(prefix)) prefix += ": ";
  print(prefix + structureForUser(o));
  return o;
}

static <A> A printStructure(A o) {
  print(structureForUser(o));
  return o;
}



static String firstNotStartingWith(Collection<String> l, String prefix) {
  for (String s : unnull(l))
    if (!startsWith(s, prefix))
      return s;
  return null;
}


// map contains function name -> snippet id
static Map<String, String> parseStdFunctionsList(String snippetSrc) {
  return parseStdFunctionsList(snippetSrc, new LinkedHashMap());
}

static Map<String, String> parseStdFunctionsList(String snippetSrc, Map<String, String> map) {
  List<String> tok = javaTok(snippetSrc);
  int i = findCodeTokens(tok, "standardFunctions", "=", "litlist", "(");
  int opening = i+6;
  int closing = indexOf(tok, ")", opening)-1;
  for (i = opening+2; i < closing; i += 4) {
    String[] f = unquote(tok.get(i)).split("/");
    map.put(f[1], f[0]);
  }
  return map;
}


static String addToStdFunctionsList(String list, String fname, String snippetID) {
  List<String> tok = javaTok(list);
  int i = findCodeTokens(tok, "standardFunctions", "=", "litlist", "(");
  int opening = i+6;
  tok.set(opening, tok.get(opening) + "\n  \"" + formatSnippetID(snippetID) + "/" + fname + "\",");
  return join(tok);
}


static String unidiff(String a, String b) {
  int contextSize = 1;
  return fromLines(BlockDiffer.generateUniDiff(toLines(a), toLines(b), contextSize));
}


static List triggerTranspilerDirty_listeners = synchroList();

static void triggerTranspilerDirty() {
  pcallFAll(triggerTranspilerDirty_listeners);
}


static int countLines(String s) {
  return l(toLines(s)); // yeah could be optimized :-)
}


static String n(long l, String name) {
  return l + " " + trim(l == 1 ? singular(name) : getPlural(name));
}

static String n(Collection l, String name) {
  return n(l(l), name);
}

static String n(Map m, String name) {
  return n(l(m), name);
}

static String n(Object[] a, String name) {
  return n(l(a), name);
}




static boolean sameSnippetID(String a, String b) {
  if (!isSnippetID(a) || !isSnippetID(b)) return false;
  return parseSnippetID(a) == parseSnippetID(b);
}




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 void _handleError(Error e) {
  call(javax(), "_handleError", e);
}


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


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 String loadSnippetSilently(Snippet s) {
  return loadSnippetQuietly(s);
}



static String loadSnippetSilently(String snippetID) {
  return loadSnippetQuietly(snippetID);
}


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 long psI(String snippetID) {
  return parseSnippetID(snippetID);
}


static File localSnippetFile(long snippetID) {
  return localSnippetsDir(snippetID + ".text");
}

static File localSnippetFile(String snippetID) {
  return localSnippetFile(parseSnippetID(snippetID));
}


static <A> A proxy(Class<A> intrface, final Object target) {
  if (target == null) return null;
  if (isInstance(intrface, target)) return (A) target;
  return (A) java.lang.reflect.Proxy.newProxyInstance(intrface.getClassLoader(),
    new Class[] { intrface },
    new proxy_InvocationHandler(target));
}

static <A> A proxy(Object target, Class<A> intrface) {
  return proxy(intrface, target);
}


static Object vm_generalMap_get(Object key) {
  return vm_generalMap().get(key);
}


  public static String bytesToHex(byte[] bytes) {
    return bytesToHex(bytes, 0, bytes.length);
  }

  public static String bytesToHex(byte[] bytes, int ofs, int len) {
    StringBuilder stringBuilder = new StringBuilder(len*2);
    for (int i = 0; i < len; i++) {
      String s = "0" + Integer.toHexString(bytes[ofs+i]);
      stringBuilder.append(s.substring(s.length()-2, s.length()));
    }
    return stringBuilder.toString();
  }



static byte[] toUtf8(String s) { try {
  return s.getBytes(utf8charset());
} catch (Exception __e) { throw rethrow(__e); } }


static boolean md5OfFile_verbose = false;

static String md5OfFile(String path) {
  return md5OfFile(newFile(path));
}

static String md5OfFile(File f) { try {
  if (!f.exists()) return "-";
  
  if (md5OfFile_verbose)
    print("Getting MD5 of " + f);
  
  MessageDigest md5 = MessageDigest.getInstance("MD5");
   FileInputStream in = new FileInputStream(f); try {

  byte buf[] = new byte[65536];
  int l;
  while (true) {
    l = in.read(buf);
    if (l <= 0) break;
    md5.update(buf, 0, l);
  }
  
  return bytesToHex(md5.digest());
} finally { _close(in); }} catch (Exception __e) { throw rethrow(__e); } }


static File getProgramFile(String progID, String fileName) {
  if (new File(fileName).isAbsolute())
    return new File(fileName);
  return new File(getProgramDir(progID), fileName);
}

static File getProgramFile(String fileName) {
  return getProgramFile(getProgramID(), fileName);
}



static String getClassName(Object o) {
  return o == null ? "null" : o instanceof Class ? ((Class) o).getName() : o.getClass().getName();
}


static String urlencode(String x) {
  try {
    return URLEncoder.encode(unnull(x), "UTF-8");
  } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); }
}


static ThreadLocal<VF1<File>> checkFileNotTooBigToRead_tl = new ThreadLocal();

static void checkFileNotTooBigToRead(File f) {
  callF(checkFileNotTooBigToRead_tl.get(), f);
}


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

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

static File newFile(String base, String... names) {
  return newFile(newFile(base), names);
}


static List<CriticalAction> beginCriticalAction_inFlight = synchroList();

static class CriticalAction {
  String description;
  
  CriticalAction() {}
  CriticalAction(String description) {
  this.description = description;}
  
  void done() {
    beginCriticalAction_inFlight.remove(this);
  }
}

static CriticalAction beginCriticalAction(String description) {
  ping();
  CriticalAction c = new CriticalAction(description);
  beginCriticalAction_inFlight.add(c);
  return c;
}

static void cleanMeUp_beginCriticalAction() {
  int n = 0;
  while (nempty(beginCriticalAction_inFlight)) {
    int m = l(beginCriticalAction_inFlight);
    if (m != n) {
      n = m;
      try {
        print("Waiting for " + n2(n, "critical actions") + ": " + join(", ", collect(beginCriticalAction_inFlight, "description")));
      } catch (Throwable __e) { printStackTrace(__e); }
    }
    sleepInCleanUp(10);
  }
}


public static File mkdirsForFile(File file) {
  File dir = file.getParentFile();
  if (dir != null) { // is null if file is in current dir
    dir.mkdirs();
    if (!dir.isDirectory())
      if (dir.isFile()) throw fail("Please delete the file " + f2s(dir) + " - it is supposed to be a directory!");
      else throw fail("Unknown IO exception during mkdirs of " + f2s(file));
  }
  return file;
}

public static String mkdirsForFile(String path) {
  mkdirsForFile(new File(path));
  return path;
}


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



static File copyFile(File src, File dest) { try {
  FileInputStream inputStream = new FileInputStream(src.getPath());
  FileOutputStream outputStream = newFileOutputStream(dest.getPath());
  try {
    copyStream(inputStream, outputStream);
    inputStream.close();
  } finally {
    outputStream.close();
  }
  return dest;
} catch (Exception __e) { throw rethrow(__e); } }


static FileOutputStream newFileOutputStream(File path) throws IOException {
  return newFileOutputStream(path.getPath());
}

static FileOutputStream newFileOutputStream(String path) throws IOException {
  return newFileOutputStream(path, false);
}

static FileOutputStream newFileOutputStream(File path, boolean append) throws IOException {
  return newFileOutputStream(path.getPath(), append);
}

static FileOutputStream newFileOutputStream(String path, boolean append) throws IOException {
  mkdirsForFile(path);
  FileOutputStream f = new FileOutputStream(path, append);
  
  _registerIO(f, path, true);
  
  return f;
}


static File javaxCachesDir_dir; // can be set to work on different base dir

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

static File javaxCachesDir(String sub) {
  return newFile(javaxCachesDir(), sub);
}


static boolean networkAllowanceTest(String url) {
  
  
  return isAllowed("networkAllowanceTest", url);
  
}


static String exceptionToStringShort(Throwable e) {
  lastException(e);
  e = getInnerException(e);
  String msg = hideCredentials(unnull(e.getMessage()));
  if (msg.indexOf("Error") < 0 && msg.indexOf("Exception") < 0)
    return baseClassName(e) + prependIfNempty(": ", msg);
  else
    return msg;
}


static void sleepSeconds(double s) {
  if (s > 0) sleep(round(s*1000));
}


static <A> A printWithTime(A a) {
  return printWithTime("", a);
}

static <A> A printWithTime(String s, A a) {
  print(hmsWithColons() + ": " + s, a);
  return a;
}


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> A getAndClearThreadLocal(ThreadLocal<A> tl) {
  A a = tl.get();
  tl.set(null);
  return a;
}


static <A> A optPar(ThreadLocal<A> tl, A defaultValue) {
  A a = tl.get();
  if (a != null) {
    tl.set(null);
    return a;
  }
  return defaultValue;
}

static <A> A optPar(ThreadLocal<A> tl) {
  return optPar(tl, null);
}

static Object optPar(Object[] params, String name) {
  return optParam(params, name);
}

static Object optPar(String name, Object[] params) {
  return optParam(params, name);
}

static Object optPar(String name, Map params) {
  return optParam(name, params);
}

static <A> A optPar(Object[] params, String name, A defaultValue) {
  return optParam(params, name, defaultValue);
}

static <A> A optPar(String name, Object[] params, A defaultValue) {
  return optParam(params, name, defaultValue);
}


static void setHeaders(URLConnection con) throws IOException {
  
  String computerID = getComputerID_quick();
  if (computerID != null) try {
    con.setRequestProperty("X-ComputerID", computerID);
    con.setRequestProperty("X-OS", System.getProperty("os.name") + " " + System.getProperty("os.version"));
  } catch (Throwable e) {
    //printShortException(e);
  }
  
}


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(MultiMap<A, B> mm) {
    return mm.keySet();
  }







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 InputStream urlConnection_getInputStream(URLConnection con) throws IOException {
  return con.getInputStream();
}


static GZIPInputStream newGZIPInputStream(File f) {
  return gzInputStream(f);
}

static GZIPInputStream newGZIPInputStream(InputStream in) {
  return gzInputStream(in);
}


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 String toHex(byte[] bytes) {
  return bytesToHex(bytes);
}

static String toHex(byte[] bytes, int ofs, int len) {
  return bytesToHex(bytes, ofs, len);
}



static byte[] utf8(String s) {
  return toUtf8(s);
}


static Matcher regexpMatcher(String pat, String s) {
  return compileRegexp(pat).matcher(unnull(s));
}


static <A> A or(A a, A b) {
  return a != null ? a : b;
}


static URLConnection openConnection(String url) { try {
  return openConnection(new URL(url));
} catch (Exception __e) { throw rethrow(__e); } }

static URLConnection openConnection(URL url) { try {
  ping();
  
  callOpt(javax(), "recordOpenURLConnection", str(url));
  
  return url.openConnection();
} catch (Exception __e) { throw rethrow(__e); } }


static int toInt(Object o) {
  if (o == null) return 0;
  if (o instanceof Number)
    return ((Number) o).intValue();
  if (o instanceof String)
    return parseInt((String) o);
  if (o instanceof Boolean)
    return boolToInt((Boolean) o);
  throw fail("woot not int: " + getClassName(o));
}

static int toInt(long l) {
  if (l != (int) l) throw fail("Too large for int: " + l);
  return (int) l;
}


static URLConnection setURLConnectionTimeouts(URLConnection con, long timeout) {
  con.setConnectTimeout(toInt(timeout));
  con.setReadTimeout(toInt(timeout));
  if (con.getConnectTimeout() != timeout || con.getReadTimeout() != timeout)
    print("Warning: Timeouts not set by JDK.");
  return con;
}


static URLConnection setURLConnectionDefaultTimeouts(URLConnection con, long timeout) {
  if (con.getConnectTimeout() == 0) {
    con.setConnectTimeout(toInt(timeout));
    if (con.getConnectTimeout() != timeout)
      print("Warning: URL connect timeout not set by JDK.");
  }
  if (con.getReadTimeout() == 0) {
    con.setReadTimeout(toInt(timeout));
    if (con.getReadTimeout() != timeout)
      print("Warning: URL read timeout not set by JDK.");
  }
  return con;
}


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


// i must point at the (possibly imaginary) opening bracket ("{")
// index returned is index of closing bracket + 1 (or l(tok))
static int tok_findEndOfBlock(List<String> tok, int i) {
  int j = i+2, level = 1, n = l(tok);
  while (j < n) {
    String t = tok.get(j);
    if ("{".equals(t)) ++level;
    else if ("}".equals(t)) --level;
    if (level == 0)
      return j+1;
    j += 2;
  }
  return n;
}


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

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


static boolean isJavaIdentifier(String s) {
  if (empty(s) || !Character.isJavaIdentifierStart(s.charAt(0)))
    return false;
  for (int i = 1; i < s.length(); i++)
    if (!Character.isJavaIdentifierPart(s.charAt(i)))
      return false;
  return true;
}


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

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

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

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

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

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

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

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

static int min(int[] c) {
  int x = Integer.MAX_VALUE;
  for (int d : c) if (d < x) x = d;
  return x;
}


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


// map: index of opening bracket -> index of closing bracket
static Map<Integer, Integer> getBracketMap(List tok) {
  return getBracketMap(tok, getBracketMap_opening, getBracketMap_closing);
}

static Map<Integer, Integer> getBracketMap(List tok, Collection<String> opening, Collection<String> closing) {
  return getBracketMap(tok, opening, closing, 0, l(tok));
}

static Map<Integer, Integer> getBracketMap(List tok, Collection<String> opening, Collection<String> closing, int from, int to) {
  TreeMap<Integer, Integer> map = new TreeMap();
  List<Integer> stack = new ArrayList();
  for (int i = from|1; i < to; i+= 2) {
    if (opening.contains(tok.get(i)))
      stack.add(i);
    else if (closing.contains(tok.get(i))) {
      if (!empty(stack))
        map.put(liftLast(stack), i);
    }
  }
  return map;
}

static Set<String> getBracketMap_opening = lithashset("{", "(");
static Set<String> getBracketMap_closing = lithashset("}", ")");


static boolean neq(Object a, Object b) {
  return !eq(a, b);
}


static <A> boolean eqGetOneOf(List<A> l, int i, A... options) {
  return eqOneOf(get(l, i), options);
}


static String trimJoinSubList(List<String> l, int i, int j) {
  return trim(join(subList(l, i, j)));
}

static String trimJoinSubList(List<String> l, int i) {
  return trim(join(subList(l, i)));
}


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


static <A, B> Map<A, B> synchronizedMRUCache(int maxSize) {
  return synchroMap(new MRUCache(maxSize));
}


static String[] codeTokensAsStringArray(List<String> tok) {
  int n = max(0, (l(tok)-1)/2);
  String[] out = new String[n];
  for (int i = 0; i < n; i++)
    out[i] = tok.get(i*2+1);
  return out;
}


static int jfind(String s, String in) {
  return jfind(javaTok(s), in);
}

static int jfind(List<String> tok, String in) {
  return jfind(tok, 1, in);
}

static int jfind(List<String> tok, int startIdx, String in) {
  return jfind(tok, startIdx, in, null);
}

static int jfind(List<String> tok, String in, Object condition) {
  return jfind(tok, 1, in, condition);
}

static int jfind(List<String> tok, String in, ITokCondition condition) { return jfind(tok, 1, in, condition); }
static int jfind(List<String> tok, int startIndex, String in, ITokCondition condition) {
  return jfind(tok, startIndex, in, (Object) condition);
}

static int jfind(List<String> tok, int startIdx, String in, Object condition) {
  //LS tokin = jfind_preprocess(javaTok(in));
  return jfind(tok, startIdx, javaTokForJFind_array(in), condition);
}

// assumes you preprocessed tokin
static int jfind(List<String> tok, List<String> tokin) {
  return jfind(tok, 1, tokin);
}

static int jfind(List<String> tok, int startIdx, List<String> tokin) {
  return jfind(tok, startIdx, tokin, null);
}

static int jfind(List<String> tok, int startIdx, String[] tokinC, Object condition) {
  return findCodeTokens(tok, startIdx, false, tokinC, condition);
}

static int jfind(List<String> tok, int startIdx, List<String> tokin, Object condition) {
  return jfind(tok, startIdx, codeTokensAsStringArray(tokin), condition);
}

static List<String> jfind_preprocess(List<String> tok) {
  for (String type : litlist("quoted", "id", "int"))
    replaceSublist(tok, ll("<", "", type, "", ">"), ll("<" + type + ">"));
  replaceSublist(tok, ll("\\", "", "*"), ll("\\*"));
  return tok;
}


// supports the usual quotings (", variable length double brackets) except ' quoting
static boolean isQuoted(String s) {
  
  
  if (isNormalQuoted(s)) return true; // use the exact version
  
  return isMultilineQuoted(s);
}


static boolean isInteger(String s) {
  int n = l(s);
  if (n == 0) return false;
  int i = 0;
  if (s.charAt(0) == '-')
    if (++i >= n) return false;
  while (i < n) {
    char c = s.charAt(i);
    if (c < '0' || c > '9') return false;
    ++i;
  }
  return true;
}


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 checkTokCondition(Object condition, List<String> tok, int i) {
  if (condition instanceof TokCondition)
    return ((TokCondition) condition).get(tok, i);
  return checkCondition(condition, tok, i);
}


static int parseInt(String s) {
  return emptyString(s) ? 0 : Integer.parseInt(s);
}

static int parseInt(char c) {
  return Integer.parseInt(str(c));
}


static <A> void replaceCollection(Collection<A> dest, Collection<A> src) {
  if (dest == src) return;
  dest.clear();
  if (src != null) dest.addAll(src);
}


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 String joinSubList(List<String> l, int i, int j) {
  return join(subList(l, i, j));
}

static String joinSubList(List<String> l, int i) {
  return join(subList(l, i));
}




static void replaceListPart(List l, int i, int j, List l2) {
  replaceSublist(l, i, j, l2);
}


static String className(Object o) {
  return getClassName(o);
}


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 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, 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, B> Pair<A, B> pair(A a, B b) {
  return new Pair(a, b);
}

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


static <A> A 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 <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 void failIfUnlicensed() {
  assertTrue("license off", licensed());
}


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

// initFunction: voidfunc(Map) - is called initially, and after clearing the map
static <A, B> Map<A, B> newDangerousWeakHashMap(Object initFunction) {
  return _registerDangerousWeakMap(synchroMap(new WeakHashMap()), initFunction);
}


static Object invokeMethod(Method m, Object o, Object... args) { try {
  try {
    return m.invoke(o, args);
  } catch (InvocationTargetException e) {
    throw rethrow(getExceptionCause(e));
  } catch (IllegalArgumentException e) {
    throw new IllegalArgumentException(e.getMessage() + " - was calling: " + m + ", args: " + joinWithSpace(classNames(args)));
  }
} catch (Exception __e) { throw rethrow(__e); } }


static boolean call_checkArgs(Method m, Object[] args, boolean debug) {
  Class<?>[] types = m.getParameterTypes();
  if (types.length != args.length) {
    if (debug)
      print("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++) {
    Object arg = args[i];
    if (!(arg == null ? !types[i].isPrimitive()
      : isInstanceX(types[i], arg))) {
      if (debug)
        print("Bad parameter " + i + ": " + arg + " vs " + types[i]);
      return false;
    }
  }
  return true;
}


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

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




static 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 CharSequence subCharSequence(CharSequence s, int x) {
  return subCharSequence(s, x, s == null ? 0 : s.length());
}

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


static int[] takeFirstOfIntArray(int[] b, int n) {
  return subIntArray(b, 0, n);
}

static int[] takeFirstOfIntArray(int n, int[] b) {
  return takeFirstOfIntArray(b, n);
}


static short[] takeFirstOfShortArray(short[] b, int n) {
  return subShortArray(b, 0, n);
}

static short[] takeFirstOfShortArray(int n, short[] b) {
  return takeFirstOfShortArray(b, n);
}


static byte[] takeFirstOfByteArray(byte[] b, int n) {
  return subByteArray(b, 0, n);
}

static byte[] takeFirstOfByteArray(int n, byte[] b) {
  return takeFirstOfByteArray(b, n);
}


static double[] takeFirstOfDoubleArray(double[] b, int n) {
  return subDoubleArray(b, 0, n);
}

static double[] takeFirstOfDoubleArray(int n, double[] b) {
  return takeFirstOfDoubleArray(b, n);
}


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 boolean endsWithLetterOrDigit(String s) {
  return s != null && s.length() > 0 && Character.isLetterOrDigit(s.charAt(s.length()-1));
}


// 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 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 String dropSuffix(String suffix, String s) {
  return nempty(suffix) && endsWith(s, suffix) ? s.substring(0, l(s)-l(suffix)) : 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 int indent_default = 2;

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

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

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

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

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


static File oneOfTheFiles(String... paths) {
  if (paths != null) for (String path : paths)
    if (fileExists(path))
      return newFile(path);
  return null;
}

static File oneOfTheFiles(File... files) {
  return oneOfTheFiles(asList(files));
}

static File oneOfTheFiles(Iterable<File> files) {
  if (files != null) for (File f : files)
    if (fileExists(f))
      return f;
  return null;
}


static File javaxSecretDir_dir; // can be set to work on different base dir

static File javaxSecretDir() {
  return javaxSecretDir_dir != null ? javaxSecretDir_dir : new File(userHome(), "JavaX-Secret");
}

static File javaxSecretDir(String sub) {
  return newFile(javaxSecretDir(), sub);
}


static File userDir() {
  return new File(userHome());
}

static File userDir(String path) {
  return new File(userHome(), path);
}


static String makePostData(Map map) {
  StringBuilder buf = new StringBuilder();
  for (Map.Entry<Object, Object> e : castMapToMapO(map).entrySet()) {
    String key =  (String) (e.getKey());
    Object val = e.getValue();
    if (val != null) {
      String value = str(val);
      if (nempty(buf)) buf.append("&");
      buf.append(urlencode(key)).append("=").append(urlencode(/*escapeMultichars*/(value)));
    }
  }
  return str(buf);
}

static String makePostData(Object... params) {
  StringBuilder buf = new StringBuilder();
  int n = l(params);
  for (int i = 0; i+1 < n; i += 2) {
    String key =  (String) (params[i]);
    Object val = params[i+1];
    if (val != null) {
      String value = str(val);
      if (nempty(buf)) buf.append("&");
      buf.append(urlencode(key)).append("=").append(urlencode(/*escapeMultichars*/(value)));
    }
  }
  return str(buf);

}



static <A> A optParam(ThreadLocal<A> tl, A defaultValue) {
  return optPar(tl, defaultValue);
}

static <A> A optParam(ThreadLocal<A> tl) {
  return optPar(tl);
}

static Object optParam(String name, Map params) {
  return mapGet(params, name);
}

// now also takes a map as single array entry
static <A> A optParam(Object[] opt, String name, A defaultValue) {
  int n = l(opt);
  if (n == 1 && opt[0] instanceof Map) {
    Map map =  (Map) (opt[0]);
    return map.containsKey(name) ? (A) map.get(name) : defaultValue;
  }
  if (!even(l(opt))) throw fail("Odd parameter length");
  for (int i = 0; i < l(opt); i += 2)
    if (eq(opt[i], name))
      return (A) opt[i+1];
  return defaultValue;
}

static Object optParam(Object[] opt, String name) {
  return optParam(opt, name, null);
}

static Object optParam(String name, Object[] params) {
  return optParam(params, name);
}


static String loadPage_utf8(URL url) {
  return loadPage_utf8(url.toString());
}

static String loadPage_utf8(String url) {
   AutoCloseable __1 = tempSetTL(loadPage_charset, "UTF-8"); try {
  return loadPage(url);
} finally { _close(__1); }}

static String loadPage_utf8(URLConnection con, URL url, boolean addHeaders) throws IOException {
   AutoCloseable __2 = tempSetTL(loadPage_charset, "UTF-8"); try {
  return loadPage(con, url, addHeaders);
} finally { _close(__2); }}


  static RuntimeException todo() {
    throw new RuntimeException("TODO");
  }
  
  static RuntimeException todo(Object msg) {
    throw new RuntimeException("TODO: " + msg);
  }


static JWindow showAnim(File imageFile) {
  return showAnimationInTopRightCorner(loadBufferedImageFixingGIFs(imageFile), "");
}

static JWindow showAnim(String imageID, double seconds) {
  return showAnimationInTopRightCorner(imageID, seconds);
}

static JWindow showAnim(String imageID) {
  return showAnimationInTopRightCorner(imageID);
}

static JWindow showAnim(String imageID, String text) {
  return showAnimationInTopRightCorner(imageID, text);
}

static JWindow showAnim(String imageID, String text, double seconds) {
  return showAnimationInTopRightCorner(imageID, text, seconds);
}

static JWindow showAnim(BufferedImage image, String text) {
  return showAnimationInTopRightCorner(image, text);
}

static JWindow showAnim(BufferedImage image, String text, double seconds) {
  return showAnimationInTopRightCorner(image, text, seconds);
}



static int moveToBottomLeftCorner_inset = 20;

static void moveToBottomLeftCorner(Component c) {
  Window w = getWindow(c);
  if (w != null) {
    Rectangle area = maxWindowBounds();
    w.setLocation(area.x+moveToBottomLeftCorner_inset,
      area.y+area.height-moveToBottomLeftCorner_inset-w.getHeight());
  }
}



static <A> AutoCloseable tempDisposeWindow(final Window w) {
  return new AutoCloseable() {
    public void close() {
      disposeWindow(w);
    }
  };
}


static boolean showAnimationInTopRightCorner_alwaysOnTop = true;
static boolean showAnimationInTopRightCorner_on = true;

// automatically switches to AWT thread for you
// text is optional text below image
static JWindow showAnimationInTopRightCorner(String imageID, String text) {
  if (isHeadless() || !showAnimationInTopRightCorner_on) return null;
  return showAnimationInTopRightCorner(imageIcon(imageID), text);
}

static JWindow showAnimationInTopRightCorner(final Image image, final String text) {
  if (image == null || isHeadless() || !showAnimationInTopRightCorner_on) return null;
  return showAnimationInTopRightCorner(imageIcon(image), text);
}

static JWindow showAnimationInTopRightCorner(final ImageIcon imageIcon, final String text) {
  if (isHeadless() || !showAnimationInTopRightCorner_on) return null;
  return (JWindow) swingAndWait(new F0<Object>() { public Object get() { try { 
    JLabel label = new JLabel(imageIcon);
    if (nempty(text)) {
      label.setText(text);
      label.setVerticalTextPosition(SwingConstants.BOTTOM);
      label.setHorizontalTextPosition(SwingConstants.CENTER);
    }
    final JWindow window = showInTopRightCorner(label);
    onClick(label, new Runnable() {  public void run() { try {  window.dispose() ;
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "window.dispose()"; }});
    if (showAnimationInTopRightCorner_alwaysOnTop)
      window.setAlwaysOnTop(true);
    return window;
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "JLabel label = new JLabel(imageIcon);\r\n    if (nempty(text)) {\r\n      label.s..."; }});
}

static JWindow showAnimationInTopRightCorner(final String imageID) {
  return showAnimationInTopRightCorner(imageID, "");
}

static JWindow showAnimationInTopRightCorner(String imageID, double seconds) {
  return showAnimationInTopRightCorner(imageID, "", seconds);
}

static JWindow showAnimationInTopRightCorner(String imageID, String text, double seconds) {
  if (isHeadless()) return null;
  return disposeWindowAfter(iround(seconds*1000), showAnimationInTopRightCorner(imageID, text));
}

static JWindow showAnimationInTopRightCorner(BufferedImage img, String text, double seconds) {
  return disposeWindowAfter(iround(seconds*1000), showAnimationInTopRightCorner(img, text));
}



static List<Snippet> snippetsFromHTML(String url) {
  String html = loadPage(url);
  return snippetsFromLoadedHTML(html);
}


static boolean endsWithLetter(String s) {
  return nempty(s) && isLetter(last(s));
}


static String structureForUser(Object o) {
  return beautifyStructure(struct_noStringSharing(o));
}


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




// usually L<S>
static String fromLines(Iterable lines) {
  StringBuilder buf = new StringBuilder();
  if (lines != null)
    for (Object line : lines)
      buf.append(str(line)).append('\n');
  return buf.toString();
}

static String fromLines(String... lines) {
  return fromLines(asList(lines));
}


static IterableIterator<String> toLines(File f) {
  return linesFromFile(f);
}

static List<String> toLines(String s) {
  List<String> lines = new ArrayList<String>();
  if (s == null) return lines;
  int start = 0;
  while (true) {
    int i = toLines_nextLineBreak(s, start);
    if (i < 0) {
      if (s.length() > start) lines.add(s.substring(start));
      break;
    }

    lines.add(s.substring(start, i));
    if (s.charAt(i) == '\r' && i+1 < s.length() && s.charAt(i+1) == '\n')
      i += 2;
    else
      ++i;

    start = i;
  }
  return lines;
}

static int toLines_nextLineBreak(String s, int start) {
  int n = s.length();
  for (int i = start; i < n; i++) {
    char c = s.charAt(i);
    if (c == '\r' || c == '\n')
      return i;
  }
  return -1;
}


static <A> List<A> synchroList() {
  return synchroList(new ArrayList<A>());
}

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



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 Map<String, String> singular_specials = litmap(
  "children", "child", "images", "image", "chess", "chess");
  
static Set<String> singular_specials2 = litciset("time", "machine", "line", "rule");

static String singular(String s) {
  if (s == null) return null;
  { String __1 = singular_specials.get(s); if (!empty(__1)) return __1; }
  //try answer hippoSingulars().get(lower(s));
  if (singular_specials2.contains(dropSuffix("s", afterLastSpace(s))))
    return dropSuffix("s", s);
  if (s.endsWith("ness")) return s;
  if (s.endsWith("ges")) return dropSuffix("s", s);
  if (endsWith(s, "bases")) return dropLast(s);
  s = dropSuffix("es", s);
  s = dropSuffix("s", s);
  return s;
}


static Set<String> getPlural_specials = litciset("sheep", "fish");

static String getPlural(String s) {
  if (contains(getPlural_specials, s)) return s;
  if (ewic(s, "y")) return dropSuffixIgnoreCase("y", s) + "ies";
  if (ewicOneOf(s, "ss", "ch")) return s + "es";
  if (ewic(s, "s")) return s;
  return s + "s";
}




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 Class javax() {
  return getJavaX();
}


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 String loadSnippetQuietly(Snippet s) {
  return loadSnippetQuietly(s.id);
}


static String loadSnippetQuietly(String snippetID) {
  loadSnippet_silent.set(true);
  try {
    return loadSnippet(snippetID);
  } finally {
    loadSnippet_silent.set(null);
  }
}


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 File localSnippetsDir() {
  return javaxDataDir("Personal Programs");
}

static File localSnippetsDir(String sub) {
  return newFile(localSnippetsDir(), sub);
}


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


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 Charset utf8charset_cache;
static Charset utf8charset() { if (utf8charset_cache == null) utf8charset_cache = utf8charset_load(); return utf8charset_cache; }

static Charset utf8charset_load() {
  return Charset.forName("UTF-8");
}


static File getProgramDir() {
  return programDir();
}

static File getProgramDir(String snippetID) {
  return programDir(snippetID);
}



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 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 List collect(Iterable c, String field) {
  return collectField(c, field);
}

static List collect(String field, Iterable c) {
  return collectField(c, field);
}

/*ifclass Concept
static L collect(Class c, S field) {
  ret collect(list(c), field);
}
endif
TODO: make translator ignore stuff in ifclass until resolved
*/


static void sleepInCleanUp(long ms) { try {
  if (ms < 0) return;
  Thread.sleep(ms);
} catch (Exception __e) { throw rethrow(__e); } }


static String f2s(File f) {
  return f == null ? null : f.getAbsolutePath();
}

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


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



static void copyStream(InputStream in, OutputStream out) { try {
  byte[] buf = new byte[65536];
  while (true) {
    int n = in.read(buf);
    if (n <= 0) return;
    out.write(buf, 0, n);
  }
} catch (Exception __e) { throw rethrow(__e); } }


static void _registerIO(Object object, String path, boolean opened) {
}


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

static File userHome(String path) {
  return new File(userDir(), path);
}


static volatile Object isAllowed_function; // func(S, O[]) -> bool
static volatile boolean isAllowed_all = true;

static boolean isAllowed(String askingMethod, Object... args) {
  // check on VM level
  Object f = vm_generalMap_get("isAllowed_function");
  if (f != null && !isTrue(callF(f, askingMethod, args))) return false;
  
  // check locally
  return isAllowed_all || isTrue(callF(isAllowed_function, askingMethod, args));
}


static Throwable getInnerException(Throwable e) {
  if (e == null) return null;
  while (e.getCause() != null)
    e = e.getCause();
  return e;
}

static Throwable getInnerException(Runnable r) {
  return getInnerException(getException(r));
}


static String baseClassName(String className) {
  return substring(className, className.lastIndexOf('.')+1);
}

static String baseClassName(Object o) {
  return baseClassName(getClassName(o));
}


static String prependIfNempty(String prefix, String s) {
  return empty(s) ? unnull(s) : prefix + s;
}


static volatile boolean sleep_noSleep = false;

static void sleep(long ms) {
  ping();
  if (ms < 0) return;
  // allow spin locks
  if (isAWTThread() && ms > 100) throw fail("Should not sleep on AWT thread");
  try {
    Thread.sleep(ms);
  } catch (Exception e) { throw new RuntimeException(e); }
}

static void sleep() { try {
  if (sleep_noSleep) throw fail("nosleep");
  print("Sleeping.");
  sleepQuietly();
} catch (Exception __e) { throw rethrow(__e); } }


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

static String round(String s) {
  return roundBracket(s);
}






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

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



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 String getComputerID_quick() {
  return computerID();
}


static Object vm_generalMap_put(Object key, Object value) {
  return mapPutOrRemove(vm_generalMap(), key, value);
}


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

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


static int gzInputStream_defaultBufferSize = 65536;

static GZIPInputStream gzInputStream(File f) { try {
  return gzInputStream(new FileInputStream(f));
} catch (Exception __e) { throw rethrow(__e); } }

static GZIPInputStream gzInputStream(File f, int bufferSize) { try {
  return gzInputStream(new FileInputStream(f), bufferSize);
} catch (Exception __e) { throw rethrow(__e); } }

static GZIPInputStream gzInputStream(InputStream in) {
  return gzInputStream(in, gzInputStream_defaultBufferSize);
}

static GZIPInputStream gzInputStream(InputStream in, int bufferSize) { try {
  return _registerIOWrap(new GZIPInputStream(in, gzInputStream_defaultBufferSize), in);
} 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 Map<String, java.util.regex.Pattern> compileRegexp_cache = syncMRUCache(10);

static java.util.regex.Pattern compileRegexp(String pat) {
  java.util.regex.Pattern p = compileRegexp_cache.get(pat);
  if (p == null) {
    
    compileRegexp_cache.put(pat, p = java.util.regex.Pattern.compile(pat));
  }
  return p;
}


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

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


static int boolToInt(boolean b) {
  return b ? 1 : 0;
}


static Object call_withVarargs(Object o, String method, Object... args) { try {
  if (o == null) return null;
  
  if (o instanceof Class) {
    Class c = (Class) o;
    _MethodCache cache = callOpt_getCache(c);
    
    Method me = cache.findStaticMethod(method, args);
    if (me != null)
      return invokeMethod(me, null, args);
      
    // try varargs
    List<Method> methods = cache.cache.get(method);
    if (methods != null) methodSearch: for (Method m : methods) {
      { if (!(m.isVarArgs())) continue; }
      { if (!(isStaticMethod(m))) continue; }
      Object[] newArgs = massageArgsForVarArgsCall(m, args);
      if (newArgs != null)
        return invokeMethod(m, null, newArgs);
    }
    
    throw fail("Method " + c.getName() + "." + method + "(" + joinWithComma(classNames(args)) + ") not found");
  } else {
    Class c = o.getClass();
    _MethodCache cache = callOpt_getCache(c);

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


static <A> A liftLast(List<A> l) {
  if (empty(l)) return null;
  int i = l(l)-1;
  A a = l.get(i);
  l.remove(i);
  return a;
}

static <A> List<A> liftLast(int n, List<A> l) {
  int i = l(l)-n;
  List<A> part = cloneSubList(l, i);
  removeSubList(l, i);
  return part;
}


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


// syntax 1: replace all occurrences of x in l with y
static <A> List<A> replaceSublist(List<A> l, List<A> x, List<A> y) {
  if (x == null) return l;
  
  int i = 0;
  while (true) {
    i = indexOfSubList(l, x, i);
    if (i < 0) break;
    
    replaceSublist(l, i, i+l(x), y);
    i += l(y);
  }
  return l;
}

// syntax 2: splice l at fromIndex-toIndex and replace middle part with y
static <A> List<A> replaceSublist(List<A> l, int fromIndex, int toIndex, List<A> y) {
  int n = y.size(), toIndex_new = fromIndex+n;
  if (toIndex_new < toIndex) {
    removeSubList(l, toIndex_new, toIndex);
    copyListPart(y, 0, l, fromIndex, n);
  } else {
    copyListPart(y, 0, l, fromIndex, toIndex-fromIndex);
    if (toIndex_new > toIndex)
      l.addAll(toIndex, subList(y, toIndex-fromIndex));
  }
  return l;
}




static boolean isNormalQuoted(String s) {
  int l = l(s);
  if (!(l >= 2 && s.charAt(0) == '"' && lastChar(s) == '"')) return false;
  int j = 1;
  while (j < l)
    if (s.charAt(j) == '"')
      return j == l-1;
    else if (s.charAt(j) == '\\' && j+1 < l)
      j += 2;
    else
      ++j;
  return false;
}


static boolean isMultilineQuoted(String s) {
  if (!startsWith(s, "[")) return false;
  int i = 1;
  while (i < s.length() && s.charAt(i) == '=') ++i;
  return i < s.length() && s.charAt(i) == '[';
}


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


static boolean checkCondition(Object condition, Object... args) {
  return isTrue(callF(condition, args));
}

static <A> boolean checkCondition(IF1<A, Boolean> condition, A arg) {
  return isTrue(callF(condition, arg));
}


static boolean emptyString(String s) {
  return s == null || s.length() == 0;
}


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 final Map<Class, HashMap<S, Field>> getOpt_cache = newDangerousWeakHashMap(f getOpt_special_init);

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

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

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

static 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 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 <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 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 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 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(Arrays.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 int strL(String s) {
  return s == null ? 0 : s.length();
}


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


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




static short[] subShortArray(short[] b, int start, int end) {
  start = max(start, 0); end = min(end, l(b));
  if (start == 0 && end == l(b)) return b;
  if (start >= end) return new short[0];
  short[] x = new short[end-start];
  System.arraycopy(b, start, x, 0, end-start);
  return x;
}


static byte[] subByteArray(byte[] b, int start) {
  return subByteArray(b, start, l(b));
}
  
static byte[] subByteArray(byte[] b, int start, int end) {
  start = max(start, 0); end = min(end, l(b));
  if (start == 0 && end == l(b)) return b;
  if (start >= end) return new byte[0];
  byte[] x = new byte[end-start];
  System.arraycopy(b, start, x, 0, end-start);
  return x;
}




static double[] subDoubleArray(double[] b, int start) { return subDoubleArray(b, start, l(b)); }
static double[] subDoubleArray(double[] b, int start, int end) {
  start = max(start, 0); end = min(end, l(b));
  if (start == 0 && end == l(b)) return b;
  if (start >= end) return new double[0];
  double[] x = new double[end-start];
  System.arraycopy(b, start, x, 0, end-start);
  return x;
}


static PersistableThrowable persistableThrowable(Throwable e) {
  return e == null ? null : new PersistableThrowable(e);
}


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 <A> List<A> replace(List<A> l, A a, A b) {
  for (int i = 0; i < l(l); i++)
    if (eq(l.get(i), a))
      l.set(i, b);
  return l;
}

static <A> List<A> replace(A a, A b, List<A> l) {
  return replace(l, a, b);
}

// replace all occurrences of a in s with b
static String replace(String s, String a, String b) {
  return s == null ? null : a == null || b == null ? s : s.replace(a, b);
}

static String replace(String s, char a, char b) {
  return s == null ? null : s.replace(a, b);
}


static boolean fileExists(String path) {
  return path != null && new File(path).exists();
}

static boolean fileExists(File f) {
  return f != null && f.exists();
}


static Map<Object, Object> castMapToMapO(Map map) {
  return map;
}


static boolean even(int i) {
  return (i & 1) == 0;
}

static boolean even(long i) {
  return (i & 1) == 0;
}

static boolean even(BigInteger n) {
  return even(n.intValue());
}


static <A> AutoCloseable tempSetTL(ThreadLocal<A> tl, A a) {
  return tempSetThreadLocal(tl, a);
}









static boolean loadBufferedImageFixingGIFs_debug = false;
static ThreadLocal<Var<byte[]>> loadBufferedImageFixingGIFs_output = new ThreadLocal();

static Image loadBufferedImageFixingGIFs(File file) { try {
  if (!file.exists()) return null;

  // Load anything but GIF the normal way
  if (!isGIF(file))
    return ImageIO.read(file);
    
  if (loadBufferedImageFixingGIFs_debug) print("loadBufferedImageFixingGIFs" + ": checking gif");

  // Get GIF reader
  ImageReader reader = ImageIO.getImageReadersByFormatName("gif").next();
  // Give it the stream to decode from
  reader.setInput(ImageIO.createImageInputStream(file));

  int numImages = reader.getNumImages(true);

  // Get 'metaFormatName'. Need first frame for that.
  IIOMetadata imageMetaData = reader.getImageMetadata(0);
  String metaFormatName = imageMetaData.getNativeMetadataFormatName();

  // Find out if GIF is bugged
  boolean foundBug = false;
  for (int i = 0; i < numImages && !foundBug; i++) {
      // Get metadata
      IIOMetadataNode root = (IIOMetadataNode)reader.getImageMetadata(i).getAsTree(metaFormatName);

      // Find GraphicControlExtension node
      int nNodes = root.getLength();
      for (int j = 0; j < nNodes; j++) {
          org.w3c.dom.Node node = root.item(j);
          if (node.getNodeName().equalsIgnoreCase("GraphicControlExtension")) {
              // Get delay value
              String delay = ((IIOMetadataNode)node).getAttribute("delayTime");

              // Check if delay is bugged
              if (Integer.parseInt(delay) == 0) {
                  foundBug = true;
              }

              break;
          }
      }
  }

  if (loadBufferedImageFixingGIFs_debug) print("loadBufferedImageFixingGIFs" + ": " + f2s(file) + " foundBug=" + foundBug);
  
  // Load non-bugged GIF the normal way
  Image image;
  if (!foundBug) {
    image = Toolkit.getDefaultToolkit().createImage(f2s(file));
  } else {
    // Prepare streams for image encoding
    ByteArrayOutputStream baoStream = new ByteArrayOutputStream();
    {
       ImageOutputStream ios = ImageIO.createImageOutputStream(baoStream); try {
      // Get GIF writer that's compatible with reader
      ImageWriter writer = ImageIO.getImageWriter(reader);
      // Give it the stream to encode to
      writer.setOutput(ios);

      writer.prepareWriteSequence(null);

      for (int i = 0; i < numImages; i++) {
          // Get input image
          BufferedImage frameIn = reader.read(i);

          // Get input metadata
          IIOMetadataNode root = (IIOMetadataNode)reader.getImageMetadata(i).getAsTree(metaFormatName);

          // Find GraphicControlExtension node
          int nNodes = root.getLength();
          for (int j = 0; j < nNodes; j++) {
              org.w3c.dom.Node node = root.item(j);
              if (node.getNodeName().equalsIgnoreCase("GraphicControlExtension")) {
                  // Get delay value
                  String delay = ((IIOMetadataNode)node).getAttribute("delayTime");

                  // Check if delay is bugged
                  if (Integer.parseInt(delay) == 0) {
                      // Overwrite with a valid delay value
                      ((IIOMetadataNode)node).setAttribute("delayTime", "10");
                  }

                  break;
              }
          }

          // Create output metadata
          IIOMetadata metadata = writer.getDefaultImageMetadata(new ImageTypeSpecifier(frameIn), null);
          // Copy metadata to output metadata
          metadata.setFromTree(metadata.getNativeMetadataFormatName(), root);

          // Create output image
          IIOImage frameOut = new IIOImage(frameIn, null, metadata);

          // Encode output image
          writer.writeToSequence(frameOut, writer.getDefaultWriteParam());
      }

      writer.endWriteSequence();
    } finally { _close(ios); }}

    // Create image using encoded data
    byte[] data = baoStream.toByteArray();
    setVar(loadBufferedImageFixingGIFs_output.get(), data);
    if (loadBufferedImageFixingGIFs_debug) print("Data size: " + l(data));
    image = Toolkit.getDefaultToolkit().createImage(data);
  }

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




static Window getWindow(Object o) {
  if (!(o instanceof Component)) return null;
  return swing(() -> {
    Component c =  (Component) o;
    while (c != null) {
      if (c instanceof Window) return ((Window) c);
      c = c.getParent();
    }
    return null;
  });
}


static Rectangle maxWindowBounds() {
  return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
}


static void disposeWindow(final Window window) {
  if (window != null) { swing(() -> {
    window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING)); // call listeners
    myFrames_list.remove(window);
    window.dispose();
  }); }
}

static void disposeWindow(final Component c) {
  disposeWindow(getWindow(c));
}

static void disposeWindow(Object o) {
  if (o != null) disposeWindow(((Component) o));
}

static void disposeWindow() {
  disposeWindow(heldInstance(Component.class));
}


static int imageIcon_cacheSize = 10;
static boolean imageIcon_verbose = false;
static Map<String, ImageIcon> imageIcon_cache;
static Lock imageIcon_lock = lock();
static ThreadLocal<Boolean> imageIcon_fixGIF = new ThreadLocal();

// not going through BufferedImage preserves animations
static ImageIcon imageIcon(String imageID) { try {
  if (imageID == null) return null;
  Lock __0 = imageIcon_lock; lock(__0); try {
  if (imageIcon_cache == null)
    imageIcon_cache = new MRUCache(imageIcon_cacheSize);
  imageID = fsI(imageID);
  ImageIcon ii = imageIcon_cache.get(imageID);
  if (ii == null) {
    if (imageIcon_verbose) print("Loading image icon: " + imageID);
    File f = loadBinarySnippet(imageID);
    
      Boolean b = imageIcon_fixGIF.get();
      if (!isFalse(b))
        ii = new ImageIcon(loadBufferedImageFixingGIFs(f));
      else
    
    ii = new ImageIcon(f.toURI().toURL());
  } else
    imageIcon_cache.remove(imageID); // move to front of cache on access
  imageIcon_cache.put(imageID, ii);
  return ii;
} finally { unlock(__0); } } catch (Exception __e) { throw rethrow(__e); } }

// doesn't fix GIFs
static ImageIcon imageIcon(File f) { try {
  return new ImageIcon(f.toURI().toURL());
} catch (Exception __e) { throw rethrow(__e); } }

static ImageIcon imageIcon(Image img) {
  return new ImageIcon(img);
}




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 JWindow showInTopRightCorner(Component c) {
  return swing(() -> {
    JWindow w = new JWindow();
    w.add(c);
    w.pack();
    moveToTopRightCorner(w);
    w.setVisible(true);
    return w;
  });
}


static <A extends JComponent> A onClick(final A c, final Object runnable) {
  if (c != null) { swing(() -> {
    c.addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        callF(runnable, e);
      }
    });
  }); }
  return c;
}

// re-interpreted for buttons
static void onClick(JButton btn, final Object runnable) {
  onEnter(btn, runnable);
}


static <A extends Window> A disposeWindowAfter(int delay, final A w) {
  if (w != null)
    swingLater(delay, new Runnable() {  public void run() { try { 
      w.dispose();
    
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "w.dispose();"; }});
  return w;
}

static <A extends Window> A disposeWindowAfter(A w, double seconds) {
  return disposeWindowAfter(toMS_int(seconds), w);
}

static <A extends Window> A disposeWindowAfter(double seconds, A w) {
  return disposeWindowAfter(w, seconds);
}


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


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





static boolean snippetsFromLoadedHTML_rowPrinted = false;

// assumes withmd5=1
static List<Snippet> snippetsFromLoadedHTML(String html) {
  List<Snippet> l = new ArrayList();
  for (List<String> row : dropFirst(new TableFinder(html).rows())) {
    if (!snippetsFromLoadedHTML_rowPrinted) {
      print("snippetsFromLoadedHTML: " + row);
      snippetsFromLoadedHTML_rowPrinted = true;
    }
    
    Snippet s = new Snippet();
    s.title = dropAllTags(htmldecode(row.get(0)));
    s.id = dropAllTags(row.get(1));
    s.md5 = dropAllTags(row.get(2));
    s.type = dropAllTags(row.get(3));
    s.isPublic = isYes(row.get(6));
    l.add(s);
  }
  return l;
}




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


static <A> A last(List<A> l) {
  return empty(l) ? null : l.get(l.size()-1);
}

static char last(String s) {
  return empty(s) ? '#' : s.charAt(l(s)-1);
}

static int last(int[] a) {
  return l(a) != 0 ? a[l(a)-1] : 0;
}

static double last(double[] a) {
  return l(a) != 0 ? a[l(a)-1] : 0;
}

static <A> A last(A[] a) {
  return l(a) != 0 ? a[l(a)-1] : null;
}

static <A> A last(Iterator<A> it) {
  A a = null;
  while  (it.hasNext()) { ping(); a = it.next(); }
  return a;
}

static <A> A last(Collection<A> l) {
  if (l == null) return null;
  if (l instanceof List) return (A) last((List) l);
  if (l instanceof SortedSet) return (A) last((SortedSet) l);
  Iterator<A> it = iterator(l);
  A a = null;
  while  (it.hasNext()) { ping(); a = it.next(); }
  return a;
}

static <A> A last(SortedSet<A> l) {
  return l == null ? null : l.last();
}












static String beautifyStructure(String s) {
  List<String> tok = javaTokForStructure(s);
  structure_addTokenMarkers(tok);
  jreplace(tok, "lhm", "");
  return join(tok);
}


static String struct_noStringSharing(Object o) {
  structure_Data d = new structure_Data();
  d.noStringSharing = true;
  return structure(o, d);
}


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


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


static CloseableIterableIterator<String> linesFromFile(File f) { return linesFromFile(f, null); }
static CloseableIterableIterator<String> linesFromFile(File f, IResourceHolder resourceHolder) { try {
  if (!f.exists()) return emptyCloseableIterableIterator();
  
  if (ewic(f.getName(), ".gz"))
    return linesFromReader(utf8bufferedReader(newGZIPInputStream(f)), resourceHolder);
  
  return linesFromReader(utf8bufferedReader(f), resourceHolder);
} catch (Exception __e) { throw rethrow(__e); } }

static CloseableIterableIterator<String> linesFromFile(String path) { return linesFromFile(path, null); }
static CloseableIterableIterator<String> linesFromFile(String path, IResourceHolder resourceHolder) {
  return linesFromFile(newFile(path), resourceHolder);
}


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) { return null; } }



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



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


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

static <A> A pcallF(IF0<A> f) { try {
  return f == null ? null : f.get();
} catch (Throwable __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) { return null; } }



static TreeSet<String> litciset(String... items) {
  TreeSet<String> set = caseInsensitiveSet();
  for (String a : items) set.add(a);
  return set;
}


static TreeSet<Symbol> litciset(Symbol... items) {
  TreeSet<Symbol> set = treeSet(); // HashSet would also do, but we might have the return type fixed somewhere, and they might want a NavigableMap.
  for (Symbol a : items) set.add(a);
  return set;
}



static String afterLastSpace(String s) {
  return s == null ? null : substring(s, s.lastIndexOf(' ')+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 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 dropSuffixIgnoreCase(String suffix, String s) {
  return ewic(s, suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}


static boolean ewicOneOf(String s, String... l) {
  if (s != null) for (String x : l) if (ewic(s, x)) return true; return false;
}




static Map<JFrame, Boolean> myFrames_list = weakHashMap();

static List<JFrame> myFrames() {
  return swing(new F0<List<JFrame>>() { public List<JFrame> get() { try {  return keysList(myFrames_list);  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "ret keysList(myFrames_list);"; }});
}



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, B> Map<A, B> newWeakMap() {
  return newWeakHashMap();
}


static File javaxDataDir_dir; // can be set to work on different base dir

static File javaxDataDir() {
  return javaxDataDir_dir != null ? javaxDataDir_dir : new File(userHome(), "JavaX-Data");
}

static File javaxDataDir(String... subs) {
  return newFile(javaxDataDir(), subs);
}


static File programDir_mine; // set this to relocate program's data

static File programDir() {
  return programDir(getProgramID());
}

static File programDir(String snippetID) {
  boolean me = sameSnippetID(snippetID, programID());
  if (programDir_mine != null && me)
    return programDir_mine;
  File dir = new File(javaxDataDir(), formatSnippetIDOpt(snippetID));
  if (me) {
    String c = caseID();
    if (nempty(c)) dir = newFile(dir, c);
  }
  return dir;
}

static File programDir(String snippetID, String subPath) {
  return new File(programDir(snippetID), subPath);
}


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


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


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 List collectField(Iterable c, String field) {
  List l = new ArrayList();
  if (c != null) for (Object a : c)
    l.add(getOpt(a, field));
  return l;
}

static List collectField(String field, Iterable c) {
  return collectField(c, field);
}


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

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


static Throwable getException(Runnable r) {
  try {
    callF(r);
    return null;
  } catch (Throwable e) {
    return e;
  }
}


static Object sleepQuietly_monitor = new Object();

static void sleepQuietly() { try {
  assertFalse(isAWTThread());
  synchronized(sleepQuietly_monitor) { sleepQuietly_monitor.wait(); }
} catch (Exception __e) { throw rethrow(__e); } }


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

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


static boolean domainIsUnder(String domain, String mainDomain) {
  return eqic(domain, mainDomain) || ewic(domain, "." + mainDomain);
}


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


static String _computerID;
static Lock computerID_lock = lock();

public static String computerID() {
  if (_computerID == null) {
    Lock __0 = computerID_lock; lock(__0); try {
    if (_computerID != null) return _computerID;
    File file = computerIDFile();
    _computerID = loadTextFile(file.getPath());
    if (_computerID == null) {
      // legacy load
      _computerID = loadTextFile(userDir(".tinybrain/computer-id"));
      if (_computerID == null)
        _computerID = makeRandomID(12, new SecureRandom());
      saveTextFile(file, _computerID);
    }
  } finally { unlock(__0); } }
  return _computerID;
}


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 Map synchroHashMap() {
  return synchronizedMap(new HashMap());
}



static <A> A _registerIOWrap(A wrapper, Object wrapped) {
  return wrapper;
}


static char lastChar(String s) {
  return empty(s) ? '\0' : s.charAt(l(s)-1);
}


static <A, B> Map<A, B> syncMRUCache(int size) {
  return synchroMap(new MRUCache(size));
}


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 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 boolean isStaticMethod(Method m) {
  return methodIsStatic(m);
}


static Object[] massageArgsForVarArgsCall(Method m, Object[] args) {
  Class<?>[] types = m.getParameterTypes();
  int n = types.length-1, nArgs = args.length;
  if (nArgs < n) return null;
  for (int i = 0; i < n; i++)
    if (!argumentCompatibleWithType(args[i], types[i]))
      return null;
  Class varArgType = types[n].getComponentType();
  for (int i = n; i < nArgs; i++)
    if (!argumentCompatibleWithType(args[i], varArgType))
      return null;
  Object[] newArgs = new Object[n+1];
  arraycopy(args, 0, newArgs, 0, n);
  Object[] varArgs = arrayOfType(varArgType, nArgs-n);
  arraycopy(args, n, varArgs, 0, nArgs-n);
  newArgs[n] = varArgs;
  return newArgs;
}


static <A> 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 <A> int indexOfSubList(List<A> x, List<A> y) {
  return indexOfSubList(x, y, 0);
}

static <A> int indexOfSubList(List<A> x, List<A> y, int i) {
  outer: for (; i+l(y) <= l(x); i++) {
    for (int j = 0; j < l(y); j++)
      if (neq(x.get(i+j), y.get(j)))
        continue outer;
    return i;
  }
  return -1;
}

static <A> int indexOfSubList(List<A> x, A[] y, int i) {
  outer: for (; i+l(y) <= l(x); i++) {
    for (int j = 0; j < l(y); j++)
      if (neq(x.get(i+j), y[j]))
        continue outer;
    return i;
  }
  return -1;
}


static <A, B extends A> void copyListPart(List<B> a, int i1, List<A> b, int i2, int n) {
  if (a == null || b == null) return;
  for (int i = 0; i < n; i++)
    b.set(i2+i, a.get(i1+i));
}


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 <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 <A extends Throwable> A printException(A e) {
  printStackTrace(e);
  return e;
}


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



  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 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 List<String> getClassNames(Collection l) {
  List<String> out = new ArrayList();
  if (l != null) for (Object o : l)
    out.add(o == null ? null : getClassName(o));
  return out;
}


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


static Throwable _storeException_value;

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


static <A> Set<A> syncIdentityHashSet() {
  return (Set) synchronizedSet(identityHashSet());
}


static Map syncHashMap() {
  return synchroHashMap();
}


static <A> AutoCloseable tempSetThreadLocalIfNecessary(ThreadLocal<A> tl, A a) {
  if (tl == null) return null;
  A prev = tl.get();
  if (eq(prev, a)) return null;
  tl.set(a);
  return new AutoCloseable() { public String toString() { return "tl.set(prev);"; } public void close() throws Exception { tl.set(prev); }};
}




static byte[] isGIF_magic = bytesFromHex("47494638"); // Actual signature is longer, but we're lazy

static boolean isGIF(byte[] data) {
  return byteArrayStartsWith(data, isGIF_magic);
}

static boolean isGIF(File f) {
  return isGIF(loadBeginningOfBinaryFile(f, l(isGIF_magic)));
}


static <A> void setVar(IVar<A> v, A value) {
  if (v != null) v.set(value);
}

static <A> IVF1<A> setVar(IVar<A> v) {
  return a -> { if (v != null) v.set(a); };
}


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> A heldInstance(Class<A> c) {
  List<Object> l = holdInstance_l.get();
  for (int i = l(l)-1; i >= 0; i--) {
    Object o = l.get(i);
    if (isInstanceOf(o, c))
      return (A) o;
  }
  throw fail("No instance of " + className(c) + " held");
}


static File loadBinarySnippet(String snippetID) {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadLibrary(snippetID);
  
  
  return loadBinarySnippet_noResourceLoader(snippetID);
}
  
static File loadBinarySnippet_noResourceLoader(String snippetID) { try {
  long id = parseSnippetID(snippetID);
  if (isImageServerSnippet(id)) return loadImageAsFile(snippetID);
  File f = DiskSnippetCache_getLibrary(id);
  if (fileSize(f) == 0)
    f = loadDataSnippetToFile_noResourceLoader(snippetID);
  return f;
} catch (Exception __e) { throw rethrow(__e); } }


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


static int moveToTopRightCorner_inset = 20;

static <A extends Component> A moveToTopRightCorner(A a) {
  return moveToTopRightCorner(moveToTopRightCorner_inset, moveToTopRightCorner_inset, a);
}

static <A extends Component> A moveToTopRightCorner(int insetX, int insetY, A a) {
  { swing(() -> {
    Window w = getWindow(a);
    if (w != null) {
      var bounds = preferredScreenBounds();
      w.setLocation(bounds.x2()-w.getWidth()-insetX, bounds.y1()+insetY);
    }
  }); }
  return a;
}



static JTextField onEnter(final JTextField tf, final Object action) {
  if (action == null || tf == null) return tf;
  tf.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent _evt) { try {
    tf.selectAll();
    callF(action);
  } catch (Throwable __e) { messageBox(__e); }}});
  return tf;
}

static JButton onEnter(JButton btn, final Object action) {
  if (action == null || btn == null) return btn;
  btn.addActionListener(actionListener(action));
  return btn;
}

static JList onEnter(JList list, Object action) {
  list.addKeyListener(enterKeyListener(rCallOnSelectedListItem(list, action)));
  return list;
}

static JComboBox onEnter(final JComboBox cb, final Object action) {
  { swing(() -> {
    if (cb.isEditable()) {
      JTextField text = (JTextField) cb.getEditor().getEditorComponent();
      onEnter(text, action);
    } else {
      cb.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "enter");
      cb.getActionMap().put("enter", abstractAction("", new Runnable() {  public void run() { try {  cb.hidePopup(); callF(action); 
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "cb.hidePopup(); callF(action);"; }}));
    }
  }); }
  return cb;
}

static JTable onEnter(final JTable table, final Object action) {  
  table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
    .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Enter");
    
  table.getActionMap().put("Enter", new AbstractAction() {
    public void actionPerformed(ActionEvent e) {
      callF(action, table.getSelectedRow());
    }
  });
  return table;
}

/*static JTextArea onEnter(final JTextArea ta, fO action) {
  addKeyListener(ta, enterKeyListener(action));
  ret ta;
}*/

static JTextField onEnter(Object action, JTextField tf) {
  return onEnter(tf, action);
}


static void swingLater(long delay, final Object r) {
  javax.swing.Timer timer = new javax.swing.Timer(toInt(delay), actionListener(wrapAsActivity(r)));
  timer.setRepeats(false);
  timer.start();
}

static void swingLater(Object r) {
  SwingUtilities.invokeLater(toRunnable(r));
}



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


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




// tok should be the output of htmlcoarsetok
static List<String> dropAllTags(List<String> tok) {
  List<String> list = new ArrayList();
  for (int i = 0; i < l(tok); i++) {
    String t = tok.get(i);
    if (odd(i) && t.startsWith("<")) {
      list.set(list.size()-1, list.get(list.size()-1) + tok.get(i+1));
      ++i;
    } else
      list.add(t);
  }
  return list;
}

// alternatively, call this convenient function
static String dropAllTags(String html) {
  if (!contains(html, '<')) return html;
  return join(dropAllTags(htmlcoarsetok(html)));
}


static String htmldecode(final String input) {
  if (input == null) return null;
  
  final int MIN_ESCAPE = 2;
  final int MAX_ESCAPE = 6;

  StringWriter writer = null;
  int len = input.length();
  int i = 1;
  int st = 0;
  while (true) {
      // look for '&'
      while (i < len && input.charAt(i-1) != '&')
          i++;
      if (i >= len)
          break;

      // found '&', look for ';'
      int j = i;
      while (j < len && j < i + MAX_ESCAPE + 1 && input.charAt(j) != ';')
          j++;
      if (j == len || j < i + MIN_ESCAPE || j == i + MAX_ESCAPE + 1) {
          i++;
          continue;
      }

      // found escape 
      if (input.charAt(i) == '#') {
          // numeric escape
          int k = i + 1;
          int radix = 10;

          final char firstChar = input.charAt(k);
          if (firstChar == 'x' || firstChar == 'X') {
              k++;
              radix = 16;
          }

          try {
              int entityValue = Integer.parseInt(input.substring(k, j), radix);

              if (writer == null) 
                  writer = new StringWriter(input.length());
              writer.append(input.substring(st, i - 1));

              if (entityValue > 0xFFFF) {
                  final char[] chrs = Character.toChars(entityValue);
                  writer.write(chrs[0]);
                  writer.write(chrs[1]);
              } else {
                  writer.write(entityValue);
              }

          } catch (NumberFormatException ex) { 
              i++;
              continue;
          }
      }
      else {
          // named escape
          CharSequence value = htmldecode_lookupMap().get(input.substring(i, j));
          if (value == null) {
              i++;
              continue;
          }

          if (writer == null) 
              writer = new StringWriter(input.length());
          writer.append(input.substring(st, i - 1));

          writer.append(value);
      }

      // skip escape
      st = j + 1;
      i = st;
  }

  if (writer != null) {
      writer.append(input.substring(st, len));
      return writer.toString();
  }
  return input;
}

static HashMap<String, CharSequence> htmldecode_lookupMap_cache;
static HashMap<String, CharSequence> htmldecode_lookupMap() { if (htmldecode_lookupMap_cache == null) htmldecode_lookupMap_cache = htmldecode_lookupMap_load(); return htmldecode_lookupMap_cache; }

static HashMap<String, CharSequence> htmldecode_lookupMap_load() {
  var map = new HashMap<String, CharSequence>();
  for (CharSequence[] seq : htmldecode_escapes()) 
    map.put(seq[1].toString(), seq[0]);
  return map;
}


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 <A> Iterator<A> iterator(Iterable<A> c) {
  return c == null ? emptyIterator() : c.iterator();
}


static List<String> javaTokForStructure(String s) {
  return javaTok_noMLS(s);
}


static String structure_addTokenMarkers(String s) {
  return join(structure_addTokenMarkers(javaTokForStructure(s)));
}
  
static List<String> structure_addTokenMarkers(List<String> tok) {
  // find references
  
  TreeSet<Integer> refs = new TreeSet();
  for (int i = 1; i < l(tok); i += 2) {
    String t = tok.get(i);
    if (t.startsWith("t") && isInteger(t.substring(1)))
      refs.add(parseInt(t.substring(1)));
  }
  
  if (empty(refs)) return tok;
  
  // add markers
  for (int i : refs) {
    int idx = i*2+1;
    if (idx >= l(tok)) continue; // broken structure
    String t = "";
    if (endsWithLetterOrDigit(tok.get(idx-1))) t = " ";
    tok.set(idx, t + "m" + i + " " + tok.get(idx));
  }
  
  return tok;
}


static boolean structure_showTiming, structure_checkTokenCount;

static String structure(Object o) {
  return structure(o, new structure_Data());
}

static String structure(Object o, structure_Data d) {
  StringWriter sw = new StringWriter();
  d.out = new PrintWriter(sw);
  structure_go(o, d);
  String s = str(sw);
  if (structure_checkTokenCount) {
    print("token count=" + d.n);
    assertEquals("token count", l(javaTokC(s)), d.n);
  }
  return s;
}

static void structure_go(Object o, structure_Data d) {
  structure_1(o, d);
  while (nempty(d.stack))
    popLast(d.stack).run();
}

static void structureToPrintWriter(Object o, PrintWriter out) { structureToPrintWriter(o, out, new structure_Data()); }
static void structureToPrintWriter(Object o, PrintWriter out, structure_Data d) {
  d.out = out;
  structure_go(o, d);
}

// leave to false, unless unstructure() breaks
static boolean structure_allowShortening = false;

// info on how to serialize objects of a certain class
static class structure_ClassInfo {
  Class c;
  List<Field> fields;
  Method customSerializer;
  IVF1<Object> serializeObject; // can be set by caller of structure function
  boolean special = false; // various special classes
  boolean nullInstances = false; // serialize all instances as null (e.g. lambdas/anonymous classes)
}

static class structure_Data {
  PrintWriter out;
  int stringSizeLimit;
  int shareStringsLongerThan = 20;
  boolean noStringSharing = false;
  boolean storeBaseClasses = false;
  boolean honorFieldOrder = true;
  String mcDollar = actualMCDollar();

  IdentityHashMap<Object, Integer> seen = new IdentityHashMap();
  //new BitSet refd;
  HashMap<String, Integer> strings = new HashMap();
  HashSet<String> concepts = new HashSet();
  HashMap<Class, structure_ClassInfo> infoByClass = new HashMap();
  HashMap<Class, Field> persistenceInfo = new HashMap();
  int n; // token count
  List<Runnable> stack = new ArrayList();
  
  // append single token
  structure_Data append(String token) { out.print(token); ++n; return this; }
  structure_Data append(int i) { out.print(i); ++n; return this; }
  
  // append multiple tokens
  structure_Data append(String token, int tokCount) { out.print(token); n += tokCount; return this; }
  
  // extend last token
  structure_Data app(String token) { out.print(token); return this; }
  structure_Data app(int i) { out.print(i); return this; }

  structure_ClassInfo infoForClass(Class c) {
    structure_ClassInfo info = infoByClass.get(c);
    if (info == null) info = newClass(c);
    return info;
  }
  
  // called when a new class is detected
  // can be overridden by clients
  structure_ClassInfo newClass(Class c) {
    structure_ClassInfo info = new structure_ClassInfo();
    info.c = c;
    infoByClass.put(c, info);
    
    if (isSyntheticOrAnonymous(c)) {
      info.special = info.nullInstances = true;
      return info;
    }
    
    if ((info.customSerializer = findMethodNamed(c, "_serialize"))
      != null) info.special = true;
      
    if (storeBaseClasses) {
      Class sup = c.getSuperclass();
      if (sup != Object.class) {
        append("bc ");
        append(shortDynClassNameForStructure(c));
        out.print(" ");
        append(shortDynClassNameForStructure(sup));
        out.print(" ");
        infoForClass(sup); // transitively write out superclass relations
      }
    }
    
    if (!isPersistableClass(c))
      warn("Class not persistable: " + c + " (anonymous or no default constructor)");
    
    return info;
  }
  
  void setFields(structure_ClassInfo info, List<Field> fields) { 
    info.fields = fields;
  }
  
  void writeObject(Object o, String shortName, Map<String, Object> fv) {
    String singleField = fv.size() == 1 ? first(fv.keySet()) : null;
  
    append(shortName);
    n += countDots(shortName)*2; // correct token count
    
  
    int l = n;
    Iterator it = fv.entrySet().iterator();
    
    stack.add(new Runnable() {  public void run() { try { 
      if (!it.hasNext()) {
        if (n != l)
          append(")");
      } else {
        Map.Entry e = (Map.Entry) it.next();
        append(n == l ? "(" : ", ");
        append((String) e.getKey()).append("=");
        stack.add(this);
        structure_1(e.getValue(), structure_Data.this);
      }
    
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (!it.hasNext()) {\r\n        if (n != l)\r\n          append(\")\");\r\n      } el..."; }});
  }
}

static void structure_1(final Object o, final structure_Data d) { try {
  if (o == null) { d.append("null"); return; }
  
  Class c = o.getClass();
  boolean concept = false;
  
  structure_ClassInfo info = d.infoForClass(c);
  
  List<Field> lFields = info.fields;
  if (lFields == null) {
    // these are never back-referenced (for readability)
    
    if (o instanceof Number) {
      PrintWriter out = d.out;
if (o instanceof Integer) { int i = ((Integer) o).intValue(); out.print(i); d.n += i < 0 ? 2 : 1; return; }
      if (o instanceof Long) { long l = ((Long) o).longValue(); out.print(l); out.print("L"); d.n += l < 0 ? 2 : 1; return; }
      if (o instanceof Short) { short s = ((Short) o).shortValue(); d.append("sh "); out.print(s); d.n += s < 0 ? 2 : 1; return; }
      if (o instanceof Float) { d.append("fl ", 2); quoteToPrintWriter(str(o), out); return; }
      if (o instanceof Double) { d.append("d(", 3); quoteToPrintWriter(str(o), out); d.append(")"); return; }
      if (o instanceof BigInteger) { out.print("bigint("); out.print(o); out.print(")"); d.n += ((BigInteger) o).signum() < 0 ? 5 : 4; return; }
    }
  
    if (o instanceof Boolean) {
      d.append(((Boolean) o).booleanValue() ? "t" : "f"); return;
    }
      
    if (o instanceof Character) {
      d.append(quoteCharacter((Character) o)); return;
    }
      
    if (o instanceof File) {
      d.append("File ").append(quote(((File) o).getPath())); return;
    }
      
    // referencable objects follow
    
    Integer ref = d.seen.get(o);
    if (o instanceof String && ref == null) ref = d.strings.get((String) o);
    if (ref != null) { /*d.refd.set(ref);*/ d.append("t").app(ref); return; }

    if (!(o instanceof String))
      d.seen.put(o, d.n); // record token number
    else {
      String s = d.stringSizeLimit != 0 ? shorten((String) o, d.stringSizeLimit) : (String) o;
      if (!d.noStringSharing) {
        if (d.shareStringsLongerThan == Integer.MAX_VALUE)
          d.seen.put(o, d.n);
        if (l(s) >= d.shareStringsLongerThan)
          d.strings.put(s, d.n);
      }
      quoteToPrintWriter(s, d.out); d.n++; return;
    }
      
    if (o instanceof Set) {
      /*O set2 = unwrapSynchronizedSet(o);
      if (set2 != o) {
        d.append("sync");
        o = set2;
      } TODO */
      
      if (((Set) o) instanceof TreeSet) {
        d.append(isCISet_gen((Set) o) ? "ciset" : "treeset");
        structure_1(new ArrayList((Set) o), d);
        return;
      }
      
      // assume it's a HashSet or LinkedHashSet
      d.append(((Set) o) instanceof LinkedHashSet ? "lhs" : "hashset");
      structure_1(new ArrayList((Set) o), d);
      return;
    }
    
    String name = c.getName();
    
    if (o instanceof Collection
      && !isJavaXClassName(name)
      /* && neq(name, "main$Concept$RefL") */) {
      
      // it's a list
    
      if (name.equals("java.util.Collections$SynchronizedList")
        || name.equals("java.util.Collections$SynchronizedRandomAccessList")) {
        d.append("sync ");
        { structure_1(unwrapSynchronizedList(((List) o)), d); return; }
      }
      else if (name.equals("java.util.LinkedList")) d.append("ll");
      d.append("[");
      final int l = d.n;
      final Iterator it = cloneList((Collection) o).iterator();
      d.stack.add(new Runnable() {  public void run() { try { 
        if (!it.hasNext())
          d.append("]");
        else {
          d.stack.add(this);
          if (d.n != l) d.append(", ");
          structure_1(it.next(), d);
        }
      
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "if (!it.hasNext())\r\n          d.append(\"]\");\r\n        else {\r\n          d.sta..."; }});
      return;
    }
    
    

    
    if (o instanceof Map && !startsWith(name, d.mcDollar)) {
      if (o instanceof LinkedHashMap) d.append("lhm");
      else if (o instanceof HashMap) d.append("hm");
      else if (o instanceof TreeMap)
        d.append(isCIMap_gen((TreeMap) o) ? "cimap" : "tm");
      else if (name.equals("java.util.Collections$SynchronizedMap")
        || name.equals("java.util.Collections$SynchronizedSortedMap")
        || name.equals("java.util.Collections$SynchronizedNavigableMap")) {
        d.append("sync "); 
        { structure_1(unwrapSynchronizedMap(((Map) o)), d); return; }
      }
      
      d.append("{");
      final int l = d.n;
      final Iterator it = cloneMap((Map) o).entrySet().iterator();
      
      d.stack.add(new Runnable() {
        boolean v = false;
        Map.Entry e;
        
        public void run() {
          if (v) {
            d.append("=");
            v = false;
            d.stack.add(this);
            structure_1(e.getValue(), d);
          } else {
            if (!it.hasNext())
              d.append("}");
            else {
              e = (Map.Entry) it.next();
              v = true;
              d.stack.add(this);
              if (d.n != l) d.append(", ");
              structure_1(e.getKey(), d);
            }
          }
        }
      });
      return;
    }
    
    if (c.isArray()) {
      if (o instanceof byte[]) {
        d.append("ba ").append(quote(bytesToHex((byte[]) o))); return;
      }
  
      final int n = Array.getLength(o);
  
      if (o instanceof boolean[]) {
        String hex = boolArrayToHex((boolean[]) o);
        int i = l(hex);
        while (i > 0 && hex.charAt(i-1) == '0' && hex.charAt(i-2) == '0') i -= 2;
        d.append("boolarray ").append(n).app(" ").append(quote(substring(hex, 0, i))); return;
      }
      
      String atype = "array"/*, sep = ", "*/; // sep is not used yet
  
      if (o instanceof int[]) {
        //ret "intarray " + quote(intArrayToHex((int[]) o));
        atype = "intarray";
        //sep = " ";
      } else if (o instanceof double[]) {
        atype = "dblarray";
        //sep = " ";
      } else {
        Pair<Class, Integer> p = arrayTypeAndDimensions(c);
        if (p.a == int.class) atype = "intarray";
        else if (p.a == byte.class) atype = "bytearray";
        else if (p.a == boolean.class) atype = "boolarray";
        else if (p.a == double.class) atype = "dblarray";
        else if (p.a == String.class) { atype = "array S"; d.n++; }
        else atype = "array"; // fail("Unsupported array type: " + p.a);
        if (p.b > 1) {
          atype += "/" + p.b; // add number of dimensions
          d.n += 2; // 2 additional tokens will be written
        }
      }
      
      d.append(atype).append("{");
      d.stack.add(new Runnable() {
        int i;
        public void run() {
          if (i >= n)
            d.append("}");
          else {
            d.stack.add(this);
            if (i > 0) d.append(", ");
            structure_1(Array.get(o, i++), d);
          }
        }
      });
      return;
    }
  
    if (o instanceof Class) {
      d.append("class(", 2).append(quote(((Class) o).getName())).append(")"); return;
    }
      
    if (o instanceof Throwable) {
      d.append("exception(", 2).append(quote(((Throwable) o).getMessage())).append(")"); return;
    }
      
    if (o instanceof BitSet) {
      BitSet bs = (BitSet) o;
      d.append("bitset{", 2);
      int l = d.n;
      for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
        if (d.n != l) d.append(", ");
        d.append(i);
      }
      d.append("}"); return;
    }
      
    // Need more cases? This should cover all library classes...
    if (name.startsWith("java.") || name.startsWith("javax.")) {
      d.append("j ").append(quote(str(o))); return; // Hm. this is not unstructure-able
    }
    
    
      
    /*if (name.equals("main$Lisp")) {
      fail("lisp not supported right now");
    }*/
    
    if (info.special) {
      if (info.customSerializer != null) {
        // custom serialization (_serialize method)
        Object o2 = invokeMethod(info.customSerializer, o);
        d.append("cu ");
        String shortName = dropPrefix(d.mcDollar, name);
        d.append(shortName);
        d.out.append(' ');
        structure_1(o2, d);
        return;
      } else if (info.nullInstances) { d.append("null"); return; }
      else if (info.serializeObject != null)
        { info.serializeObject.get(o); return; }
      else throw fail("unknown special type");
    }
    
    String dynName = shortDynClassNameForStructure(o);
    if (concept && !d.concepts.contains(dynName)) {
      d.concepts.add(dynName);
      d.append("c ");
    }
    
    // serialize an object with fields.
    // first, collect all fields and values in fv.
    
    TreeSet<Field> fields = new TreeSet<Field>(new Comparator<Field>() {
      public int compare(Field a, Field b) {
        return stdcompare(a.getName(), b.getName());
      }
    });
    
    Class cc = c;
    while (cc != Object.class) {
      for (Field field : getDeclaredFields_cached(cc)) {
        String fieldName = field.getName();
        if (fieldName.equals("_persistenceInfo"))
          d.persistenceInfo.put(c, field);
        if ((field.getModifiers() & (java.lang.reflect.Modifier.STATIC | java.lang.reflect.Modifier.TRANSIENT)) != 0)
          continue;

        fields.add(field);
        
        // put special cases here...?
      }
        
      cc = cc.getSuperclass();
    }
    
    lFields = asList(d.honorFieldOrder ? fieldObjectsInFieldOrder(c, fields) : fields);

    // Render this$0/this$1 first because unstructure needs it for constructor call.
    
    int n = l(lFields);
    for (int i = 0; i < n; i++) {
      Field f = lFields.get(i);
      if (f.getName().startsWith("this$")) {
        lFields.remove(i);
        lFields.add(0, f);
        break;
      }
    }
  
    
    d.setFields(info, lFields);
  } // << if (lFields == null)
  else { // ref handling for lFields != null
    Integer ref = d.seen.get(o);
    if (ref != null) { /*d.refd.set(ref);*/ d.append("t").app(ref); return; }
    d.seen.put(o, d.n); // record token number
  }

  // get _persistenceInfo from field and/or dynamic field
  Field persistenceInfoField =  (Field) (d.persistenceInfo.get(c));
  Map<String, Object> persistenceInfo = persistenceInfoField == null ? null : (Map) persistenceInfoField.get(o);
  
  if (persistenceInfoField == null && o instanceof DynamicObject)
    persistenceInfo = (Map<String, Object>) getOptDynOnly(((DynamicObject) o), "_persistenceInfo");

  
  LinkedHashMap<String, Object> fv = new LinkedHashMap();
  for (Field f : lFields) {
    Object value;
    try {
      value = f.get(o);
    } catch (Exception e) {
      value = "?";
    }
      
    if (value != null && (persistenceInfo == null
      || !Boolean.FALSE.equals(persistenceInfo.get(f.getName()))))
      fv.put(f.getName(), value);
    
  }
  
  String name = c.getName();
  String shortName = dropPrefix("loadableUtils.utils$", dropPrefix(d.mcDollar, name));
  if (startsWithDigit(shortName)) shortName = name; // for anonymous classes
    
  // Now we have fields & values. Process fieldValues if it's a DynamicObject.
  
  // omit field "className" if equal to class's name
  if (concept && eq(fv.get("className"), shortName))
    fv.remove("className");
          
  if (o instanceof DynamicObject) {
    putAll(fv, (Map) fv.get("fieldValues"));
    fv.remove("fieldValues");
    shortName = shortDynClassNameForStructure(o);
    fv.remove("className");
  }
  
  d.writeObject(o, shortName, fv);
} catch (Exception __e) { throw rethrow(__e); } }



static CloseableIterableIterator emptyCloseableIterableIterator_instance = new CloseableIterableIterator() {
  public Object next() { throw fail(); }
  public boolean hasNext() { return false; }
};

static <A> CloseableIterableIterator<A> emptyCloseableIterableIterator() {
  return emptyCloseableIterableIterator_instance; 
}


static CloseableIterableIterator<String> linesFromReader(Reader r) { return linesFromReader(r, null); }
static CloseableIterableIterator<String> linesFromReader(Reader r, IResourceHolder resourceHolder) {
  final BufferedReader br = bufferedReader(r);
  return holdResource(resourceHolder, iteratorFromFunction_f0_autoCloseable(new F0<String>() { public String get() { try {  return readLineFromReaderWithClose(br);  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "ret readLineFromReaderWithClose(br);"; }}, _wrapIOCloseable(r)));
}


static BufferedReader utf8bufferedReader(InputStream in) { try {
  return in == null ? null : bufferedReader(_registerIOWrap(new InputStreamReader(in, "UTF-8"), in));
} catch (Exception __e) { throw rethrow(__e); } }

static BufferedReader utf8bufferedReader(File f) { try {
  return utf8bufferedReader(newFileInputStream(f));
} catch (Exception __e) { throw rethrow(__e); } }


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


static TreeSet<String> caseInsensitiveSet() {
  return caseInsensitiveSet_treeSet();
}

static TreeSet<String> caseInsensitiveSet(Collection<String> c) {
  return caseInsensitiveSet_treeSet(c);
}


static <A> TreeSet<A> treeSet() {
  return new TreeSet();
}


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


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 ThreadLocal<List<Object>> holdInstance_l = new ThreadLocal();

static AutoCloseable holdInstance(Object o) {
  if (o == null) return null;
  listThreadLocalAdd(holdInstance_l, o);
  return new AutoCloseable() {
    public void close() {
      listThreadLocalPopLast(holdInstance_l);
    }
  };
}


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


static <A, B> List<A> keysList(Map<A, B> map) {
  return cloneListSynchronizingOn(keys(map), map);
}




static void _onJavaXSet() {}


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

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


static volatile String caseID_caseID;

static String caseID() { return caseID_caseID; }

static void caseID(String id) {
  caseID_caseID = id;
}


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


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




static String formatWithThousandsSeparator(long l) {
  return NumberFormat.getInstance(new Locale("en_US")).format(l);
}


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 File computerIDFile() {
  return javaxDataDir("Basic Info/computer-id.txt");
}


static String makeRandomID(int length) {
  return makeRandomID(length, defaultRandomGenerator());
}

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

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


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

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


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


static boolean argumentCompatibleWithType(Object arg, Class type) {
  return arg == null ? !type.isPrimitive() : isInstanceX(type, arg);
}


static void arraycopy(Object[] a, Object[] b) {
  if (a != null && b != null)
    arraycopy(a, 0, b, 0, Math.min(a.length, b.length));
}

static void arraycopy(Object src, int srcPos, 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 <A> A[] arrayOfType(Class<A> type, int n) {
  return makeArray(type, n);
}

static <A> A[] arrayOfType(int n, Class<A> type) {
  return arrayOfType(type, n);
}


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 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> IF0<A> f0ToIF0(F0<A> f) {
  return f == null ? null : () -> f.get();
}


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 <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 byte[] bytesFromHex(String s) {
  return hexToBytes(s);
}


static boolean byteArrayStartsWith(byte[] a, byte[] b) {
  if (a == null || b == null) return false;
  if (a.length < b.length) return false;
  for (int i = 0; i < b.length; i++)
    if (a[i] != b[i])
      return false;
  return true;
}


static byte[] loadBeginningOfBinaryFile(File file, int maxBytes) {
  return loadBinaryFilePart(file, 0, maxBytes);
}


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


static boolean isImageServerSnippet(long id) {
  return id >= 1100000 && id < 1200000;
}



static File loadImageAsFile(String snippetIDOrURL) { try {
  if (isURL(snippetIDOrURL))
    throw fail("not implemented");

  if (!isSnippetID(snippetIDOrURL)) throw fail("Not a URL or snippet ID: " + snippetIDOrURL);
  String snippetID = "" + parseSnippetID(snippetIDOrURL);
  
  File file = imageSnippetCacheFile(snippetID);
  if (fileSize(file) > 0) return file;

  String imageURL = snippetImageURL_noHttps(snippetID);
  System.err.println("Loading image: " + imageURL);
  byte[] data = loadBinaryPage(imageURL);

  saveBinaryFile(file, data);
  return file;
} catch (Exception __e) { throw rethrow(__e); } }



// If you change this, also change DiskSnippetCache_fileToLibID
static File DiskSnippetCache_file(long snippetID) {
  return new File(getGlobalCache(), "data_" + snippetID + ".jar");
}
  
  // Data files are immutable, use centralized cache
public static File DiskSnippetCache_getLibrary(long snippetID) throws IOException {
  File file = DiskSnippetCache_file(snippetID);
  return file.exists() ? file : null;
}

public static void DiskSnippetCache_putLibrary(long snippetID, byte[] data) throws IOException {
  saveBinaryFile(DiskSnippetCache_file(snippetID), data);
}

static byte[] loadDataSnippetImpl(String snippetID) throws IOException {
  byte[] data;
  try {
    URL url = new URL(dataSnippetLink(snippetID));
    print("Loading library: " + hideCredentials(url));
    try {
      data = loadBinaryPage(url.openConnection());
    } catch (RuntimeException e) {
      data = null;
    }
    
    if (data == null || data.length == 0) {
      url = new URL(tb_mainServer() + "/blobs/" + parseSnippetID(snippetID));
      print("Loading library: " + hideCredentials(url));
      data = loadBinaryPage(url.openConnection());
    }
    print("Bytes loaded: " + data.length);
  } catch (FileNotFoundException e) {
    throw new IOException("Binary snippet #" + snippetID + " not found or not public");
  }
  return data;
}


static long fileSize(String path) { return getFileSize(path); }
static long fileSize(File f) { return getFileSize(f); }



static File loadDataSnippetToFile(String snippetID) { try {
  
  IResourceLoader rl = vm_getResourceLoader();
  if (rl != null)
    return rl.loadLibrary(snippetID);
  
  
  return loadDataSnippetToFile_noResourceLoader(snippetID);
} catch (Exception __e) { throw rethrow(__e); } }
  
static File loadDataSnippetToFile_noResourceLoader(String snippetID) { try {
  snippetID = fsI(snippetID);
  
  File f = DiskSnippetCache_file(parseSnippetID(snippetID));
  List<URL> urlsTried = new ArrayList();
  List<Throwable> errors = new ArrayList();
  try {
    URL url = addAndReturn(urlsTried, new URL(dataSnippetLink(snippetID)));
    print("Loading library: " + hideCredentials(url));
    try {
      loadBinaryPageToFile(openConnection(url), f);
      if (fileSize(f) == 0) throw fail();
    } catch (Throwable e) {
      errors.add(e);
      url = addAndReturn(urlsTried, new URL(tb_mainServer() + "/blobs/" + psI(snippetID)));
      print(e);
      print("Trying other server: " + hideCredentials(url));
      loadBinaryPageToFile(openConnection(url), f);
      print("Got bytes: " + fileSize(f));
    }
    // TODO: check if we hit the "LOADING" message
    if (fileSize(f) == 0) throw fail();
    System.err.println("Bytes loaded: " + fileSize(f));
  } catch (Throwable e) {
    //printStackTrace(e);
    errors.add(e);
    throw fail("Binary snippet " + snippetID + " not found or not public. URLs tried: " + allToString(urlsTried) + ", errors: " + allToString(errors));
  }
  return f;
} catch (Exception __e) { throw rethrow(__e); } }


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 Runnable asRunnable(Object o) {
  return toRunnable(o);
}




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


static Rect preferredScreenBounds() {
  return screenBounds_safe(preferredScreen());
}


static void messageBox(final String msg) {
  if (headless()) print(msg);
  else { swing(() -> {
    JOptionPane.showMessageDialog(null, msg, "JavaX", JOptionPane.INFORMATION_MESSAGE);
  }); }
}

static void messageBox(Throwable e) {
  //showConsole();
  printStackTrace(e);
  messageBox(hideCredentials(innerException2(e)));
}


static ActionListener actionListener(final Object runnable) {
  return actionListener(runnable, null);
}

static ActionListener actionListener(final Object runnable, final Object instanceToHold) {
  if (runnable instanceof ActionListener) return (ActionListener) runnable;
  final Object info = _threadInfo();
  return new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent _evt) { try {
    _threadInheritInfo(info);
     AutoCloseable __1 = holdInstance(instanceToHold); try {
    pcallF(runnable);
  } finally { _close(__1); }} catch (Throwable __e) { messageBox(__e); }}};
}


static KeyListener enterKeyListener(final Object action) {
  return new KeyAdapter() {
    public void keyPressed(KeyEvent ke) {
      if (ke.getKeyCode() == KeyEvent.VK_ENTER)
        pcallF(action);
    }
  };
}


static Runnable rCallOnSelectedListItem(final JList list, final Object action) {
  return new Runnable() {  public void run() { try {  pcallF(action, getSelectedItem(list)) ;
} catch (Exception __e) { throw rethrow(__e); } }  public String toString() { return "pcallF(action, getSelectedItem(list))"; }};
}


static AbstractAction abstractAction(String name, final Object runnable) {
  return new AbstractAction(name) {
    public void actionPerformed(ActionEvent evt) {
      pcallF(runnable);
    }
  };
}


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 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 int toInt_checked(long l) {
  if (l != (int) l) throw fail("Too large for int: " + l);
  return (int) l;
}


static double parseDouble(String s) {
  return empty(s) ? 0.0 : Double.parseDouble(s);
}


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 <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 odd(int i) {
  return (i & 1) != 0;
}

static boolean odd(long i) {
  return (i & 1) != 0;
}

static boolean odd(BigInteger i) { return odd(toInt(i)); }


// TODO: process CDATA, scripts

static List<String> htmlcoarsetok(String s) {
  List<String> tok = new ArrayList();
  int l = s == null ? 0 : s.length();
  
  int i = 0;
  while (i < l) {
    int j = i;
    char c;
    
    // scan for non-tags
    while (j < l) {
      if (s.charAt(j) != '<')
        // regular character
        ++j;
      else if (s.substring(j, Math.min(j+4, l)).equals("<!--")) {
        // HTML comment
        j = j+3;
        do ++j; while (j < l && !s.substring(j, Math.min(j+3, l)).equals("-->"));
        j = Math.min(j+3, l);
      } else {
        char d = charAt(s, j+1); // character after <
        if (d == '/' || isLetter(d))
          // it's a tag
          break;
        else
          ++j;
      }
    }
    
    tok.add(s.substring(i, j)); // add non-tag content
    i = j;
    if (i >= l) break;
    c = s.charAt(i);

    // scan over tag
    if (c == '<') {
      ++j;
      
      while (j < l && s.charAt(j) != '>') ++j; // TODO: strings in tag?
      if (j < l) ++j;
    }

    tok.add(s.substring(i, j)); // add tag
    i = j;
  }
  
  if ((tok.size() & 1) == 0) tok.add("");
  return tok;
}


static String[][] htmldecode_escapes() {
  return htmldecode_ESCAPES;
}

static final String[][] htmldecode_ESCAPES = {
    {"\"",     "quot"}, // " - double-quote
    {"&",      "amp"}, // & - ampersand
    {"<",      "lt"}, // < - less-than
    {">",      "gt"}, // > - greater-than

    // Mapping to escape ISO-8859-1 characters to their named HTML 3.x equivalents.
    {"\u00A0", "nbsp"}, // non-breaking space
    {"\u00A1", "iexcl"}, // inverted exclamation mark
    {"\u00A2", "cent"}, // cent sign
    {"\u00A3", "pound"}, // pound sign
    {"\u00A4", "curren"}, // currency sign
    {"\u00A5", "yen"}, // yen sign = yuan sign
    {"\u00A6", "brvbar"}, // broken bar = broken vertical bar
    {"\u00A7", "sect"}, // section sign
    {"\u00A8", "uml"}, // diaeresis = spacing diaeresis
    {"\u00A9", "copy"}, // copyright sign
    {"\u00AA", "ordf"}, // feminine ordinal indicator
    {"\u00AB", "laquo"}, // left-pointing double angle quotation mark = left pointing guillemet
    {"\u00AC", "not"}, // not sign
    {"\u00AD", "shy"}, // soft hyphen = discretionary hyphen
    {"\u00AE", "reg"}, // registered trademark sign
    {"\u00AF", "macr"}, // macron = spacing macron = overline = APL overbar
    {"\u00B0", "deg"}, // degree sign
    {"\u00B1", "plusmn"}, // plus-minus sign = plus-or-minus sign
    {"\u00B2", "sup2"}, // superscript two = superscript digit two = squared
    {"\u00B3", "sup3"}, // superscript three = superscript digit three = cubed
    {"\u00B4", "acute"}, // acute accent = spacing acute
    {"\u00B5", "micro"}, // micro sign
    {"\u00B6", "para"}, // pilcrow sign = paragraph sign
    {"\u00B7", "middot"}, // middle dot = Georgian comma = Greek middle dot
    {"\u00B8", "cedil"}, // cedilla = spacing cedilla
    {"\u00B9", "sup1"}, // superscript one = superscript digit one
    {"\u00BA", "ordm"}, // masculine ordinal indicator
    {"\u00BB", "raquo"}, // right-pointing double angle quotation mark = right pointing guillemet
    {"\u00BC", "frac14"}, // vulgar fraction one quarter = fraction one quarter
    {"\u00BD", "frac12"}, // vulgar fraction one half = fraction one half
    {"\u00BE", "frac34"}, // vulgar fraction three quarters = fraction three quarters
    {"\u00BF", "iquest"}, // inverted question mark = turned question mark
    {"\u00C0", "Agrave"}, // ? - uppercase A, grave accent
    {"\u00C1", "Aacute"}, // ? - uppercase A, acute accent
    {"\u00C2", "Acirc"}, // ? - uppercase A, circumflex accent
    {"\u00C3", "Atilde"}, // ? - uppercase A, tilde
    {"\u00C4", "Auml"}, // ? - uppercase A, umlaut
    {"\u00C5", "Aring"}, // ? - uppercase A, ring
    {"\u00C6", "AElig"}, // ? - uppercase AE
    {"\u00C7", "Ccedil"}, // ? - uppercase C, cedilla
    {"\u00C8", "Egrave"}, // ? - uppercase E, grave accent
    {"\u00C9", "Eacute"}, // ? - uppercase E, acute accent
    {"\u00CA", "Ecirc"}, // ? - uppercase E, circumflex accent
    {"\u00CB", "Euml"}, // ? - uppercase E, umlaut
    {"\u00CC", "Igrave"}, // ? - uppercase I, grave accent
    {"\u00CD", "Iacute"}, // ? - uppercase I, acute accent
    {"\u00CE", "Icirc"}, // ? - uppercase I, circumflex accent
    {"\u00CF", "Iuml"}, // ? - uppercase I, umlaut
    {"\u00D0", "ETH"}, // ? - uppercase Eth, Icelandic
    {"\u00D1", "Ntilde"}, // ? - uppercase N, tilde
    {"\u00D2", "Ograve"}, // ? - uppercase O, grave accent
    {"\u00D3", "Oacute"}, // ? - uppercase O, acute accent
    {"\u00D4", "Ocirc"}, // ? - uppercase O, circumflex accent
    {"\u00D5", "Otilde"}, // ? - uppercase O, tilde
    {"\u00D6", "Ouml"}, // ? - uppercase O, umlaut
    {"\u00D7", "times"}, // multiplication sign
    {"\u00D8", "Oslash"}, // ? - uppercase O, slash
    {"\u00D9", "Ugrave"}, // ? - uppercase U, grave accent
    {"\u00DA", "Uacute"}, // ? - uppercase U, acute accent
    {"\u00DB", "Ucirc"}, // ? - uppercase U, circumflex accent
    {"\u00DC", "Uuml"}, // ? - uppercase U, umlaut
    {"\u00DD", "Yacute"}, // ? - uppercase Y, acute accent
    {"\u00DE", "THORN"}, // ? - uppercase THORN, Icelandic
    {"\u00DF", "szlig"}, // ? - lowercase sharps, German
    {"\u00E0", "agrave"}, // ? - lowercase a, grave accent
    {"\u00E1", "aacute"}, // ? - lowercase a, acute accent
    {"\u00E2", "acirc"}, // ? - lowercase a, circumflex accent
    {"\u00E3", "atilde"}, // ? - lowercase a, tilde
    {"\u00E4", "auml"}, // ? - lowercase a, umlaut
    {"\u00E5", "aring"}, // ? - lowercase a, ring
    {"\u00E6", "aelig"}, // ? - lowercase ae
    {"\u00E7", "ccedil"}, // ? - lowercase c, cedilla
    {"\u00E8", "egrave"}, // ? - lowercase e, grave accent
    {"\u00E9", "eacute"}, // ? - lowercase e, acute accent
    {"\u00EA", "ecirc"}, // ? - lowercase e, circumflex accent
    {"\u00EB", "euml"}, // ? - lowercase e, umlaut
    {"\u00EC", "igrave"}, // ? - lowercase i, grave accent
    {"\u00ED", "iacute"}, // ? - lowercase i, acute accent
    {"\u00EE", "icirc"}, // ? - lowercase i, circumflex accent
    {"\u00EF", "iuml"}, // ? - lowercase i, umlaut
    {"\u00F0", "eth"}, // ? - lowercase eth, Icelandic
    {"\u00F1", "ntilde"}, // ? - lowercase n, tilde
    {"\u00F2", "ograve"}, // ? - lowercase o, grave accent
    {"\u00F3", "oacute"}, // ? - lowercase o, acute accent
    {"\u00F4", "ocirc"}, // ? - lowercase o, circumflex accent
    {"\u00F5", "otilde"}, // ? - lowercase o, tilde
    {"\u00F6", "ouml"}, // ? - lowercase o, umlaut
    {"\u00F7", "divide"}, // division sign
    {"\u00F8", "oslash"}, // ? - lowercase o, slash
    {"\u00F9", "ugrave"}, // ? - lowercase u, grave accent
    {"\u00FA", "uacute"}, // ? - lowercase u, acute accent
    {"\u00FB", "ucirc"}, // ? - lowercase u, circumflex accent
    {"\u00FC", "uuml"}, // ? - lowercase u, umlaut
    {"\u00FD", "yacute"}, // ? - lowercase y, acute accent
    {"\u00FE", "thorn"}, // ? - lowercase thorn, Icelandic
    {"\u00FF", "yuml"}, // ? - lowercase y, umlaut
    {"\u2013", "ndash"},
    {"\u2018", "lsquo"},
    {"\u2019", "rsquo"},
    {"\u201D", "rdquo"},
    {"\u201C", "ldquo"},
    {"\u2014", "mdash"},
    
    {"'", "apos"}, // the controversial (but who cares!) &apos;
      // stackoverflow.com/questions/2083754/why-shouldnt-apos-be-used-to-escape-single-quotes
  };



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(__22 -> isLetterOrDigit(__22), s);
  else return "" + first(s);
}


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


static List<String> javaTok_noMLS(String s) {
  ArrayList<String> tok = new ArrayList();
  int l = s == null ? 0 : s.length();
  
  int i = 0, n = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
    // scan for whitespace
    while (j < l) {
      c = s.charAt(j);
      d = j+1 >= l ? '\0' : s.charAt(j+1);
      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
        ++j;
      else if (c == '/' && d == '*') {
        do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
        j = Math.min(j+2, l);
      } else if (c == '/' && d == '/') {
        do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
      } else
        break;
    }
    
    tok.add(javaTok_substringN(s, i, j));
    ++n;
    i = j;
    if (i >= l) break;
    c = s.charAt(i);
    d = i+1 >= l ? '\0' : s.charAt(i+1);

    // scan for non-whitespace
    
    if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        int c2 = s.charAt(j);
        if (c2 == opener || c2 == '\n' && opener == '\'') { // allow multi-line strings, but not for '
          ++j;
          break;
        } else if (c2 == '\\' && j+1 < l)
          j += 2;
        else
          ++j;
      }
    } else if (Character.isJavaIdentifierStart(c))
      do ++j; while (j < l && Character.isJavaIdentifierPart(s.charAt(j)));
    else if (Character.isDigit(c)) {
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
      if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L
    } else
      ++j;
      
    tok.add(javaTok_substringC(s, i, j));
    ++n;
    i = j;
  }
  
  if ((tok.size() % 2) == 0) tok.add("");
  return tok;
}


static <A> A assertEquals(Object x, A y) {
  return assertEquals("", x, y);
}

static <A> A assertEquals(String msg, Object x, A y) {
  if (assertVerbose()) return assertEqualsVerbose(msg, x, y);
  if (!(x == null ? y == null : x.equals(y)))
    throw fail((msg != null ? msg + ": " : "") + y + " != " + x);
  return y;
}




static List<String> javaTokC(String s) {
  if (s == null) return null;
  int l = s.length();
  ArrayList<String> tok = new ArrayList();
  
  int i = 0;
  while (i < l) {
    int j = i;
    char c, d;
    
    // scan for whitespace
    while (j < l) {
      c = s.charAt(j);
      d = j+1 >= l ? '\0' : s.charAt(j+1);
      if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
        ++j;
      else if (c == '/' && d == '*') {
        do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/"));
        j = Math.min(j+2, l);
      } else if (c == '/' && d == '/') {
        do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0);
      } else
        break;
    }
    
    i = j;
    if (i >= l) break;
    c = s.charAt(i);
    d = i+1 >= l ? '\0' : s.charAt(i+1);

    // scan for non-whitespace
    if (c == '\'' || c == '"') {
      char opener = c;
      ++j;
      while (j < l) {
        if (s.charAt(j) == opener || s.charAt(j) == '\n') { // end at \n to not propagate unclosed string literal errors
          ++j;
          break;
        } else if (s.charAt(j) == '\\' && j+1 < l)
          j += 2;
        else
          ++j;
      }
    } else if (Character.isJavaIdentifierStart(c))
      do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || "'".indexOf(s.charAt(j)) >= 0)); // for stuff like "don't"
    else if (Character.isDigit(c)) {
      do ++j; while (j < l && Character.isDigit(s.charAt(j)));
      if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L
    } else if (c == '[' && d == '[') {
      do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]"));
      j = Math.min(j+2, l);
    } else if (c == '[' && d == '=' && i+2 < l && s.charAt(i+2) == '[') {
      do ++j; while (j+2 < l && !s.substring(j, j+3).equals("]=]"));
      j = Math.min(j+3, l);
    } else
      ++j;
      
    tok.add(javaTok_substringC(s, i, j));
    i = j;
  }
  
  return tok;
}


static <A> A popLast(List<A> l) {
  return liftLast(l);
}

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




static String actualMCDollar() {
  return actualMC().getName() + "$";
}


static boolean isSyntheticOrAnonymous(Class c) {
  return c != null && (c.isSynthetic() || isAnonymousClassName(c.getName()));
}


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


// keeps package names for dynamic code (package dyn.*)
static String shortDynClassNameForStructure(Object o) {
   if (o instanceof DynamicObject && ((DynamicObject) o).className != null)
    return ((DynamicObject) o).className;
  if (o == null) return null;
  Class c = o instanceof Class ? (Class) o : o.getClass();
  String name = c.getName();
  return name.startsWith("dyn.") ? classNameToVM(name) : shortenClassName(name);
}


static boolean isPersistableClass(Class c) {
  if (isAnonymousClassName(c.getName())) return false;
  if (isBoxedType(c)) return true;
  if (isArrayType(c)) return true;
  if (c == String.class || c == File.class) return true;
  
  if (hasThisDollarFields(c))
    return hasSingleArgumentConstructor(c);
  else
    return getDefaultConstructor(c) != 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 int countDots(String s) {
  int n = l(s), count = 0;
  for (int i = 0; i < n; i++) if (s.charAt(i) == '.') ++count;
  return count;
}


static void quoteToPrintWriter(String s, PrintWriter out) {
  if (s == null) { out.print("null"); return; }
  out.print('"');
  int l = s.length();
  for (int i = 0; i < l; i++) {
    char c = s.charAt(i);
    if (c == '\\' || c == '"') {
      out.print('\\'); out.print(c);
    } else if (c == '\r')
      out.print("\\r");
    else if (c == '\n')
      out.print("\\n");
    else if (c == '\0')
      out.print("\\0");
    else
      out.print(c);
  }
  out.print('"');
}


static String quoteCharacter(char c) {
  if (c == '\'') return "'\\''";
  if (c == '\\') return "'\\\\'";
  if (c == '\r') return "'\\r'";
  if (c == '\n') return "'\\n'";
  if (c == '\t') return "'\\t'";
  return "'" + c + "'";
}



static int shorten_default = 100;

static String shorten(CharSequence s) { return shorten(s, shorten_default); }

static String shorten(CharSequence s, int max) {
  return shorten(s, max, "...");
}

static String shorten(CharSequence s, int max, String shortener) {
  if (s == null) return "";
  if (max < 0) return str(s);
  return s.length() <= max ? str(s) : subCharSequence(s, 0, min(s.length(), max-l(shortener))) + shortener;
}

static String shorten(int max, CharSequence s) { return shorten(s, max); }


static boolean isCISet_gen(Iterable<String> l) {
  return l instanceof TreeSet && className(((TreeSet) l).comparator()).contains("CIComp");
}


static boolean isJavaXClassName(String s) {
  return startsWithOneOf(s, "main$", "loadableUtils.");
}


static <A> List<A> unwrapSynchronizedList(List<A> l) {
  
  if (eqOneOf(className(l),
    "java.util.Collections$SynchronizedList",
    "java.util.Collections$SynchronizedRandomAccessList"))
    return (List) get_raw(l, "list");
  return l;
}


static boolean isCIMap_gen(Map map) {
  return map instanceof TreeMap && className(((TreeMap) map).comparator()).contains("CIComp");
}


// works for both java.util-wrapped maps as well as our own
static <A, B> Map<A, B> unwrapSynchronizedMap(Map<A, B> map) {
  if (eqOneOf(shortClassName(map),
    "SynchronizedMap",
    "SynchronizedSortedMap",
    "SynchronizedNavigableMap"))
    return (Map) get_raw(map, "m");
  return map;
}


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 String boolArrayToHex(boolean[] a) {
  return bytesToHex(boolArrayToBytes(a));
}


static Pair<Class, Integer> arrayTypeAndDimensions(Object o) {
  return arrayTypeAndDimensions(_getClass(o));
}

static Pair<Class, Integer> arrayTypeAndDimensions(Class c) {
  if (c == null || !c.isArray()) return null;
  Class elem = c.getComponentType();
  if (elem.isArray())
    return mapPairB(arrayTypeAndDimensions(elem), dim -> dim+1);
  return pair(elem, 1);
}


static String dropPrefix(String prefix, String s) {
  return s == null ? null : s.startsWith(prefix) ? s.substring(l(prefix)) : s;
}


static int stdcompare(Number a, Number b) {
  return cmp(a, b);
}

static int stdcompare(String a, String b) {
  return cmp(a, b);
}

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

static int stdcompare(Object a, Object b) {
  return cmp(a, b);
}



static Map<Class, Field[]> getDeclaredFields_cache = newDangerousWeakHashMap();

static Field[] getDeclaredFields_cached(Class c) {
  Field[] fields;
  synchronized(getDeclaredFields_cache) {
    fields = getDeclaredFields_cache.get(c);
    if (fields == null) {
      getDeclaredFields_cache.put(c, fields = c.getDeclaredFields());
      for (Field f : fields)
        makeAccessible(f);
    }
  }
  return fields;
}


static Set<Field> fieldObjectsInFieldOrder(Class c, Set<Field> fields) {
  try {
    var byName = mapToKey(f -> f.getName(), fields);
    LinkedHashSet<Field> out = new LinkedHashSet();
    for (String name : unnullForIteration(getFieldOrder(c))) {
      Field f = byName.get(name);
      if (f != null) {
        byName.remove(name);
        out.add(f);
      }
    }
    addAll(out, fields);
    return out;
  } catch (Throwable __0) { printStackTrace(__0);
    return fields;
  }
}


static boolean startsWithDigit(String s) {
  return nempty(s) && isDigit(s.charAt(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 BufferedReader bufferedReader(Reader r) { return bufferedReader(r, 8192); }
static BufferedReader bufferedReader(Reader r, int bufSize) {
  if (r == null) return null;
  return r instanceof BufferedReader ? (BufferedReader) r : _registerIOWrap(new BufferedReader(r, bufSize), r);
}


static <A extends AutoCloseable> A holdResource(IResourceHolder holder, A a) {
  { if (holder != null) holder.add(a); }
  return a;
}


static <A> CloseableIterableIterator<A> iteratorFromFunction_f0_autoCloseable(final F0<A> f, final AutoCloseable closeable) {
  class IFF2 extends CloseableIterableIterator<A> {
    A a;
    boolean done = false;
    
    public boolean hasNext() {
      getNext();
      return !done;
    }
    
    public A next() {
      getNext();
      if (done) throw fail();
      A _a = a;
      a = null;
      return _a;
    }
    
    void getNext() {
      if (done || a != null) return;
      a = f.get();
      done = a == null;
    }
    
    public void close() throws Exception {
      if (closeable != null) closeable.close();
    }
  };
  return new IFF2();
}


static String readLineFromReaderWithClose(BufferedReader r) { try {
  String s = r.readLine();
  if (s == null) r.close();
  return s;
} catch (Exception __e) { throw rethrow(__e); } }


static AutoCloseable _wrapIOCloseable(final AutoCloseable c) {
  return c == null ? null : new AutoCloseable() { public String toString() { return "c.close();\r\n    _registerIO(c, null, false);"; } public void close() throws Exception { c.close();
    _registerIO(c, null, false);
  }};
}



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

static FileInputStream newFileInputStream(String path) throws IOException {
  FileInputStream f = new FileInputStream(path);
  _registerIO(f, path, true);
  return f;
}


static TreeSet<String> caseInsensitiveSet_treeSet() {
  return new TreeSet(caseInsensitiveComparator());
}

static TreeSet<String> caseInsensitiveSet_treeSet(Collection<String> c) {
  return toCaseInsensitiveSet_treeSet(c);
}


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 boolean regionMatchesIC(String a, int offsetA, String b, int offsetB, int len) {
  
  
    return a != null && a.regionMatches(true, offsetA, b, offsetB, len);
  
}




static boolean loadBufferedImage_useImageCache = true;

static BufferedImage loadBufferedImage(String snippetIDOrURLOrFile) { try {
  ping();
  if (snippetIDOrURLOrFile == null) return null;
  if (isURL(snippetIDOrURLOrFile))
    return imageIO_readURL(snippetIDOrURLOrFile);

  if (isSnippetID(snippetIDOrURLOrFile)) {
    String snippetID = "" + parseSnippetID(snippetIDOrURLOrFile);
    
    
    IResourceLoader rl = vm_getResourceLoader();
    if (rl != null)
      return loadBufferedImage(rl.loadLibrary(snippetID));
    
    
    File dir = imageSnippetsCacheDir();
    if (loadBufferedImage_useImageCache) {
      dir.mkdirs();
      File file = new File(dir, snippetID + ".png");
      if (file.exists() && file.length() != 0)
        try {
          return ImageIO.read(file);
        } catch (Throwable e) {
          e.printStackTrace();
          // fall back to loading from sourceforge
        }
    }
  
    String imageURL = snippetImageURL_http(snippetID);
    print("Loading image: " + imageURL);
    BufferedImage image = imageIO_readURL(imageURL);
  
    if (loadBufferedImage_useImageCache) {
      File tempFile = new File(dir, snippetID + ".tmp." + System.currentTimeMillis());
      ImageIO.write(image, "png", tempFile);
      tempFile.renameTo(new File(dir, snippetID + ".png"));
      //Log.info("Cached image.");
    }
  
    //Log.info("Loaded image.");
    return image;
  } else
    return loadBufferedImage(new File(snippetIDOrURLOrFile));
} catch (Exception __e) { throw rethrow(__e); } }

static BufferedImage loadBufferedImage(File file) {
  return loadBufferedImageFile(file);
}


static <A> void listThreadLocalAdd(ThreadLocal<List<A>> tl, A a) {
  List<A> l = tl.get();
  if (l == null) tl.set(l = new ArrayList());
  l.add(a);
}


static <A> A listThreadLocalPopLast(ThreadLocal<List<A>> tl) {
  List<A> l = tl.get();
  if (l == null) return null;
  A a = popLast(l);
  if (empty(l)) tl.set(null);
  return a;
}


static <A> ArrayList<A> cloneListSynchronizingOn(Collection<A> l, Object mutex) {
  if (l == null) return new ArrayList();
  synchronized(mutex) {
    return new ArrayList<A>(l);
  }
}


static Random defaultRandomGenerator() {
  return ThreadLocalRandom.current();
}


static <A> A[] makeArray(Class<A> type, int n) {
  return (A[]) Array.newInstance(type, n);
}


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



static byte[] hexToBytes(String s) {
  if (odd(l(s))) throw fail("Hex string has odd length: " + quote(shorten(10, s)));
  int n = l(s) / 2;
  byte[] bytes = new byte[n];
  for (int i = 0; i < n; i++) {
    int a = parseHexChar(s.charAt(i*2));
    int b = parseHexChar(s.charAt(i*2+1));
    if (a < 0 || b < 0)
      throw fail("Bad hex byte: " + quote(substring(s, i*2, i*2+2)) + " at " + i*2 + "/" + l(s));
    bytes[i] = (byte) ((a << 4) | b);
  }
  return bytes;
}


static byte[] loadBinaryFilePart(File file, long start, long end) { try {
  RandomAccessFile raf = new RandomAccessFile(file, "r");
  int n = toInt(min(raf.length(), end-start));
  byte[] buffer = new byte[n];
  try {
    raf.seek(start);
    raf.readFully(buffer, 0, n);
    return buffer;
  } finally {
    raf.close();
  }
} catch (Exception __e) { throw rethrow(__e); } }


static boolean isURL(String s) {
  return startsWithOneOf(s, "http://", "https://", "file:");
}


static File imageSnippetCacheFile(String snippetID) {
  File dir = imageSnippetsCacheDir();
  
  if (!loadBufferedImage_useImageCache) return null;
  
  return new File(dir, parseSnippetID(snippetID) + ".png");
}


static String snippetImageURL_noHttps(String snippetID) {
  return snippetImageURL_noHttps(snippetID, "png");
}

static String snippetImageURL_noHttps(String snippetID, String contentType) {
  return snippetImageURL(snippetID, contentType)
    .replace("https://www.botcompany.de:8443/", "http://www.botcompany.de:8080/")
    .replace("https://botcompany.de/", "http://botcompany.de/");
}


static ThreadLocal<Map<String, List<String>>> loadBinaryPage_responseHeaders = new ThreadLocal();
static ThreadLocal<Map<String, String>> loadBinaryPage_extraHeaders = new ThreadLocal();

static byte[] loadBinaryPage(String url) { try {
  print("Loading " + url);
  return loadBinaryPage(loadPage_openConnection(new URL(url)));
} catch (Exception __e) { throw rethrow(__e); } }

static byte[] loadBinaryPage(URLConnection con) { try {
  Map<String, String> extraHeaders = getAndClearThreadLocal(loadBinaryPage_extraHeaders);
  setHeaders(con);
  for (String key : keys(extraHeaders))
    con.setRequestProperty(key, extraHeaders.get(key));
  return loadBinaryPage_noHeaders(con);
} catch (Exception __e) { throw rethrow(__e); } }

static byte[] loadBinaryPage_noHeaders(URLConnection con) { try {
  ByteArrayOutputStream buf = new ByteArrayOutputStream();
  InputStream inputStream = con.getInputStream();
  loadBinaryPage_responseHeaders.set(con.getHeaderFields());
  long len = 0;
  try { len = con.getContentLength/*Long*/(); } catch (Throwable e) { printStackTrace(e); }
int n = 0;
  while (true) {
    int ch = inputStream.read();
    if (ch < 0)
      break;
    buf.write(ch);
    if (++n % 100000 == 0)
      println("  " + n + (len != 0 ? "/" + len : "") + " bytes loaded.");
  }
  inputStream.close();
  return buf.toByteArray();
} catch (Exception __e) { throw rethrow(__e); } }



/** writes safely (to temp file, then rename) */
public static byte[] saveBinaryFile(String fileName, byte[] contents) { try {
  File file = new File(fileName);
  File parentFile = file.getParentFile();
  if (parentFile != null)
    parentFile.mkdirs();
  String tempFileName = fileName + "_temp";
  FileOutputStream fileOutputStream = newFileOutputStream(tempFileName);
  fileOutputStream.write(contents);
  fileOutputStream.close();
  if (file.exists() && !file.delete())
    throw new IOException("Can't delete " + fileName);

  if (!new File(tempFileName).renameTo(file))
    throw new IOException("Can't rename " + tempFileName + " to " + fileName);
    
  
  vmBus_send("wroteFile", file);
  
  return contents;
} catch (Exception __e) { throw rethrow(__e); } }

static byte[] saveBinaryFile(File fileName, byte[] contents) {
  return saveBinaryFile(fileName.getPath(), contents);
}


static String dataSnippetLink(String snippetID) {
  long id = parseSnippetID(snippetID);
  if (id >= 1100000 && id < 1200000)
    return imageServerURL() + id;
  if (id >= 1200000 && id < 1300000) { // Woody files, actually
    String pw = muricaPassword();
    if (empty(pw)) throw fail("Please set 'murica password by running #1008829");
    return "https://botcompany.de/files/" + id + "?_pass=" + pw; // XXX, although it typically gets hidden when printing
  }
  return fileServerURL() + "/" + id /*+ "?_pass=" + muricaPassword()*/;
}


static <B, A extends B> A addAndReturn(Collection<B> c, A a) {
  if (c != null) c.add(a);
  return a;
}


static void loadBinaryPageToFile(String url, File file) { try {
  print("Loading " + url);
  loadBinaryPageToFile(openConnection(new URL(url)), file);
} catch (Exception __e) { throw rethrow(__e); } }

static void loadBinaryPageToFile(URLConnection con, File file) { try {
  setHeaders(con);
  loadBinaryPageToFile_noHeaders(con, file);
} catch (Exception __e) { throw rethrow(__e); } }

static void loadBinaryPageToFile_noHeaders(URLConnection con, File file) { try {
  File ftemp = new File(f2s(file) + "_temp");
  FileOutputStream buf = newFileOutputStream(mkdirsFor(ftemp));
  try {
    InputStream inputStream = con.getInputStream();
    long len = 0;
    try { len = con.getContentLength/*Long*/(); } catch (Throwable e) { printStackTrace(e); }
    String pat = "  {*}" + (len != 0 ? "/" + len : "") + " bytes loaded.";
    copyStreamWithPrints(inputStream, buf, pat);
    inputStream.close();
    buf.close();
    file.delete();
    renameFile_assertTrue(ftemp, file);
  } finally {
    if (buf != null) buf.close();
  }
} catch (Exception __e) { throw rethrow(__e); } }



static List<String> allToString(Iterable c) {
  List<String> l = new ArrayList();
  for (Object o : unnull(c)) l.add(str(o));
  return l;
}

static List<String> allToString(Object[] c) {
  List<String> l = new ArrayList();
  for (Object o : unnull(c)) l.add(str(o));
  return l;
}


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

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


static Rect screenBounds_safe(int iScreen) {
  return screenBounds(min(iScreen, screenCount()-1));
}


static  IF0<Integer> preferredScreen;
static int preferredScreen() { return preferredScreen != null ? preferredScreen.get() : preferredScreen_base(); }
final static int preferredScreen_fallback(IF0<Integer> _f) { return _f != null ? _f.get() : preferredScreen_base(); }
static int preferredScreen_base() {
  return 0;
}


static boolean headless() {
  return isHeadless();
}


static Throwable innerException2(Throwable e) {
  if (e == null) return null;
  while (empty(e.getMessage()) && e.getCause() != null)
    e = e.getCause();
  return e;
}


static String getSelectedItem(JList l) {
  return (String) l.getSelectedValue();
}

static String getSelectedItem(JComboBox cb) {
  return strOrNull(cb.getSelectedItem());
}


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 char charAt(String s, int i) {
  return s != null && i >= 0 && i < s.length() ? s.charAt(i) : '\0';
}


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 ThreadLocal<Boolean> assertVerbose_value = new ThreadLocal();

static void assertVerbose(boolean b) {
  assertVerbose_value.set(b);
}

static boolean assertVerbose() { return isTrue(assertVerbose_value.get()); }


static <A> A assertEqualsVerbose(Object x, A y) {
  assertEqualsVerbose((String) null, x, y);
  return y;
}

// x = expected, y = actual
static <A> A assertEqualsVerbose(String msg, Object x, A y) {
  if (!eq(x, y)) {
    

    throw fail((nempty(msg) ? msg + ": " : "") + "expected: "+ x + ", got: " + y);
  } else
    print("OK" + (empty(msg) ? "" : " " + msg) + ": " + /*sfu*/(x));
  return y;
}




static String nullIfEmpty(String s) {
  return isEmpty(s) ? null : s;
}

static <A, B> Map<A, B> nullIfEmpty(Map<A, B> map) {
  return isEmpty(map) ? null : map;
}

static <A> List<A> nullIfEmpty(List<A> l) {
  return isEmpty(l) ? null : l;
}


static Class actualMC() {
  return or((Class) realMC(), mc());
}


static boolean isAnonymousClassName(String s) {
  for (int i = 0; i < l(s); i++)
    if (s.charAt(i) == '$' && Character.isDigit(s.charAt(i+1)))
      return true;
  return false;
}


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 boolean isBoxedType(Class type) {
  return type == Boolean.class
    || type == Integer.class
    || type == Long.class
    || type == Float.class
    || type == Short.class
    || type == Character.class
    || type == Byte.class
    || type == Double.class;
}


static boolean isArrayType(Class type) {
  return type != null && type.isArray();
}


static boolean hasThisDollarFields(Object o) {
  Matches m = new Matches();
  for (var f : allFieldObjects_dontMakeAccessible(o))
    if (startsWith(f.getName(), "this$", m) && isInteger(m.rest()))
      return true;
  return false;
}


static boolean hasSingleArgumentConstructor(Class c) {
  if (c != null)
    for (Constructor m : c.getDeclaredConstructors())
      if (l(m.getParameterTypes()) == 1)
        return true;
  return false;
}


static Constructor getDefaultConstructor(Class c) {
  if (c != null)
    for (Constructor m : c.getDeclaredConstructors())
      if (empty(m.getParameterTypes()))
        return m;
  return null;
}


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


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 byte[] boolArrayToBytes(boolean[] a) {
  byte[] b = new byte[(l(a)+7)/8];
  for (int i = 0; i < l(a); i++)
    if (a[i])
      b[i/8] |= 1 << (i & 7);
  return b;
}


static <A, B, C> List<Pair<A, C>> mapPairB(final Object f, Iterable<Pair<A, B>> l) {
  return map(l, new F1<Pair<A, B>, Pair<A, C>>() { public Pair<A, C> get(Pair<A, B> p) { try { 
    return p == null ? null : pair(p.a, (C) callF(f, p.b));
   } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "p == null ? null : pair(p.a, (C) callF(f, p.b))"; }});
}

static <A, B, C> List<Pair<A, C>> mapPairB(final F1<B, C> f, Iterable<Pair<A, B>> l) {
  return mapPairB((Object) f, l);
}

static <A, B, C> List<Pair<A, C>> mapPairB(final IF1<B, C> f, Iterable<Pair<A, B>> l) {
  return mapPairB((Object) f, l);
}

static <A, B, C> List<Pair<A, C>> mapPairB(Iterable<Pair<A, B>> l, IF1<B, C> f) {
  return mapPairB((Object) f, l);
}

static <A, B, C> Pair<A, C> mapPairB(IF1<B, C> f, Pair<A, B> p) {
  return pairMapB(f, p);
}

static <A, B, C> Pair<A, C> mapPairB(Pair<A, B> p, IF1<B, C> f) {
  return pairMapB(f, p);
}


static <A, B> Map<B, A> mapToKey(Iterable<A> l, IF1<A, B> f) {
  return mapToKeys(l, f);
}



static <A, B> Map<B, A> mapToKey(IF1<A, B> f, Iterable<A> l) {
  return mapToKeys(f, l);
}


static Map<Class, List<String>> getFieldOrder_cache = weakMap();

static List<String> getFieldOrder(Object o) {
  return getFieldOrder(_getClass(o));
}

static List<String> getFieldOrder(Class c) {
  if (c == null) return null;
  return getOrCreate(getFieldOrder_cache, c,
    () -> splitAtSpace(toStringOpt(getOpt(c, "_fieldOrder"))));
}


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 boolean isDigit(char c) {
  return Character.isDigit(c);
}


static Comparator<String> caseInsensitiveComparator() {
  
  
  return betterCIComparator();
  
}


static TreeSet<String> toCaseInsensitiveSet_treeSet(Iterable<String> c) {
  if (isCISet(c)) return (TreeSet) c;
  TreeSet<String> set = caseInsensitiveSet_treeSet();
  addAll(set, c);
  return set;
}

static TreeSet<String> toCaseInsensitiveSet_treeSet(String... x) {
  TreeSet<String> set = caseInsensitiveSet_treeSet();
  addAll(set, x);
  return set;
}




static BufferedImage imageIO_readURL(String url) { try {
  return ImageIO.read(new URL(url));
} catch (Exception __e) { throw rethrow(__e); } }



static File imageSnippetsCacheDir() {
  return javaxCachesDir("Image-Snippets");
}


static String snippetImageURL_http(String snippetID) {
  return snippetImageURL_http(snippetID, "png");
}

static String snippetImageURL_http(String snippetID, String contentType) {
  return replacePrefix("https://", "http://", snippetImageURL(snippetID, contentType)).replace(":8443", ":8080");
}


static BufferedImage loadBufferedImageFile(File file) { try {
  return isFile(file) ? ImageIO.read(file) : null;
} catch (Exception __e) { throw rethrow(__e); } }


static int parseHexChar(char c) {
  if (c >= '0' && c <= '9') return charDiff(c, '0');
  if (c >= 'a' && c <= 'f') return charDiff(c, 'a')+10;
  if (c >= 'A' && c <= 'F') return charDiff(c, 'A')+10;
  return -1;
}


static String snippetImageURL(long snippetID) {
  return snippetImageURL(fsI(snippetID));
}

static String snippetImageURL(String snippetID) {
  return snippetImageURL(snippetID, "png");
}

static String snippetImageURL(String snippetID, String contentType) {
  if (snippetID == null || isURL(snippetID)) return snippetID;
  long id = parseSnippetID(snippetID);
  String url;
  if (isImageServerSnippet(id))
    url = imageServerLink(id);
  else
    //url = "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_" + id + "&contentType=image/" + contentType;
    url = "https://botcompany.de/img/" + id;
  return url;
}


static <A> A println(A a) {
  return print(a);
}


static String imageServerURL() {
  return or2(trim(loadTextFile(javaxDataDir("image-server-url.txt"))), "http://botcompany.de/images/raw/");
}


static volatile boolean muricaPassword_pretendNotAuthed = false;

static String muricaPassword() {
  if (muricaPassword_pretendNotAuthed) return null;
  return trim(loadTextFile(muricaPasswordFile()));
}


static String fileServerURL() {
  return "https://botcompany.de/files";
}


public static File mkdirsFor(File file) {
  return mkdirsForFile(file);
}



static void copyStreamWithPrints(InputStream in, OutputStream out, String pat) { try {
  byte[] buf = new byte[65536];
  int total = 0;
  while (true) {
    int n = in.read(buf);
    if (n <= 0) return;
    out.write(buf, 0, n);
    if ((total+n)/100000 > total/100000)
      print(pat.replace("{*}", str(roundDownTo(100000, total))));
    total += n;
  }
} catch (Exception __e) { throw rethrow(__e); } }


static File renameFile_assertTrue(File a, File b) { try {
  if (a.equals(b)) return b; // no rename necessary
  if (!a.exists()) throw fail("Source file not found: " + f2s(a));
  if (b.exists()) throw fail("Target file exists: " + f2s(b));
  mkdirsForFile(b);
  
  
  if (!a.renameTo(b))
    throw fail("Can't rename " + f2s(a) + " to " + f2s(b));
  
  return b;
} catch (Exception __e) { throw rethrow(__e); } }


static Rect screenBounds(GraphicsDevice screen) {
  return screen == null ? null : toRect(screen.getDefaultConfiguration().getBounds());
}

static Rect screenBounds(int iScreen) {
  return screenBounds(get(screenDevices(), iScreen));
}


static int screenCount() {
  return l(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices());
}


static String strOrNull(Object o) {
  return o == null ? null : str(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 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 String appendColonIfNempty(String s) {
  return empty(s) ? "" : s + ": ";
}


static boolean isEmpty(Collection c) {
  return c == null || c.isEmpty();
}

static boolean isEmpty(CharSequence s) {
  return s == null || s.length() == 0;
}

static boolean isEmpty(Object[] a) { return a == null || a.length == 0; }
static boolean isEmpty(byte[] a) { return a == null || a.length == 0; }

static boolean isEmpty(Map map) {
  return map == null || map.isEmpty();
}






static Object realMC() {
  return getThreadLocal(realMC_tl());
}


static List<Field> allFieldObjects_dontMakeAccessible(Object o) {
  List<Field> fields = new ArrayList();
  Class _c = _getClass(o);
  do {
    addAll(fields, _c.getDeclaredFields());
    _c = _c.getSuperclass();
  } while (_c != null);
  return fields;
}


static Pair pairMapB(Object f, Pair p) {
  return p == null ? null : pair(p.a, callF(f, p.b));
}

static <A, B, C> Pair<A, C> pairMapB(IF1<B, C> f, Pair<A, B> p) {
  return p == null ? null : pair(p.a, f.get(p.b));
}

static Pair pairMapB(Pair p, Object f) {
  return pairMap(f, p);
}



static <A, B> Map<B, A> mapToKeys(Iterable<A> l, IF1<A, B> f) {
  if (l == null) return null;
  HashMap<B, A> map = new HashMap();
  for (A a : l)
    map.put(f.get(a), a);
  return map;
}

static <A, B> Map<B, A> mapToKeys(IF1<A, B> f, A[] l) { return mapToKeys(f, asList(l)); }
static <A, B> Map<B, A> mapToKeys(IF1<A, B> f, Iterable<A> l) {
  return mapToKeys(l, f);
}


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


// allows null keys but not null values

static <A, B> B getOrCreate(Map<A, B> map, A key, Class<? extends B> c) { try {
  B b = map.get(key);
  if (b == null)
    map.put(key, b = c.newInstance());
  return b;
} catch (Exception __e) { throw rethrow(__e); } }

// f : func -> B
static <A, B> B getOrCreate(Map<A, B> map, A key, Object f) { try {
  B b = map.get(key);
  if (b == null)
    map.put(key, b = (B) callF(f));
  return b;
} catch (Exception __e) { throw rethrow(__e); } }

static <A, B> B getOrCreate(IF0<B> f, Map<A, B> map, A key) {
  return getOrCreate(map, key, f);
}

static <A, B> B getOrCreate(Map<A, B> map, A key, IF0<B> f) {
  B b = map.get(key);
  if (b == null)
    map.put(key, b = f.get());
  return b;
}

static <A, B> B getOrCreate(Class<? extends B> c, Map<A, B> map, A key) {
  return getOrCreate(map, key, c);
}


static String toStringOpt(Object o) {
  return o instanceof String ? ((String) o) : null;
}


static betterCIComparator_C betterCIComparator_instance;

static betterCIComparator_C betterCIComparator() {
  if (betterCIComparator_instance == null)
    betterCIComparator_instance = new betterCIComparator_C();
  return betterCIComparator_instance;
}

final static class betterCIComparator_C implements Comparator<String> {
  public int compare(String s1, String s2) {
    if (s1 == null) return s2 == null ? 0 : -1;
    if (s2 == null) return 1;
  
    int n1 = s1.length();
    int n2 = s2.length();
    int min = Math.min(n1, n2);
    for (int i = 0; i < min; i++) {
        char c1 = s1.charAt(i);
        char c2 = s2.charAt(i);
        if (c1 != c2) {
            c1 = Character.toUpperCase(c1);
            c2 = Character.toUpperCase(c2);
            if (c1 != c2) {
                c1 = Character.toLowerCase(c1);
                c2 = Character.toLowerCase(c2);
                if (c1 != c2) {
                    // No overflow because of numeric promotion
                    return c1 - c2;
                }
            }
        }
    }
    return n1 - n2;
  }
}


static boolean isCISet(Iterable<String> l) {
  return l instanceof TreeSet && ((TreeSet) l).comparator() == caseInsensitiveComparator();
}




static String replacePrefix(String prefix, String replacement, String s) {
  if (!startsWith(s, prefix)) return s;
  return replacement + substring(s, l(prefix));
}


static boolean isFile(File f) {
  return f != null && f.isFile();
}

static boolean isFile(String path) {
  return isFile(newFile(path));
}


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

static int charDiff(String a, char b) {
  return charDiff(stringToChar(a), b);
}


static String imageServerLink(String md5OrID) {
  if (possibleMD5(md5OrID))
    return "https://botcompany.de/images/md5/" + md5OrID;
  return imageServerLink(parseSnippetID(md5OrID));
}

static String imageServerLink(long id) {
  return "https://botcompany.de/images/" + id;
}


static String or2(String a, String b) {
  return nempty(a) ? a : b;
}

static String or2(String a, String b, String c) {
  return or2(or2(a, b), c);
}


static File muricaPasswordFile() {
  return new File(javaxSecretDir(), "murica/muricaPasswordFile");
}


// TODO: optimize to x-(x%n) in case that's the same thing
// (or x-mod(x,n)?)
static int roundDownTo(int n, int x) {
  return x/n*n;
}

static long roundDownTo(long n, long x) {
  return x/n*n;
}




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 List<GraphicsDevice> screenDevices() {
  return asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices());
}


static ThreadLocal realMC_tl_tl = new ThreadLocal();

static ThreadLocal realMC_tl() {
  return realMC_tl_tl;
}


static Pair pairMap(Object f, Pair p) {
  return p == null ? null : pair(callF(f, p.a), callF(f, p.b));
}

static <A> Pair<A, A> pairMap(IF1<A, A> f, Pair<A, A> p) {
  return p == null ? null : pair(callF(f, p.a), callF(f, p.b));
}

static Pair pairMap(Pair p, Object f) {
  return pairMap(f, p);
}





static char stringToChar(String s) {
  if (l(s) != 1) throw fail("bad stringToChar: " + s);
  return firstChar(s);
}


static boolean possibleMD5(String s) { return isMD5(s); }




static char firstChar(String s) {
  return s.charAt(0);
}


static boolean isMD5(String s) {
  return l(s) == 32 && isLowerHexString(s);
}




static boolean isLowerHexString(String s) {
  for (int i = 0; i < l(s); i++) {
    char c = s.charAt(i);
    if (c >= '0' && c <= '9' || c >= 'a' && c <= 'f') {
      // ok
    } else
      return false;
  }
  return true;
}




// 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;
    while (_c != null) {
      for (Method m : _c.getDeclaredMethods())
        if (!isAbstract(m) && !reflection_isForbiddenMethod(m))
          multiMapPut(cache, m.getName(), makeAccessible(m));
      _c = _c.getSuperclass();
    }
    
    // add default methods - this might lead to a duplication
    // because the overridden method is also added, but it's not
    // a problem except for minimal performance loss.
    for (Class intf : allInterfacesImplementedBy(c))
      for (Method m : intf.getDeclaredMethods())
        if (m.isDefault() && !reflection_isForbiddenMethod(m))
          multiMapPut(cache, m.getName(), makeAccessible(m));

    
  }
  
  // Returns only matching methods
  Method findMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
    
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
  
  Method findStaticMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (isStaticMethod(me) && call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
}
static abstract class VF1<A> implements IVF1<A> {
  public abstract void get(A a);
}
static class Matches {
  String[] m;
  
  Matches() {}
  Matches(String... m) {
  this.m = m;}
  
  String get(int i) { return i < m.length ? m[i] : null; }
  String unq(int i) { return unquote(get(i)); }
  
  String tlc(int i) { return unq(i).toLowerCase(); }
  boolean bool(int i) { return "true".equals(unq(i)); }
  String rest() { return m[m.length-1]; } // for matchStart
  int psi(int i) { return Integer.parseInt(unq(i)); }
  
  public String toString() { return "Matches(" + joinWithComma(quoteAll(asList(m))) + ")"; }
  
  public int hashCode() { return _hashCode(toList(m)); }
  public boolean equals(Object o) { return o instanceof Matches && arraysEqual(m, ((Matches) o).m); }
}

// 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 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 ITokCondition {
  boolean get(List<String> tok, int i); // i = N Index
}
static abstract class TokCondition implements ITokCondition {
  public abstract boolean get(List<String> tok, int i); // i = N Index
}

static class BlockDiff {
  public CopyBlock asCopyBlock() { return null; }
  public NewBlock  asNewBlock () { return null; }
}

static class CopyBlock extends BlockDiff {
  int firstLine, lines;

  CopyBlock(int firstLine, int lines) {
    this.firstLine = firstLine;
    this.lines = lines;
  }

  public CopyBlock asCopyBlock() { return this; }
  public int getFirstLine() { return firstLine; }
  public int getLines() { return lines; }
}

static class NewBlock extends BlockDiff {
  int originalStart;
  List<String> contents;

  NewBlock(int originalStart, List<String> contents) {
    this.originalStart = originalStart;
    this.contents = contents;
  }

  public NewBlock  asNewBlock () { return this; }

  public int getOriginalStart() {
    return originalStart;
  }

  public List<String> getContents() {
    return contents;
  }
}

static class ExplodedLine {
  int type;
  String left, right;
  int leftIndex, rightIndex;

  ExplodedLine(int type, String left, String right, int leftIndex, int rightIndex) {
    this.type = type;
    this.left = left;
    this.right = right;
    this.leftIndex = leftIndex;
    this.rightIndex = rightIndex;
  }
  
  public int getType() {
    return type;
  }

  public String getLeft() {
    return left;
  }

  public String getRight() {
    return right;
  }

  public int getLeftIndex() {
    return leftIndex;
  }

  public int getRightIndex() {
    return rightIndex;
  }
}

static class BlockDiffer {
  public static final int IDENTICAL = 0;
  public static final int DIFFERENT = 1;
  public static final int LEFT_ONLY = 2;
  public static final int RIGHT_ONLY = 3;

  private static void printChange(EGDiff.change change) {
    if (change != null) {
      System.out.println("line0="+change.line0+", line1="+change.line1
        +", inserted="+change.inserted+", deleted="+change.deleted);
      printChange(change.link);
    }
  }


  /** Generates the text content of a Unified-format context diff between 2 files
   *  (NB the 'files-changed' header must be added separately).
   */
  public static List<String> generateUniDiff(List<String> fileA, List<String> fileB, int contextSize) {
    EGDiff diff = new EGDiff(fileA.toArray(), fileB.toArray());
    EGDiff.change change = diff.diff_2(false);

    if (change != null) {
      int inserted, deleted;
      List<String> hunkLines = new ArrayList<String>();
      int cumulExtraLinesBwrtA = 0;

      // Each hunk is generated with a header
      do {
        int line0 = change.line0, line1 = change.line1;
        int changeStart = ((line1 < line0) ? line1 : line0);
        int contextStart = ((changeStart > contextSize) ? changeStart - contextSize : 0);
        int headerPosn = hunkLines.size();

        // Provide the first lines of context
        for (int i = contextStart; i < changeStart; i++)
          //System.out.println(" " + fileA.get(i));
          hunkLines.add(" " + fileA.get(i));

        boolean hunkFinish = false;
        // Step through each change giving the change lines and following context
        do {
          inserted = change.inserted;
          deleted = change.deleted;
          line0 = change.line0;
          line1 = change.line1;

          if (line1 < line0) // An insert comes earlier
            while (inserted-- > 0)
              hunkLines.add("+" + fileB.get(line1++));
          while (deleted-- > 0)
            hunkLines.add("-" + fileA.get(line0++));
          while (inserted-- > 0)
            hunkLines.add("+" + fileB.get(line1++));

          // Lines following are trailing context, identical in fileA and fileB
          // The next change may overlap the context, so check and if so, form one hunk
          EGDiff.change nextChange = change.link;
          int nextChangeStart = fileA.size();
          if (nextChange != null)
            nextChangeStart = ((nextChange.line1 < nextChange.line0) ? nextChange.line1 : nextChange.line0);

          if (nextChangeStart - line0 > contextSize * 2) { // A separate hunk
            nextChangeStart = line0 + contextSize;
            hunkFinish = true;
          }
          if (nextChangeStart > fileA.size())
            nextChangeStart = fileA.size(); // Limit to file size
          while (line0 < nextChangeStart) {
            hunkLines.add(" " + fileA.get(line0++));
            line1++; // Keep in sync with trailing context
          }

          change = change.link;
        } while (!hunkFinish && change != null);

        int hunkStartB = contextStart + cumulExtraLinesBwrtA;
        int hunkTotA = line0 - contextStart;
        int hunkTotB = line1 - hunkStartB;

        hunkLines.add(headerPosn, "@@ -" + (contextStart + 1) + ',' + hunkTotA +
                        " +" + (hunkStartB + 1) + ',' + hunkTotB + " @@");
        cumulExtraLinesBwrtA += hunkTotB - hunkTotA;

      } while (change != null);

      return hunkLines;
    }
    return null;
  }

/* For testing:
  private static void printUniDiff(List<String> fileA, List<String> fileB, int contextSize) {
    List<String> uniDiff = generateUniDiff(fileA, fileB, contextSize);

    if (uniDiff != null)
      for (int j = 0; j < uniDiff.size(); j++)
        System.out.println(uniDiff.get(j));
  }
 */


  public static List<BlockDiff> diffLines(List<String> lines, List<String> reference) {
    List<BlockDiff> diffs = new ArrayList<BlockDiff>();

    EGDiff diff = new EGDiff(reference.toArray(), lines.toArray());
    EGDiff.change change = diff.diff_2(false);
    //printChange(change);
    //printUniDiff(reference, lines, 3);

    int l0 = 0, l1 = 0;
    while (change != null) {
      if (change.line0 > l0 && change.line1 > l1)
        diffs.add(new CopyBlock(l0, change.line0-l0));

      if (change.inserted != 0)
        diffs.add(new NewBlock(change.line1, lines.subList(change.line1, change.line1+change.inserted)));

      l0 = change.line0 + change.deleted;
      l1 = change.line1 + change.inserted;
      change = change.link;
    }

    if (l0 < reference.size())
      diffs.add(new CopyBlock(l0, reference.size()-l0));

    return diffs;
  }

  /** fills files with empty lines to align matching blocks
   *
   * @param file1 first file
   * @param file2 second file
   * @return an array with two lists
   */
  public static List<ExplodedLine> explode(List<String> file1, List<String> file2) {
    List<ExplodedLine> lines = new ArrayList<ExplodedLine>();
    List<BlockDiff> diffs = BlockDiffer.diffLines(file2, file1);
    int lastLineCopied = 0, rightOnlyStart = -1, rightPosition = 0;
    for (int i = 0; i < diffs.size(); i++) {
      BlockDiff diff = diffs.get(i);
      if (diff instanceof CopyBlock) {
        CopyBlock copyBlock = (CopyBlock) diff;
        if (lastLineCopied < copyBlock.getFirstLine()) {
          if (rightOnlyStart >= 0) {
            int overlap = Math.min(lines.size()-rightOnlyStart, copyBlock.getFirstLine()-lastLineCopied);
            //lines.subList(rightOnlyStart, rightOnlyStart+overlap).clear();
            convertRightOnlyToDifferent(lines, rightOnlyStart, overlap, file1, lastLineCopied);
            lastLineCopied += overlap;
          }
          addBlock(lines, LEFT_ONLY, file1, lastLineCopied, copyBlock.getFirstLine(), lastLineCopied, -1);
        }
        addBlock(lines, IDENTICAL, file1, copyBlock.getFirstLine(), copyBlock.getFirstLine()+copyBlock.getLines(),
          copyBlock.getFirstLine(), rightPosition);
        rightPosition += copyBlock.getLines();
        lastLineCopied = copyBlock.getFirstLine()+copyBlock.getLines();
        rightOnlyStart = -1;
      } else if (diff instanceof NewBlock) {
        NewBlock newBlock = (NewBlock) diff;
        /*if (nextDiff instanceof BlockDiffer.CopyBlock) {
          BlockDiffer.CopyBlock copyBlock = (BlockDiffer.CopyBlock) nextDiff;
          copyBlock.getFirstLine()-lastLineCopied*/
        rightOnlyStart = lines.size();
        addBlock(lines, RIGHT_ONLY, newBlock.getContents(), 0, newBlock.getContents().size(), -1, rightPosition);
        rightPosition += newBlock.getContents().size();
      }
    }

    if (rightOnlyStart >= 0) {
      int overlap = Math.min(lines.size()-rightOnlyStart, file1.size()-lastLineCopied);
      //lines.subList(rightOnlyStart, rightOnlyStart+overlap).clear();
      convertRightOnlyToDifferent(lines, rightOnlyStart, overlap, file1, lastLineCopied);
      lastLineCopied += overlap;
    }
    addBlock(lines, LEFT_ONLY, file1, lastLineCopied, file1.size(), lastLineCopied, -1);

    return lines;
  }

  private static void convertRightOnlyToDifferent(List<ExplodedLine> lines, int start, int numLines,
                                                  List<String> leftLines, int leftStart) {
    for (int i = 0; i < numLines; i++) {
      ExplodedLine line = lines.get(start+i);
      lines.set(start+i,
        new ExplodedLine(DIFFERENT, leftLines.get(i+leftStart), line.getRight(), i+leftStart, line.getRightIndex()));
    }
  }

  private static void addBlock(List<ExplodedLine> lines, int type, List<String> srcLines, int start, int end,
                               int leftStart, int rightStart) {
    for (int i = start; i < end; i++)
      lines.add(new ExplodedLine(type, type == RIGHT_ONLY ? "" : srcLines.get(i),
                                       type == LEFT_ONLY ? "" : srcLines.get(i),
                                       type == RIGHT_ONLY ? -1 : i - start + leftStart,
                                       type == LEFT_ONLY ? -1 : i - start + rightStart));
  }

  public static List<ExplodedLine> condense(List<ExplodedLine> lines) {
    List<ExplodedLine> result = new ArrayList<ExplodedLine>();
    for (Iterator<ExplodedLine> i = lines.iterator(); i.hasNext();) {
      ExplodedLine line = i.next();
      if (line.getType() == IDENTICAL) {
        if (result.isEmpty() || result.get(result.size()-1).getType() != IDENTICAL)
          result.add(new ExplodedLine(IDENTICAL, "[...]", "[...]", -1, -1));
      } else
        result.add(line);
    }
    return result;
  }
}
static class MultiMap<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);
  }}
  
  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);
    }
  }}

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

  Set<A> keys() { 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 int keyCount(){ return keysSize(); }
int keysSize() { synchronized(data) { return l(data); }}
  
  // full size - note: expensive operation
  final int fullSize(){ return size(); }
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); }
}
static interface IResourceLoader {
  String loadSnippet(String snippetID);
  String getTranspiled(String snippetID); // with libs
  int getSnippetType(String snippetID);
  String getSnippetTitle(String snippetID);
  File loadLibrary(String snippetID);
  
  //ifndef NoJavaXJar
  default File pathToJavaXJar() { return pathToJavaxJar_noResourceLoader(); }
  //endifndef

  // may return null, then caller compiles themselves
  default File getSnippetJar(String snippetID, String transpiledSrc) { return null; }
}
/*
 * @(#)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 proxy_InvocationHandler implements InvocationHandler {
  Object target;
  
  proxy_InvocationHandler() {}
  proxy_InvocationHandler(Object target) {
  this.target = target;}
  
  public Object invoke(Object proxy, Method method, Object[] args) {
    return call(target, method.getName(), unnull(args));
  }
}
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 Pair<A, B> implements Comparable<Pair<A, B>> {
  A a;
  B b;

  Pair() {}
  Pair(A a, B b) {
  this.b = b;
  this.a = a;}
  
  public int hashCode() {
    return hashCodeFor(a) + 2*hashCodeFor(b);
  }
  
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Pair)) return false;
    Pair t = (Pair) o;
    return eq(a, t.a) && eq(b, t.b);
  }
  
  public String toString() {
    return "<" + a + ", " + b + ">";
  }
  
  public int compareTo(Pair<A, B> p) {
    if (p == null) return 1;
    int i = ((Comparable<A>) a).compareTo(p.a);
    if (i != 0) return i;
    return ((Comparable<B>) b).compareTo(p.b);
  }
}
static interface IResourceHolder {
  <A extends AutoCloseable> A add(A a);
  Collection<AutoCloseable> takeAll();
}
final 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; }
  
  Rectangle getRectangle() {
    return new Rectangle(x, y, w, h);
  }
  
  public String toString() {
    return x + "," + y + " / " + w + "," + h;
  }
  
  int x1() { return x; }
  int y1() { return y; }
  int x2() { return x + w; }
  int y2() { return y + h; }
  
  boolean contains(Pt p) {
    return contains(p.x, p.y);
  }
  
  boolean contains(int _x, int _y) {
    return _x >= x && _y >= y && _x < x+w && _y < y+h;
  }
  
  boolean contains(Rectangle r) {
    return rectContains(this, r);
  }
  
  boolean empty() { return w <= 0 || h <= 0; }
  
  int getWidth() { return w; }
  int getHeight() { return h; }
}
static class TableFinder {
  List<String> tok; // list of tokens in HTML document
  List<String> table; // list of tokens in table
  List<List<String>> rows; // for every row, list of tokens in row
  List<List<String>> data; // for every row, for every cell, inner data
  int i;
  boolean debug = false;
  
  TableFinder() {}
  TableFinder(String html) { go(html); }
  TableFinder(String html, boolean findFirstTable) { go(html, findFirstTable); }
  
  void go(String html) { go(html, true); }
  void go(String html, boolean findFirstTable) {
    tok = htmlcoarsetok(html);
    i = 1;
    if (findFirstTable) findTable();
  }

  boolean findTable() {
    if (debug) print("Finding table.");
    for (; i < tok.size(); i += 2)
      if (isTag(tok.get(i), "table"))
        for (int j = i+2; j < tok.size(); j += 2)
          if (isTag(tok.get(j), "/table")) {
            if (debug) print("Table found!");
            table = tok.subList(i-1, j+2);
            findRows();
            i = j;
            return true;
          }
    return false;
  }
  
  void findRows() {
    List<String> tok = table;
    rows = new ArrayList<List<String>>();
    data = new ArrayList<List<String>>();
    int rowStart = 0;
    
    for (int i = 1; i < table.size(); i += 2) {
      //print(tok.get(i));
      if (isTag(tok.get(i), "tr"))
        rowStart = i;
      else if (isTag(tok.get(i), "/tr") && rowStart != 0) {
        List<String> row = table.subList(rowStart-1, i+2);
        rows.add(row);
        data.add(getData(row));
      }
    }
    
    if (debug) print(rows.size() + " row(s)");
    if (debug) print("Top left cell: " + data.get(0).get(0));
  }
  
  boolean isTag(String token, String tag) {
    return token.regionMatches(true, 0, "<" + tag + " ", 0, tag.length()+2)
      || token.regionMatches(true, 0, "<" + tag + ">", 0, tag.length()+2);
  }
  
  // called internally
  List<String> getData(List<String> row) {
    int colStart = 0;
    List<String> cols = new ArrayList();
    
    for (int i = 1; i < row.size(); i += 2) {
      String t = row.get(i);
      if (isTag(t, "td") || isTag(t, "th"))
        colStart = i;
      else if ((isTag(t, "/td") || isTag(t, "/th")) && colStart != 0)
        cols.add(join(row.subList(colStart+1, i)));
    }
    return cols;
  }
  
  // for clients
  List<String> getRow(int row) {
    return data.get(row);
  }
  
  List<List<String>> rows() { return data; }
}

static abstract class F0<A> {
  abstract A get();
}
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();
  }
}
static class Snippet {
  String id, title, md5, type, text;
  boolean isPublic = false;
  
  Snippet() {}
  Snippet(String id, String title) {
  this.title = title;
  this.id = id;}
  Snippet(String id, String title, String md5) {
  this.md5 = md5;
  this.title = title;
  this.id = id;}
  
  public String toString() { return id + " - " + title; }
  
  public boolean equals(Object o) { return stdEq2(this, o); }
public int hashCode() { return stdHash2(this); }
}
// elements are put to front when added (not when accessed)
static class MRUCache<A, B> extends LinkedHashMap<A, B> {
  int maxSize = 10;

  MRUCache() {}
  MRUCache(int maxSize) {
  this.maxSize = maxSize;}
  
  protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > maxSize;
  }
  
  Object _serialize() {
    return ll(maxSize, cloneLinkedHashMap(this));
  }
  
  static MRUCache _deserialize(List l) {
    MRUCache m = new MRUCache();
    m.maxSize = (int) first(l);
    m.putAll((LinkedHashMap) second(l));
    return m;
  }
}
static interface IF0<A> {
  A get();
}
static interface Hasher<A> {
  int hashCode(A a);
  boolean equals(A a, A b);
}
static interface IContentsIndexedList2<A> extends List<A> {
  TreeSet<HasIndex> indicesOf_treeSetOfHasIndex(A o);
}
static abstract class CloseableIterableIterator<A> extends IterableIterator<A> implements AutoCloseable {
  public void close() throws Exception {}
}
static interface IF2<A, B, C> {
  C get(A a, B b);
}

static interface IF1<A, B> {
  B get(A a);
}
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;
  }
}
static interface IVF1<A> {
  void get(A a);
}
static interface IVar<A> extends IF0<A> {
  void set(A a);
  A get();
  
  default boolean has() { return get() != null; }
  default void clear() { set(null); }
  
}
static class HasIndex implements Comparable<HasIndex> {
  int idx;
  
  HasIndex() {}
  HasIndex(int idx) {
  this.idx = idx;}
  
  public int compareTo(HasIndex h) {
    return idx-h.idx;
  }
}


/**
 * A class to compare vectors of objects.  The result of comparison
 * is a list of <code>change</code> objects which form an
 * edit script.  The objects compared are traditionally lines
 * of text from two files.  Comparison options such as "ignore
 * whitespace" are implemented by modifying the <code>equals</code>
 * and <code>hashcode</code> methods for the objects compared.
 * <p/>
 * The basic algorithm is described in: </br>
 * "An O(ND) Difference Algorithm and its Variations", Eugene Myers,
 * Algorithmica Vol. 1 No. 2, 1986, p 251.
 * <p/>
 * This class outputs different results from GNU diff 1.15 on some
 * inputs.  Our results are actually better (smaller change list, smaller
 * total size of changes), but it would be nice to know why.  Perhaps
 * there is a memory overwrite bug in GNU diff 1.15.
 *
 * @author Stuart D. Gathman, translated from GNU diff 1.15
 *         Copyright (C) 2000  Business Management Systems, Inc.
 *         <p/>
 *         This program is free software; you can redistribute it and/or modify
 *         it under the terms of the GNU General Public License as published by
 *         the Free Software Foundation; either version 1, or (at your option)
 *         any later version.
 *         <p/>
 *         This program is distributed in the hope that it will be useful,
 *         but WITHOUT ANY WARRANTY; without even the implied warranty of
 *         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *         GNU General Public License for more details.
 *         <p/>
 *         You should have received a copy of the <a href=COPYING.txt>
 *         GNU General Public License</a>
 *         along with this program; if not, write to the Free Software
 *         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

static class EGDiff {

  /**
   * Prepare to find differences between two arrays.  Each element of
   * the arrays is translated to an "equivalence number" based on
   * the result of <code>equals</code>.  The original Object arrays
   * are no longer needed for computing the differences.  They will
   * be needed again later to print the results of the comparison as
   * an edit script, if desired.
   */
  public EGDiff(Object[] a, Object[] b) {
    Hashtable h = new Hashtable(a.length + b.length);
    filevec[0] = new file_data(a, h);
    filevec[1] = new file_data(b, h);
  }

  /**
   * 1 more than the maximum equivalence value used for this or its
   * sibling file.
   */
  private int equiv_max = 1;

  /**
   * When set to true, the comparison uses a heuristic to speed it up.
   * With this heuristic, for files with a constant small density
   * of changes, the algorithm is linear in the file size.
   */
  public boolean heuristic = false;

  /**
   * When set to true, the algorithm returns a guarranteed minimal
   * set of changes.  This makes things slower, sometimes much slower.
   */
  public boolean no_discards = false;

  private int[] xvec, yvec;	/* Vectors being compared. */
  private int[] fdiag;		/* Vector, indexed by diagonal, containing
				   the X coordinate of the point furthest
				   along the given diagonal in the forward
				   search of the edit matrix. */
  private int[] bdiag;		/* Vector, indexed by diagonal, containing
				   the X coordinate of the point furthest
				   along the given diagonal in the backward
				   search of the edit matrix. */
  private int fdiagoff, bdiagoff;
  private final file_data[] filevec = new file_data[2];
  private int cost;

  /**
   * Find the midpoint of the shortest edit script for a specified
   * portion of the two files.
   * <p/>
   * We scan from the beginnings of the files, and simultaneously from the ends,
   * doing a breadth-first search through the space of edit-sequence.
   * When the two searches meet, we have found the midpoint of the shortest
   * edit sequence.
   * <p/>
   * The value returned is the number of the diagonal on which the midpoint lies.
   * The diagonal number equals the number of inserted lines minus the number
   * of deleted lines (counting only lines before the midpoint).
   * The edit cost is stored into COST; this is the total number of
   * lines inserted or deleted (counting only lines before the midpoint).
   * <p/>
   * This function assumes that the first lines of the specified portions
   * of the two files do not match, and likewise that the last lines do not
   * match.  The caller must trim matching lines from the beginning and end
   * of the portions it is going to specify.
   * <p/>
   * Note that if we return the "wrong" diagonal value, or if
   * the value of bdiag at that diagonal is "wrong",
   * the worst this can do is cause suboptimal diff output.
   * It cannot cause incorrect diff output.
   */

  private int diag(int xoff, int xlim, int yoff, int ylim) {
    final int[] fd = fdiag;	// Give the compiler a chance.
    final int[] bd = bdiag;	// Additional help for the compiler.
    final int[] xv = xvec;		// Still more help for the compiler.
    final int[] yv = yvec;		// And more and more . . .
    final int dmin = xoff - ylim;	// Minimum valid diagonal.
    final int dmax = xlim - yoff;	// Maximum valid diagonal.
    final int fmid = xoff - yoff;	// Center diagonal of top-down search.
    final int bmid = xlim - ylim;	// Center diagonal of bottom-up search.
    int fmin = fmid, fmax = fmid;	// Limits of top-down search.
    int bmin = bmid, bmax = bmid;	// Limits of bottom-up search.
    /* True if southeast corner is on an odd
				     diagonal with respect to the northwest. */
    final boolean odd = (fmid - bmid & 1) != 0;

    fd[fdiagoff + fmid] = xoff;
    bd[bdiagoff + bmid] = xlim;

    for (int c = 1; ; ++c) {
      int d;			/* Active diagonal. */
      boolean big_snake = false;

      /* Extend the top-down search by an edit step in each diagonal. */
      if (fmin > dmin)
        fd[fdiagoff + --fmin - 1] = -1;
      else
        ++fmin;
      if (fmax < dmax)
        fd[fdiagoff + ++fmax + 1] = -1;
      else
        --fmax;
      for (d = fmax; d >= fmin; d -= 2) {
        int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1];

        if (tlo >= thi)
          x = tlo + 1;
        else
          x = thi;
        oldx = x;
        y = x - d;
        while (x < xlim && y < ylim && xv[x] == yv[y]) {
          ++x;
          ++y;
        }
        if (x - oldx > 20)
          big_snake = true;
        fd[fdiagoff + d] = x;
        if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d]) {
          cost = 2 * c - 1;
          return d;
        }
      }

      /* Similar extend the bottom-up search. */
      if (bmin > dmin)
        bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE;
      else
        ++bmin;
      if (bmax < dmax)
        bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE;
      else
        --bmax;
      for (d = bmax; d >= bmin; d -= 2) {
        int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1];

        if (tlo < thi)
          x = tlo;
        else
          x = thi - 1;
        oldx = x;
        y = x - d;
        while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) {
          --x;
          --y;
        }
        if (oldx - x > 20)
          big_snake = true;
        bd[bdiagoff + d] = x;
        if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d]) {
          cost = 2 * c;
          return d;
        }
      }

      /* Heuristic: check occasionally for a diagonal that has made
         lots of progress compared with the edit distance.
         If we have any such, find the one that has made the most
         progress and return it as if it had succeeded.

         With this heuristic, for files with a constant small density
         of changes, the algorithm is linear in the file size.  */

      if (c > 200 && big_snake && heuristic) {
        int best = 0;
        int bestpos = -1;

        for (d = fmax; d >= fmin; d -= 2) {
          int dd = d - fmid;
          if ((fd[fdiagoff + d] - xoff) * 2 - dd > 12 * (c + (dd > 0 ? dd : -dd))) {
            if (fd[fdiagoff + d] * 2 - dd > best
              && fd[fdiagoff + d] - xoff > 20
              && fd[fdiagoff + d] - d - yoff > 20) {
              int k;
              int x = fd[fdiagoff + d];

              /* We have a good enough best diagonal;
                 now insist that it end with a significant snake.  */
              for (k = 1; k <= 20; k++)
                if (xvec[x - k] != yvec[x - d - k])
                  break;

              if (k == 21) {
                best = fd[fdiagoff + d] * 2 - dd;
                bestpos = d;
              }
            }
          }
        }
        if (best > 0) {
          cost = 2 * c - 1;
          return bestpos;
        }

        best = 0;
        for (d = bmax; d >= bmin; d -= 2) {
          int dd = d - bmid;
          if ((xlim - bd[bdiagoff + d]) * 2 + dd > 12 * (c + (dd > 0 ? dd : -dd))) {
            if ((xlim - bd[bdiagoff + d]) * 2 + dd > best
              && xlim - bd[bdiagoff + d] > 20
              && ylim - (bd[bdiagoff + d] - d) > 20) {
              /* We have a good enough best diagonal;
                 now insist that it end with a significant snake.  */
              int k;
              int x = bd[bdiagoff + d];

              for (k = 0; k < 20; k++)
                if (xvec[x + k] != yvec[x - d + k])
                  break;
              if (k == 20) {
                best = (xlim - bd[bdiagoff + d]) * 2 + dd;
                bestpos = d;
              }
            }
          }
        }
        if (best > 0) {
          cost = 2 * c - 1;
          return bestpos;
        }
      }
    }
  }

  /**
   * Compare in detail contiguous subsequences of the two files
   * which are known, as a whole, to match each other.
   * <p/>
   * The results are recorded in the vectors filevec[N].changed_flag, by
   * storing a 1 in the element for each line that is an insertion or deletion.
   * <p/>
   * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
   * <p/>
   * Note that XLIM, YLIM are exclusive bounds.
   * All line numbers are origin-0 and discarded lines are not counted.
   */

  private void compareseq(int xoff, int xlim, int yoff, int ylim) {
    /* Slide down the bottom initial diagonal. */
    while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) {
      ++xoff;
      ++yoff;
    }
    /* Slide up the top initial diagonal. */
    while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) {
      --xlim;
      --ylim;
    }
    
    /* Handle simple cases. */
    if (xoff == xlim)
      while (yoff < ylim)
        filevec[1].changed_flag[1 + filevec[1].realindexes[yoff++]] = true;
    else if (yoff == ylim)
      while (xoff < xlim)
        filevec[0].changed_flag[1 + filevec[0].realindexes[xoff++]] = true;
    else {
      /* Find a point of correspondence in the middle of the files.  */

      int d = diag(xoff, xlim, yoff, ylim);
      int c = cost;
      int b = bdiag[bdiagoff + d];

      if (c == 1) {
        /* This should be impossible, because it implies that
           one of the two subsequences is empty,
           and that case was handled above without calling `diag'.
           Let's verify that this is true.  */
        throw new IllegalArgumentException("Empty subsequence");
      } else {
        /* Use that point to split this problem into two subproblems.  */
        compareseq(xoff, b, yoff, b - d);
        /* This used to use f instead of b,
           but that is incorrect!
           It is not necessarily the case that diagonal d
           has a snake from b to f.  */
        compareseq(b, xlim, b - d, ylim);
      }
    }
  }

  /**
   * Discard lines from one file that have no matches in the other file.
   */

  private void discard_confusing_lines() {
    filevec[0].discard_confusing_lines(filevec[1]);
    filevec[1].discard_confusing_lines(filevec[0]);
  }

  private boolean inhibit = false;

  /**
   * Adjust inserts/deletes of blank lines to join changes
   * as much as possible.
   */

  private void shift_boundaries() {
    if (inhibit)
      return;
    filevec[0].shift_boundaries(filevec[1]);
    filevec[1].shift_boundaries(filevec[0]);
  }

  public interface ScriptBuilder {
    /**
     * Scan the tables of which lines are inserted and deleted,
     * producing an edit script.
     *
     * @param changed0 true for lines in first file which do not match 2nd
     * @param len0     number of lines in first file
     * @param changed1 true for lines in 2nd file which do not match 1st
     * @param len1     number of lines in 2nd file
     * @return a linked list of changes - or null
     */
    public change build_script(boolean[] changed0, int len0,
                               boolean[] changed1, int len1);
  }

  /**
   * Scan the tables of which lines are inserted and deleted,
   * producing an edit script in reverse order.
   */

  static class ReverseScript implements ScriptBuilder {
    public change build_script(final boolean[] changed0, int len0,
                               final boolean[] changed1, int len1) {
      change script = null;
      int i0 = 0, i1 = 0;
      while (i0 < len0 || i1 < len1) {
        if (changed0[1 + i0] || changed1[1 + i1]) {
          int line0 = i0, line1 = i1;

          /* Find # lines changed here in each file.  */
          while (changed0[1 + i0]) ++i0;
          while (changed1[1 + i1]) ++i1;

          /* Record this change.  */
          script = new change(line0, line1, i0 - line0, i1 - line1, script);
        }

        /* We have reached lines in the two files that match each other.  */
        i0++;
        i1++;
      }

      return script;
    }
  }

  static class ForwardScript implements ScriptBuilder {
    /**
     * Scan the tables of which lines are inserted and deleted,
     * producing an edit script in forward order.
     */
    public change build_script(final boolean[] changed0, int len0,
                               final boolean[] changed1, int len1) {
      change script = null;
      int i0 = len0, i1 = len1;

      while (i0 >= 0 || i1 >= 0) {
        if (changed0[i0] || changed1[i1]) {
          int line0 = i0, line1 = i1;

          /* Find # lines changed here in each file.  */
          while (changed0[i0]) --i0;
          while (changed1[i1]) --i1;

          /* Record this change.  */
          script = new change(i0, i1, line0 - i0, line1 - i1, script);
        }

        /* We have reached lines in the two files that match each other.  */
        i0--;
        i1--;
      }

      return script;
    }
  }

  /**
   * Standard ScriptBuilders.
   */
  public final static ScriptBuilder
    forwardScript = new ForwardScript(),
  reverseScript = new ReverseScript();

  /* Report the differences of two files.  DEPTH is the current directory
     depth. */
  public final change diff_2(final boolean reverse) {
    return diff(reverse ? reverseScript : forwardScript);
  }

  /**
   * Get the results of comparison as an edit script.  The script
   * is described by a list of changes.  The standard ScriptBuilder
   * implementations provide for forward and reverse edit scripts.
   * Alternate implementations could, for instance, list common elements
   * instead of differences.
   *
   * @param bld an object to build the script from change flags
   * @return the head of a list of changes
   */
  public change diff(final ScriptBuilder bld) {

    /* Some lines are obviously insertions or deletions
       because they don't match anything.  Detect them now,
       and avoid even thinking about them in the main comparison algorithm.  */

    discard_confusing_lines();

    /* Now do the main comparison algorithm, considering just the
       undiscarded lines.  */

    xvec = filevec[0].undiscarded;
    yvec = filevec[1].undiscarded;

    int diags =
      filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3;
    fdiag = new int[diags];
    fdiagoff = filevec[1].nondiscarded_lines + 1;
    bdiag = new int[diags];
    bdiagoff = filevec[1].nondiscarded_lines + 1;

    compareseq(0, filevec[0].nondiscarded_lines,
      0, filevec[1].nondiscarded_lines);
    fdiag = null;
    bdiag = null;

    /* Modify the results slightly to make them prettier
       in cases where that can validly be done.  */

    shift_boundaries();

    /* Get the results of comparison in the form of a chain
       of `struct change's -- an edit script.  */
    return bld.build_script(filevec[0].changed_flag,
      filevec[0].buffered_lines,
      filevec[1].changed_flag,
      filevec[1].buffered_lines);

  }

  /**
   * The result of comparison is an "edit script": a chain of change objects.
   * Each change represents one place where some lines are deleted
   * and some are inserted.
   * <p/>
   * LINE0 and LINE1 are the first affected lines in the two files (origin 0).
   * DELETED is the number of lines deleted here from file 0.
   * INSERTED is the number of lines inserted here in file 1.
   * <p/>
   * If DELETED is 0 then LINE0 is the number of the line before
   * which the insertion was done; vice versa for INSERTED and LINE1.
   */

  public static class change {
    /**
     * Previous or next edit command.
     */
    public change link;
    /**
     * # lines of file 1 changed here.
     */
    public final int inserted;
    /**
     * # lines of file 0 changed here.
     */
    public final int deleted;
    /**
     * Line number of 1st deleted line.
     */
    public final int line0;
    /**
     * Line number of 1st inserted line.
     */
    public final int line1;

    /**
     * Cons an additional entry onto the front of an edit script OLD.
     * LINE0 and LINE1 are the first affected lines in the two files (origin 0).
     * DELETED is the number of lines deleted here from file 0.
     * INSERTED is the number of lines inserted here in file 1.
     * <p/>
     * If DELETED is 0 then LINE0 is the number of the line before
     * which the insertion was done; vice versa for INSERTED and LINE1.
     */
    public change(int line0, int line1, int deleted, int inserted, change old) {
      this.line0 = line0;
      this.line1 = line1;
      this.inserted = inserted;
      this.deleted = deleted;
      this.link = old;
      //System.err.println(line0+","+line1+","+inserted+","+deleted);
    }
  }

  /**
   * Data on one input file being compared.
   */

  class file_data {

    /**
     * Allocate changed array for the results of comparison.
     */
    void clear() {
      /* Allocate a flag for each line of each file, saying whether that line
	 is an insertion or deletion.
	 Allocate an extra element, always zero, at each end of each vector.
       */
      changed_flag = new boolean[buffered_lines + 2];
    }

    /**
     * Return equiv_count[I] as the number of lines in this file
     * that fall in equivalence class I.
     *
     * @return the array of equivalence class counts.
     */
    int[] equivCount() {
      int[] equiv_count = new int[equiv_max];
      for (int i = 0; i < buffered_lines; ++i)
        ++equiv_count[equivs[i]];
      return equiv_count;
    }

    /**
     * Discard lines that have no matches in another file.
     * <p/>
     * A line which is discarded will not be considered by the actual
     * comparison algorithm; it will be as if that line were not in the file.
     * The file's `realindexes' table maps virtual line numbers
     * (which don't count the discarded lines) into real line numbers;
     * this is how the actual comparison algorithm produces results
     * that are comprehensible when the discarded lines are counted.
     * <p/>
     * When we discard a line, we also mark it as a deletion or insertion
     * so that it will be printed in the output.
     *
     * @param f the other file
     */
    void discard_confusing_lines(file_data f) {
      clear();
      /* Set up table of which lines are going to be discarded. */
      final byte[] discarded = discardable(f.equivCount());

      /* Don't really discard the provisional lines except when they occur
         in a run of discardables, with nonprovisionals at the beginning
         and end.  */
      filterDiscards(discarded);

      /* Actually discard the lines. */
      discard(discarded);
    }

    /**
     * Mark to be discarded each line that matches no line of another file.
     * If a line matches many lines, mark it as provisionally discardable.
     *
     * @param counts The count of each equivalence number for the other file.
     * @return 0=nondiscardable, 1=discardable or 2=provisionally discardable
     *         for each line
     */

    private byte[] discardable(final int[] counts) {
      final int end = buffered_lines;
      final byte[] discards = new byte[end];
      final int[] equivs = this.equivs;
      int many = 5;
      int tem = end / 64;

      /* Multiply MANY by approximate square root of number of lines.
	 That is the threshold for provisionally discardable lines.  */
      while ((tem = tem >> 2) > 0)
        many *= 2;

      for (int i = 0; i < end; i++) {
        int nmatch;
        if (equivs[i] == 0)
          continue;
        nmatch = counts[equivs[i]];
        if (nmatch == 0)
          discards[i] = 1;
        else if (nmatch > many)
          discards[i] = 2;
      }
      return discards;
    }

    /**
     * Don't really discard the provisional lines except when they occur
     * in a run of discardables, with nonprovisionals at the beginning
     * and end.
     */

    private void filterDiscards(final byte[] discards) {
      final int end = buffered_lines;

      for (int i = 0; i < end; i++) {
        /* Cancel provisional discards not in middle of run of discards.  */
        if (discards[i] == 2)
          discards[i] = 0;
        else if (discards[i] != 0) {
          /* We have found a nonprovisional discard.  */
          int j;
          int length;
          int provisional = 0;

          /* Find end of this run of discardable lines.
             Count how many are provisionally discardable.  */
          for (j = i; j < end; j++) {
            if (discards[j] == 0)
              break;
            if (discards[j] == 2)
              ++provisional;
          }

          /* Cancel provisional discards at end, and shrink the run.  */
          while (j > i && discards[j - 1] == 2) {
            discards[--j] = 0;
            --provisional;
          }

          /* Now we have the length of a run of discardable lines
             whose first and last are not provisional.  */
          length = j - i;

          /* If 1/4 of the lines in the run are provisional,
             cancel discarding of all provisional lines in the run.  */
          if (provisional * 4 > length) {
            while (j > i)
              if (discards[--j] == 2)
                discards[j] = 0;
          } else {
            int consec;
            int minimum = 1;
            int tem = length / 4;

            /* MINIMUM is approximate square root of LENGTH/4.
               A subrun of two or more provisionals can stand
               when LENGTH is at least 16.
               A subrun of 4 or more can stand when LENGTH >= 64.  */
            while ((tem = tem >> 2) > 0)
              minimum *= 2;
            minimum++;

            /* Cancel any subrun of MINIMUM or more provisionals
               within the larger run.  */
            for (j = 0, consec = 0; j < length; j++)
              if (discards[i + j] != 2)
                consec = 0;
              else if (minimum == ++consec)
              /* Back up to start of subrun, to cancel it all.  */
                j -= consec;
              else if (minimum < consec)
                discards[i + j] = 0;

            /* Scan from beginning of run
               until we find 3 or more nonprovisionals in a row
               or until the first nonprovisional at least 8 lines in.
               Until that point, cancel any provisionals.  */
            for (j = 0, consec = 0; j < length; j++) {
              if (j >= 8 && discards[i + j] == 1)
                break;
              if (discards[i + j] == 2) {
                consec = 0;
                discards[i + j] = 0;
              } else if (discards[i + j] == 0)
                consec = 0;
              else
                consec++;
              if (consec == 3)
                break;
            }

            /* I advances to the last line of the run.  */
            i += length - 1;

            /* Same thing, from end.  */
            for (j = 0, consec = 0; j < length; j++) {
              if (j >= 8 && discards[i - j] == 1)
                break;
              if (discards[i - j] == 2) {
                consec = 0;
                discards[i - j] = 0;
              } else if (discards[i - j] == 0)
                consec = 0;
              else
                consec++;
              if (consec == 3)
                break;
            }
          }
        }
      }
    }

    /**
     * Actually discard the lines.
     *
     * @param discards flags lines to be discarded
     */
    private void discard(final byte[] discards) {
      final int end = buffered_lines;
      int j = 0;
      for (int i = 0; i < end; ++i)
        if (no_discards || discards[i] == 0) {
          undiscarded[j] = equivs[i];
          realindexes[j++] = i;
        } else
          changed_flag[1 + i] = true;
      nondiscarded_lines = j;
    }

    file_data(Object[] data, Hashtable h) {
      buffered_lines = data.length;

      equivs = new int[buffered_lines];
      undiscarded = new int[buffered_lines];
      realindexes = new int[buffered_lines];

      for (int i = 0; i < data.length; ++i) {
        Integer ir = (Integer) h.get(data[i]);
        if (ir == null)
          h.put(data[i], new Integer(equivs[i] = equiv_max++));
        else
          equivs[i] = ir.intValue();
      }
    }

    /**
     * Adjust inserts/deletes of blank lines to join changes
     * as much as possible.
     * <p/>
     * We do something when a run of changed lines include a blank
     * line at one end and have an excluded blank line at the other.
     * We are free to choose which blank line is included.
     * `compareseq' always chooses the one at the beginning,
     * but usually it is cleaner to consider the following blank line
     * to be the "change".  The only exception is if the preceding blank line
     * would join this change to other changes.
     *
     * @param f the file being compared against
     */

    void shift_boundaries(file_data f) {
      final boolean[] changed = changed_flag;
      final boolean[] other_changed = f.changed_flag;
      int i = 0;
      int j = 0;
      int i_end = buffered_lines;
      int preceding = -1;
      int other_preceding = -1;

      for (; ;) {
        int start, end, other_start;

        /* Scan forwards to find beginning of another run of changes.
           Also keep track of the corresponding point in the other file.  */

        while (i < i_end && !changed[1 + i]) {
          while (other_changed[1 + j++])
            /* Non-corresponding lines in the other file
               will count as the preceding batch of changes.  */
            other_preceding = j;
          i++;
        }

        if (i == i_end)
          break;

        start = i;
        other_start = j;

        for (; ;) {
          /* Now find the end of this run of changes.  */

          while (i < i_end && changed[1 + i]) i++;
          end = i;

          /* If the first changed line matches the following unchanged one,
       and this run does not follow right after a previous run,
       and there are no lines deleted from the other file here,
       then classify the first changed line as unchanged
       and the following line as changed in its place.  */

          /* You might ask, how could this run follow right after another?
       Only because the previous run was shifted here.  */

          if (end != i_end
            && equivs[start] == equivs[end]
            && !other_changed[1 + j]
            && end != i_end
            && !((preceding >= 0 && start == preceding)
            || (other_preceding >= 0
            && other_start == other_preceding))) {
            changed[1 + end] = true;
            changed[1 + start++] = false;
            ++i;
            /* Since one line-that-matches is now before this run
               instead of after, we must advance in the other file
               to keep in synch.  */
            ++j;
          } else
            break;
        }

        preceding = i;
        other_preceding = j;
      }
    }

    /**
     * Number of elements (lines) in this file.
     */
    final int buffered_lines;

    /**
     * Vector, indexed by line number, containing an equivalence code for
     * each line.  It is this vector that is actually compared with that
     * of another file to generate differences.
     */
    private final int[] equivs;

    /**
     * Vector, like the previous one except that
     * the elements for discarded lines have been squeezed out.
     */
    final int[] undiscarded;

    /**
     * Vector mapping virtual line numbers (not counting discarded lines)
     * to real ones (counting those lines).  Both are origin-0.
     */
    final int[] realindexes;

    /**
     * Total number of nondiscarded lines.
     */
    int nondiscarded_lines;

    /**
     * Array, indexed by real origin-1 line number,
     * containing true for a line that is an insertion or a deletion.
     * The results of comparison are stored here.
     */
    boolean[] changed_flag;

  }
}
static interface IFieldsToList {
  Object[] _fieldsToList();
}
static class Pt implements Comparable<Pt> {
  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;
  }
}
static interface ISetter<A> {
 void set(A a);
}



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

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


static boolean reflection_isForbiddenMethod(Method m) {
  return m.getDeclaringClass() == Object.class
    && eqOneOf(m.getName(), "finalize", "clone", "registerNatives");
}


static Set<Class> allInterfacesImplementedBy(Class c) {
  if (c == null) return null;
  HashSet<Class> set = new HashSet();
  allInterfacesImplementedBy_find(c, set);
  return set;
}

static void allInterfacesImplementedBy_find(Class c, Set<Class> set) {
  if (c.isInterface() && !set.add(c)) return;
  do {
    for (Class intf : c.getInterfaces())
      allInterfacesImplementedBy_find(intf, set);
  } while ((c = c.getSuperclass()) != null);
}


static Method findMethod(Object o, String method, Object... args) {
  return findMethod_cached(o, method, args);
}

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


static Method findStaticMethod(Class c, String method, Object... args) {
  Class _c = c;
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (!m.getName().equals(method))
        continue;

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

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

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



static 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 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 <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> 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> 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> 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 File loadLibrary(String snippetID) {
  return loadBinarySnippet(snippetID);
}


// TODO: use actualUserHome()?
// (there was a problem with onLocallyInferiorJavaX() always triggering inside #1013896)

static File pathToJavaxJar() {
  
    
    IResourceLoader rl = vm_getResourceLoader();
    if (rl != null)
      return rl.pathToJavaXJar();
    
    
    return pathToJavaxJar_noResourceLoader();
  
  
}
    
static File pathToJavaxJar_noResourceLoader() { try {
  
    int x = latestInstalledJavaX();
    File xfile = new File(userHome(), ".javax/x" + Math.max(x, 30) + ".jar");
    if (!xfile.isFile()) {
      print("Saving " + f2s(xfile));
      String url = x30JarServerURL();
      byte[] data = loadBinaryPage(url);
      if (data.length < 1000000)
        throw fail("Could not load " + url);
      saveBinaryFile(xfile.getPath(), data);
    }
    return xfile;
  
  
  
} catch (Exception __e) { throw rethrow(__e); } }


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 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 int hashCodeFor(Object a) {
  return a == null ? 0 : a.hashCode();
}


static int boostHashCombine(int a, int b) {
  return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}


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 isTag(String token, String tag) {
  return token.regionMatches(true, 0, "<" + tag + " ", 0, tag.length()+2)
    || token.regionMatches(true, 0, "<" + tag + ">", 0, tag.length()+2);
}


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


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


static int stdHash2(Object a) {
  if (a == null) return 0;
  return stdHash(a, toStringArray(allFields(a)));
}


static <A, B> LinkedHashMap<A, B> cloneLinkedHashMap(Map<A, B> map) {
  return map == null ? new LinkedHashMap() : new LinkedHashMap(map);
}


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

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

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


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






static char second(String s) {
  return charAt(s, 1);
}




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 void change() {
  //mainConcepts.allChanged();
  // safe version for now cause function is sometimes included unnecessarily (e.g. by EGDiff)
  callOpt(getOptMC("mainConcepts"), "allChanged");
}


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




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


static int latestInstalledJavaX() {
  File[] files = new File(userHome(), ".javax").listFiles();
  int v = 0;
  if (files != null) for (File f : files) {
    Matcher m = regexpMatcher("x(\\d\\d\\d?)\\.jar", f.getName());
    if (m.matches())
      v = Math.max(v, Integer.parseInt(m.group(1)));
  }
  return v;
}


static String x30JarServerURL() {
  return "http://botcompany.de:8081/x30.jar";
}


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


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 Map<Class, Set<String>> allFields_cache = weakHashMap();

static Set<String> allFields(Object o) {
  if (o == null) return emptySet();
  Class _c = _getClass(o);
  Set<String> fields = allFields_cache.get(_c);
  if (fields == null)
    allFields_cache.put(_c, fields = asTreeSet(keys(getOpt_getFieldMap(o))));
  return fields;
}


static int stdHash(Object a, String... fields) {
  if (a == null) return 0;
  int hash = getClassName(a).hashCode();
  for (String field : fields)
    hash = boostHashCombine(hash, hashCode(getOpt(a, field)));
  return hash;
}


static String[] toStringArray(Collection<String> c) {
  String[] a = new String[l(c)];
  Iterator<String> it = c.iterator();
  for (int i = 0; i < l(a); i++)
    a[i] = it.next();
  return a;
}

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



static 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 Object getOptMC(String field) {
  return getOpt(mc(), field);
}


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 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 Set emptySet() {
  return new HashSet();
}


static <A> TreeSet<A> asTreeSet(Collection<A> set) {
  return set == null ? null : set instanceof TreeSet ? (TreeSet) set : new TreeSet(set);
}


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

static int hashCode(long l) {
  return Long.hashCode(l);
}


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



}