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