java/ch/wlkl/env/All2S.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package ch.wlkl.env;
import static ch.wlkl.env.Env.env;
import static ch.wlkl.env.Ut.addWords;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Set;
import java.util.List;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Map;
/**
*
* @author walter
*/
public class All2S {
final IdentityHashMap<Object, Entry> o2e = new IdentityHashMap();
List<Entry> todo = new ArrayList();
final Set<String> deep;
int depth = 99;
int showDef = 1;
boolean afterClear = false;
StringBuilder b = new StringBuilder();
String afterPref = "\n\t";
int printStackTrace = 1; // 0 do not print, 1 print to sysout, 2 send to normal message
public All2S () {
addNO(null, "null");
deep = addWords(new HashSet(), "boolean byte short int long float d(ouble Boolean Byte Short Integer Long Float Double String ");
}
/**
* format one object, expand children downto level showdef, add childern to todo
* @param o
*/
protected void o2s(Object o) {
if (o == null || o instanceof CharSequence || o instanceof Number || o instanceof Class) {
b.append(o);
} else {
Entry e = name(o);
if (e.toDep < depth) {
e.toDep = depth;
todo.add(e);
}
b.append(e.name);
if (e.defDep < 1) {
b.append('=');
if (! define(e))
b.setLength(b.length()-1);
}
}
}
/**
* add the formatted text for a2s list of objects and Strings, add a2s space between objects, but not before/after strings
* @param l
*/
public void a2s(Object... l) {
if (l == null || l.length == 0)
return;
boolean afStr = true;
for(Object o: l) {
if (o instanceof String) {
b.append(o);
afStr = true;
} else {
if (! afStr) {
b.append(' ');
} else {
afStr = false;
}
o2s(o);
}
}
}
/**
* add the formatted text of the list of objects and Strings, add missing defines, return content and empty buffer
* @param l
*/
public String a2r(Object... l) {
return pa2r(afterPref, l);
}
/**
* add the formatted text of the list of objects and Strings, add missing defines, return content and empty buffer
* @param l
*/
public String pa2r(String ap, Object... l) {
a2s(l);
after(ap);
return flush();
}
/**
* format all (recursively) missing definitions
* @param pref string to prepend before each definition
*/
public void after(String pref) {
assert showDef >= 1;
int oldDep = depth;
for (int i = 0; i < todo.size(); i++) {
Entry e = todo.get(i);
if (e.toDep > 0 && e.defDep < e.toDep ) {
depth = e.toDep;
if (e.defDep < 1) {
b.append(pref).append(e.name).append("=");
if ( ! define(e))
b.setLength(b.length()-1);
} else {
int oLen = b.length();
define(e);
b.setLength(oLen);
}
}
}
if (afterClear) {
for (Entry e : todo) {
if (e.defDep != Integer.MAX_VALUE)
e.toDep = e.defDep = -9;
}
}
todo.clear();
depth = oldDep;
}
public void clear() {
o2e.clear();
addNO(null, "null");
restart();
}
public void restart () {
todo.clear();
for (Entry e: o2e.values()) {
if (e.defDep != Integer.MAX_VALUE)
e.toDep = e.defDep = -9;
};
}
/**
* create and return the entry with of object o2s and its name
* @param o
* @param opt bits as follows 1 --> class prefix
* 2 --> suffix count
* 4 --> no details available
* @param mid middle part, if not null
* @return
*/
public Entry name(Object o, int opt, String mid) {
Class c = o.getClass();
String n = ((opt & 1) == 0 ? "" : c.getSimpleName())
+ (mid == null || mid.length() == 0 ? "" : ((opt & 1) == 0 ? "" : "-") + mid)
+ ((opt & 2) == 0 ? "" : "@" + o2e.size());
Entry e;
if ((opt & 4) == 0)
e = add(o, n, depth);
else
e = addNO(o, n);
todo.add(e);
return e;
}
/**
* retrieve or create entry and name for o2s
* @param o
* @return
*/
public Entry name(Object o) {
Entry e = o2e.get(o);
if (e != null) {
if (e.toDep < depth) {
e.toDep = depth;
todo.add(e);
}
} else if (o instanceof A2S) {
((A2S) o).a2sName(this);
e = o2e.get(o);
assert e != null;
} else {
Class c = o.getClass();
if (c.isArray()) {
e = Array.getLength(o) == 0 ? name(o, 5, "[0:]") : name(o, 3, null);
} else if (o instanceof Collection || o instanceof Map) {
e = name(o, 3, null);
} else if (o instanceof Throwable) {
e = name(o, 3, ((Throwable) o).getMessage());
} else {
String s = o.toString();
String w = c.getName();
if (s.length() >= w.length() && w.equals(s.substring(0, w.length()))) {
s = s.substring(w.length()); // remove full class name
}
int cx = s.lastIndexOf('@');
if (cx >= 0 && s.substring(cx+1).equals(Integer.toHexString(o.hashCode()))) {
s = s.substring(0, cx); // remove hash of address
}
e = name(o, 7, s);
}
}
assert e != null;
return e;
}
/**
* return the contents after emptying it
* @return
*/
public String flush() {
String r = b.toString();
b.setLength(0);
return r;
}
/**
* create, add and return entry for object, with name n and toDepth t
* @param o
* @param n
* @param t
* @return
*/
protected Entry add(Object o, String n, int t) {
assert ! o2e.containsKey(o);
Entry e = new Entry(o, n, t);
o2e.put(o, e);
return e;
}
/**
* create, add and return entry for o with name n - name only, i.e. no define necessary
* @param o
* @param n
* @return
*/
protected Entry addNO(Object o, String n) {
Entry e = add(o, n, Integer.MAX_VALUE);
e.defDep = Integer.MAX_VALUE;
return e;
}
protected boolean define(Entry e) {
int oldDep = depth;
int oldShow = showDef;
int oldDef = e.defDep;
Object o = e.obj;
boolean ret = true;
depth = e.defDep = e.toDep;
if (o == null) {
env.out("null");
} else if (o.getClass().isArray()) {
oneArray(o);
} else if (o instanceof Collection) {
oneColl((Collection) o);
} else if (o instanceof Map) {
oneMap((Map) o);
} else if (showDef <= 0) {
ret = false;
e.defDep = oldDef;
} else if (o instanceof A2S) {
depth--;
showDef--;
((A2S) o).a2s(this);
} else if (o instanceof Throwable) {
Throwable t = (Throwable) o, u;
int l = b.length();
if (ret = (u = t.getCause()) != null) {
b.append("cause=");
name(u);
while ((u = u.getCause()) != null) {
b.append(", cause=");
name(u);
}
}
if (printStackTrace > 0) {
String st = stackTrace(t);
if (printStackTrace == 1) {
System.out.println("\t<<< stacktrace " + name(t).name + ' ' + b.subSequence(l, b.length()) + ": " + st + "\t>>>");
} else {
b.append(" <<< stackTrace " + st + ">>>");
ret = true;
}
}
} else {
b.append(o);
}
depth = oldDep;
showDef = oldShow;
return ret;
}
public static String stackTrace(Throwable t) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
t.printStackTrace(new PrintStream(os));
return os.toString();
}
protected void oneArray(Object a) {
String n = a.getClass().getSimpleName();
if (deep.contains(n.substring(0, n.indexOf('[')))) {
b.append('[').append(Array.getLength(a));
int l = b.length();
b.append(Arrays.deepToString(new Object[] {a}));
b.setCharAt(l, ':');
b.setCharAt(l+1, ' ');
b.setLength(b.length() - 1);
} else {
oneArray1((Object[]) a);
}
}
protected <T> void oneArray1(T[] a) {
boolean mult = a.getClass().getComponentType().isArray();
b.append('[').append(a.length);
if (a.length == 0) {
b.append(": ");
} else {
int l = b.length();
for (Object o : a) {
b.append(", ");
if (o != null && mult) {
oneArray1((Object[]) o);
} else {
o2s(o);
}
}
b.setCharAt(l, ':');
}
b.append(']');
}
protected void oneColl(Collection c) {
int sz = c.size();
if (sz == 0) {
b.append("{}");
} else {
b.append('{').append(sz).append(": ");
for (Object o : c) {
o2s(o);
b.append(", ");
}
int l = b.length();
b.setCharAt(l - 2, '}');
b.setLength(l - 1);
}
}
protected void oneMap(Map<Object, Object> m) {
int sz = m.size();
if (sz == 0) {
b.append("{}");
} else {
b.append('{').append(sz).append(": ");
for (Map.Entry e : m.entrySet()) {
b.append(name(e.getKey()).name).append('=');
o2s(e.getValue());
b.append(", ");
}
int l = b.length();
b.setCharAt(l - 2, '}');
b.setLength(l - 1);
}
}
static class Entry {
final Object obj;
final String name;
int defDep = -9;
int toDep = -9;
public Entry(Object o, String n, int d) {
obj = o;
name = n;
toDep = d;
}
}
}