From 635e41ea181c5fee8860a7044c84ae3557b51189 Mon Sep 17 00:00:00 2001 From: Benedikt Ritter Date: Sun, 9 Nov 2014 12:47:12 +0000 Subject: [PATCH] =?UTF-8?q?LANG-1052:=20Multiline=20recursive=20to=20strin?= =?UTF-8?q?g=20style.=20This=20fixes=20#34=20from=20github.=20Thanks=20to?= =?UTF-8?q?=20Jan=20Mat=C3=A8rne.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1637671 13f79535-47bb-0310-9956-ffa450edef68 --- src/changes/changes.xml | 1 + .../MultilineRecursiveToStringStyle.java | 219 +++++++++++++++ .../MultilineRecursiveToStringStyleTest.java | 264 ++++++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java create mode 100644 src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 2ddb50e8e..501a78b49 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,7 @@ + Multiline recursive to string style Add isSorted() to ArrayUtils Fix MethodUtilsTest so it does not depend on JDK method ordering CompareToBuilder's doc doesn't specify precedence of fields it uses in performing comparisons diff --git a/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java new file mode 100644 index 000000000..1a0ec05f0 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyle.java @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.lang3.builder; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.SystemUtils; + +/** + *

Works with {@link ToStringBuilder} to create a "deep" toString. + * But instead a single line like the {@link RecursiveToStringStyle} this creates a multiline String + * similar to the {@link ToStringStyle.MultiLineToStringStyle}.

+ * + *

To use this class write code as follows:

+ * + *
+ * public class Job {
+ *   String title;
+ *   ...
+ * }
+ * 
+ * public class Person {
+ *   String name;
+ *   int age;
+ *   boolean smoker;
+ *   Job job;
+ * 
+ *   ...
+ * 
+ *   public String toString() {
+ *     return new ReflectionToStringBuilder(this, new MultilineRecursiveToStringStyle()).toString();
+ *   }
+ * }
+ * 
+ * + *

+ * This will produce a toString of the format:
+ * Person@7f54[
+ *   name=Stephen,
+ *   age=29,
+ *   smoker=false,
+ *   job=Job@43cd2[
+ *     title=Manager
+ *   ]
+ * ] + *
+ *

+ * + * @since 3.4 + * @version $Id$ + */ +class MultilineRecursiveToStringStyle extends RecursiveToStringStyle { + + /** + * Required for serialization support. + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** Indenting of inner lines. */ + private int indent = 2; + + /** Current indenting. */ + private int spaces = 2; + + /** + * Constructor. + */ + public MultilineRecursiveToStringStyle() { + super(); + resetIndent(); + } + + /** + * Resets the fields responsible for the line breaks and indenting. + * Must be invoked after changing the {@link #spaces} value. + */ + private void resetIndent() { + setArrayStart("{" + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setArraySeparator("," + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setArrayEnd(SystemUtils.LINE_SEPARATOR + spacer(spaces - indent) + "}"); + + setContentStart("[" + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setFieldSeparator("," + SystemUtils.LINE_SEPARATOR + spacer(spaces)); + setContentEnd(SystemUtils.LINE_SEPARATOR + spacer(spaces - indent) + "]"); + } + + /** + * Creates a string(buffer) responsible for the indenting. + * @param spaces how far to indent + * @return + */ + private StringBuilder spacer(int spaces) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < spaces; i++) { + sb.append(" "); + } + return sb; + } + + @Override + public void appendDetail(StringBuffer buffer, String fieldName, Object value) { + if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && !String.class.equals(value.getClass()) + && accept(value.getClass())) { + spaces += indent; + resetIndent(); + buffer.append(ReflectionToStringBuilder.toString(value, this)); + spaces -= indent; + resetIndent(); + } else { + super.appendDetail(buffer, fieldName, value); + } + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) { + spaces += indent; + resetIndent(); + super.appendDetail(buffer, fieldName, array); + spaces -= indent; + resetIndent(); + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java new file mode 100644 index 000000000..abf64f005 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/MultilineRecursiveToStringStyleTest.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.lang3.builder; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.SystemUtils; +import org.junit.Test; + +/** + * @version $Id$ + */ +public class MultilineRecursiveToStringStyleTest { + + private final String BR = SystemUtils.LINE_SEPARATOR; + + @Test + public void simpleObject() { + Transaction tx = new Transaction("2014.10.15", 100); + String expected = getClassPrefix(tx) + "[" + BR + + " amount=100.0," + BR + + " date=2014.10.15" + BR + + "]"; + assertEquals(expected, toString(tx)); + } + + @Test + public void nestedElements() { + Customer customer = new Customer("Douglas Adams"); + Bank bank = new Bank("ASF Bank"); + customer.bank = bank; + String exp = getClassPrefix(customer) + "[" + BR + + " name=Douglas Adams," + BR + + " bank=" + getClassPrefix(bank) + "[" + BR + + " name=ASF Bank" + BR + + " ]," + BR + + " accounts=" + BR + + "]"; + assertEquals(exp, toString(customer)); + } + + @Test + public void nestedAndArray() { + Account acc = new Account(); + Transaction tx1 = new Transaction("2014.10.14", 100); + Transaction tx2 = new Transaction("2014.10.15", 50); + acc.transactions.add(tx1); + acc.transactions.add(tx2); + String expected = getClassPrefix(acc) + "[" + BR + + " owner=," + BR + + " transactions=" + getClassPrefix(acc.transactions) + "{" + BR + + " " + getClassPrefix(tx1) + "[" + BR + + " amount=100.0," + BR + + " date=2014.10.14" + BR + + " ]," + BR + + " " + getClassPrefix(tx2) + "[" + BR + + " amount=50.0," + BR + + " date=2014.10.15" + BR + + " ]" + BR + + " }" + BR + + "]"; + assertEquals(expected, toString(acc)); + } + + @Test + public void noArray() { + WithArrays wa = new WithArrays(); + String exp = getClassPrefix(wa) + "[" + BR + + " boolArray=," + BR + + " charArray=," + BR + + " intArray=," + BR + + " doubleArray=," + BR + + " longArray=," + BR + + " stringArray=" + BR + + "]"; + assertEquals(exp, toString(wa)); + } + + @Test + public void boolArray() { + WithArrays wa = new WithArrays(); + wa.boolArray = new boolean[] { true, false, true }; + String exp = getClassPrefix(wa) + "[" + BR + + " boolArray={" + BR + + " true," + BR + + " false," + BR + + " true" + BR + + " }," + BR + + " charArray=," + BR + + " intArray=," + BR + + " doubleArray=," + BR + + " longArray=," + BR + + " stringArray=" + BR + + "]"; + assertEquals(exp, toString(wa)); + } + + @Test + public void charArray() { + WithArrays wa = new WithArrays(); + wa.charArray = new char[] { 'a', 'A' }; + String exp = getClassPrefix(wa) + "[" + BR + + " boolArray=," + BR + + " charArray={" + BR + + " a," + BR + + " A" + BR + + " }," + BR + + " intArray=," + BR + + " doubleArray=," + BR + + " longArray=," + BR + + " stringArray=" + BR + + "]"; + assertEquals(exp, toString(wa)); + } + + @Test + public void intArray() { + WithArrays wa = new WithArrays(); + wa.intArray = new int[] { 1, 2 }; + String exp = getClassPrefix(wa) + "[" + BR + + " boolArray=," + BR + + " charArray=," + BR + + " intArray={" + BR + + " 1," + BR + + " 2" + BR + + " }," + BR + + " doubleArray=," + BR + + " longArray=," + BR + + " stringArray=" + BR + + "]"; + assertEquals(exp, toString(wa)); + } + + @Test + public void doubleArray() { + WithArrays wa = new WithArrays(); + wa.doubleArray = new double[] { 1, 2 }; + String exp = getClassPrefix(wa) + "[" + BR + + " boolArray=," + BR + + " charArray=," + BR + + " intArray=," + BR + + " doubleArray={" + BR + + " 1.0," + BR + + " 2.0" + BR + + " }," + BR + + " longArray=," + BR + + " stringArray=" + BR + + "]"; + assertEquals(exp, toString(wa)); + } + + @Test + public void longArray() { + WithArrays wa = new WithArrays(); + wa.longArray = new long[] { 1L, 2L }; + String exp = getClassPrefix(wa) + "[" + BR + + " boolArray=," + BR + + " charArray=," + BR + + " intArray=," + BR + + " doubleArray=," + BR + + " longArray={" + BR + + " 1," + BR + + " 2" + BR + + " }," + BR + + " stringArray=" + BR + + "]"; + assertEquals(exp, toString(wa)); + } + + @Test + public void stringArray() { + WithArrays wa = new WithArrays(); + wa.stringArray = new String[] { "a", "A" }; + String exp = getClassPrefix(wa) + "[" + BR + + " boolArray=," + BR + + " charArray=," + BR + + " intArray=," + BR + + " doubleArray=," + BR + + " longArray=," + BR + + " stringArray={" + BR + + " a," + BR + + " A" + BR + + " }" + BR + + "]"; + assertEquals(exp, toString(wa)); + } + + private String getClassPrefix(Object object) { + return object.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(object)); + } + + private String toString(Object object) { + return new ReflectionToStringBuilder(object, new MultilineRecursiveToStringStyle()).toString(); + } + + static class WithArrays { + boolean[] boolArray; + char[] charArray; + int[] intArray; + double[] doubleArray; + long[] longArray; + String[] stringArray; + } + + static class Bank { + String name; + + public Bank(String name) { + this.name = name; + } + } + + static class Customer { + String name; + Bank bank; + List accounts; + + public Customer(String name) { + this.name = name; + } + } + + static class Account { + Customer owner; + List transactions = new ArrayList(); + + public double getBalance() { + double balance = 0; + for (Transaction tx : transactions) { + balance += tx.amount; + } + return balance; + } + } + + static class Transaction { + double amount; + String date; + + public Transaction(String datum, double betrag) { + this.date = datum; + this.amount = betrag; + } + } + +}