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 static x30_pkg.x30_util.DynamicObject;
import java.awt.geom.*;
import java.text.NumberFormat;

class main {

static class G22RegionThinner implements Steppable , IFieldsToList{
  IImageRegion<?> originalRegion;
  G22RegionThinner() {}
  G22RegionThinner(IImageRegion<?> originalRegion) {
  this.originalRegion = originalRegion;}
  public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + originalRegion + ")"; }public Object[] _fieldsToList() { return new Object[] {originalRegion}; }

  Rect bounds;
  
  // true = part of region
  // index is in bounds coordinates
  boolean[] pixels;
  
   final public IImageRegion getRegion(){ return region(); }
public IImageRegion region() { return region; }
 IImageRegion region = new IImageRegion() {
    public Rect bounds() { return bounds; }
  
    public boolean contains(int x, int y) {
      return containsPt(bounds, x, y) && pixels[idx(x, y)];
    }
    
    public IterableIterator<Pt> pixelIterator() {
      return iff_null(new IF0<Pt>() {
        int idx = 0;
        
        public Pt get() {
          for (; idx < pixels.length; idx++)
            if (pixels[idx])
              return idxToPt(idx++);
          return null;
        }
      });
    }
  };
  
  int idx(int x, int y) {
    return (y-bounds.y)*bounds.w+x-bounds.x;
  }
  
  Pt idxToPt(int idx) {
    return pt(bounds.x+(idx % bounds.w), bounds.y+idx/bounds.w);
  }
  
  void init() {
    if (bounds != null) return;
    bounds = originalRegion.bounds();
    pixels = new boolean[area(bounds)];
    for (Pt p : originalRegion.pixelIterator())
      pixels[idx(p.x, p.y)] = true;
  }
  
  boolean change = false;
  
  public boolean step() {
    init();
    
    change = false;

    List<PtBuffer> traces = g22_allBorderTraces_withDiagonals(region);
    
    for (var points : traces) {
      if (eq(first(points), last(points))) removeLast(points);
      
      for (int i = 0; i < l(points); i++) {
        ping();
        if (deletableBorderPoint(points, i)) {
          Pt p = points.get(i);
          pixels[idx(p.x, p.y)] = false;
          change = true;
        }
      }
      points.clear();
    }

    return change;
  }
  
  boolean deletableBorderPoint(PtBuffer points, int i) {
    List<Pt> range = cyclicSubList(points, i-1, i+2);
    Pt p = points.get(i);
        
    // check all 8 points around border point
    for (int dir = 1; dir <= 8; dir++) {
      Pt p2 = ptPlus(p, onePathDirection(dir));
      
      // If neighboring point is in region but not in current border,
      // it makes the border point deletable
      
      if (region.contains(p2.x, p2.y)
        && !range.contains(p2))
          return true;
    }

    return false;
  }
  
  IImageRegion get() { return region; }
}
static String shortClassName_dropNumberPrefix(Object o) {
  return dropNumberPrefix(shortClassName(o));
}


static boolean containsPt(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 containsPt(Rect a, int x, int y) {
  return a != null && a.contains(x, y);
}

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


// f: func -> A (stream ends when f returns null)
static <A> IterableIterator<A> iff_null(final Object f) {
  return iteratorFromFunction(f);
}


static <A> IterableIterator<A> iff_null(F0<A> f) {
  return iteratorFromFunction(f);
}



static <A> IterableIterator<A> iff_null(IF0<A> f) {
  return iteratorFromFunction(f);
}


static Pt pt(int x, int y) {
  return new Pt(x, y);
}

static Pt pt(int x) {
  return new Pt(x, x);
}


static int area(Rect r) {
  return rectArea(r);
}

static double area(DoubleRect r) {
  return r == null ? 0 : r.w*r.h;
}


static List<PtBuffer> g22_allBorderTraces_withDiagonals(IImageRegion region) {
  RegionBorder_innerPoints_withDiagonals walker = new RegionBorder_innerPoints_withDiagonals(region);
  List<PtBuffer> out = new ArrayList();
  walker.onNewTrace(hole -> out.add(new PtBuffer()));
  walker.onFoundPoint(p -> last(out).add(p));
  walker.run();
  return out;
}


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 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 byte first(ByteBuffer buf) {
  return buf.get(0);
}


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

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

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


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




static <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 byte last(byte[] a) {
  return l(a) != 0 ? a[l(a)-1] : 0;
}

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 byte last(ByteBuffer buf) {
  return buf.get(buf.size()-1);
}





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



static void removeLast(List l) {
  if (!l.isEmpty())
    l.remove(l(l)-1);
}

static void removeLast(List l, int n) {
  removeSubList(l, l(l)-n);
}

static void removeLast(int n, List l) {
  removeLast(l, n);
}


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

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

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






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













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



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












//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 <A> List<A> cyclicSubList(List<A> l, int startIndex, int endIndex) {
  if (l == null) return null;
  List<A> subList = new ArrayList();
  for (int i = startIndex; i < endIndex; i++)
    subList.add(cyclicGet(l, i));
  return subList;
}



static Pt ptPlus(Pt a, Pt b) {
  return addPts(a, b);
}


static Pt onePathDirection(int index) {
  return onePathDirections()[index];
}




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


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


// f: func -> A (stream ends when f returns null)
static <A> IterableIterator<A> iteratorFromFunction(final Object f) {
  class IFF extends IterableIterator<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 = (A) callF(f);
      done = a == null;
    }
  };
  return new IFF();
}

// optimized version for F0 argument

static <A> IterableIterator<A> iteratorFromFunction(F0<A> f) {
  return iteratorFromFunction_f0(f);
}


static <A> IterableIterator<A> iteratorFromFunction(IF0<A> f) {
  return iteratorFromFunction_if0(f);
}


static int rectArea(Rect r) {
  return r == null ? 0 : r.w*r.h;
}


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

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

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


static boolean empty(Collection c) { return c == null || c.isEmpty(); }
static boolean empty(Iterable c) { return c == null || !c.iterator().hasNext(); }
static boolean empty(CharSequence s) { return s == null || s.length() == 0; }
static boolean empty(Map map) { return map == null || map.isEmpty(); }
static boolean empty(Object[] o) { return o == null || o.length == 0; }
static boolean empty(BitSet bs) { return bs == null || bs.isEmpty(); }


static boolean empty(Object o) {
  if (o instanceof Collection) return empty((Collection) o);
  if (o instanceof String) return empty((String) o);
  if (o instanceof Map) return empty((Map) o);
  if (o instanceof Object[]) return empty((Object[]) o);
  if (o instanceof byte[]) return empty((byte[]) o);
  if (o == null) return true;
  throw fail("unknown type for 'empty': " + getType(o));
}


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

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




static boolean empty(IMultiMap mm) { return mm == null || mm.size() == 0; }


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








static boolean empty(LongBuffer b) { return b == null || b.isEmpty(); }



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





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



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


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 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 <A, B> Map<A, B> newWeakHashMap() {
  return _registerWeakMap(synchroMap(new WeakHashMap()));
}


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


// TODO: test if android complains about this
static boolean isAWTThread() {
  if (isAndroid()) return false;
  if (isHeadless()) return false;
  return isAWTThread_awt();
}

static boolean isAWTThread_awt() {
  return SwingUtilities.isEventDispatchThread();
}


static boolean isTrue(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  if (o == null) return false;
  if (o instanceof ThreadLocal) // TODO: remove this
    return isTrue(((ThreadLocal) o).get());
  throw fail(getClassName(o));
}

static boolean isTrue(Boolean b) {
  return b != null && b.booleanValue();
}


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


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


static 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 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 <A> A cyclicGet(List<A> l, int i) {
  return empty(l) ? null : get(l, mod(i, l(l)));
}


static Pt addPts(Pt a, Pt b) {
  return a == null ? b : b == null ? a : new Pt(a.x+b.x, a.y+b.y);
}




static final Pt[] onePathDirections_directions = {
  pt(0, 0),
  pt(-1, -1),
  pt(0, -1),
  pt(1, -1),
  pt(1, 0),
  pt(1, 1),
  pt(0, 1),
  pt(-1, 1),
  pt(-1, 0)
};

static Pt[] onePathDirections() { return onePathDirections_directions; }




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

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

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

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

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

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

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

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




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


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



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



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



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



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



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


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






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


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

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


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


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

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

// used internally
static ArrayList<Method> callF_makeCache(Class c) {
  ArrayList<Method> l = new ArrayList();
  Class _c = c;
  do {
    for (Method m : _c.getDeclaredMethods())
      if (m.getName().equals("get")) {
        makeAccessible(m);
        l.add(m);
      }
    if (!l.isEmpty()) break;
    _c = _c.getSuperclass();
  } while (_c != null);
  callF_cache.put(c, l);
  return l;
}





static <A> IterableIterator<A> iteratorFromFunction_f0(final F0<A> f) {
  class IFF2 extends IterableIterator<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;
    }
  };
  return new IFF2();
}


static <A> IterableIterator<A> iteratorFromFunction_if0(final IF0<A> f) {
  class IFF2 extends IterableIterator<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;
    }
  };
  return new IFF2();
}


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 <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 Iterator emptyIterator() {
  return Collections.emptyIterator();
}


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

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

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

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




static 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 Map synchroMap() {
  return synchroHashMap();
}

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


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 String getClassName(Object o) {
  return o == null ? "null" : o instanceof Class ? ((Class) o).getName() : o.getClass().getName();
}


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 RuntimeException asRuntimeException(Throwable t) {
  
  if (t instanceof Error)
    _handleError((Error) t);
  
  return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}


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


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


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

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

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

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





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 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 boolean isDigit(char c) {
  return Character.isDigit(c);
}


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

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

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

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


static <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 != l(args)) {
    if (debug)
      print("Bad parameter length: " + args.length + " vs " + types.length);
    return false;
  }
  for (int i = 0; i < types.length; i++) {
    Object arg = args[i];
    if (!(arg == null ? !types[i].isPrimitive()
      : isInstanceX(types[i], arg))) {
      if (debug)
        print("Bad parameter " + i + ": " + arg + " vs " + types[i]);
      return false;
    }
  }
  return true;
}


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

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

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


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


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 short[] emptyShortArray = new short[0];
static short[] emptyShortArray() { return emptyShortArray; }


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


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> List<A> ll(A... a) {
  ArrayList l = new ArrayList(a.length);
  if (a != null) for (A x : a) l.add(x);
  return l;
}


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


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

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



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

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

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


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


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 <A> A assertNotNull(A a) {
  assertTrue(a != null);
  return a;
}

static <A> A assertNotNull(String msg, A a) {
  assertTrue(msg, a != null);
  return a;
}




static Map synchroHashMap() {
  return synchronizedMap(new HashMap());
}



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

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




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



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



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



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


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






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


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

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

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

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


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


static void ping_okInCleanUp() {


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


}


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 Object getOptDynOnly(DynamicObject o, String field) {
  if (o == null || o.fieldValues == null) return null;
  return o.fieldValues.get(field);
}


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

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




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


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



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


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


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

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

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


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

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


static <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 String combinePrintParameters(String s, Object o) {
  return (endsWithLetterOrDigit(s) ? s + ": " : s) + o;
}


// 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 Class mc() {
  return main.class;
}


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

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


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


static Map vm_generalMap_map;

static Map vm_generalMap() {
  if (vm_generalMap_map == null)
    
    
    
    vm_generalMap_map = (Map) get(javax(), "generalMap");
    
    
  return vm_generalMap_map;
}


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




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 ArrayList emptyList() {
  return new ArrayList();
  //ret Collections.emptyList();
}

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

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

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

// get correct type at once
static <A> ArrayList<A> emptyList(Class<A> c) {
  return new ArrayList();
}


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


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

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

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

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

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

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


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



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


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 void _onJavaXSet() {}


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


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


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

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

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

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

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



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


// PersistableThrowable doesn't hold GC-disturbing class references in backtrace
static volatile PersistableThrowable lastException_lastException;

static PersistableThrowable lastException() {
  return lastException_lastException;
}

static void lastException(Throwable e) {
  lastException_lastException = persistableThrowable(e);
}


static String hideCredentials(URL url) { return url == null ? null : hideCredentials(str(url)); }

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

static String hideCredentials(Object o) {
  return hideCredentials(str(o));
}


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

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

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

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

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

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

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

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

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

static <A> int indexOf(A[] x, A a) {
  int n = l(x);
  for (int i = 0; i < n; i++)
    if (eq(x[i], a))
      return i;
  return -1;
}

static <A> int indexOf(Iterable<A> l, A a) {
  if (l == null) return -1;
  int i = 0;
  for (A x : l) {
    if (eq(x, a))
      return i;
    i++;
  }
  return -1;
}



static void rotateStringBuffer(StringBuffer buf, int max) { try {
  if (buf == null) return;
  synchronized(buf) {
    if (buf.length() <= max) return;
    
    try {
      int newLength = max/2;
      int ofs = buf.length()-newLength;
      String newString = buf.substring(ofs);
      buf.setLength(0);
      buf.append("[...] ").append(newString);
    } catch (Exception e) {
      buf.setLength(0);
    }
    buf.trimToSize();
  }
} catch (Exception __e) { throw rethrow(__e); } }


static void rotateStringBuilder(StringBuilder buf, int max) { try {
  if (buf == null) return;
  synchronized(buf) {
    if (buf.length() <= max) return;
    
    try {
      int newLength = max/2;
      int ofs = buf.length()-newLength;
      String newString = buf.substring(ofs);
      buf.setLength(0);
      buf.append("[...] ").append(newString);
    } catch (Exception e) {
      buf.setLength(0);
    }
    buf.trimToSize();
  }
} catch (Exception __e) { throw rethrow(__e); } }


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




static <A> 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 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 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> ArrayList<A> cloneList(Iterable<A> l) {
  return l instanceof Collection ? cloneList((Collection) l) : asList(l);
}

static <A> ArrayList<A> cloneList(Collection<A> l) {
  if (l == null) return new ArrayList();
  synchronized(collectionMutex(l)) {
    return new ArrayList<A>(l);
  }
}


static Object pcallF_minimalExceptionHandling(Object f, Object... args) {
  try {
    return callFunction(f, args);
  } catch (Throwable e) {
    System.out.println(getStackTrace(e));
    _storeException(e);
  }
  return null;
}


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



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



static 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 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 PersistableThrowable persistableThrowable(Throwable e) {
  return e == null ? null : new PersistableThrowable(e);
}


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

static boolean startsWithOneOf(String s, Matches m, String... l) {
  for (String x : l) if (startsWith(s, x, m)) return true; return false;
}


static boolean isAGIBlueDomain(String domain) {
  return domainIsUnder(domain, theAGIBlueDomain());
}


static String hostNameFromURL(String url) { try {
  return empty(url) ? null : new URL(url).getHost();
} catch (Exception __e) { throw rethrow(__e); } }




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


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


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

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

static Object collectionMutex(Object o) {
  

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


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


static Throwable _storeException_value;

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


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


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


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

static void clear(Map map) {
  if (map != null) map.clear();
}


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

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


static <A, B> B 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 boolean startsWith(String a, String b) {
  return a != null && a.startsWith(unnull(b));
}

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


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


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




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


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




static 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 String className(Object o) {
  return getClassName(o);
}


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 <A> HashSet<A> litset(A... items) {
  return lithashset(items);
}


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


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


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


static boolean eqic(String a, String b) {
  
  
    if ((a == null) != (b == null)) return false;
    if (a == null) return true;
    return a.equalsIgnoreCase(b);
  
}


static boolean eqic(Symbol a, Symbol b) {
  return eq(a, b);
}

static boolean eqic(Symbol a, String b) {
  return eqic(asString(a), b);
}


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


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


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





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



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


static Map emptyMap() {
  return new HashMap();
}


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


static boolean endsWithIgnoreCase(String a, String b) {
  int la = l(a), lb = l(b);
  return la >= lb && regionMatchesIC(a, la-lb, b, 0, lb);
}


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





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




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

final static class _MethodCache {
  final Class c;
  final HashMap<String, List<Method>> cache = new HashMap();
  
  _MethodCache(Class c) {
  this.c = c; _init(); }
  
  void _init() {
    Class _c = c;
    java.lang.Module myModule = getClass().getModule();
    boolean anyHiddenClasses = false;
    
    while (_c != null) {
      boolean exported = classIsExportedTo(_c, myModule);
       
      
      if (!exported)
        anyHiddenClasses = true;
      else
        for (Method m : _c.getDeclaredMethods())
          if ((anyHiddenClasses || !isAbstract(m))
            && !reflection_isForbiddenMethod(m))
            multiMapPut(cache, m.getName(), makeAccessible(m));

      _c = _c.getSuperclass();
    }
    
    // add default methods - this might lead to a duplication
    // because the overridden method is also added, but it's not
    // a problem except for minimal performance loss.
    // If any classes in the hierarchy were inaccessible, we add
    // all interface methods (see test_callForbiddenMethodByReflection for a test)
    
    for (Class intf : allInterfacesImplementedBy(c))
      for (Method m : intf.getDeclaredMethods())
        if ((anyHiddenClasses || m.isDefault()) && !reflection_isForbiddenMethod(m))
          multiMapPut(cache, m.getName(), makeAccessible(m));

     
  }
  
  // Returns only matching methods
  Method findMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
     
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
  
  Method findStaticMethod(String method, Object[] args) { try {
    List<Method> m = cache.get(method);
    if (m == null) return null;
    int n = m.size();
    for (int i = 0; i < n; i++) {
      Method me = m.get(i);
      if (isStaticMethod(me) && call_checkArgs(me, args, false))
        return me;
    }
    return null;
  } catch (Exception __e) { throw rethrow(__e); } }
  
  //Cl<Method> allMethods() { ret allValues(cache); }
}
static class Matches {
  String[] m;
  
  Matches() {}
  Matches(String... m) {
  this.m = m;}
  
  String get(int i) { return i < m.length ? m[i] : null; }
  String unq(int i) { return unquote(get(i)); }
  
  String tlc(int i) { return unq(i).toLowerCase(); }
  boolean bool(int i) { return "true".equals(unq(i)); }
  String rest() { return m[m.length-1]; } // for matchStart
  int psi(int i) { return Integer.parseInt(unq(i)); }
  
  public String toString() { return "Matches(" + joinWithComma(quoteAll(asList(m))) + ")"; }
  
  public int hashCode() { return _hashCode(toList(m)); }
  public boolean equals(Object o) { return o instanceof Matches && arraysEqual(m, ((Matches) o).m); }
}

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

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

  // implementation of CharSequence methods
  
  public int length() { return text.length(); }
  public char charAt(int index) { return text.charAt(index); }
  public CharSequence subSequence(int start, int end) {
    return text.substring(start, end);
  }
}
interface IImageRegion<Img extends WidthAndHeight> {
  // REQUIRED METHODS
  
  // smallest rectangle that all of the region's pixels are contained in
  public Rect bounds();
  
  public IterableIterator<Pt> pixelIterator();
  public boolean contains(int x, int y);
  
  // OPTIONAL METHODS
  
  // get whole image that the region refers to
  public default Img image() { return null; }
  
  // which object made this
  public default Object creator() { return null; }
  
  public default int indexInCreator() { return 0; }
  
  public default int numberOfPixels() { return l(pixelIterator()); }
  public default Pt firstPixel() { return first(pixelIterator()); }
  
  // gets the region's color (what exactly this means is defined
  // by the crea)
  public default RGB color() { return null; }
  
  public default int brightness() { return -1; }
}
static class DoubleRect {
  double x, y, w, h;
  
  DoubleRect() {}
  DoubleRect(Rectangle r) {
    x = r.x;
    y = r.y;
    w = r.width;
    h = r.height;
  }
  DoubleRect(double x, double y, double w, double h) {
  this.h = h;
  this.w = w;
  this.y = y;
  this.x = x;}
  
  // Huh. not implementing equals()/hashCode? Stefan is mysterious
  boolean eq(Object o) {
    if (!(o instanceof DoubleRect)) return false;
    if (o == this) return true;
    DoubleRect r =  (DoubleRect) o;
    return x == r.x && y == r.y && w == r.w && h == r.h;
  }
  
  public String toString() {
    return x + "," + y + " / " + w + "," + h;
  }
  
  double x1() { return x; }
  double y1() { return y; }
  double x2() { return x + w; }
  double y2() { return y + h; }
  
  boolean contains(Pt p) {
    return contains(p.x, p.y);
  }
  
  boolean contains(double _x, double _y) {
    return _x >= x && _y >= y && _x < x+w && _y < y+h;
  }
  
  boolean empty() { return w <= 0 || h <= 0; }
}
static class MultiMap<A,B> implements IMultiMap<A, B> {
  Map<A, List<B>> data = new HashMap<A, List<B>>();
  int fullSize;
  
  MultiMap() {}
  MultiMap(boolean useTreeMap) { if (useTreeMap) data = new TreeMap(); }
  MultiMap(MultiMap<A, B> map) { putAll(map); }
  MultiMap(Map<A, List<B>> data) {
  this.data = data;}

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

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

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

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

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

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

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

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

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

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

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

  B getFirst(A key) { synchronized(data) {
    List<B> list = get(key);
    return list.isEmpty() ? null : list.get(0);
  }}
  
  void addAll(MultiMap<A, B> map) { putAll(map); }
  
  void putAll(MultiMap<A, B> map) { synchronized(data) {
    for (A key : map.keySet())
      putAll(key, map.get(key));
  }}
  
  void putAll(Map<A, B> map) { synchronized(data) {
    if (map != null) for (Map.Entry<A, B> e : map.entrySet())
      put(e.getKey(), e.getValue());
  }}
  
  final public int keyCount(){ return keysSize(); }
public int keysSize() { synchronized(data) { return l(data); }}
  
  // full size - note: expensive operation
  final public int fullSize(){ return size(); }
public int size() { synchronized(data) {
    return fullSize;
  }}
  
  // expensive operation
  List<A> reverseGet(B b) { synchronized(data) {
    List<A> l = new ArrayList();
    for (A key : data.keySet())
      if (data.get(key).contains(b))
        l.add(key);
    return l;
  }}
  
  Map<A, List<B>> asMap() { synchronized(data) {
    return cloneMap(data);
  }}
  
  boolean isEmpty() { synchronized(data) { return data.isEmpty(); }}
  
  // override in subclasses
  List<B> _makeEmptyList() {
    return new ArrayList();
  }
  
  // returns live lists
  Collection<List<B>> allLists() {
    synchronized(data) {
      return new ArrayList(data.values());
    }
  }
  Collection<List<B>> values() { return allLists(); }
  
  List<B> allValues() {
    return concatLists(data.values());
  }
  
  Object mutex() { return data; }
  
  public String toString() { return "mm" + str(data); }
}
/*
 * @(#)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 interface Steppable {
  public boolean step(); // return false if done
}
// both the outer outline and the outline of a hole are called a "trace"
// should return outer outline first and then outline of holes

// seems to return first point again at the end sometimes

static class RegionBorder_innerPoints_withDiagonals extends AbstractSteppable implements IFieldsToList{
  IImageRegion region;
  RegionBorder_innerPoints_withDiagonals() {}
  RegionBorder_innerPoints_withDiagonals(IImageRegion region) {
  this.region = region;}
  public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + region + ")"; }public Object[] _fieldsToList() { return new Object[] {region}; }

  transient Set<IVF1<Boolean>> onNewTrace;
public RegionBorder_innerPoints_withDiagonals onNewTrace(IVF1<Boolean> f) { onNewTrace = createOrAddToSyncLinkedHashSet(onNewTrace, f); return this; }
public RegionBorder_innerPoints_withDiagonals removeNewTraceListener(IVF1<Boolean> f) { main.remove(onNewTrace, f); return this; }
public void newTrace(boolean isHole) {  pcallFAll(onNewTrace, isHole); }
  transient Set<IVF1<Pt>> onFoundPoint;
public RegionBorder_innerPoints_withDiagonals onFoundPoint(IVF1<Pt> f) { onFoundPoint = createOrAddToSyncLinkedHashSet(onFoundPoint, f); return this; }
public RegionBorder_innerPoints_withDiagonals removeFoundPointListener(IVF1<Pt> f) { main.remove(onFoundPoint, f); return this; }
public void foundPoint(Pt p) {  pcallFAll(onFoundPoint, p); }
  transient Set<Runnable> onTraceDone;
public RegionBorder_innerPoints_withDiagonals onTraceDone(Runnable r) { onTraceDone = createOrAddToSyncLinkedHashSet(onTraceDone, r); return this; }
public RegionBorder_innerPoints_withDiagonals removeTraceDoneListener(Runnable r) { main.remove(onTraceDone, r); return this; }
public void traceDone() {  pcallFAll(onTraceDone); }
  
   final public RegionBorder_innerPoints_withDiagonals setIncludeHoles(boolean includeHoles){ return includeHoles(includeHoles); }
public RegionBorder_innerPoints_withDiagonals includeHoles(boolean includeHoles) { this.includeHoles = includeHoles; return this; }  final public boolean getIncludeHoles(){ return includeHoles(); }
public boolean includeHoles() { return includeHoles; }
 boolean includeHoles = true;
  
  Rect bounds; // region bounds
  int x, y;
  int dir; // current direction (1 to 8)
  Iterator<Pt> it;
  byte[] reachedInDirections; // bits 0 to 7
  boolean tracing = false;
  int nTrace; // 1 for outline, 2+ for hole

  void init() {
    bounds = region.bounds();
    reachedInDirections = new byte[area(bounds)];
    it = region.pixelIterator();
  }

  public boolean step() {
    if (reachedInDirections == null) init();
    
    if (tracing)
      return walkOnePixel();
    else
      return findFirstPixel();
  }
  
  boolean walkOnePixel() {
    int posInBounds = (y-bounds.y)*bounds.w+x-bounds.x;
    if ((reachedInDirections[posInBounds] & (1 << (dir-1))) != 0) {
      traceDone();
      tracing = false;
      return includeHoles;
    }
    
    reachedInDirections[posInBounds] |= (byte) 1 << (dir-1);
     
    foundPoint(x, y);
      
    // try left, half left, straight, half right, right, back
    for (int turn = -2; turn <= 4; turn++) {
      int newDir = modRange_incl(dir+turn, 1, 8);
      Pt d = onePathDirection(newDir);
      int x2 = x+d.x, y2 = y+d.y;
      boolean b = region.contains(x2, y2);
      if (b) {
         
        x = x2; y = y2; dir = newDir;
        return true;
      }
    }
    
     
    return true; // no black pixels found in any direction - region must be a single pixel
  }
    
  boolean findFirstPixel() {
    // search for first border pixel
    
    if (!it.hasNext()) return false; // done
    Pt p = it.next();
    x = p.x; y = p.y;
      
    int posInBounds = (y-bounds.y)*bounds.w+x-bounds.x;
    if (reachedInDirections[posInBounds] != 0) return true; // seen pixel before
    
    // if pixel above is empty, walk to the right
    if (!region.contains(x, y-1))
      { startTrace(4); return true; }
      
    // if pixel on the left is empty, walk upwards
    if (!region.contains(x-1, y))
      { startTrace(2); return true; }
    
    // if pixel on the right is empty, walk downwards
    if (!region.contains(x+1, y))
      { startTrace(6); return true; }
    
    // if pixel below is empty, walk left
    if (!region.contains(x, y+1))
      { startTrace(8); return true; }
  
    // not a border pixel, continue search
    return true;
  }
  
  void startTrace(int dir) {
    this.dir = dir;
    
    // mark point reached from all directions in next step
    int posInBounds = (y-bounds.y)*bounds.w+x-bounds.x;
    reachedInDirections[posInBounds] = (byte) ~(1 << (dir-1));
      
    tracing = true;
     
    newTrace(++nTrace > 1);
  }
  
  void foundPoint(int x, int y) { foundPoint(pt(x, y)); }
  
  // get all points as list
  
   List<Pt> allPoints_cache;
 List<Pt> allPoints() { if (allPoints_cache == null) allPoints_cache = allPoints_load(); return allPoints_cache;}

 List<Pt> allPoints_load() {
    PtBuffer l = new PtBuffer();
    onFoundPoint(p -> l.add(p));
    run();
    return l;
  }
  
  // get outline as OnePath
  
   OnePath onePath_cache;
 OnePath onePath() { if (onePath_cache == null) onePath_cache = onePath_load(); return onePath_cache;}

 OnePath onePath_load() {
    includeHoles(false);
    return new OnePath(allPoints(), true);
  }
  
  // or as OnePathWithOrigin
  
   OnePathWithOrigin onePathWithOrigin_cache;
 OnePathWithOrigin onePathWithOrigin() { if (onePathWithOrigin_cache == null) onePathWithOrigin_cache = onePathWithOrigin_load(); return onePathWithOrigin_cache;}

 OnePathWithOrigin onePathWithOrigin_load() {
    includeHoles(false);
    return new OnePathWithOrigin(allPoints(), true);
  }

  // for debugging 
  void runAndPrint() {
    onNewTrace(hole -> print(!hole ? "new outline" : "new hole"));
    onTraceDone(() -> print("traceDone"));
    onFoundPoint(p -> print("foundPoint " + p));
    stepMaxWithStats(this, 10000);
  }
  
  boolean tracingHole() { return nTrace > 1; }
}
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 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)); }
}
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 Pt implements Comparable<Pt>, IDoublePt {
  int x, y;
  
  Pt() {}
  Pt(Point p) {
    x = p.x;
    y = p.y;
  }
  Pt(int x, int y) {
  this.y = y;
  this.x = x;}
  
  Point getPoint() {
    return new Point(x, y);
  }
  
  public boolean equals(Object o) {
    return o instanceof Pt && x == ((Pt) o).x && y == ((Pt) o).y;
  }
  
  public int hashCode() {
    return boostHashCombine(x, y);
  }
  
  // compare in scan order
  public int compareTo(Pt p) {
    if (y != p.y) return cmp(y, p.y);
    return cmp(x, p.x);
  }
  
  public String toString() {
    return x + ", " + y;
  }
  
  double length() { return sqrt(x*x+y*y); }
  
  public Pt minus(Pt p) { return ptMinus(this, p); }
  
  public double x_double() { return x; }
  public double y_double() { return y; }
}
static 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();
  }
}
public static interface IF0<A> {
  A get();
}
static interface Hasher<A> {
  int hashCode(A a);
  boolean equals(A a, A b);
}

static interface IFieldsToList {
  Object[] _fieldsToList();
}
static class PtBuffer extends RandomAccessAbstractList  < Pt > {
  LongBuffer buf = new LongBuffer();
  
  PtBuffer() {}
  PtBuffer(int size) { buf.allocate(size); }
  
  PtBuffer(Iterable<Pt> l) {
    if (l != null) for (Pt p : l) add(p);
  }
  
  public int size() { return buf.size(); }
  
  public Pt get(int i) {
    return longToPt(buf.get(i));
  }
  
  public Pt set(int i, Pt val) {
    Pt old = get(i);
    buf.set(i, ptToLong(val));
    return old;
  }
  
  public boolean add(Pt p) {
    buf.add(ptToLong(p));
    return true;
  }
  
  public void add(int x, int y) {
    buf.add(ptToLong(x, y));
  }
  
  public Pt remove(int i) {
    Pt p = get(i);
    buf.remove(i);
    return p;
  }
  
  public void clear() { buf.clear(); }
}
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;
  }
  
  RuntimeException asRuntimeException() {
    return new Fail(this);
  }
}
static interface IVF1<A> {
  void get(A a);
}


abstract static class AbstractSteppable implements Steppable {
  public void run() { try { stepAll(this); } catch (Exception __e) { throw rethrow(__e); } }
}
static class LongBuffer implements Iterable<Long>, ILongQueue {
  long[] data;
  int size;
  
  LongBuffer() {}
  LongBuffer(int size) { if (size != 0) data = new long[size]; }
  LongBuffer(Iterable<Long> l) {
    if (l instanceof Collection) allocate(((Collection) l).size());
    addAll(l);
  }
  
  public void add(long i) {
    if (size >= lLongArray(data)) {
      data = resizeLongArray(data, Math.max(1, toInt(Math.min(maximumSafeArraySize(), lLongArray(data)*2L))));
      if (size >= data.length) throw fail("LongBuffer too large: " + size);
    }
    data[size++] = i;
  }
  
  void allocate(int n) {
    data = resizeLongArray(data, max(n, size()));
  }
  
  void addAll(Iterable<Long> l) {
    if (l != null) for (long i : l) add(i);
  }
  
  long[] toArray() {
    return size == 0 ? null : resizeLongArray(data, size);
  }
  
  List<Long> toList() {
    return longArrayToList(data, 0, size);
  }
  
  List<Long> asVirtualList() {
    return listFromFunction(__28 -> get(__28), size);
  }
  
  void reset() { size = 0; }
  void clear() { reset(); }
  
  int size() { return size; }
  public boolean isEmpty() { return size == 0; }
  
  long get(int idx) {
    if (idx >= size) throw fail("Index out of range: " + idx + "/" + size);
    return data[idx];
  }
  
  void set(int idx, long value) {
    if (idx >= size) throw fail("Index out of range: " + idx + "/" + size);
    data[idx] = value;
  }
  
  long popLast() {
    if (size == 0) throw fail("empty buffer");
    return data[--size];
  }
  
  long last() { return data[size-1]; }
  long nextToLast() { return data[size-2]; }
  
  public String toString() { return squareBracket(joinWithSpace(toList())); }
  
  public Iterator<Long> iterator() {
    return new IterableIterator<Long>() {
      int i = 0;
      
      public boolean hasNext() { return i < size; }
      public Long next() {
        if (!hasNext()) throw fail("Index out of bounds: " + i);
        return data[i++];
      }
    };
  }
  
  void trimToSize() {
    data = resizeLongArray(data, size);
  }
  
  void remove(int idx) {
    arraycopy(data, idx+1, data, idx, size-1-idx);
    --size;
  }
  
  // don't rely on return value if buffer is empty
  public long poll() { 
    return size == 0 ? -1 : data[--size];
  }
}
static class OnePathWithOrigin extends OnePath {
   final public OnePathWithOrigin setOrigin(Pt origin){ return origin(origin); }
public OnePathWithOrigin origin(Pt origin) { this.origin = origin; return this; }  final public Pt getOrigin(){ return origin(); }
public Pt origin() { return origin; }
 Pt origin = pt(0, 0);
  
  OnePathWithOrigin() {}
  OnePathWithOrigin(String path) { super(path); }
  OnePathWithOrigin(Pt origin, String path) { super(path);
  this.origin = origin; }
  OnePathWithOrigin(int originX, int originY, String path) { super(path); origin = pt(originX, originY); }
  OnePathWithOrigin(Iterable<Pt> points, boolean close) {
    var l = asList(points);
    origin = first(l);
    fromPoints(l, close);
  }
  
  public String toString() {
    return "Origin (" + origin + "), path: " + super.toString();
  }
}
interface IMultiMap<A, B> {
  public Set<A> keySet();
  public Collection<B> get(A a);
  public int size();
  public int keyCount();
}
interface IDoublePt {
  public double x_double();
  public double y_double();
}
abstract static class RandomAccessAbstractList<A> extends AbstractList<A> implements RandomAccess {
}
static class RGB {
  // usually in range [0, 1]
  public float r, g, b; // can't be final cause persistence
  
  RGB() {}
  
  public RGB(float r, float g, float b) {
    this.r = r;
    this.g = g;
    this.b = b;
  }

  public RGB(double r, double g, double b) {
    this.r = (float) r;
    this.g = (float) g;
    this.b = (float) b;
  }
  
  public RGB(double[] rgb) {
    this(rgb[0], rgb[1], rgb[2]);
  }

  public RGB(int rgb) {
    this(new Color(rgb));
  }
  
  public RGB(double brightness) {
    this.r = this.g = this.b = max(0f, min(1f, (float) brightness));
  }

  public RGB(Color color) {
    this.r = color.getRed()/255f;
    this.g = color.getGreen()/255f;
    this.b = color.getBlue()/255f;
  }

  // TODO: 3-char version
  public RGB(String hex) {
    int i = l(hex)-6;
    r = Integer.parseInt(hex.substring(i, i+2), 16)/255f;
    g = Integer.parseInt(hex.substring(i+2, i+4), 16)/255f;
    b = Integer.parseInt(hex.substring(i+4, i+6), 16)/255f;
  }

  public float getComponent(int i) {
    return i == 0 ? r : i == 1 ? g : b;
  }

  public int getInt(int i) {
    return i == 0 ? redInt() : i == 1 ? greenInt() : blueInt();
  }
  
  public Color getColor() {
    return new Color(r, g, b);
  }

  public static RGB newSafe(float r, float g, float b) {
    return new RGB(Math.max(0, Math.min(1, r)), Math.max(0, Math.min(1, g)), Math.max(0, Math.min(1, b)));
  }

  int asInt() { return getColor().getRGB() & 0xFFFFFF; }
  int getInt() { return getColor().getRGB() & 0xFFFFFF; }
  int asIntWithAlpha() { return rgbInt(redInt(), greenInt(), blueInt()) | 0xFF000000; }

  public float getBrightness() {
    return (r+g+b)/3.0f;
  }

  public String getHexString() {
    return Integer.toHexString(asInt() | 0xFF000000).substring(2).toUpperCase();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof RGB)) return false;

    RGB rgb = (RGB) o;

    if (Float.compare(rgb.b, b) != 0) return false;
    if (Float.compare(rgb.g, g) != 0) return false;
    if (Float.compare(rgb.r, r) != 0) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = (r != +0.0f ? Float.floatToIntBits(r) : 0);
    result = 31 * result + (g != +0.0f ? Float.floatToIntBits(g) : 0);
    result = 31 * result + (b != +0.0f ? Float.floatToIntBits(b) : 0);
    return result;
  }

  public boolean isBlack() {
    return r == 0f && g == 0f && b == 0f;
  }

  public boolean isWhite() {
    return r == 1f && g == 1f && b == 1f;
  }

  public String toString() {
    return getHexString();
  }
  
  int redInt() { return iround(r*255); }
  int greenInt() { return iround(g*255); }
  int blueInt() { return iround(b*255); }
  
  static float brightnessToFloat(int brightness) { return brightness/255f; }
}
static class OnePath {
  ByteBuffer steps = new ByteBuffer();
  
  OnePath() {}
  OnePath(String path) {
    int n = l(path);
    steps.allocate(n);
    for (int i = 0; i < n; i++)
      steps.add(parseDigit(path, i));
  }

  // uses first point as origin, so there will be l(points)-1 steps.
  // Unless you set close to true, then it adds the first point at the end again.
  
  OnePath(Iterable<Pt> points, boolean close) {
    fromPoints(points, close);
  }
  
  void fromPoints(Iterable<Pt> points, boolean close) {
    var it = iterator(points);
    if (empty(it)) return;
    Pt firstPoint = it.next(), p = firstPoint;
    while (it.hasNext()) {
      Pt p2 = it.next();
      steps.add(ptToDigit(ptMinus(p2, p)));
      p = p2;
    }
    
    if (close)
      steps.add(ptToDigit(ptMinus(firstPoint, p)));
  }
  
  final int length(){ return size(); }
final int nSteps(){ return size(); }
int size() { return steps.size(); }
  
  public String toString() { return pathString(); }
  
  String pathString() {
    return singleDigitBytesToString(steps);
  }
  
  // includes first point, so returns length()+1 points in total
  IterableIterator<Pt> pointIterator() { return pointIterator(origin()); }
IterableIterator<Pt> pointIterator(Pt startPt) {
    return new IterableIterator<Pt>() {
      int i = 0, n = length();
      Pt p = startPt;
      
      public boolean hasNext() { return i <= n; }
      
      public Pt next() {
        var p = this.p;
        if (i < n) this.p = translatePt(this.p, getStepAsPt(i));
        ++i;
        return p;
      }
    };
  }
  
  List<Pt> pointList() { return ptBuffer(pointIterator()); }
  
  int getStep(int i) { return steps.get(i); }
  
  Pt getStepAsPt(int i) {
    return onePathDirections()[steps.get(i)];
  }
  
  static int ptToDigit(Pt p) {
    return p.y < 0 ? p.x < 0 ? 1 : p.x == 0 ? 2 : 3
      : p.y == 0 ? p.x < 0 ? 8 : p.x == 0 ? 0 : 4
      : p.x < 0 ? 7 : p.x == 0 ? 6 : 5;
  }
  
  Pt drift() {
    return ptMinus(last(pointIterator()), first(pointIterator()));
  }
  
  Pt origin() { return main.origin(); }
  
  void addStep(int p_x, int p_y) { addStep(pt(p_x, p_y)); }

void addStep(Pt p) {
    int step = onePathLookupDirection(p);
    if (step < 0) throw fail("Invalid one path step: " + p);
    addStep(step);
  }
  
  void addStep(int step) {
    assertBetween(0, 8, step);
    steps.add((byte) step);
  }
}
static interface WidthAndHeight {
  default int w(){ return getWidth(); }
int getWidth();
  default int h(){ return getHeight(); }
int getHeight();
  
  public default Rect bounds() { return rect(0, 0, getWidth(), getHeight()); }
}


static class ByteBuffer implements Iterable<Byte> {
  byte[] data;
  int size;
  
  ByteBuffer() {}
  ByteBuffer(int size) { if (size != 0) data = new byte[size]; }
  ByteBuffer(Iterable<Byte> l) {
    if (l instanceof Collection) allocate(((Collection) l).size());
    addAll(l);
  }
  ByteBuffer(byte[] data) { this.data = data; size = l(data); }
  
  void add(int i) { add((byte) i); }
  
  void add(byte i) {
    if (size >= lByteArray(data)) {
      allocate(Math.max(1, toInt(Math.min(maximumSafeArraySize(), lByteArray(data)*2L))));
      if (size >= data.length) throw fail("ByteBuffer too large: " + size);
    }
    data[size++] = i;
  }
  
  void allocate(int n) {
    data = resizeByteArray(data, max(n, size()));
  }
  
  void addAll(Iterable<Byte> l) {
    if (l != null) for (byte i : l) add(i);
  }
  
  final byte[] toByteArray(){ return toArray(); }
byte[] toArray() {
    return size == 0 ? null : resizeByteArray(data, size);
  }
  
  List<Byte> toList() {
    return byteArrayToList(data, 0, size);
  }

  List<Byte> asVirtualList() {
    return new RandomAccessAbstractList<Byte>() {
      public int size() { return size; }
      public Byte get(int i) { return ByteBuffer.this.get(i); }
      public Byte set(int i, Byte val) {
        Byte a = get(i);
        data[i] = val;
        return a;
      }
    };
  }
  
  void reset() { size = 0; }
  void clear() { reset(); }
  
  int size() { return size; }
  boolean isEmpty() { return size == 0; }
  
  byte get(int idx) {
    if (idx >= size) throw fail("Index out of range: " + idx + "/" + size);
    return data[idx];
  }
  
  void set(int idx, byte value) {
    if (idx >= size) throw fail("Index out of range: " + idx + "/" + size);
    data[idx] = value;
  }
  
  byte popLast() {
    if (size == 0) throw fail("empty buffer");
    return data[--size];
  }
  
  // unefficient
  byte popFirst() {
    if (size == 0) throw fail("empty buffer");
    byte b = data[0];
    arraycopy(data, 1, 0, --size);
    return b;
  }
  
  byte last() { return data[size-1]; }
  byte nextToLast() { return data[size-2]; }
  
  public String toString() { return squareBracket(joinWithSpace(toList())); }
  
  public Iterator<Byte> iterator() {
    return new IterableIterator<Byte>() {
      int i = 0;
      
      public boolean hasNext() { return i < size; }
      public Byte next() {
        //if (!hasNext()) fail("Index out of bounds: " + i);
        return data[i++];
      }
    };
  }
  
  /*public ByteIterator byteIterator() {
    ret new ByteIterator {
      int i = 0;
      
      public bool hasNext() { ret i < size; }
      public int next() {
        //if (!hasNext()) fail("Index out of bounds: " + i);
        ret data[i++];
      }
      toString { ret "Iterator@" + i + " over " + ByteBuffer.this; }
    };
  }*/
  
  void trimToSize() {
    data = resizeByteArray(data, size);
  }
  
  int indexOf(byte b) {
    for (int i = 0; i < size; i++)
      if (data[i] == b)
        return i;
    return -1;
  }
  
  byte[] subArray(int start, int end) {
    return subByteArray(data, start, min(end, size));
  }
}
static interface ILongQueue {
  public boolean isEmpty();
  public void add(long element);
  
  // return value is undefined if queue is empty
  public long poll();
}


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

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

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


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


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

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


static boolean reflection_isForbiddenMethod(Method m) {
  return m.getDeclaringClass() == Object.class
    && eqOneOf(m.getName(), "finalize", "clone", "registerNatives");
}


static Set<Class> allInterfacesImplementedBy(Class c) {
  if (c == null) return null;
  HashSet<Class> set = new HashSet();
  allInterfacesImplementedBy_find(c, set);
  return set;
}

static void allInterfacesImplementedBy_find(Class c, Set<Class> set) {
  if (c.isInterface() && !set.add(c)) return;
  do {
    for (Class intf : c.getInterfaces())
      allInterfacesImplementedBy_find(intf, set);
  } while ((c = c.getSuperclass()) != null);
}


static Method findMethod(Object o, String method, Object... args) {
  return findMethod_cached(o, method, args);
}

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


static Method findStaticMethod(Class c, String method, Object... args) {
  Class _c = c;
  while (c != null) {
    for (Method m : c.getDeclaredMethods()) {
      if (!m.getName().equals(method))
        continue;

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

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

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



static String unquote(String s) {
  if (s == null) return null;
  if (startsWith(s, '[')) {
    int i = 1;
    while (i < s.length() && s.charAt(i) == '=') ++i;
    if (i < s.length() && s.charAt(i) == '[') {
      String m = s.substring(1, i);
      if (s.endsWith("]" + m + "]"))
        return s.substring(i+1, s.length()-i-1);
    }
  }
  
  if (s.length() > 1) {
    char c = s.charAt(0);
    if (c == '\"' || c == '\'') {
      int l = endsWith(s, c) ? s.length()-1 : s.length();
      StringBuilder sb = new StringBuilder(l-1);
  
      for (int i = 1; i < l; i++) {
        char ch = s.charAt(i);
        if (ch == '\\') {
          char nextChar = (i == l - 1) ? '\\' : s.charAt(i + 1);
          // Octal escape?
          if (nextChar >= '0' && nextChar <= '7') {
              String code = "" + nextChar;
              i++;
              if ((i < l - 1) && s.charAt(i + 1) >= '0'
                      && s.charAt(i + 1) <= '7') {
                  code += s.charAt(i + 1);
                  i++;
                  if ((i < l - 1) && s.charAt(i + 1) >= '0'
                          && s.charAt(i + 1) <= '7') {
                      code += s.charAt(i + 1);
                      i++;
                  }
              }
              sb.append((char) Integer.parseInt(code, 8));
              continue;
          }
          switch (nextChar) {
          case '\"': ch = '\"'; break;
          case '\\': ch = '\\'; break;
          case 'b': ch = '\b'; break;
          case 'f': ch = '\f'; break;
          case 'n': ch = '\n'; break;
          case 'r': ch = '\r'; break;
          case 't': ch = '\t'; break;
          case '\'': ch = '\''; break;
          // Hex Unicode: u????
          case 'u':
              if (i >= l - 5) {
                  ch = 'u';
                  break;
              }
              int code = Integer.parseInt(
                      "" + s.charAt(i + 2) + s.charAt(i + 3)
                         + s.charAt(i + 4) + s.charAt(i + 5), 16);
              sb.append(Character.toChars(code));
              i += 5;
              continue;
          default:
            ch = nextChar; // added by Stefan
          }
          i++;
        }
        sb.append(ch);
      }
      return sb.toString();
    }
  }
    
  return s; // not quoted - return original
}


static List<String> quoteAll(String[] l) {
  return quoteAll(asList(l));
}

static List<String> quoteAll(Collection<String> l) {
  List<String> x = new ArrayList();
  for (String s : l)
    x.add(quote(s));
  return x;
}


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


static boolean arraysEqual(Object[] a, Object[] b) {
  if (a.length != b.length) return false;
  for (int i = 0; i < a.length; i++)
    if (neq(a[i], b[i])) return false;
  return true;
}


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

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

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

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

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

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




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



static <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 boolean nempty(Collection c) {
  return !empty(c);
}

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

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

static boolean nempty(BitSet bs) { return !empty(bs); }

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

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


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



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







static boolean nempty(LongBuffer b) { return b != null && !b.isEmpty(); }



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







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

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

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

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


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


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

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




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





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

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




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





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


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


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

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


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

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


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





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


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

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



static Method hashMap_findKey_method;

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


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


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 int modRange_incl(int i, int start, int end) {
  return start+mod(i-start, end-start+1);
}


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


static void stepMaxWithStats(Steppable s, long maxSteps) {
  stepAllWithStats(s, maxSteps);
}

static void stepMaxWithStats(long maxSteps, Steppable s) {
  stepMaxWithStats(s, maxSteps);
}


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


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

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


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


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


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

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

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

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

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


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


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


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


static void add(BitSet bs, int i) {
  bs.set(i);
}

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


static void add(Container c, Component x) {
  addToContainer(c, x);
}


static long add(AtomicLong l, long b) {
  return l.addAndGet(b);
}


static Pt longToPt(long l) {
  return new Pt(firstIntFromLong(l), secondIntFromLong(l));
}


static long ptToLong(Pt p) {
  return p == null ? -1: intPairToLong(p.x, p.y);
}

static long ptToLong(int x, int y) {
  return intPairToLong(x, y);
}


static void stepAll(Steppable s) {
  if (s != null) while  (s.step()) { ping(); }
}



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

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

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



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

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

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


static int lLongArray(long[] a) {
  return a == null ? 0 : a.length;
}


static long[] resizeLongArray(long[] a, int n) {
  if (n == lLongArray(a)) return a;
  long[] b = new long[n];
  arraycopy(a, 0, b, 0, Math.min(lLongArray(a), n));
  return b;
}


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 int maximumSafeArraySize() {
  return Integer.MAX_VALUE-8;
}


static <A> ArrayList<Long> longArrayToList(long[] a) {
  if (a == null) return null;
  return longArrayToList(a, 0, a.length);
}

// no range checking on from/to in the interest of S P E E E E E EEED
static <A> ArrayList<Long> longArrayToList(long[] a, int from, int to) {
  if (a == null) return null;
  ArrayList < Long > l = new ArrayList<>(to-from);
  for (int i = from; i < to; i++) l.add(a[i]);
  return l;
}


static <A> List<A> asVirtualList(A[] a) {
  return wrapArrayAsList(a);
}


static <A> List<A> listFromFunction(int n, IF1<Integer, A> f) {
  return new RandomAccessAbstractList<A>() {
    public int size() { return n; }
    public A get(int i) { return f.get(i); }
  };
}

static <A> List<A> listFromFunction(IF1<Integer, A> f, int n) {
  return listFromFunction(n, f);
}


static String squareBracket(String s) {
  return "[" + s + "]";
}


static Pt origin() {
  return pt(0, 0);
}


static Color getColor(BufferedImage img, int x, int y) {
  return colorFromRGBA(img.getRGB(x, y));
}

static Color getColor(BufferedImage img, Pt p) {
  return colorFromRGBA(img.getRGB(p.x, p.y));
}


static int rgbInt(int r, int g, int b) {
  return (clamp(r, 0, 255) << 16) | (clamp(g, 0, 255) << 8) | clamp(b, 0, 255);
}


static int asInt(Object o) {
  return toInt(o);
}


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


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



static int parseDigit(char c) {
  return c >= '0' && c <= '9' ? c-'0' : 0;
}

static int parseDigit(String s, int i) {
  return parseDigit(s.charAt(i));
}


static String singleDigitBytesToString(ByteBuffer l) {
  if (l == null) return null;
  int n = l.size();
  char[] data = new char[n];
  for (int i = 0; i < n; i++)
    data[i] = (char) ('0' + ubyteToInt(l.get(i)) % 10);
  return str(data);
}


static int length(Object[] array) {
  return array == null ? 0 : array.length;
}

static int length(List list) {
  return list == null ? 0 : list.size();
}

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


static Pt translatePt(Pt a, Pt b) {
  return addPts(a, b);
}

static Pt translatePt(int x, int y, Pt a) {
  return addPts(a, pt(x, y));
}

static Pt translatePt(Pt a, int x, int y) {
  return translatePt(x, y, a);
}


static PtBuffer ptBuffer(Iterable<Pt> l) {
  return new PtBuffer(l);
}


static int onePathLookupDirection(Pt p) {
  return indexOf(onePathDirections(), p);
}



  static void assertBetween(long a, long b, long x) {
    { if (x >= a && x <= b) return; }
    throw fail(x + " is not between " + a + " and " + b);
  }


  static void assertBetween(int a, int b, int x) {
    { if (x >= a && x <= b) return; }
    throw fail(x + " is not between " + a + " and " + b);
  }




static int getWidth(Component c) {
  return c == null ? 0 : (int) swingCall(c, "getWidth");
}


static int getHeight(Component c) {
  return c == null ? 0 : (int) swingCall(c, "getHeight");
}


static Rect rect(int x, int y, int w, int h) {
  return new Rect(x, y, w, h);
}

static Rect rect(Pt p, int w, int h) {
  return new Rect(p.x, p.y, w, h);
}

static Rect rect(int w, int h) {
  return new Rect(0, 0, w, h);
}




static int lByteArray(byte[] a) {
  return a == null ? 0 : a.length;
}


static byte[] resizeByteArray(byte[] a, int n) {
  if (n == l(a)) return a;
  byte[] b = new byte[n];
  arraycopy(a, 0, b, 0, min(l(a), n));
  return b;
}


static Object[] toArray(Collection c) {
  return toObjectArray(c);
}

static <A> A[] toArray(Class<A> type, Iterable<A> c) {
  return toArray(c, type);
}

static <A> A[] toArray(Class<A> type, Collection<A> c) {
  return toArray(c, type);
}

static <A> A[] toArray(Collection<A> c, Class<A> type) {
  A[] a = arrayOfType(l(c), type);
  if (a.length == 0) return a;
  asList(c).toArray(a);
  return a;
}

static <A> A[] toArray(Iterable<A> c, Class<A> type) {
  var c2 = asList(c);
  A[] a = arrayOfType(l(c2), type);
  if (a.length == 0) return a;
  c2.toArray(a);
  return a;
}

// array must have correct length and will be filled
static <A> A[] toArray(A[] array, Collection c) {
  if (array == null || c == null) return null;
  asList(c).toArray(array);
  return array;
}


static <A> ArrayList<Byte> byteArrayToList(byte[] a) {
  if (a == null) return null;
  return byteArrayToList(a, 0, a.length);
}

// no range checking on from/to in the interest of speed
static <A> ArrayList<Byte> byteArrayToList(byte[] a, int from, int to) {
  if (a == null) return null;
  ArrayList < Byte > l = new ArrayList<>(to-from);
  for (int i = from; i < to; i++) l.add(a[i]);
  return l;
}


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 <A> A popLast(List<A> l) {
  return liftLast(l);
}

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




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

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

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

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

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


static <A> A nextToLast(List<A> l) {
  return get(l, l(l)-2);
}


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 boolean eqOneOf(Object o, Object... l) {
  for (Object x : l) if (eq(o, x)) return true; return false;
}


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



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

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


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




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

static String quote(String s) {
  if (s == null) return "null";
  StringBuilder out = new StringBuilder((int) (l(s)*1.5+2));
  quote_impl(s, out);
  return out.toString();
}
  
static void quote_impl(String s, StringBuilder out) {
  out.append('"');
  int l = s.length();
  for (int i = 0; i < l; i++) {
    char c = s.charAt(i);
    if (c == '\\' || c == '"')
      out.append('\\').append(c);
    else if (c == '\r')
      out.append("\\r");
    else if (c == '\n')
      out.append("\\n");
    else if (c == '\t')
      out.append("\\t");
    else if (c == '\0')
      out.append("\\0");
    else
      out.append(c);
  }
  out.append('"');
}


static HashMap litmap(Object... x) {
  HashMap map = new HashMap();
  litmap_impl(map, x);
  return map;
}

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


static <A, B> int lKeys(MultiMap<A, B> mm) {
  return mm == null ? 0 : mm.keysSize();
}


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

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

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


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


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


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


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



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



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


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

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

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



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


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

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


static void stepAllWithStats(Steppable s) { stepAllWithStats(s, null); }
static void stepAllWithStats(Steppable s, Long maxSteps) {
  if (s == null) return;
  long n = 0;
  long time = sysNow();
  if (maxSteps == null)
    while  (s.step()) { ping(); ++n; }
  else
    while  (n < maxSteps && s.step()) { ping(); ++n; }
  time = sysNow()-time;
  print(n2(n, "step") + " in " + n2(time) + " ms");
}



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 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(DoubleRect r) {
  if (r == null) return null;
  int x = iround(r.x), y = iround(r.y);
  return new Rect(x, y, iround(r.x2())-x, iround(r.y2())-y);
}


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


static void addToContainer(Container a, Component... b) {
  if (a == null) return;
  { swing(() -> { 
    for (Component c : unnullForIteration(b))
      if (c != null) 
        a.add(c);
  }); }
}


static int firstIntFromLong(long l) {
  return (int) (l >> 32);
}


static int secondIntFromLong(long l) {
  return (int) l;
}


static long intPairToLong(IntPair p) {
  return p == null ? 0 : (((long) p.a) << 32) | (((long) p.b) & 0xFFFFFFFF);
}

static long intPairToLong(int a, int b) {
  return (((long) a) << 32) | (((long) b) & 0xFFFFFFFF);
}


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

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


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

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


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


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


static Color colorFromRGBA(int rgba) {
  return new Color(rgba, true);
}


static float clamp(float x, float a, float b) {
  return x < a ? a : x > b ? b : x;
}

static double clamp(double x, double a, double b) {
  return x < a ? a : x > b ? b : x;
}

static int clamp(int x, int a, int b) {
  return x < a ? a : x > b ? b : x;
}

static long clamp(long x, long a, long b) {
  return x < a ? a : x > b ? b : x;
}


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 int ubyteToInt(byte b) {
  return b & 0x0FF;
}

static int ubyteToInt(char c) {
  return c & 0x0FF;
}


static Object swingCall(final Object o, final String method, final Object... args) {
  return swing(new F0<Object>() { public Object get() { try {  return call(o, method, args);  } catch (Exception __e) { throw rethrow(__e); } }
  public String toString() { return "return call(o, method, args);"; }});
}


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

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



static <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 void smartSet(Field f, Object o, Object value) throws Exception {
  try {
    f.set(o, value);
  } catch (Exception e) {
    Class type = f.getType();
    
    // take care of common case (long to int)
    if (type == int.class && value instanceof Long)
      { f.set(o, ((Long) value).intValue()); return; }
      
    if (type == boolean.class && value instanceof String)
      { f.set(o, isTrueOrYes(((String) value))); return; }
    
    if (type == LinkedHashMap.class && value instanceof Map)
      { f.set(o, asLinkedHashMap((Map) value)); return; }
    
    
    throw e;
  }
}


static <A> 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> 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 char lastChar(String s) {
  return empty(s) ? '\0' : s.charAt(l(s)-1);
}


static <A> A[] dropLast(A[] a) { return dropLast(a, 1); }
static <A> A[] dropLast(A[] a, int n) {
  if (a == null) return null;
  n = Math.min(n, a.length);
  A[] b = arrayOfSameType(a, a.length-n);
  System.arraycopy(a, 0, b, 0, b.length);
  return b;
}

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

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

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

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

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

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



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


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


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


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

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



static boolean _inCore() {
  return false;
}


static List hotwire_copyOver_after = synchroList();

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


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

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


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


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

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

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

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

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

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

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

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

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

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

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




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



static String 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 Object swing(Object f) {
  return swingAndWait(f);
}

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

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

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


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

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


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


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


static <A> A[] makeArray(Class<A> type, int n) {
  return (A[]) Array.newInstance(type, n);
}


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 <A> List<A> newSubList(List<A> l, int startIndex, int endIndex) {
  return cloneList(subList(l, startIndex, endIndex));
}

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




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


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


static Object mainBot;

static Object getMainBot() {
  return mainBot;
}


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

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

static void setOpt(Class c, String field, Object value) {
  if (c == null) return;
  try {
    Field f = setOpt_findStaticField(c, field); // TODO: optimize
    if (f != null)
      smartSet(f, null, value);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
  
static Field setOpt_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0) {
        makeAccessible(f);
        return f;
      }
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}


static Object 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 String formatWithThousands(long l) {
  return formatWithThousandsSeparator(l);
}


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


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

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

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

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




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


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 boolean isTrueOpt(Object o) {
  if (o instanceof Boolean)
    return ((Boolean) o).booleanValue();
  return false;
}

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


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


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

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




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


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

static void setOpt_raw(Class c, String field, Object value) { try {
  if (c == null) return;
  Field f = setOpt_raw_findStaticField(c, field);
  if (f != null) {
    makeAccessible(f);
    smartSet(f, null, value);
  }
} catch (Exception __e) { throw rethrow(__e); } }
  
static Field setOpt_raw_findStaticField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}

static Field setOpt_raw_findField(Class<?> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}


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

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




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


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


static 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 <A> ArrayList<A> litlist(A... a) {
  ArrayList l = new ArrayList(a.length);
  for (A x : a) l.add(x);
  return l;
}


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


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

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

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


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




static void setDynObjectValue(DynamicObject o, String field, Object value) {
  dynamicObject_setRawFieldValue(o, field, value);
}


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

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


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


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


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


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

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


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 <A, B> LinkedHashMap<A, B> syncMapPut2_createLinkedHashMap(LinkedHashMap<A, B> map, A key, B value) {
  if (key != null)
    if (value != null) {
      if (map == null) map = new LinkedHashMap();
      synchronized(collectionMutex(map)) { map.put(key, value); }
    } else if (map != null) synchronized(collectionMutex(map)) { map.remove(key); }
  return map;
}


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


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




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




// Meta - a "minimal" approach to adding meta-level to Java objects

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

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

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

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

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

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

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

volatile Object meta;

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

// ...and the interface methods

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

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

// Scaffolding convenience functions

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

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

  AppendableChain() {} // only used internally
  AppendableChain(A element) {
  this.element = element; size = 1; last = this; }
  
  // intermediate constructor called by itemPlusChain()
  AppendableChain(A element, AppendableChain<A> next) {
  this.next = next;
  this.element = element;
    if (next == null) return;
    
    MinimalChain<A> b = new MinimalChain();
    b.element = next.element;
    b.next = next.next;
    this.next = b;
    last = next.last;
    size = next.size+1;
  }
  
  public String toString() { return str(toList()); }
  
  // append at the end
  boolean add(A a) {
    MinimalChain newLast = new MinimalChain(a);
    last.next = newLast;
    last = newLast;
    ++size;
    return true;
  }
  
  // drop first element
  AppendableChain<A> popFirst() {
    if (next == null) return null;
    element = next.element;
    if (last == next) last = this;
    next = next.next;
    --size;
    return this;
  }
  
  ArrayList<A> toList() {
    ArrayList<A> l = emptyList(size);
    MinimalChain<A> c = this;
    while (c != null) {
      l.add(c.element);
      c = c.next;
    }
    return l;
  }
  
  //public Iterator<A> iterator() { ret toList().iterator(); }
  
  class ACIt extends IterableIterator  < A > {
    MinimalChain<A> c = AppendableChain.this;
    
    public boolean hasNext() {
      return c != null;
    }
    
    public A next() {
      var a = c.element;
      c = c.next;
      return a;
    }
  }
  
  public IterableIterator<A> iterator() {
    return new ACIt();
  }
}
static class Var<A> implements IVar<A>, ISetter<A> {
  Var() {}
  Var(A v) {
  this.v = v;}

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

public String toString() { return str(this.get()); }
}
static interface IMeta {
  // see class "Meta" for the bla bla
  
  public void _setMeta(Object meta);
  public Object _getMeta();
  default public IAutoCloseableF0 _tempMetaMutex() {
    return new IAutoCloseableF0() {
      public Object get() { return IMeta.this; }
      public void close() {}
    };
  }
  
  // actually query another object
  default public Object getMeta(Object obj, Object key){ return metaGet(obj, key); }
default public Object metaGet(Object obj, Object key) {
    // call global function
    return metaMapGet(obj, key);
  }
  
  default public Object metaGet(String key, Object obj) {
    // call global function
    return metaMapGet(obj, key);
  }
  
  default public Object getMeta(Object key){ return metaGet(key); }
default public Object metaGet(Object key) {
    if (key == null) return null;
    Object meta = _getMeta();
    if (meta instanceof Map) return ((Map) meta).get(key);
    return null;
  }
  
  default public void metaSet(IMeta obj, Object key, Object value){ metaPut(obj, key, value); }
default public void metaPut(IMeta obj, Object key, Object value) {
    // call global function
    metaMapPut(obj, key, value);
  }
  
  default public void metaSet(Object key, Object value){ metaPut(key, value); }
default public void metaPut(Object key, Object value) {
    if (key == null) return;
    Map map = convertObjectMetaToMap(this);
    syncMapPutOrRemove(map, key, value);
  }
}
// -has fast nextElement() and prevElement()
// -design allows for more functions like reordering the list
// -Saves up to 34% in space over LinkedHashSet
//    (e.g. 22% for a set of 1,000 Ints)
static class CompactLinkedHashSet<A> extends AbstractSet<A> {
  UnsynchronizedCompactHashSet<Entry<A>> entries = new UnsynchronizedCompactHashSet();
  Entry<A> head, tail;
  
  static class Entry<A> {
    A value;
    Entry<A> prev, next;
    
    public int hashCode() {
      return _hashCode(value);
    }
    
    // "magic" equals function for CompactHashSet lookup without temp object
    public boolean equals(Object o) {
      return o == this || eq(value, o);
    }
  }
  
  public boolean add(A a) {
    if (entries.contains(a)) return false;
    Entry<A> n = new Entry();
    n.value = a;
    n.prev = tail;
    if (tail != null) tail.next = n;
    tail = n;
    if (head == null) head = n;
    entries.add(n);
    return true;
  }
  
  public boolean remove(Object a) {
    return remove(entries.find(a));
  }
  
  public boolean remove(Entry<A> node) {
    if (node == null) return false;
    if (node.next != null) node.next.prev = node.prev; else tail = node.prev;
    if (node.prev != null) node.prev.next = node.next; else head = node.next;
    entries.remove(node);
    return true;
  }
  
  public int size() { return entries.size(); }
  
  public IterableIterator<A> iterator() {
    return new IterableIterator<A>() {
      Entry<A> entry = head, prev = null;
      public boolean hasNext() { return entry != null; }
      public A next() {
        A a = entry.value;
        prev = entry;
        entry = entry.next;
        return a;
      }
      
      // untested
      public void remove() {
        if (prev == null) throw new IllegalStateException();
        CompactLinkedHashSet.this.remove(prev);
        prev = null;
      }
    };
  }
  
  public void clear() {
    entries.clear();
    head = tail = null;
  }
  
  public boolean contains(Object a) {
    return entries.contains(a);
  }
  
  public A find(Object o) {
    Entry<A> e = entries.find(o);
    return e == null ? null : e.value;
  }
  
  public A prevElement(A a) {
    Entry<A> e = entries.find(a);
    if (e == null || e.prev == null) return null;
    return e.prev.value;
  }
  
  public A nextElement(A a) {
    Entry<A> e = entries.find(a);
    if (e == null || e.next == null) return null;
    return e.next.value;
  }
  
  public A first() { return head == null ? null : head.value; }
  public A last() { return tail == null ? null : tail.value; }
  
  boolean removeIfSame(Object o) {
    A value = find(o);
    if (value == o) {
      remove(value);
      return true;
    }
    return false;
  }
}
static interface IVar<A> extends IF0<A> {
  void set(A a);
  A get();
  
  // reified type of value (if available)
  default Class<A> getType() { return null; }
  
  
  default boolean has() { return get() != null; }
  default void clear() { set(null); }
  
}
static final class IntPair implements Comparable<IntPair> , IFieldsToList{
  int a;
  int b;
  IntPair() {}
  IntPair(int a, int b) {
  this.b = b;
  this.a = a;}
  public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + a + ", " + b + ")"; }public Object[] _fieldsToList() { return new Object[] {a, b}; }

  public boolean equals(Object o) {
    if (!(o instanceof IntPair)) return false;
    IntPair x =  (IntPair) o;
    return a == x.a && b == x.b;
  }

  public int hashCode() {
    int h = -672893111;
    h = boostHashCombine(h, _hashCode(a));
    h = boostHashCombine(h, _hashCode(b));
    return h;
  }
  
  public int compareTo(IntPair p) {
    if (p == null) return 1;
    int pa = p.a;
    if (a < pa) return -1;
    if (a > pa) return 1;
    return Integer.compare(b, p.b);
  }
}


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

  MinimalChain() {}
  MinimalChain(A element) {
  this.element = element;}
  MinimalChain(A element, MinimalChain<A> next) {
  this.next = next;
  this.element = element;}
  
  public String toString() { return str(toList()); }
  
  ArrayList<A> toList() {
    ArrayList<A> l = new ArrayList();
    MinimalChain<A> c = this;
    while (c != null) {
      l.add(c.element);
      c = c.next;
    }
    return l;
  }
  
  void setElement(A a) { element = a; }
  void setNext(MinimalChain<A> next) { this.next = next; }
  
  // TODO: optimize
  public Iterator<A> iterator() { return toList().iterator(); }
  
  A get() { return element; }
}
static interface ISetter<A> {
 void set(A a);
}

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

// modified by Stefan Reich

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      
        modCount++;
      
      elements++;

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

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

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

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

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

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

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

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

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

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

      newObjects[index] = (A) o;
    }

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

    
      private int expectedModCount;
    

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

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

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

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


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

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

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

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


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

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


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



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


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


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


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

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




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


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


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


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


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




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

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

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

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

  CompactHashMap() {
    this(INITIAL_SIZE);
  }

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

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

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

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

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

    return false;
  }

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

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

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

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

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

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

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

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

      //modCount++;
      elements++;

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

  /**
   * INTERNAL: Rehashes the hashmap to a bigger size.
   */
  void rehash(int newCapacity) {
    int oldCapacity = tableSize();
    Object[] newTable = new Object[newCapacity*2];

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

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

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

      newTable[index*2] = k;
      newTable[index*2+1] = value(ix);
    }

    table = newTable;
    freecells = tableSize() - elements;
  }

  /**
   * Returns the value for the key k, if there is one, and null if
   * there is none.
   */
  public synchronized V get(Object k) {
    return value(findKeyIndex(k));
  }

  /**
   * Returns a virtual read-only collection containing all the values
   * in the map.
   */
  public synchronized Collection<V> values() {
    return new ValueCollection();
  }

  /**
   * Returns a virtual read-only set of all the keys in the map.
   */
  public synchronized Set<K> keySet() {
    return new KeySet();
  }

  // --- Internal utilities

  final int findKeyIndex(Object k) {
    if (k == null)
      k = nullObject;

    int hash = k.hashCode();
    int index = (hash & 0x7FFFFFFF) % tableSize();
    int offset = 1;

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

      if (offset == -1)
        offset = 2;
    }
    return index;
  }
  
  // --- Key set

  class KeySet<K> extends AbstractSet<K> {
    public synchronized int size() {
      return elements;
    }

    public synchronized boolean contains(Object k) {
      return containsKey(k);
    }

    public synchronized Iterator<K> iterator() {
      return new KeyIterator();
    }
  }

  class KeyIterator<K> implements Iterator<K> {
    private int ix;
    
    private KeyIterator() {
      // walk up to first value, so that hasNext() and next() return
      // correct results
      for (; ix < tableSize(); ix++)
        if (value(ix) != null && key(ix) != deletedObject)
          break;
    }

    public synchronized boolean hasNext() {
      return ix < tableSize();
    }

    public synchronized void remove() {
      throw new UnsupportedOperationException("Collection is read-only");
    }

    public synchronized K next() {
      if (ix >= tableSize())
        throw new NoSuchElementException();
      K key = (K) key(ix++);
      
      // walk up to next value
      for (; ix < tableSize(); ix++)
        if (key(ix) != null && key(ix) != deletedObject)
          break;
      
      // ix now either points to next key, or outside array (if no next)
      return key;
    }
  }
  
  // --- Value collection

  class ValueCollection<V> extends AbstractCollection<V> {
    public synchronized int size() {
      return elements;
    }

    public synchronized Iterator<V> iterator() {
      return new ValueIterator();
    }

    public synchronized boolean contains(Object v) {
      return containsValue(v);
    }
  }

  class ValueIterator<V> implements Iterator<V> {
    private int ix;
    
    private ValueIterator() {
      // walk up to first value, so that hasNext() and next() return
      // correct results
      for (; ix < table.length/2; ix++)
        if (value(ix) != null && value(ix) != deletedObject)
          break;
    }

    public synchronized boolean hasNext() {
      return ix < tableSize();
    }

    public synchronized void remove() {
      throw new UnsupportedOperationException("Collection is read-only");
    }

    public synchronized V next() {
      if (ix >= tableSize())
        throw new NoSuchElementException();
      V value = (V) value(ix++);
      
      // walk up to next value
      for (; ix < tableSize(); ix++)
        if (value(ix) != null && value(ix) != deletedObject)
          break;
      
      // ix now either points to next value, or outside array (if no next)
      return value;
    }
  }
  
  K key(int i) { return (K) table[i*2]; }
  void key(int i, Object key) { table[i*2] = key; }
  V value(int i) { return (V) table[i*2+1]; }
  void value(int i, Object value) { table[i*2+1] = value; }
  
  int tableSize() { return table.length/2; }
}


abstract static class CompactAbstractMap<K, V> implements Map<K, V> {
  public int size() {
      return entrySet().size();
  }

  public boolean isEmpty() {
      return size() == 0;
  }

  public boolean containsValue(Object value) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (value == null) {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getValue() == null)
                  return true;
          }
      } else {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (value.equals(e.getValue()))
                  return true;
          }
      }
      return false;
  }

  public boolean containsKey(Object key) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (key == null) {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getKey() == null)
                  return true;
          }
      } else {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (key.equals(e.getKey()))
                  return true;
          }
      }
      return false;
  }

  public V get(Object key) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (key == null) {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getKey() == null)
                  return e.getValue();
          }
      } else {
          while (i.hasNext()) {
              Entry<K, V> e = i.next();
              if (key.equals(e.getKey()))
                  return e.getValue();
          }
      }
      return null;
  }

  public V put(K key, V value) {
      throw new UnsupportedOperationException();
  }

  public V remove(Object key) {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      Entry<K, V> correctEntry = null;
      if (key == null) {
          while (correctEntry == null && i.hasNext()) {
              Entry<K, V> e = i.next();
              if (e.getKey() == null)
                  correctEntry = e;
          }
      } else {
          while (correctEntry == null && i.hasNext()) {
              Entry<K, V> e = i.next();
              if (key.equals(e.getKey()))
                  correctEntry = e;
          }
      }

      V oldValue = null;
      if (correctEntry != null) {
          oldValue = correctEntry.getValue();
          i.remove();
      }
      return oldValue;
  }

  public void putAll(Map<? extends K, ? extends V> m) {
      for (Entry<? extends K, ? extends V> e : m.entrySet())
          put(e.getKey(), e.getValue());
  }

  public void clear() {
      entrySet().clear();
  }

  public Set<K> keySet() {
      return new AbstractSet<K>() {
          public Iterator<K> iterator() {
              return new Iterator<K>() {
                  private Iterator<Entry<K, V>> i = entrySet().iterator();

                  public boolean hasNext() {
                      return i.hasNext();
                  }

                  public K next() {
                      return i.next().getKey();
                  }

                  public void remove() {
                      i.remove();
                  }
              };
          }

          public int size() {
              return CompactAbstractMap.this.size();
          }

          public boolean isEmpty() {
              return CompactAbstractMap.this.isEmpty();
          }

          public void clear() {
              CompactAbstractMap.this.clear();
          }

          public boolean contains(Object k) {
              return CompactAbstractMap.this.containsKey(k);
          }
      };
  }

  public Collection<V> values() {
      return new AbstractCollection<V>() {
          public Iterator<V> iterator() {
              return new Iterator<V>() {
                  private Iterator<Entry<K, V>> i = entrySet().iterator();

                  public boolean hasNext() {
                      return i.hasNext();
                  }

                  public V next() {
                      return i.next().getValue();
                  }

                  public void remove() {
                      i.remove();
                  }
              };
          }

          public int size() {
              return CompactAbstractMap.this.size();
          }

          public boolean isEmpty() {
              return CompactAbstractMap.this.isEmpty();
          }

          public void clear() {
              CompactAbstractMap.this.clear();
          }

          public boolean contains(Object v) {
              return CompactAbstractMap.this.containsValue(v);
          }
      };
  }

  public abstract Set<Entry<K, V>> entrySet();

  public boolean equals(Object o) {
      if (o == this)
          return true;

      if (!(o instanceof Map))
          return false;
      Map<?, ?> m = (Map<?, ?>) o;
      if (m.size() != size())
          return false;

      try {
          for (Entry<K, V> e : entrySet()) {
              K key = e.getKey();
              V value = e.getValue();
              if (value == null) {
                  if (!(m.get(key) == null && m.containsKey(key)))
                      return false;
              } else {
                  if (!value.equals(m.get(key)))
                      return false;
              }
          }
      } catch (ClassCastException unused) {
          return false;
      } catch (NullPointerException unused) {
          return false;
      }

      return true;
  }

  public int hashCode() {
      int h = 0;
      for (Entry<K, V> entry : entrySet())
          h += entry.hashCode();
      return h;
  }

  public String toString() {
      Iterator<Entry<K, V>> i = entrySet().iterator();
      if (!i.hasNext())
          return "{}";

      StringBuilder sb = new StringBuilder();
      sb.append('{');
      for (; ; ) {
          Entry<K, V> e = i.next();
          K key = e.getKey();
          V value = e.getValue();
          sb.append(key == this ? "(this Map)" : key);
          sb.append('=');
          sb.append(value == this ? "(this Map)" : value);
          if (!i.hasNext())
              return sb.append('}').toString();
          sb.append(',').append(' ');
      }
  }

  protected Object clone() throws CloneNotSupportedException {
      CompactAbstractMap<?, ?> result = (CompactAbstractMap<?, ?>) super.clone();
      return result;
  }

  public static class SimpleEntry<K, V>
          implements Entry<K, V>, java.io.Serializable {
      @java.io.Serial
      private static final long serialVersionUID = -8499721149061103585L;

      @SuppressWarnings("serial")
      private final K key;
      @SuppressWarnings("serial")
      private V value;

      public SimpleEntry(K key, V value) {
          this.key = key;
          this.value = value;
      }

      public SimpleEntry(Entry<? extends K, ? extends V> entry) {
          this.key = entry.getKey();
          this.value = entry.getValue();
      }

      public K getKey() {
          return key;
      }

      public V getValue() {
          return value;
      }

      public V setValue(V value) {
          V oldValue = this.value;
          this.value = value;
          return oldValue;
      }

      public boolean equals(Object o) {
          if (!(o instanceof Map.Entry))
              return false;
          Entry<?, ?> e = (Entry<?, ?>) o;
          return eq(key, e.getKey()) && eq(value, e.getValue());
      }

      public int hashCode() {
          return (key == null ? 0 : key.hashCode()) ^
                  (value == null ? 0 : value.hashCode());
      }

      public String toString() {
          return key + "=" + value;
      }

  }

  public static class SimpleImmutableEntry<K, V>
          implements Entry<K, V>, java.io.Serializable {
      @java.io.Serial
      private static final long serialVersionUID = 7138329143949025153L;

      @SuppressWarnings("serial")
      private final K key;
      @SuppressWarnings("serial")
      private final V value;

      public SimpleImmutableEntry(K key, V value) {
          this.key = key;
          this.value = value;
      }

      public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
          this.key = entry.getKey();
          this.value = entry.getValue();
      }

      public K getKey() {
          return key;
      }

      public V getValue() {
          return value;
      }

      public V setValue(V value) {
          throw new UnsupportedOperationException();
      }

      public boolean equals(Object o) {
          if (!(o instanceof Map.Entry))
              return false;
          Entry<?, ?> e = (Entry<?, ?>) o;
          return eq(key, e.getKey()) && eq(value, e.getValue());
      }

      public int hashCode() {
          return (key == null ? 0 : key.hashCode()) ^
                  (value == null ? 0 : value.hashCode());
      }

      public String toString() {
          return key + "=" + value;
      }
  }
}


static boolean scaffoldingEnabled(Object o) {
  return metaGet(o, "scaffolding") != null;
}


static <A> Value<A> value(A a) {
  return new Value<A>(a);
}


static <A, B> boolean containsKey(Map<A, B> map, A key) {
  return map != null && map.containsKey(key);
}




static class Value<A> implements IF0<A> , IFieldsToList{
  A value;
  Value() {}
  Value(A value) {
  this.value = value;}

public boolean equals(Object o) {
if (!(o instanceof Value)) return false;
    Value __1 =  (Value) o;
    return eq(value, __1.value);
}

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

  public A get() { return value; }
  
  public String toString() { return str(get()); }
}

}