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.*;
// TODO: does maxSteps 1 do anything?
import static x30_pkg.x30_util.DynamicObject;
import java.awt.geom.*;
class main {
static RGB varyColor(RGB rgb) { return varyColor(rgb, 25); }
static RGB varyColor(RGB rgb, int maxSteps) {
float max = maxSteps/255f;
int s = random(3);
if (s == 0)
return new RGB(vary0to1(rgb.r, max), rgb.g, rgb.b);
else if (s == 1)
return new RGB(rgb.r, vary0to1(rgb.g, max), rgb.b);
else
return new RGB(rgb.r, rgb.g, vary0to1(rgb.b, max));
}
static Color varyColor(Color color) { return varyColor(color, 25); }
static Color varyColor(Color color, int maxSteps) {
return toColor(varyColor(new RGB(color), maxSteps));
}
static int random(int n) { return random(n, defaultRandomGenerator()); }
static int random(int n, Random r) {
return random(r, n);
}
static int random(Random r, int n) {
return n <= 0 ? 0 : getRandomizer(r).nextInt(n);
}
static double random(double max) {
return random()*max;
}
static double random() {
return defaultRandomGenerator().nextInt(100001)/100000.0;
}
static double random(double min, double max) {
return min+random()*(max-min);
}
// min <= value < max
static int random(int min, int max) {
return min+random(max-min);
}
static int random(int min, int max, Random r) {
return random(r, min, max);
}
static int random(Random r, int min, int max) {
return min+random(r, max-min);
}
static A random(List l) {
return oneOf(l);
}
static A random(Collection c) {
if (c instanceof List) return random((List) c);
int i = random(l(c));
return collectionGet(c, i);
}
static Pair random(Map map) {
return entryToPair(random(entries(map)));
}
static float vary0to1(float x) { return vary0to1(x, 0.1f); }
static float vary0to1(float x, float max) {
return Math.max(0f, Math.min(1f, (float) (x+random(-max, max))));
}
static Color toColor(RGB rgb) {
return rgb == null ? null : rgb.getColor();
}
static Color toColor(int rgb) {
return new Color(rgb);
}
static Color toColor(String hex) {
return awtColor(hex);
}
static Color toColor(int[] rgb) {
return new Color(rgb[0], rgb[1], rgb[2]);
}
static Random defaultRandomGenerator() {
{ Random r = customRandomizerForThisThread(); if (r != null) return r; }
return ThreadLocalRandom.current();
}
static Random getRandomizer(Random r) {
return r != null ? r : defaultRandomGenerator();
}
static A oneOf(List l) {
if (empty(l)) return null;
int n = l.size();
return n == 1 ? first(l) : l.get(defaultRandomizer().nextInt(n));
}
static char oneOf(String s) {
return empty(s) ? '?' : s.charAt(random(l(s)));
}
static String oneOf(String... l) {
return oneOf(asList(l));
}
static 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 A collectionGet(Collection c, int idx) {
if (c == null || idx < 0 || idx >= l(c)) return null;
if (c instanceof List) return listGet((List) c, idx);
Iterator it = c.iterator();
for (int i = 0; i < idx; i++) if (it.hasNext()) it.next(); else return null;
return it.hasNext() ? it.next() : null;
}
static Pair entryToPair(Map.Entry e) {
return mapEntryToPair(e);
}
static Set> entries(Map map) {
return _entrySet(map);
}
static java.awt.Color awtColor(String hex) {
byte[] b = bytesFromHex(dropPrefix("#", hex));
return new Color(ubyteToInt(b[0]), ubyteToInt(b[1]), ubyteToInt(b[2]));
}
static Random customRandomizerForThisThread() {
return customRandomizerForThisThread_tl().get();
}
static boolean empty(Collection c) { return c == null || c.isEmpty(); }
static boolean empty(Iterable c) { return c == null || !c.iterator().hasNext(); }
static boolean empty(CharSequence s) { return s == null || s.length() == 0; }
static boolean empty(Map map) { return map == null || map.isEmpty(); }
static boolean empty(Object[] o) { return o == null || o.length == 0; }
static boolean empty(BitSet bs) { return bs == null || bs.isEmpty(); }
static boolean empty(Object o) {
if (o instanceof Collection) return empty((Collection) o);
if (o instanceof String) return empty((String) o);
if (o instanceof Map) return empty((Map) o);
if (o instanceof Object[]) return empty((Object[]) o);
if (o instanceof byte[]) return empty((byte[]) o);
if (o == null) return true;
throw fail("unknown type for 'empty': " + getType(o));
}
static boolean empty(Iterator i) { return i == null || !i.hasNext(); }
static boolean empty(double[] a) { return a == null || a.length == 0; }
static boolean empty(float[] a) { return a == null || a.length == 0; }
static boolean empty(int[] a) { return a == null || a.length == 0; }
static boolean empty(long[] a) { return a == null || a.length == 0; }
static boolean empty(byte[] a) { return a == null || a.length == 0; }
static boolean empty(short[] a) { return a == null || a.length == 0; }
static boolean empty(IMultiMap mm) { return mm == null || mm.size() == 0; }
static boolean empty(File f) { return getFileSize(f) == 0; }
static boolean empty(Rect r) { return !(r != null && r.w != 0 && r.h != 0); }
static Object first(Object list) {
return first((Iterable) list);
}
static A first(List list) {
return empty(list) ? null : list.get(0);
}
static A first(A[] bla) {
return bla == null || bla.length == 0 ? null : bla[0];
}
static Pair first(Map map) {
return mapEntryToPair(first(entrySet(map)));
}
static Pair first(MultiMap 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 first(IterableIterator i) {
return first((Iterator) i);
}
static A first(Iterator i) {
return i == null || !i.hasNext() ? null : i.next();
}
static A first(Iterable i) {
if (i == null) return null;
Iterator 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 first(Pair p) {
return p == null ? null : p.a;
}
static Byte first(byte[] l) {
return empty(l) ? null : l[0];
}
static A first(A[] l, IF1 pred) {
return firstThat(l, pred);
}
static A first(Iterable l, IF1 pred) {
return firstThat(l, pred);
}
static A first(IF1 pred, Iterable l) {
return firstThat(pred, l);
}
static Random defaultRandomizer() {
return defaultRandomGenerator();
}
// unclear semantics as to whether return null on null
static ArrayList asList(A[] a) {
return a == null ? new ArrayList() : new ArrayList(Arrays.asList(a));
}
static ArrayList asList(int[] a) {
if (a == null) return null;
ArrayList l = emptyList(a.length);
for (int i : a) l.add(i);
return l;
}
static ArrayList asList(long[] a) {
if (a == null) return null;
ArrayList l = emptyList(a.length);
for (long i : a) l.add(i);
return l;
}
static ArrayList asList(float[] a) {
if (a == null) return null;
ArrayList l = emptyList(a.length);
for (float i : a) l.add(i);
return l;
}
static ArrayList asList(double[] a) {
if (a == null) return null;
ArrayList l = emptyList(a.length);
for (double i : a) l.add(i);
return l;
}
static ArrayList asList(short[] a) {
if (a == null) return null;
ArrayList l = emptyList(a.length);
for (short i : a) l.add(i);
return l;
}
static ArrayList asList(Iterator it) {
ArrayList l = new ArrayList();
if (it != null)
while (it.hasNext())
l.add(it.next());
return l;
}
// disambiguation
static ArrayList asList(IterableIterator s) {
return asList((Iterator) s);
}
static ArrayList asList(Iterable 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 ArrayList asList(Enumeration e) {
ArrayList l = new ArrayList();
if (e != null)
while (e.hasMoreElements())
l.add(e.nextElement());
return l;
}
static List asList(Pair p) {
return p == null ? null : ll(p.a, p.b);
}
static int iteratorCount_int_close(Iterator 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 listGet(List l, int idx) {
return l != null && idx >= 0 && idx < l(l) ? l.get(idx) : null;
}
static Pair mapEntryToPair(Map.Entry e) {
return e == null ? null : pair(e.getKey(), e.getValue());
}
static Set> _entrySet(Map map) {
return map == null ? Collections.EMPTY_SET : map.entrySet();
}
static byte[] bytesFromHex(String s) {
return hexToBytes(s);
}
static String dropPrefix(String prefix, String s) {
return s == null ? null : s.startsWith(prefix) ? s.substring(l(prefix)) : s;
}
static int ubyteToInt(byte b) {
return b & 0x0FF;
}
static int ubyteToInt(char c) {
return c & 0x0FF;
}
static ThreadLocal customRandomizerForThisThread_tl = new ThreadLocal();
static ThreadLocal customRandomizerForThisThread_tl() {
return customRandomizerForThisThread_tl;
}
static RuntimeException fail() { throw new RuntimeException("fail"); }
static RuntimeException fail(Throwable e) { throw asRuntimeException(e); }
static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); }
static RuntimeException fail(Object... objects) { throw new Fail(objects); }
static RuntimeException fail(String msg) { throw new RuntimeException(msg == null ? "" : msg); }
static RuntimeException fail(String msg, Throwable innerException) { throw new RuntimeException(msg, innerException); }
static String 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 Set> entrySet(Map map) {
return _entrySet(map);
}
static Pair pair(A a, B b) {
return new Pair(a, b);
}
static Pair pair(A a) {
return new Pair(a, a);
}
static A firstThat(Iterable l, IF1 pred) {
for (A a : unnullForIteration(l))
if (pred.get(a))
return a;
return null;
}
static A firstThat(A[] l, IF1 pred) {
for (A a : unnullForIteration(l))
if (pred.get(a))
return a;
return null;
}
static A firstThat(IF1 pred, Iterable l) {
return firstThat(l, pred);
}
static A firstThat(IF1 pred, A[] l) {
return firstThat(l, pred);
}
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 ArrayList emptyList(Class c) {
return new ArrayList();
}
static List ll(A... a) {
ArrayList l = new ArrayList(a.length);
if (a != null) for (A x : a) l.add(x);
return l;
}
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 byte[] hexToBytes(String s) {
if (odd(l(s))) throw fail("Hex string has odd length: " + quote(shorten(10, s)));
int n = l(s) / 2;
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++) {
int a = parseHexChar(s.charAt(i*2));
int b = parseHexChar(s.charAt(i*2+1));
if (a < 0 || b < 0)
throw fail("Bad hex byte: " + quote(substring(s, i*2, i*2+2)) + " at " + i*2 + "/" + l(s));
bytes[i] = (byte) ((a << 4) | b);
}
return bytes;
}
static RuntimeException asRuntimeException(Throwable t) {
if (t instanceof Error)
_handleError((Error) t);
return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}
static String getClassName(Object o) {
return o == null ? "null" : o instanceof Class ? ((Class) o).getName() : o.getClass().getName();
}
static String unnullForIteration(String s) {
return s == null ? "" : s;
}
static Collection unnullForIteration(Collection l) {
return l == null ? immutableEmptyList() : l;
}
static List unnullForIteration(List 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 Map unnullForIteration(Map l) {
return l == null ? immutableEmptyMap() : l;
}
static Iterable unnullForIteration(Iterable i) {
return i == null ? immutableEmptyList() : i;
}
static 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 Pair unnullForIteration(Pair p) {
return p != null ? p : new Pair(null, null);
}
static long unnullForIteration(Long l) { return l == null ? 0L : l; }
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 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 max(A a, A b) {
return cmp(a, b) >= 0 ? a : b;
}
static void _handleError(Error e) {
//call(javax(), '_handleError, e);
}
static boolean odd(int i) {
return (i & 1) != 0;
}
static boolean odd(long i) {
return (i & 1) != 0;
}
static boolean odd(BigInteger i) { return odd(toInt(i)); }
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 int shorten_default = 100;
static String shorten(CharSequence s) { return shorten(s, shorten_default); }
static String shorten(CharSequence s, int max) {
return shorten(s, max, "...");
}
static String shorten(CharSequence s, int max, String shortener) {
if (s == null) return "";
if (max < 0) return str(s);
return s.length() <= max ? str(s) : subCharSequence(s, 0, min(s.length(), max-l(shortener))) + shortener;
}
static String shorten(int max, CharSequence s) { return shorten(s, max); }
static int parseHexChar(char c) {
if (c >= '0' && c <= '9') return charDiff(c, '0');
if (c >= 'a' && c <= 'f') return charDiff(c, 'a')+10;
if (c >= 'A' && c <= 'F') return charDiff(c, 'A')+10;
return -1;
}
static String 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 List 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 Map 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 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 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 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 CharSequence subCharSequence(CharSequence s, int x) {
return subCharSequence(s, x, s == null ? 0 : s.length());
}
static CharSequence subCharSequence(CharSequence s, int x, int y) {
if (s == null) return null;
if (x < 0) x = 0;
if (x >= s.length()) return "";
if (y < x) y = x;
if (y > s.length()) y = s.length();
return s.subSequence(x, y);
}
static int min(int a, int b) {
return Math.min(a, b);
}
static long min(long a, long b) {
return Math.min(a, b);
}
static float min(float a, float b) { return Math.min(a, b); }
static float min(float a, float b, float c) { return min(min(a, b), c); }
static double min(double a, double b) {
return Math.min(a, b);
}
static double min(double[] c) {
double x = Double.MAX_VALUE;
for (double d : c) x = Math.min(x, d);
return x;
}
static float min(float[] c) {
float x = Float.MAX_VALUE;
for (float d : c) x = Math.min(x, d);
return x;
}
static byte min(byte[] c) {
byte x = 127;
for (byte d : c) if (d < x) x = d;
return x;
}
static short min(short[] c) {
short x = 0x7FFF;
for (short d : c) if (d < x) x = d;
return x;
}
static int min(int[] c) {
int x = Integer.MAX_VALUE;
for (int d : c) if (d < x) x = d;
return x;
}
static int charDiff(char a, char b) {
return (int) a-(int) b;
}
static int charDiff(String a, char b) {
return charDiff(stringToChar(a), b);
}
static int strL(String s) {
return s == null ? 0 : s.length();
}
static int lCharSequence(CharSequence s) {
return s == null ? 0 : s.length();
}
static WeakHasherMap symbol_map = new WeakHasherMap(new Hasher() {
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 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 char stringToChar(String s) {
if (l(s) != 1) throw fail("bad stringToChar: " + s);
return firstChar(s);
}
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 boolean emptyString(String s) {
return s == null || s.length() == 0;
}
static char firstChar(String s) {
return s.charAt(0);
}
static class MultiMap implements IMultiMap {
Map> data = new HashMap>();
int fullSize;
MultiMap() {}
MultiMap(boolean useTreeMap) { if (useTreeMap) data = new TreeMap(); }
MultiMap(MultiMap map) { putAll(map); }
MultiMap(Map> data) {
this.data = data;}
void put(A key, B value) { synchronized(data) {
List 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 values) { putAll(key, values); }
void addAllIfNotThere(A key, Collection 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 keys, B value) { synchronized(data) {
for (A key : unnullForIteration(keys))
put(key, value);
}}
void putAll(A key, Collection values) { synchronized(data) {
if (nempty(values)) getActual(key).addAll(values);
}}
void putAll(Iterable> pairs) { synchronized(data) {
for (Pair p : unnullForIteration(pairs))
put(p.a, p.b);
}}
void removeAll(A key, Collection values) { synchronized(data) {
for (B value : values)
remove(key, value);
}}
public List get(A key) { synchronized(data) {
List list = data.get(key);
return list == null ? Collections. emptyList() : list;
}}
List getOpt(A key) { synchronized(data) {
return data.get(key);
}}
List getAndClear(A key) { synchronized(data) {
List l = cloneList(data.get(key));
remove(key);
return l;
}}
// returns actual mutable live list
// creates the list if not there
List getActual(A key) { synchronized(data) {
List list = data.get(key);
if (list == null)
data.put(key, list = _makeEmptyList());
return list;
}}
void clean(A key) { synchronized(data) {
List list = data.get(key);
if (list != null && list.isEmpty()) {
fullSize -= l(list);
data.remove(key);
}
}}
final public Set keys(){ return keySet(); }
public Set keySet() { synchronized(data) {
return data.keySet();
}}
void remove(A key) { synchronized(data) {
fullSize -= l(this.getOpt(key));
data.remove(key);
}}
final void remove(Pair p){ removePair(p); }
void removePair(Pair p) {
if (p != null) remove(p.a, p.b);
}
void remove(A key, B value) { synchronized(data) {
List 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 list = get(key);
return list.isEmpty() ? null : list.get(0);
}}
void addAll(MultiMap map) { putAll(map); }
void putAll(MultiMap map) { synchronized(data) {
for (A key : map.keySet())
putAll(key, map.get(key));
}}
void putAll(Map map) { synchronized(data) {
if (map != null) for (Map.Entry 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 reverseGet(B b) { synchronized(data) {
List l = new ArrayList();
for (A key : data.keySet())
if (data.get(key).contains(b))
l.add(key);
return l;
}}
Map> asMap() { synchronized(data) {
return cloneMap(data);
}}
boolean isEmpty() { synchronized(data) { return data.isEmpty(); }}
// override in subclasses
List _makeEmptyList() {
return new ArrayList();
}
// returns live lists
Collection> allLists() {
synchronized(data) {
return new ArrayList(data.values());
}
}
Collection> values() { return allLists(); }
List allValues() {
return concatLists(data.values());
}
Object mutex() { return data; }
public String toString() { return "mm" + str(data); }
}
static interface Hasher {
int hashCode(A a);
boolean equals(A a, A b);
}
static interface IF1 {
B get(A a);
}
// 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);
}
}
/*
* @(#)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 extends AbstractMap implements Map {
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 {
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 hash;
/* Reference queue for cleared WeakKeys */
private ReferenceQueue super K> queue = new ReferenceQueue();
/* 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 WeakHashMap
with the given
* initial capacity and the given load factor.
*
* @param initialCapacity the initial capacity of the
* WeakHashMap
*
* @param loadFactor the load factor of the WeakHashMap
*
* @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(initialCapacity, loadFactor);
}
/**
* Constructs a new, empty WeakHashMap
with the given
* initial capacity and the default load factor, which is
* 0.75
.
*
* @param initialCapacity the initial capacity of the
* WeakHashMap
*
* @throws IllegalArgumentException If the initial capacity is less than
* zero
*/
public WeakHasherMap(int initialCapacity) {
hash = new HashMap(initialCapacity);
}
/**
* Constructs a new, empty WeakHashMap
with the default
* capacity and the default load factor, which is 0.75
.
*/
public WeakHasherMap() {
hash = new HashMap();
}
/**
* Constructs a new, empty WeakHashMap
with the default
* capacity and the default load factor, which is 0.75
.
* The WeakHashMap
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();
hasher = h;
}
/* -- Simple queries -- */
/**
* Returns the number of key-value mappings in this map.
* Note: In contrast to most implementations of the
* Map
interface, the time required by this operation is
* linear in the size of the map.
*/
/*@Pure*/
@Override
public int size() {
return entrySet().size();
}
/**
* Returns true
if this map contains no key-value mappings.
*/
/*@Pure*/
@Override
public boolean isEmpty() {
return entrySet().isEmpty();
}
/**
* Returns true
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 key
.
* If this map does not contain a value for this key, then return
* null
.
*
* @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 key
maps to the given
* value
. If the map previously contained a mapping for
* key
then that mapping is replaced and the previous value is
* returned.
*
* @param key the key that is to be mapped to the given
* value
* @param value the value to which the given key
is to be
* mapped
*
* @return the previous value to which this key was mapped, or
* null
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 key
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 null
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 implements Map.Entry {
private Map.Entry 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 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 e /* Object o*/) {
// if (! (o instanceof Map.Entry)) return false;
// Map.Entry e = (Map.Entry)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> {
Set> hashEntrySet = hash.entrySet();
@Override
public Iterator> iterator() {
return new Iterator>() {
Iterator> hashIterator = hashEntrySet.iterator();
Map.Entry next = null;
@Override
public boolean hasNext() {
while (hashIterator.hasNext()) {
Map.Entry 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(ent, k);
return true;
}
return false;
}
@Override
public Map.Entry next() {
if ((next == null) && !hasNext())
throw new NoSuchElementException();
Map.Entry 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> 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 e = (Map.Entry)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> i = hashEntrySet.iterator(); i.hasNext(); ) {
Map.Entry 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> entrySet = null;
/**
* Returns a Set
view of the mappings in this map.
*/
/*@SideEffectFree*/
@Override
public Set> 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();
}
}
// you still need to implement hasNext() and next()
static abstract class IterableIterator implements Iterator, Iterable {
public Iterator iterator() {
return this;
}
public void remove() {
unsupportedOperation();
}
}
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 Pair implements Comparable> {
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 p) {
if (p == null) return 1;
int i = ((Comparable) a).compareTo(p.a);
if (i != 0) return i;
return ((Comparable) b).compareTo(p.b);
}
}
static class Fail extends RuntimeException implements IFieldsToList{
Object[] objects;
Fail() {}
Fail(Object... objects) {
this.objects = objects;}public Object[] _fieldsToList() { return new Object[] {objects}; }
Fail(Throwable cause, Object... objects) {
super(cause);
this.objects = objects;
}
public String toString() { return joinNemptiesWithColon("Fail", commaCombine(getCause(), objects)); }
}
static interface IFieldsToList {
Object[] _fieldsToList();
}
interface IMultiMap {
public Set keySet();
public Collection get(A a);
public int size();
public int keyCount();
}
static Map putAll(Map a, Map extends A,? extends B> b) {
if (a != null && b != null) a.putAll(b);
return a;
}
static MultiMap putAll(MultiMap a, Map extends A,? extends B> b) {
if (a != null) a.putAll((Map) b);
return a;
}
static Map putAll(Map a, Object... b) {
if (a != null)
litmap_impl(a, b);
return a;
}
static void put(Map map, A a, B b) {
if (map != null) map.put(a, b);
}
static void put(List l, int i, A a) {
if (l != null && i >= 0 && i < l(l)) l.set(i, a);
}
// get purpose 1: access a list/array/map (safer version of x.get(y))
static A get(List l, int idx) {
return l != null && idx >= 0 && idx < l(l) ? l.get(idx) : null;
}
// seems to conflict with other signatures
/*static B get(Map map, A key) {
ret map != null ? map.get(key) : null;
}*/
static A get(A[] l, int idx) {
return idx >= 0 && idx < l(l) ? l[idx] : null;
}
// default to false
static boolean get(boolean[] l, int idx) {
return idx >= 0 && idx < l(l) ? l[idx] : false;
}
// get purpose 2: access a field by reflection or a map
static Object get(Object o, String field) {
try {
if (o == null) return null;
if (o instanceof Class) return get((Class) o, field);
if (o instanceof Map)
return ((Map) o).get(field);
Field f = getOpt_findField(o.getClass(), field);
if (f != null) {
makeAccessible(f);
return f.get(o);
}
if (o instanceof DynamicObject)
return getOptDynOnly(((DynamicObject) o), field);
} catch (Exception e) {
throw asRuntimeException(e);
}
throw new RuntimeException("Field '" + field + "' not found in " + o.getClass().getName());
}
static Object get_raw(String field, Object o) {
return get_raw(o, field);
}
static Object get_raw(Object o, String field) { try {
if (o == null) return null;
Field f = get_findField(o.getClass(), field);
makeAccessible(f);
return f.get(o);
} catch (Exception __e) { throw rethrow(__e); } }
static Object get(Class c, String field) {
try {
Field f = get_findStaticField(c, field);
makeAccessible(f);
return f.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static Field get_findStaticField(Class> c, String field) {
Class _c = c;
do {
for (Field f : _c.getDeclaredFields())
if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
return f;
_c = _c.getSuperclass();
} while (_c != null);
throw new RuntimeException("Static field '" + field + "' not found in " + c.getName());
}
static Field get_findField(Class> c, String field) {
Class _c = c;
do {
for (Field f : _c.getDeclaredFields())
if (f.getName().equals(field))
return f;
_c = _c.getSuperclass();
} while (_c != null);
throw new RuntimeException("Field '" + field + "' not found in " + c.getName());
}
static Object get(String field, Object o) {
return get(o, field);
}
static boolean get(BitSet bs, int idx) {
return bs != null && bs.get(idx);
}
static boolean nempty(Collection c) {
return !empty(c);
}
static boolean nempty(CharSequence s) {
return !empty(s);
}
static boolean nempty(Object[] o) { return !empty(o); }
static boolean nempty(byte[] o) { return !empty(o); }
static boolean nempty(int[] o) { return !empty(o); }
static boolean nempty(BitSet bs) { return !empty(bs); }
static boolean nempty(Map m) {
return !empty(m);
}
static boolean nempty(Iterator i) {
return i != null && i.hasNext();
}
static boolean nempty(IMultiMap mm) { return mm != null && mm.size() != 0; }
static boolean nempty(Object o) { return !empty(o); }
static boolean nempty(Rect r) { return r != null && r.w != 0 && r.h != 0; }
static void remove(List l, int i) {
if (l != null && i >= 0 && i < l(l))
l.remove(i);
}
static void remove(Collection l, A a) {
if (l != null) l.remove(a);
}
static B remove(Map map, Object a) {
return map == null ? null : map.remove(a);
}
static void remove(BitSet bs, int i) {
bs.clear(i);
}
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 getAndClear(IVar v) {
A a = v.get();
v.set(null);
return a;
}
static ArrayList cloneList(Iterable l) {
return l instanceof Collection ? cloneList((Collection) l) : asList(l);
}
static ArrayList cloneList(Collection l) {
if (l == null) return new ArrayList();
synchronized(collectionMutex(l)) {
return new ArrayList(l);
}
}
static Set keys(Map map) {
return map == null ? new HashSet() : map.keySet();
}
// convenience shortcut for keys_gen
static Set keys(Object map) {
return keys((Map) map);
}
static Set keys(IMultiMap mm) {
return mm.keySet();
}
static Set keySet(Map map) {
return map == null ? new HashSet() : map.keySet();
}
static Set keySet(Object map) {
return keys((Map) map);
}
static Set keySet(MultiMap mm) {
return mm.keySet();
}
static int keysSize(MultiMap mm) {
return lKeys(mm);
}
static A reverseGet(List l, int idx) {
if (l == null || idx < 0) return null;
int n = l(l);
return idx < n ? l.get(n-1-idx) : null;
}
static Map cloneMap(Map 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 List cloneMap(Iterable l, IF1 f) {
List x = emptyList(l);
if (l != null) for (A o : cloneList(l))
x.add(f.get(o));
return x;
}
static Collection values(Map map) {
return map == null ? emptyList() : map.values();
}
// convenience shortcut for values_gen
static Collection values(Object map) {
return values((Map) map);
}
static Collection values(MultiMap mm) {
return mm == null ? emptyList() : concatLists(values(mm.data));
}
static > List allValues(Map map) {
List out = new ArrayList();
for (var l : values(map))
addAll(out, l);
return out;
}
static List concatLists(Iterable... lists) {
List l = new ArrayList();
if (lists != null) for (Iterable list : lists)
addAll(l, list);
return l;
}
static List concatLists(Collection extends Iterable> lists) {
List l = new ArrayList();
if (lists != null) for (Iterable list : lists)
addAll(l, list);
return l;
}
static int _hashCode(Object a) {
return a == null ? 0 : a.hashCode();
}
static Iterator iterator(Iterable c) {
return c == null ? emptyIterator() : c.iterator();
}
static Method hashMap_findKey_method;
static A hashMap_findKey(HashMap map, Object key) { try {
if (hashMap_findKey_method == null)
hashMap_findKey_method = findMethodNamed(HashMap.class, "getNode");
Map.Entry entry = (Map.Entry) hashMap_findKey_method.invoke(map, hashMap_internalHash(key), key);
// java.util.Map.Entry 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 UnsupportedOperationException unsupportedOperation() {
throw new UnsupportedOperationException();
}
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 hashCodeFor(Object a) {
return a == null ? 0 : a.hashCode();
}
static String joinNemptiesWithColon(String... strings) {
return joinNempties(": ", strings);
}
static String joinNemptiesWithColon(Collection strings) {
return joinNempties(": ", strings);
}
static String commaCombine(Object... l) {
return joinNemptiesWithComma(flattenCollectionsAndArrays(ll(l)));
}
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 Field getOpt_findField(Class> c, String field) {
Class _c = c;
do {
for (Field f : _c.getDeclaredFields())
if (f.getName().equals(field))
return f;
_c = _c.getSuperclass();
} while (_c != null);
return null;
}
static Field makeAccessible(Field f) {
try {
f.setAccessible(true);
} catch (Throwable e) {
// Note: The error reporting only works with Java VM option --illegal-access=deny
vmBus_send("makeAccessible_error", e, f);
}
return f;
}
static Method makeAccessible(Method m) {
try {
m.setAccessible(true);
} catch (Throwable e) {
vmBus_send("makeAccessible_error", e, m);
}
return m;
}
static Constructor makeAccessible(Constructor c) {
try {
c.setAccessible(true);
} catch (Throwable e) {
vmBus_send("makeAccessible_error", e, c);
}
return c;
}
static Object getOptDynOnly(DynamicObject o, String field) {
if (o == null || o.fieldValues == null) return null;
return o.fieldValues.get(field);
}
//static final Map> 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> getOpt_cache = _registerDangerousWeakMap(synchroMap(new getOpt_Map()));
//static final Map> 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 getOpt_getFieldMap(Object o) {
Class c = _getClass(o);
HashMap 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 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 getOpt_makeCache(Class c) {
HashMap 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;
}
// 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 int lKeys(MultiMap mm) {
return mm == null ? 0 : mm.keysSize();
}
static void addAll(Collection c, Iterable b) {
if (c != null && b != null) for (A a : b) c.add(a);
}
static boolean addAll(Collection c, Collection b) {
return c != null && b != null && c.addAll(b);
}
static boolean addAll(Collection c, B... b) {
return c != null && b != null && c.addAll(Arrays.asList(b));
}
static Map addAll(Map a, Map extends A,? extends B> b) {
if (a != null && b != null) a.putAll(b);
return a;
}
static A addAll(A c, Collection extends Component> components) {
return addComponents(c, components);
}
static A addAll(A c, Component... components) {
return addComponents(c, components);
}
static Iterator emptyIterator() {
return Collections.emptyIterator();
}
// 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 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 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 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 void clear(Collection c) {
if (c != null) c.clear();
}
static void clear(Map map) {
if (map != null) map.clear();
}
static List _registerDangerousWeakMap_preList;
static A _registerDangerousWeakMap(A map) {
return _registerDangerousWeakMap(map, null);
}
static A _registerDangerousWeakMap(A map, Object init) {
callF(init, map);
if (init instanceof String) {
final String f = (String) init;
init = new VF1