import java.util.*;
import java.util.zip.*;
import java.util.List;
import java.util.regex.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;
import java.util.function.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.table.*;
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import java.lang.ref.*;
import java.lang.management.*;
import java.security.*;
import java.security.spec.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.imageio.*;
import java.math.*;
import java.time.Duration;
import java.lang.invoke.VarHandle;
import java.lang.invoke.MethodHandles;
import java.awt.geom.*;
import static x30_pkg.x30_util.DynamicObject;
import java.text.*;
import java.text.NumberFormat;
import java.util.TimeZone;
class main {
static class HuffmanByteArray implements BitIO {
HuffmanByteArray() {}
final public HuffmanByteArray setData(byte[] data){ return data(data); }
public HuffmanByteArray data(byte[] data) { this.data = data; return this; } final public byte[] getData(){ return data(); }
public byte[] data() { return data; }
byte[] data;
final public int[] getHistogram(){ return histogram(); }
public int[] histogram() { return histogram; }
int[] histogram;
final public HuffmanTreeMaker getTm(){ return tm(); }
public HuffmanTreeMaker tm() { return tm; }
HuffmanTreeMaker tm;
final public HuffmanTree getTree(){ return tree(); }
public HuffmanTree tree() { return tree; }
HuffmanTree tree = new HuffmanTree();
// options
final public HuffmanByteArray setStartWithMagicCode(boolean startWithMagicCode){ return startWithMagicCode(startWithMagicCode); }
public HuffmanByteArray startWithMagicCode(boolean startWithMagicCode) { this.startWithMagicCode = startWithMagicCode; return this; } final public boolean getStartWithMagicCode(){ return startWithMagicCode(); }
public boolean startWithMagicCode() { return startWithMagicCode; }
boolean startWithMagicCode = false;
final public HuffmanByteArray setMagicCode(String magicCode){ return magicCode(magicCode); }
public HuffmanByteArray magicCode(String magicCode) { this.magicCode = magicCode; return this; } final public String getMagicCode(){ return magicCode(); }
public String magicCode() { return magicCode; }
String magicCode = "HUF1";
HuffmanByteArray(byte[] data) {
this.data = data;}
public void readWrite(BitHead head) {
if (head.writeMode() && histogram == null) {
histogram = byteHistogramArray(data);
tm = new HuffmanTreeMaker();
tm.importByteHistogram(histogram);
tree = tm.get();
}
// Part 1 (magic code)
if (startWithMagicCode)
head.exchangeASCII(magicCode);
// Part 2 (Huffman character tree)
tree.readWrite(head);
// Part 3a (size)
new VariableSizeUIntForBitHead().chunkSize(3).readWrite(head, () -> data.length, size -> data = new byte[size]);
//print("length " + data.length);
// Part 3 (text)
if (head.writeMode()) {
for (var b : unnullForIteration(data))
tree.writeCharacter(b, head);
}
if (head.readMode()) {
for (int i = 0; i < l(data); i++) {
if (head.isEOF()) break;
data[i] = toUByte(tree.readSymbol(head));
}
}
}
byte[] get() { return data; }
// update selfType
public HuffmanByteArray fromByteArray(byte[] data) { BitIO.super.fromByteArray(data); return this; }
public HuffmanByteArray load(byte[] data) { BitIO.super.load(data); return this; }
}
static int[] byteHistogramArray(byte[] data) {
var histogram = new int[256];
if (data != null)
for (var b : data)
histogram[b & 0xFF]++;
return histogram;
}
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 byte[] unnullForIteration(byte[] l) { return l == null ? emptyByteArray() : 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 l(Object[] a) { return a == null ? 0 : a.length; }
static int l(boolean[] a) { return a == null ? 0 : a.length; }
static int l(byte[] a) { return a == null ? 0 : a.length; }
static int l(short[] a) { return a == null ? 0 : a.length; }
static int l(long[] a) { return a == null ? 0 : a.length; }
static int l(int[] a) { return a == null ? 0 : a.length; }
static int l(float[] a) { return a == null ? 0 : a.length; }
static int l(double[] a) { return a == null ? 0 : a.length; }
static int l(char[] a) { return a == null ? 0 : a.length; }
static int l(Collection c) { return c == null ? 0 : c.size(); }
static int l(Iterator i) { return iteratorCount_int_close(i); } // consumes the iterator && closes it if possible
static int l(Map m) { return m == null ? 0 : m.size(); }
static int l(CharSequence s) { return s == null ? 0 : s.length(); }
static long l(File f) { return f == null ? 0 : f.length(); }
static int l(IMultiMap mm) { return mm == null ? 0 : mm.size(); }
static int l(AppendableChain a) { return a == null ? 0 : a.size; }
static int l(IntSize o) { return o == null ? 0 : o.size(); }
static byte toUByte(int i) {
if (!isUByte(i))
throw fail("Not a u-byte: " + i);
return (byte) i;
}
static List immutableEmptyList() {
return Collections.emptyList();
}
static byte[] emptyByteArray_a = new byte[0];
static byte[] emptyByteArray() { return emptyByteArray_a; }
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 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 boolean isUByte(int i) {
return (i & 0xFF) == i;
}
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 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 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 RuntimeException asRuntimeException(Throwable t) {
if (t instanceof Error)
_handleError((Error) t);
return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t);
}
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 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 void _handleError(Error e) {
//call(javax(), '_handleError, e);
}
interface BitIO {
void readWrite(BitHead head);
default byte[] saveAsByteArray(){ return saveToByteArray(); }
default byte[] toByteArray(){ return saveToByteArray(); }
default byte[] saveToByteArray() { return saveToByteArray(new BitHead()); }
default byte[] saveAsByteArray(BitHead head){ return saveToByteArray(head); }
default byte[] toByteArray(BitHead head){ return saveToByteArray(head); }
default byte[] saveToByteArray(BitHead head) {
var baos = byteArrayOutputStream();
head.outputStream(baos);
readWrite(head);
head.finish();
return baos.toByteArray();
}
default String toHexString() {
return main.toHexString(toByteArray());
}
default File saveToFile(File file) {
OutputStream out = bufferedFileOutputStream(file); try {
var head = new BitHead(out);
readWrite(head);
head.finish();
return file;
} finally { _close(out); }}
default BitIO fromByteArray(byte[] data){ return load(data); }
default BitIO load(byte[] data) {
readWrite(new BitHead(new ByteArrayInputStream(data)));
return this;
}
default BitIO load(File file) {
InputStream in = bufferedInputStream(file); try {
readWrite(new BitHead(in));
return this;
} finally { _close(in); }}
}
static class BitHead extends ByteHead {
final public int getAlign(){ return align(); }
public int align() { return align; }
int align = 8;
final public int getCurrentByte(){ return currentByte(); }
public int currentByte() { return currentByte; }
int currentByte;
final public BitHead setDebug(boolean debug){ return debug(debug); }
public BitHead debug(boolean debug) { this.debug = debug; return this; } final public boolean getDebug(){ return debug(); }
public boolean debug() { return debug; }
boolean debug = false;
BitHead() {}
BitHead(InputStream inputStream) { super(inputStream); }
BitHead(OutputStream outputStream) { super(outputStream); }
boolean writeArraysSlowly() { return false; }
// choose fast or slow version depending on alignment
void write(byte[] data) {
if (align == 0 && !writeArraysSlowly())
super.write(data);
else
for (var b : data) write(b);
}
void writeByte(int i) {
i &= 0xFF;
if (debug) print("writeByte: align=" + align + ", byteCounter=" + byteCounter + ", value=" + i);
if (align == 0)
super.writeByte(i);
else {
currentByte |= i << align;
super.writeByte(currentByte);
currentByte = i >> (8-align);
}
}
void writePartialByte(int i, int bits) {
for (int bit = 0; bit < bits; bit++) {
writeBit(i);
i >>>= 1;
}
}
void writeBit(int i) {
writeBit((i & 1) != 0);
}
void writeBit(boolean b) {
align &= 7;
if (b) currentByte |= 1 << align;
if (align == 7) {
super.writeByte(currentByte);
currentByte = align = 0;
} else
++align;
}
int readPartialByte(int bits) {
int value = 0;
for (int bit = 0; bit < bits; bit++)
if (readBit()) value |= 1 << bit;
return isEOF() ? -1 : value;
}
int readByte() {
if (debug) print("readByte: align=" + align + ", byteCounter=" + byteCounter);
int value;
if (align == 0) {
value = currentByte;
align = 8;
} else if (align == 8)
value = super.readByte();
else {
value = currentByte >> align;
int align = this.align;
bufferNextByte();
if (isEOF()) return -1;
this.align = align;
value |= (currentByte << (8-align)) & 0xFF;
}
if (debug) print("value: " + value);
return value;
}
boolean readBit() {
boolean bitSet = peekBit();
advanceBit();
return bitSet;
}
boolean peekBit() {
if (currentByte < 0)
throw fail("eof");
if (align == 8)
bufferNextByte();
return (currentByte & (1 << align)) != 0;
}
void bufferNextByte() {
currentByte = super.readByte();
align = 0;
}
void advanceBit() {
if (currentByte < 0) return;
if (align == 8)
bufferNextByte();
++align;
}
boolean byteAligned() {
return (align & 7) == 0;
}
final void flushBits(){ completeByte(); }
final void finishByte(){ completeByte(); }
final void finish(){ completeByte(); }
void completeByte() { completeByte(false); }
final void flushBits(boolean padWithOnes){ completeByte(padWithOnes); }
final void finishByte(boolean padWithOnes){ completeByte(padWithOnes); }
final void finish(boolean padWithOnes){ completeByte(padWithOnes); }
void completeByte(boolean padWithOnes) {
if (debug) print("Finishing byte " + byteCounter + " (align " + align + ")");
if (byteAligned()) return;
if (writeMode()) {
if (padWithOnes) currentByte |= 0xFF << align;
super.writeByte(currentByte);
}
currentByte = 0;
align = readMode() ? 8 : 0;
}
// TODO: switch to more compact version saving 5 bits on average
final void trailingBitCount(){ writeTrailingBitCount(); }
void writeTrailingBitCount() { writeTrailingBitCount(false); }
final void trailingBitCount(boolean padWithOnes){ writeTrailingBitCount(padWithOnes); }
void writeTrailingBitCount(boolean padWithOnes) {
if (!writeMode()) return;
int bitCount = modRange_incl(align(), 1, 8);
completeByte(padWithOnes);
writeByte(bitCount);
}
void exchange(BitIO writable) {
if (writable != null) writable.readWrite(this);
}
void exchangeBit(IF0 getter, IVF1 setter) {
if (writeMode())
writeBit(getter.get());
if (readMode())
setter.get(readBit());
}
void exchangeBit(int i) { exchangeBit(odd(i)); }
void exchangeBit(boolean i) {
exchangeBit(() -> i, j -> assertEquals(i, j));
}
void exchange(BitIO getter, Runnable setter) {
if (writeMode())
getter.readWrite(this);
if (readMode())
setter.run();
}
boolean isEOF() { return currentByte < 0; }
}
// unsigned values only
static class VariableSizeUIntForBitHead implements BitIO {
// How many bits are written at once
final public VariableSizeUIntForBitHead setChunkSize(int chunkSize){ return chunkSize(chunkSize); }
public VariableSizeUIntForBitHead chunkSize(int chunkSize) { this.chunkSize = chunkSize; return this; } final public int getChunkSize(){ return chunkSize(); }
public int chunkSize() { return chunkSize; }
int chunkSize = 3;
final public VariableSizeUIntForBitHead setValue(int value){ return value(value); }
public VariableSizeUIntForBitHead value(int value) { this.value = value; return this; } final public int getValue(){ return value(); }
public int value() { return value; }
int value;
final public int getBitCount(){ return bitCount(); }
public int bitCount() { return bitCount; }
int bitCount;
public void readWrite(BitHead head) {
if (head.writeMode()) {
bitCount = max(1, numberOfBits(value));
int value = this.value;
head.writePartialByte(value, chunkSize);
value >>>= chunkSize;
while (value != 0) {
head.writeBit(1);
head.writePartialByte(value, chunkSize);
value >>>= chunkSize;
}
head.writeBit(0);
}
if (head.readMode()) {
int value = head.readPartialByte(chunkSize);
int shift = 0;
bitCount = chunkSize;
while (!head.isEOF() && head.readBit()) {
value |= head.readPartialByte(chunkSize) << bitCount;
bitCount += chunkSize;
}
value(value);
}
}
void readWrite(BitHead head, IF0 getter, IVF1 setter) {
if (head.writeMode())
value(getter.get());
readWrite(head);
if (head.readMode())
setter.get(value());
}
}
static interface Hasher {
int hashCode(A a);
boolean equals(A a, A b);
}
// 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);
}
}
// Builds a HuffmanTree from a byte histogram
static class HuffmanTreeMaker {
// output
final public HuffmanTree getTree(){ return tree(); }
public HuffmanTree tree() { return tree; }
HuffmanTree tree = new HuffmanTree();
// internal
TreeSet pool = new TreeSet(); // combinable trees sorted by incidence (symbol frequency)
int indexCounter; // counter to keep order deterministic
class Head implements Comparable {
int index = indexCounter++;
final public Head setNode(HuffmanTree.Node node){ return node(node); }
public Head node(HuffmanTree.Node node) { this.node = node; return this; } final public HuffmanTree.Node getNode(){ return node(); }
public HuffmanTree.Node node() { return node; }
HuffmanTree.Node node;
final public Head setFreq(double freq){ return freq(freq); }
public Head freq(double freq) { this.freq = freq; return this; } final public double getFreq(){ return freq(); }
public double freq() { return freq; }
double freq; // using floating point to keep things futureproof
// sort by frequency first, then by index (order of creation)
public int compareTo(Head h) {
if (this == h) return 0;
var c = cmp(freq, h.freq);
if (c != 0) return c;
return cmp(index, h.index);
}
public String toString() { return freq + "x " + node; }
}
// histogram[0] = incidence for byte 00
// histogram[1] = incidence for byte 01
// etc. up to 255 (or less)
HuffmanTreeMaker importByteHistogram(int[] histogram) {
int n = l(histogram);
for (int i = 0; i < n; i++) {
double freq = histogram[i];
if (freq != 0)
pool.add(new Head().freq(freq).node(tree.new Leaf(i)));
}
return this;
}
public void run() { try {
while (l(pool) > 1) { ping();
var it = iterator(pool);
var a = it.next();
it.remove();
var b = it.next();
it.remove();
//print("Merging frequencies: " + commaCombine(a.freq, b.freq));
var newHead = new Head()
.freq(a.freq+b.freq)
.node(tree.new Branch().zero(b.node).one(a.node));
//print("Merging: " + commaCombine(a, b) + " to " + newHead);
pool.add(newHead);
}
} catch (Exception __e) { throw rethrow(__e); } }
HuffmanTree get() {
run();
if (nempty(pool))
tree.root(popFirst(pool).node());
return tree;
}
}
/*
* @(#)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();
}
}
static class HuffmanTree implements BitIO {
final public HuffmanTree setRoot(Node root){ return root(root); }
public HuffmanTree root(Node root) { this.root = root; return this; } final public Node getRoot(){ return root(); }
public Node root() { return root; }
Node root;
Leaf[] characterMap = new Leaf[256];
final public HuffmanTree setDebug(boolean debug){ return debug(debug); }
public HuffmanTree debug(boolean debug) { this.debug = debug; return this; } final public boolean getDebug(){ return debug(); }
public boolean debug() { return debug; }
boolean debug = false;
/*
algebraic Node {
constructor Branch {
settable Node zero;
settable Node one;
}
constructor Leaf {
settable byte literal;
}
}
*/
abstract class Node implements BitIO {
final public Node setParent(Branch parent){ return parent(parent); }
public Node parent(Branch parent) { this.parent = parent; return this; } final public Branch getParent(){ return parent(); }
public Branch parent() { return parent; }
Branch parent;
void buildSymbol(BitBuffer head, Node child) {
{ if (parent != null) parent.buildSymbol(head, this); }
}
abstract Leaf readSymbol(BitHead head);
}
class Branch extends Node {
final public Node getZero(){ return zero(); }
public Node zero() { return zero; }
Node zero;
final public Node getOne(){ return one(); }
public Node one() { return one; }
Node one;
Branch zero(Node zero) {
this.zero = zero;
zero.parent = this;
return this;
}
Branch one(Node one) {
this.one = one;
one.parent = this;
return this;
}
public void readWrite(BitHead head) {
head.exchangeBit(1);
head.exchange(zero, () -> zero(readNode(head)));
head.exchange(one, () -> one(readNode(head)));
}
void buildSymbol(BitBuffer head, Node child) {
head.add(child == one);
super.buildSymbol(head, child);
}
Leaf readSymbol(BitHead head) {
boolean bit = head.readBit();
if (debug) print("Read bit " + bit);
return (bit ? one : zero).readSymbol(head);
}
}
class Leaf extends Node {
final public byte getLiteral(){ return literal(); }
public byte literal() { return literal; }
byte literal;
Leaf() {}
Leaf(int literal) { literal((byte) literal); }
byte get() { return literal; }
Leaf literal(byte b) {
literal = b;
fillCharacterMap();
return this;
}
public void readWrite(BitHead head) {
head.exchangeBit(0);
head.exchangeByte(literal, __3 -> literal(__3));
if (debug) print(this);
}
void fillCharacterMap() {
characterMap[ubyteToInt(literal)] = this;
}
Leaf readSymbol(BitHead head) {
if (debug) print("Reached symbol " + this);
return this;
}
public String toString() {
return "Literal " + byteToHex(literal);
}
}
void makeRootIfEmpty() {
if (root == null) root = new Leaf(0);
}
Node readNode(BitHead head) {
boolean type = head.peekBit();
Node node = type ? new Branch() : new Leaf();
if (debug) print(type ? "Branch" : "Leaf");
node.readWrite(head);
return node;
}
public void readWrite(BitHead head) {
if (head.writeMode()) {
makeRootIfEmpty();
root.readWrite(head);
}
if (head.readMode())
root = readNode(head);
}
public String toString() {
makeRootIfEmpty();
List out = new ArrayList();
StringBuilder symbol = new StringBuilder();
final class _C_1 { void toString_collect(Node node) {
if (node instanceof Leaf) {
out.add(symbol + ": " + /*ubyteToHex*/quote(codePoint(ubyteToInt(((Leaf) node).get()))));
} else {
symbol.append("0");
toString_collect(((Branch) node).zero());
removeLast(symbol);
symbol.append("1");
toString_collect(((Branch) node).one());
removeLast(symbol);
}
} } final _C_1 __2 = new _C_1();
__2.toString_collect(root);
return lines(out);
}
final void writeSymbol(int sym, BitHead head){ writeCharacter(sym, head); }
void writeCharacter(int sym, BitHead head) {
sym &= 0xFF;
Leaf node = characterMap[sym];
if (node == null)
throw fail("Symbol cannot be encoded: " + sym);
BitBuffer buffer = new BitBuffer();
node.buildSymbol(buffer, null);
// when there is only one symbol
if (buffer.isEmpty())
buffer.add(0);
for (int i = l(buffer)-1; i >= 0; i--)
head.writeBit(buffer.get(i));
}
int readSymbol(BitHead head) {
if (head.isEOF()) return -1;
if (root instanceof Leaf)
head.advanceBit(); // don't even care
return ubyteToInt(root.readSymbol(head).get());
}
}
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", getMessage()); }
public String getMessage() { return commaCombine(getCause(), objects); }
}
public static interface IF0 {
A get();
}
static interface IFieldsToList {
Object[] _fieldsToList();
}
static interface IVF1 {
void get(A a);
}
// We use big-endian as DataOutputStream does
// (TODO: decide on an endianness!)
static class ByteHead /*is DataOutput*/ {
final public ByteHead setReadMode(boolean readMode){ return readMode(readMode); }
public ByteHead readMode(boolean readMode) { this.readMode = readMode; return this; } final public boolean getReadMode(){ return readMode(); }
public boolean readMode() { return readMode; }
boolean readMode = false;
final public ByteHead setWriteMode(boolean writeMode){ return writeMode(writeMode); }
public ByteHead writeMode(boolean writeMode) { this.writeMode = writeMode; return this; } final public boolean getWriteMode(){ return writeMode(); }
public boolean writeMode() { return writeMode; }
boolean writeMode = false;
final public InputStream getInputStream(){ return inputStream(); }
public InputStream inputStream() { return inputStream; }
InputStream inputStream;
final public OutputStream getOutputStream(){ return outputStream(); }
public OutputStream outputStream() { return outputStream; }
OutputStream outputStream;
final public ByteHead setByteCounter(long byteCounter){ return byteCounter(byteCounter); }
public ByteHead byteCounter(long byteCounter) { this.byteCounter = byteCounter; return this; } final public long getByteCounter(){ return byteCounter(); }
public long byteCounter() { return byteCounter; }
long byteCounter;
ByteHead() {}
ByteHead(InputStream inputStream) { inputStream(inputStream); }
ByteHead(OutputStream outputStream) { outputStream(outputStream); }
ByteHead inputStream(InputStream inputStream) { this.inputStream = inputStream; readMode(true); return this; }
ByteHead outputStream(OutputStream outputStream) { this.outputStream = outputStream; writeMode(true); return this; }
void write(byte[] data) { try {
ensureWriteMode();
{ if (outputStream != null) outputStream.write(data); }
byteCounter += data.length;
} catch (Exception __e) { throw rethrow(__e); } }
void writeLong(long l) {
writeInt((int) (l >> 32));
writeInt((int) l);
}
void writeInt(int i) {
write(i >> 24);
write(i >> 16);
write(i >> 8);
write(i);
}
void writeShort(int i) {
write(i >> 8);
write(i);
}
final void write(int i){ writeByte(i); }
void writeByte(int i) { try {
ensureWriteMode();
{ if (outputStream != null) outputStream.write(i); }
byteCounter++;
} catch (Exception __e) { throw rethrow(__e); } }
void writeASCII(char c) {
write(toASCII(c));
}
void writeASCII(String s) {
write(toASCII(s));
}
void exchangeASCII(String s) {
exchangeConstantBytes(toASCII(s));
}
void exchangeConstantBytes(byte[] data) {
for (int i = 0; i < l(data); i++)
exchangeByte(data[i]);
}
long readLong() {
long i = readInt() << 32;
return i | (readInt() & 0xFFFFFFFFL);
}
int readInt() {
int i = read() << 24;
i |= read() << 16;
i |= read() << 8;
return i | read();
}
short readShort() {
int i = read() << 8;
return (short) (i | read());
}
// -1 for EOF
final int read(){ return readByte(); }
int readByte() { try {
ensureReadMode();
++byteCounter;
return inputStream.read();
} catch (Exception __e) { throw rethrow(__e); } }
void ensureReadMode() {
if (!readMode) throw fail("Not in read mode");
}
void ensureWriteMode() {
if (!writeMode) throw fail("Not in write mode");
}
// exchange = read or write depending on mode
void exchangeByte(byte getter, IVF1 setter) {
exchangeByte(() -> getter, setter);
}
void exchangeByte(IF0 getter, IVF1 setter) {
if (writeMode())
writeByte(getter.get());
if (readMode())
setter.get(toUByte(readByte()));
}
void exchangeShort(IF0 getter, IVF1 setter) {
if (writeMode())
writeShort(getter.get());
if (readMode())
setter.get(readShort());
}
void exchangeLong(IVar var) {
exchangeLong(var.getter(), var.setter());
}
void exchangeLong(IF0 getter, IVF1 setter) {
if (writeMode())
writeLong(getter.get());
if (readMode())
setter.get(readLong());
}
void exchangeByte(byte i) {
exchangeByte(() -> i, j -> assertEquals(i, j));
}
void exchangeInt(int i) {
exchangeInt(() -> i, j -> assertEquals(i, j));
}
void exchangeInt(IF0 getter, IVF1 setter) {
if (writeMode())
writeInt(getter.get());
if (readMode())
setter.get(readInt());
}
void exchange(ByteIO writable) {
if (writable != null) writable.readWrite(this);
}
void exchangeAll(Iterable extends ByteIO> writables) {
if (writables != null)
for (var writable : writables)
exchange(writable);
}
// write size in bytes of element first (as int),
// then the element itself.
// upon reading, size is actually ignored.
void exchangeWithSize(ByteIO writable) {
if (writeMode()) {
byte[] data = writable.saveToByteArray();
writeInt(l(data));
write(data);
}
if (readMode()) {
int n = readInt();
writable.readWrite(this);
}
}
void finish() {}
}
// little-endian
static class BitBuffer implements IntSize {
ByteBuffer byteBuffer = new ByteBuffer(); // TODO (optimization): use a growing circular buffer
int currentByte, currentBit;
void add(int b) {
add(odd(b));
}
void add(boolean b) {
if (b) currentByte |= 1 << currentBit;
if (currentBit == 7) {
byteBuffer.add((byte) currentByte);
currentByte = 0;
currentBit = 0;
} else
++currentBit;
}
boolean get(int iBit) {
int iByte = iBit >> 3;
int theByte = iByte == byteBuffer.size()
? currentByte
: byteBuffer.get(iByte);
return (theByte & (1 << (iBit & 7))) != 0;
}
boolean hasFullByte() {
return !byteBuffer.isEmpty();
}
byte popFullByte() {
return byteBuffer.popFirst();
}
public int size() {
return byteBuffer.size()*8 + currentBit;
}
boolean isEmpty() { return size() == 0; }
// TODO: optimize void writeBits(int data, int nBits)
}
interface ByteIO {
void readWrite(ByteHead head);
default byte[] saveAsByteArray(){ return saveToByteArray(); }
default byte[] toByteArray(){ return saveToByteArray(); }
default byte[] saveToByteArray() { return saveToByteArray(new ByteHead()); }
default byte[] saveAsByteArray(ByteHead head){ return saveToByteArray(head); }
default byte[] toByteArray(ByteHead head){ return saveToByteArray(head); }
default byte[] saveToByteArray(ByteHead head) {
var baos = byteArrayOutputStream();
head.outputStream(baos);
readWrite(head);
head.finish();
return baos.toByteArray();
}
default String toHexString() {
return main.toHexString(toByteArray());
}
default File saveToFile(File file) {
OutputStream out = bufferedFileOutputStream(file); try {
var head = new ByteHead(out);
readWrite(head);
head.finish();
return file;
} finally { _close(out); }}
default ByteIO fromByteArray(byte[] data){ return load(data); }
default ByteIO load(byte[] data) {
readWrite(new ByteHead(new ByteArrayInputStream(data)));
return this;
}
default ByteIO load(File file) {
InputStream in = bufferedInputStream(file); try {
readWrite(new ByteHead(in));
return this;
} finally { _close(in); }}
}
interface IntSize {
int size();
}
static interface IVar extends IF0 {
void set(A a);
A get();
// reified type of value (if available)
default Class getType() { return null; }
default IF0 getter() { return () -> get(); }
default IVF1 setter() { return __1 -> set(__1); }
default boolean has() { return get() != null; }
default void clear() { set(null); }
}
static class ByteBuffer implements Iterable {
byte[] data;
int size;
ByteBuffer() {}
ByteBuffer(int size) { if (size != 0) data = new byte[size]; }
ByteBuffer(Iterable l) {
if (l instanceof Collection) allocate(((Collection) l).size());
addAll(l);
}
ByteBuffer(byte[] data) { this.data = data; size = l(data); }
// TODO *(ByteBuffer buf) { ... }
void add(int idx, int i) {
add(0);
arraycopy(data, idx, data, idx+1, size-(idx+1));
data[idx] = (byte) i;
}
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 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 toList() {
return byteArrayToList(data, 0, size);
}
List asVirtualList() {
return new RandomAccessAbstractList() {
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 iterator() {
return new IterableIterator() {
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));
}
}
// you still need to implement hasNext() and next()
static abstract class IterableIterator implements Iterator, Iterable {
public Iterator iterator() {
return this;
}
public void remove() {
unsupportedOperation();
}
}
abstract static class RandomAccessAbstractList extends AbstractList implements RandomAccess {
}
static ByteArrayOutputStream byteArrayOutputStream() {
return new ByteArrayOutputStream();
}
static String toHexString(byte[] a) {
return bytesToHex(a);
}
static String toHexString(List a) {
return bytesToHex(byteListToArray(a));
}
static byte[] toByteArray(ByteArrayOutputStream baos) {
return baos == null ? null : baos.toByteArray();
}
static byte[] toByteArray(Iterator extends Number> it) {
ByteBuffer buf = new ByteBuffer();
while (it.hasNext())
buf.add((byte) it.next().intValue());
return buf.toByteArray();
}
static byte[] toByteArray(Collection extends Number> it) {
int n = l(it), i = 0;
byte[] a = new byte[n];
for (var x : it)
a[i++] = (byte) x.intValue();
return a;
}
static byte[] toByteArray(Object o) {
if (o == null) return null;
if (o instanceof byte[]) return ((byte[]) o);
if (o instanceof Iterator)
return toByteArray((Iterator) o);
if (o instanceof Collection)
return toByteArray((Collection) ((Collection) o));
// not sure what else to put here
throw fail("toByteArray", o);
}
static BufferedOutputStream bufferedFileOutputStream(File f) { try {
return bufferedOutputStream(newFileOutputStream(f));
} catch (Exception __e) { throw rethrow(__e); } }
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 Object load(String varName) {
readLocally(varName);
return get(mc(), varName);
}
static Object load(String progID, String varName) {
readLocally(progID, varName);
return get(mc(), varName);
}
static int bufferedInputStream_bufferSize = 65536;
static BufferedInputStream bufferedInputStream(int bufSize, File f) { try {
return bufferedInputStream(bufSize, newFileInputStream(f));
} catch (Exception __e) { throw rethrow(__e); } }
static BufferedInputStream bufferedInputStream(File f) { try {
return bufferedInputStream(newFileInputStream(f));
} catch (Exception __e) { throw rethrow(__e); } }
static BufferedInputStream bufferedInputStream(InputStream in) {
return new BufferedInputStream(in, bufferedInputStream_bufferSize);
}
static BufferedInputStream bufferedInputStream(int bufSize, InputStream in) {
return new BufferedInputStream(in, bufSize);
}
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