Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java toString - ToStringBuilder not sufficient; won't traverse

Tags:

java

I need to be able to traverse through my entire object graph and log all contents of all member fields.

For example: Object A has a collection of Object B's which has a collection of Object C's and A, B, C have additional fields on them, etc.

Apache Commons ToStringBuilder is not sufficient since it won't traverse down an object graph or output contents of a collection.

Does anyone know of another library that will do this or have a code snippet that does this?

like image 526
BestPractices Avatar asked Jun 30 '10 13:06

BestPractices


2 Answers

You can traverse the whole tree using org.apache.commons.lang.builder.ReflectionToStringBuilder. The trick is that in ToStringStyle you need to traverse into the value. ToStringStyle will take care of values, already processed, and will not allow recursion. Here we go:

System.out.println(ReflectionToStringBuilder.toString(schema, new RecursiveToStringStyle(5)));  private static class RecursiveToStringStyle extends ToStringStyle {      private static final int    INFINITE_DEPTH  = -1;      /**      * Setting {@link #maxDepth} to 0 will have the same effect as using original {@link #ToStringStyle}: it will      * print all 1st level values without traversing into them. Setting to 1 will traverse up to 2nd level and so      * on.      */     private int                 maxDepth;      private int                 depth;      public RecursiveToStringStyle() {         this(INFINITE_DEPTH);     }      public RecursiveToStringStyle(int maxDepth) {         setUseShortClassName(true);         setUseIdentityHashCode(false);          this.maxDepth = maxDepth;     }      @Override     protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {         if (value.getClass().getName().startsWith("java.lang.")                     || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {             buffer.append(value);         }         else {             depth++;             buffer.append(ReflectionToStringBuilder.toString(value, this));             depth--;         }     }      // another helpful method     @Override     protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {          depth++;          buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true));          depth--;     } } 
like image 64
dma_k Avatar answered Sep 30 '22 01:09

dma_k


Here is a modified version of @dma_k's solution featuring single buffer reuse, thread safety, multi-line indent and use of object's toString method if it has been overridden.

Sample output:

ToStringTest.ParentStub {     array = {a,b,c}     map = {key2=null, key1=value1}     child = ToStringTest.Stub {         field1 = 12345         field2 = Hello         superField = abc     }     empty = <null>     superField = abc } 

Code:

class RecursiveToStringStyle extends ToStringStyle {      private static final RecursiveToStringStyle INSTANCE = new RecursiveToStringStyle(13);      public static ToStringStyle getInstance() {         return INSTANCE;     }      public static String toString(Object value) {         final StringBuffer sb = new StringBuffer(512);         INSTANCE.appendDetail(sb, null, value);         return sb.toString();     }      private final int maxDepth;     private final String tabs;      // http://stackoverflow.com/a/16934373/603516     private ThreadLocal<MutableInteger> depth = new ThreadLocal<MutableInteger>() {         @Override         protected MutableInteger initialValue() {             return new MutableInteger(0);         }     };      protected RecursiveToStringStyle(int maxDepth) {         this.maxDepth = maxDepth;         tabs = StringUtils.repeat("\t", maxDepth);          setUseShortClassName(true);         setUseIdentityHashCode(false);         setContentStart(" {");         setFieldSeparator(SystemUtils.LINE_SEPARATOR);         setFieldSeparatorAtStart(true);         setFieldNameValueSeparator(" = ");         setContentEnd("}");     }      private int getDepth() {         return depth.get().get();     }      private void padDepth(StringBuffer buffer) {         buffer.append(tabs, 0, getDepth());     }      private StringBuffer appendTabified(StringBuffer buffer, String value) {         //return buffer.append(String.valueOf(value).replace("\n", "\n" + tabs.substring(0, getDepth())));         Matcher matcher = Pattern.compile("\n").matcher(value);         String replacement = "\n" + tabs.substring(0, getDepth());         while (matcher.find()) {             matcher.appendReplacement(buffer, replacement);         }         matcher.appendTail(buffer);         return buffer;     }       @Override     protected void appendFieldSeparator(StringBuffer buffer) {         buffer.append(getFieldSeparator());         padDepth(buffer);     }      @Override     public void appendStart(StringBuffer buffer, Object object) {         depth.get().increment();         super.appendStart(buffer, object);     }      @Override     public void appendEnd(StringBuffer buffer, Object object) {         super.appendEnd(buffer, object);         buffer.setLength(buffer.length() - getContentEnd().length());         buffer.append(SystemUtils.LINE_SEPARATOR);         depth.get().decrement();         padDepth(buffer);         appendContentEnd(buffer);     }      @Override     protected void removeLastFieldSeparator(StringBuffer buffer) {         int len = buffer.length();         int sepLen = getFieldSeparator().length() + getDepth();         if (len > 0 && sepLen > 0 && len >= sepLen) {             buffer.setLength(len - sepLen);         }     }      private boolean noReflectionNeeded(Object value) {         try {             return value != null &&                     (value.getClass().getName().startsWith("java.lang.")                     || value.getClass().getMethod("toString").getDeclaringClass() != Object.class);         } catch (NoSuchMethodException e) {             throw new IllegalStateException(e);         }     }      @Override     protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {         if (getDepth() >= maxDepth || noReflectionNeeded(value)) {             appendTabified(buffer, String.valueOf(value));         } else {             new ReflectionToStringBuilder(value, this, buffer, null, false, false).toString();         }     }      // another helpful method, for collections:     @Override     protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) {         buffer.append(ReflectionToStringBuilder.toString(coll.toArray(), this, true, true));     }      static class MutableInteger {         private int value;         MutableInteger(int value) { this.value = value; }         public final int get() { return value; }         public final void increment() { ++value; }         public final void decrement() { --value; }     } } 
like image 25
Vadzim Avatar answered Sep 30 '22 02:09

Vadzim