solidstack.reflect.Dumper.java Source code

Java tutorial

Introduction

Here is the source code for solidstack.reflect.Dumper.java

Source

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

package solidstack.reflect;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import solidstack.io.FatalIOException;
import solidstack.lang.Assert;
import solidstack.script.java.Java;

public class Dumper {
    private IdentityHashMap<Object, Integer> visited = new IdentityHashMap<Object, Integer>();
    private int id;

    private boolean hideTransients;
    private boolean singleLine;
    private boolean hideIds;

    private Set<String> skip = new HashSet<String>();
    private String[] skipDefault = new String[] { "org.hibernate.internal.SessionImpl",
            "org.springframework.beans.factory.support.DefaultListableBeanFactory", "org.enhydra.jdbc.util.Logger",
            "org.h2.jdbc.JdbcConnection", "org.h2.expression.Expression[]", "org.h2.engine.Session" };

    private Set<String> overriddenCollection = new HashSet<String>();

    public Dumper() {
        for (String cls : this.skipDefault)
            this.skip.add(cls);
    }

    public Dumper hideTransients(boolean hideTransients) {
        this.hideTransients = hideTransients;
        return this;
    }

    public Dumper setSingleLine(boolean singleLine) {
        this.singleLine = singleLine;
        return this;
    }

    public Dumper hideIds(boolean hideIds) {
        this.hideIds = hideIds;
        return this;
    }

    public Dumper skip(String cls) {
        this.skip.add(cls);
        return this;
    }

    public Set<String> getSkips() {
        return this.skip;
    }

    public Dumper removeSkip(String cls) {
        this.skip.remove(cls);
        return this;
    }

    public Dumper overrideCollection(String cls) {
        this.overriddenCollection.add(cls);
        return this;
    }

    public Set<String> getCollectionOverrides() {
        return this.overriddenCollection;
    }

    public Dumper removeCollectionOverride(String cls) {
        this.overriddenCollection.remove(cls);
        return this;
    }

    public void resetIds() {
        this.visited.clear();
        this.id = 0;
    }

    public String dump(Object o) {
        Writer out = new StringWriter();
        dumpTo(o, out);
        return out.toString();
    }

    public void dumpTo(Object o, Writer out) {
        DumpWriter writer = new DumpWriter(out);
        dumpTo(o, writer);
    }

    public void dumpTo(Object o, File file) {
        try {
            Writer out = new FileWriter(file);
            try {
                dumpTo(o, out);
            } finally {
                out.close();
            }
        } catch (IOException e) {
            throw Java.throwUnchecked(e);
        }
    }

    public void dumpTo(Object o, DumpWriter out) {
        try {
            if (o == null) {
                out.append("<null>");
                return;
            }
            Class<?> cls = o.getClass();
            if (cls == String.class) {
                out.append("\"").append(((String) o).replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r")
                        .replace("\t", "\\t").replace("\"", "\\\"")).append("\"");
                return;
            }
            if (o instanceof CharSequence) {
                out.append("(").append(o.getClass().getName()).append(")");
                dumpTo(o.toString(), out);
                return;
            }
            if (cls == char[].class) {
                out.append("(char[])");
                dumpTo(String.valueOf((char[]) o), out);
                return;
            }
            if (cls == byte[].class) {
                out.append("byte[").append(Integer.toString(((byte[]) o).length)).append("]");
                return;
            }
            if (cls == Class.class) {
                out.append(((Class<?>) o).getCanonicalName()).append(".class");
                return;
            }
            if (cls == File.class) {
                out.append("File( \"").append(((File) o).getPath()).append("\" )");
                return;
            }
            if (cls == AtomicInteger.class) {
                out.append("AtomicInteger( ").append(Integer.toString(((AtomicInteger) o).get())).append(" )");
                return;
            }
            if (cls == AtomicLong.class) {
                out.append("AtomicLong( ").append(Long.toString(((AtomicLong) o).get())).append(" )");
                return;
            }
            if (o instanceof ClassLoader) {
                out.append(o.getClass().getCanonicalName());
                return;
            }

            if (cls == java.lang.Short.class || cls == java.lang.Long.class || cls == java.lang.Integer.class
                    || cls == java.lang.Float.class || cls == java.lang.Byte.class
                    || cls == java.lang.Character.class || cls == java.lang.Double.class
                    || cls == java.lang.Boolean.class || cls == BigInteger.class || cls == BigDecimal.class) {
                out.append("(").append(cls.getSimpleName()).append(")").append(o.toString());
                return;
            }

            String className = cls.getCanonicalName();
            if (className == null)
                className = cls.getName();
            out.append(className);

            if (this.skip.contains(className) || o instanceof java.lang.Thread) {
                out.append(" (skipped)");
                return;
            }

            Integer id = this.visited.get(o);
            if (id == null) {
                id = ++this.id;
                this.visited.put(o, id);
                if (!this.hideIds)
                    out.append(" <id=" + id + ">");
            } else {
                out.append(" <refid=" + id + ">");
                return;
            }

            if (cls.isArray()) {
                if (Array.getLength(o) == 0)
                    out.append(" []");
                else {
                    out.newlineOrSpace().append("[").newlineOrSpace().indent().setFirst();
                    int rowCount = Array.getLength(o);
                    for (int i = 0; i < rowCount; i++) {
                        out.comma();
                        dumpTo(Array.get(o, i), out);
                    }
                    out.newlineOrSpace().unIndent().append("]");
                }
            } else if (o instanceof Collection && !this.overriddenCollection.contains(className)) {
                Collection<?> list = (Collection<?>) o;
                if (list.isEmpty())
                    out.append(" []");
                else {
                    out.newlineOrSpace().append("[").newlineOrSpace().indent().setFirst();
                    for (Object value : list) {
                        out.comma();
                        dumpTo(value, out);
                    }
                    out.newlineOrSpace().unIndent().append("]");
                }
            } else if (o instanceof Properties && !this.overriddenCollection.contains(className)) // Properties is a Map, so it must come before the Map
            {
                Field def = cls.getDeclaredField("defaults");
                if (!def.isAccessible())
                    def.setAccessible(true);
                Properties defaults = (Properties) def.get(o);
                Hashtable<?, ?> map = (Hashtable<?, ?>) o;
                out.newlineOrSpace().append("[").newlineOrSpace().indent().setFirst();
                for (Map.Entry<?, ?> entry : map.entrySet()) {
                    out.comma();
                    dumpTo(entry.getKey(), out);
                    out.append(": ");
                    dumpTo(entry.getValue(), out);
                }
                if (defaults != null && !defaults.isEmpty()) {
                    out.comma().append("defaults: ");
                    dumpTo(defaults, out);
                }
                out.newlineOrSpace().unIndent().append("]");
            } else if (o instanceof Map && !this.overriddenCollection.contains(className)) {
                Map<?, ?> map = (Map<?, ?>) o;
                if (map.isEmpty())
                    out.append(" []");
                else {
                    out.newlineOrSpace().append("[").newlineOrSpace().indent().setFirst();
                    for (Map.Entry<?, ?> entry : map.entrySet()) {
                        out.comma();
                        dumpTo(entry.getKey(), out);
                        out.append(": ");
                        dumpTo(entry.getValue(), out);
                    }
                    out.newlineOrSpace().unIndent().append("]");
                }
            } else if (o instanceof Method) {
                out.newlineOrSpace().append("{").newlineOrSpace().indent().setFirst();

                Field field = cls.getDeclaredField("clazz");
                if (!field.isAccessible())
                    field.setAccessible(true);
                out.comma().append("clazz").append(": ");
                dumpTo(field.get(o), out);

                field = cls.getDeclaredField("name");
                if (!field.isAccessible())
                    field.setAccessible(true);
                out.comma().append("name").append(": ");
                dumpTo(field.get(o), out);

                field = cls.getDeclaredField("parameterTypes");
                if (!field.isAccessible())
                    field.setAccessible(true);
                out.comma().append("parameterTypes").append(": ");
                dumpTo(field.get(o), out);

                field = cls.getDeclaredField("returnType");
                if (!field.isAccessible())
                    field.setAccessible(true);
                out.comma().append("returnType").append(": ");
                dumpTo(field.get(o), out);

                out.newlineOrSpace().unIndent().append("}");
            } else {
                ArrayList<Field> fields = new ArrayList<Field>();
                while (cls != Object.class) {
                    Field[] fs = cls.getDeclaredFields();
                    for (Field field : fs)
                        fields.add(field);
                    cls = cls.getSuperclass();
                }

                Collections.sort(fields, new Comparator<Field>() {
                    public int compare(Field left, Field right) {
                        return left.getName().compareTo(right.getName());
                    }
                });

                if (fields.isEmpty())
                    out.append(" {}");
                else {
                    out.newlineOrSpace().append("{").newlineOrSpace().indent().setFirst();
                    for (Field field : fields)
                        if ((field.getModifiers() & Modifier.STATIC) == 0)
                            if (!this.hideTransients || (field.getModifiers() & Modifier.TRANSIENT) == 0) {
                                out.comma().append(field.getName()).append(": ");

                                if (!field.isAccessible())
                                    field.setAccessible(true);

                                if (field.getType().isPrimitive())
                                    if (field.getType() == boolean.class) // TODO More?
                                        out.append(field.get(o).toString());
                                    else
                                        out.append("(").append(field.getType().getName()).append(")")
                                                .append(field.get(o).toString());
                                else
                                    dumpTo(field.get(o), out);
                            }
                    out.newlineOrSpace().unIndent().append("}");
                }
            }
        } catch (IOException e) {
            throw new FatalIOException(e);
        } catch (Exception e) {
            dumpTo(e.toString(), out);
        }
    }

    public class DumpWriter {
        private Writer out;
        private String tabs = "\t\t\t\t\t\t\t\t";
        private int indent;
        private boolean needIndent;
        private boolean first;

        public DumpWriter(Writer out) {
            this.out = out;
        }

        public DumpWriter append(String s) throws IOException {
            if (this.needIndent) {
                while (this.indent > this.tabs.length())
                    this.tabs += this.tabs;
                this.out.write(this.tabs.substring(0, this.indent));
                this.needIndent = false;
            }
            this.out.write(s);
            return this;
        }

        public DumpWriter newlineOrSpace() throws IOException {
            if (!Dumper.this.singleLine) {
                this.out.write("\n");
                this.needIndent = true;
            } else
                this.out.write(" ");
            return this;
        }

        public DumpWriter indent() {
            this.indent++;
            return this;
        }

        public DumpWriter unIndent() {
            this.indent--;
            Assert.isTrue(this.indent >= 0);
            return this;
        }

        public DumpWriter setFirst() {
            this.first = true;
            return this;
        }

        public DumpWriter comma() throws IOException {
            if (this.first)
                this.first = false;
            else
                append(",").newlineOrSpace();
            return this;
        }
    }
}