import java.lang.*;
import java.lang.reflect.*;
import java.lang.ref.*;

import java.util.*;

public class Dumper {
  private HashSet<Object> dumped;
  private boolean beatifyBoxes = false;
  private boolean beatifyStrings = false;
  private boolean hideTransient = false;
  private boolean hideWeakrefs = false;
  private boolean pathMode = false;
  private Object target = null;

  public void setBeatifyStrings(boolean beatify) { beatifyStrings = beatify; }
  public void setBeatifyBoxes(boolean beatify) { beatifyBoxes = beatify; }
  public void setHideTransient(boolean hide) { hideTransient = hide; }
  public void setHideWeakrefs(boolean hide) { hideWeakrefs = hide; }
  
  private void indented(StringBuffer buf, int lvl, String output) {
    for(int i = 0; i < lvl; i++) {
      buf.append("  ");
    }
    buf.append(output);
    buf.append("\n");
  }
  
  private boolean dumpValue(StringBuffer buf, int lvl, String name, Class cl, Object value) {
    try {
      if(value == null) {
        indented(buf, lvl, name + " = <null>");
      } else if(cl.isPrimitive()) {
        indented(buf, lvl, name + " = " + value);
      } else if(beatifyBoxes && (
          value instanceof Byte ||
          value instanceof Integer ||
          value instanceof Long ||
          value instanceof Boolean ||
          value instanceof Float ||
          value instanceof Double)) {
        indented(buf, lvl, name + " = " + value + "  " + value.getClass());
      } else if(beatifyBoxes && value instanceof Character) {
        indented(buf, lvl, name + " = '" + value + "'  " + value.getClass());
      } else if(beatifyStrings && value instanceof String) {
        indented(buf, lvl, name + " = \"" + value + "\"  " + value.getClass());
      } else if(hideWeakrefs && (
          value instanceof SoftReference ||
          value instanceof WeakReference ||
          value instanceof PhantomReference)) {
        indented(buf, lvl, name + " = \"" + value + "\"  " + value.getClass());
      } else if(dumped.contains(value)) {
        indented(buf, lvl, name + " = <already dumped>");
      } else {
        indented(buf, lvl, name);
        StringBuffer maybeBuf = new StringBuffer();
        if(dump(maybeBuf, lvl + 1, value) || !pathMode) {
          buf.append(maybeBuf);
          return true;
        }

        return false;
      }
    } catch(NullPointerException npe) {
      indented(buf, lvl, "NullPointerException occured during dump, continuing...");
    }

    return false;
  }
  
  private boolean dump(StringBuffer buf, int lvl, Object o) {
    boolean found = false;

    indented(buf, lvl++, "Dump of " + o + "  " + o.getClass());

    if(pathMode && o == target) return true;

    if(o == null) return false;
    if(dumped.contains(o)) {
      indented(buf, lvl, "<already dumped>");
      return false;
    }

    dumped.add(o);
    
    Class oClass = o.getClass();
    if(oClass.isArray()) {
      Class compClass = oClass.getComponentType();
      for(int pos = 0; pos < Array.getLength(o); pos++) {
        Object value = Array.get(o, pos);

        StringBuffer maybeBuf = new StringBuffer();
        if(dumpValue(maybeBuf, lvl, "index " + pos, compClass, value) || !pathMode) {
          found = true;
          buf.append(maybeBuf);
        }
      }
    } else {
      List<Field> allFields = new LinkedList<Field>();
      List<Field> staticFields = new LinkedList<Field>();

      while(true) {
        Field[] fields = oClass.getDeclaredFields();
        
        for(Field field: fields) {
          try {
            field.setAccessible(true);

            if(Modifier.isStatic(field.getModifiers())) {
              if(dumped.contains(field)) continue;
              staticFields.add(field);
            }

            allFields.add(field);
          } catch(SecurityException se) {
            indented(buf, lvl, field.toString() + " Access not granted");
          }
        }
        
        if(oClass.equals(Object.class)) break;
        oClass = oClass.getSuperclass();
      }

      for(Field field: staticFields) {
        dumped.add(field);
      }

      for(Field field: allFields) {
        if(hideTransient && Modifier.isTransient(field.getModifiers())) continue;
        
        try {
          Class t = field.getType();
          Object value = field.get(o);
          StringBuffer maybeBuf = new StringBuffer();
          if(dumpValue(maybeBuf, lvl, field.toString(), t, value) || !pathMode) {
            found = true;
            buf.append(maybeBuf);
          }
        } catch(IllegalAccessException iae) {
          indented(buf, lvl, field.toString() + " Access illegal");
        }
      }
    }

    return found;
  }
  
  public StringBuffer dump(Object o) {
    StringBuffer ret = new StringBuffer();
    pathMode = false;
    
    dump(ret, 0, o);
    return ret;
  }

  public StringBuffer path(Object o, Object needle) {
    StringBuffer ret = new StringBuffer();
    target = needle;
    pathMode = true;

    if(!dump(ret, 0, o)) {
      ret.append("no path exists");
    }

    return ret;
  }

  public Dumper() {
    dumped = new HashSet<Object>();
    dumped.add(this);
  }

  public Dumper(boolean beatify) {
    this();

    setBeatifyStrings(beatify);
    setBeatifyBoxes(beatify);
  }
  
  public static void main(String[] args) {
    Dumper d = new Dumper(true);
    System.out.println(d.dump(new HashMap<Object, Object>()));
    System.out.println(d.path(ClassLoader.getSystemClassLoader(), Dumper.class));
  }
}
