View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.builder;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertTrue;
21  
22  import java.math.BigDecimal;
23  import java.math.BigInteger;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.concurrent.atomic.AtomicBoolean;
29  import java.util.concurrent.atomic.AtomicInteger;
30  import java.util.concurrent.atomic.AtomicLong;
31  
32  import org.apache.commons.lang3.AbstractLangTest;
33  import org.apache.commons.lang3.mutable.MutableBoolean;
34  import org.apache.commons.lang3.mutable.MutableByte;
35  import org.apache.commons.lang3.mutable.MutableDouble;
36  import org.apache.commons.lang3.mutable.MutableFloat;
37  import org.apache.commons.lang3.mutable.MutableInt;
38  import org.apache.commons.lang3.mutable.MutableLong;
39  import org.apache.commons.lang3.mutable.MutableShort;
40  import org.junit.jupiter.api.AfterEach;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  
44  /**
45   * Tests {@link RecursiveToStringStyleTest}.
46   */
47  class RecursiveToStringStyleTest extends AbstractLangTest {
48  
49      static class Job {
50  
51          /**
52           * Test String field.
53           */
54          String title;
55      }
56  
57      static class Person {
58  
59          /**
60           * Test String field.
61           */
62          String name;
63  
64          /**
65           * Test integer field.
66           */
67          int age;
68  
69          /**
70           * Test boolean field.
71           */
72          boolean smoker;
73  
74          /**
75           * Test Object field.
76           */
77          Job job;
78      }
79  
80      private final Integer base = Integer.valueOf(5);
81  
82      private final String baseStr = base.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(base));
83  
84      @BeforeEach
85      public void setUp() {
86          ToStringBuilder.setDefaultStyle(new RecursiveToStringStyle());
87      }
88  
89      @AfterEach
90      public void tearDown() {
91          ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
92      }
93  
94      @Test
95      void testAppendSuper() {
96          assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());
97          assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString());
98  
99          assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString());
100         assertEquals(baseStr + "[<null>,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString());
101         assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString());
102     }
103 
104     @Test
105     void testAtomicsArray() {
106         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
107                 new ToStringBuilder(base).append(new Object[] { null, base, new AtomicLong[] { new AtomicLong(3), new AtomicLong(6) } }).toString());
108         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
109                 new ToStringBuilder(base).append(new Object[] { null, base, new AtomicInteger[] { new AtomicInteger(3), new AtomicInteger(6) } }).toString());
110         assertEquals(baseStr + "[{<null>,5,{true,false}}]",
111                 new ToStringBuilder(base).append(new Object[] { null, base, new AtomicBoolean[] { new AtomicBoolean(true), new AtomicBoolean(false) } }).toString());
112     }
113 
114     @Test
115     void testBigDecimal() {
116         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
117                 new ToStringBuilder(base).append(new Object[] { null, base, new BigDecimal[] { BigDecimal.valueOf(3), BigDecimal.valueOf(6) } }).toString());
118         assertEquals(baseStr + "[{<null>,5,{3.0,6.0}}]",
119                 new ToStringBuilder(base).append(new Object[] { null, base, new BigDecimal[] { BigDecimal.valueOf(3.0), BigDecimal.valueOf(6.0) } }).toString());
120     }
121 
122     @Test
123     void testBigInteger() {
124         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
125                 new ToStringBuilder(base).append(new Object[] { null, base, new BigInteger[] { BigInteger.valueOf(3), BigInteger.valueOf(6) } }).toString());
126     }
127 
128     @Test
129     void testBlank() {
130         assertEquals(baseStr + "[]", new ToStringBuilder(base).toString());
131     }
132 
133     @Test
134     void testLong() {
135         assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString());
136         assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3L).toString());
137         assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString());
138     }
139 
140     @Test
141     void testLongArray() {
142         long[] array = {1, 2, -3, 4};
143         assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString());
144         assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString());
145         array = null;
146         assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
147         assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
148     }
149 
150     @Test
151     void testLongArrayArray() {
152         long[][] array = {{1, 2}, null, {5}};
153         assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString());
154         assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString());
155         array = null;
156         assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
157         assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
158     }
159 
160     /**
161      * With an output-length limit configured, nested objects past the limit are elided with a
162      * clear truncation marker.
163      */
164     @Test
165     void testMaxOutputLengthTruncates() {
166         final List<Object> list = new ArrayList<>();
167         for (int i = 0; i < 100; i++) {
168             list.add(new ArrayList<>(Collections.singletonList("item" + i)));
169         }
170         final String s = new ToStringBuilder(base, RecursiveToStringStyle.builder().setMaxOutputLength(200).get()).append("g", list, true).toString();
171         assertTrue(s.contains("...<truncated>"), "Truncation marker expected: " + s);
172         assertTrue(s.length() < 10_000, "Output must be throttled, was " + s.length());
173     }
174 
175     @Test
176     void testMutableWrapperArray() {
177         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
178                 new ToStringBuilder(base).append(new Object[] { null, base, new MutableLong[] { new MutableLong(3), new MutableLong(6) } }).toString());
179         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
180                 new ToStringBuilder(base).append(new Object[] { null, base, new MutableInt[] { new MutableInt(3), new MutableInt(6) } }).toString());
181         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
182                 new ToStringBuilder(base).append(new Object[] { null, base, new MutableShort[] { new MutableShort(3), new MutableShort(6) } }).toString());
183         assertEquals(baseStr + "[{<null>,5,{3,6}}]",
184                 new ToStringBuilder(base).append(new Object[] { null, base, new MutableByte[] { new MutableByte((byte) 3), new MutableByte((byte) 6) } }).toString());
185         assertEquals(baseStr + "[{<null>,5,{3.0,6.0}}]",
186                 new ToStringBuilder(base).append(new Object[] { null, base, new MutableFloat[] { new MutableFloat(3f), new MutableFloat(6f) } }).toString());
187         assertEquals(baseStr + "[{<null>,5,{3.0,6.0}}]",
188                 new ToStringBuilder(base).append(new Object[] { null, base, new MutableDouble[] { new MutableDouble(3d), new MutableDouble(6d) } }).toString());
189         assertEquals(baseStr + "[{<null>,5,{true,false}}]",
190                 new ToStringBuilder(base).append(new Object[] { null, base, new MutableBoolean[] { new MutableBoolean(true), new MutableBoolean(false) } }).toString());
191     }
192 
193     @Test
194     void testObject() {
195         final Integer i3 = Integer.valueOf(3);
196         final Integer i4 = Integer.valueOf(4);
197         final ArrayList<Object> emptyList = new ArrayList<>();
198 
199         assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).toString());
200         assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString());
201         assertEquals(baseStr + "[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).toString());
202         assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).toString());
203         assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
204         assertEquals(baseStr + "[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString());
205         assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", emptyList, false).toString());
206         assertEquals(baseStr + "[a=java.util.ArrayList@" + Integer.toHexString(System.identityHashCode(emptyList)) + "{}]",
207                 new ToStringBuilder(base).append("a", emptyList, true).toString());
208         assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new HashMap<>(), false).toString());
209         assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", new HashMap<>(), true).toString());
210         assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new String[0], false).toString());
211         assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", (Object) new String[0], true).toString());
212     }
213 
214     @Test
215     void testObjectArray() {
216         Object[] array = {null, base, new int[] {3, 6}};
217         assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString());
218         assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString());
219         array = null;
220         assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString());
221         assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
222     }
223 
224     @Test
225     void testPerson() {
226         final Person p = new Person();
227         p.name = "John Doe";
228         p.age = 33;
229         p.smoker = false;
230         p.job = new Job();
231         p.job.title = "Manager";
232         final String baseStr = p.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p));
233         final String jobStr = p.job.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p.job));
234         final ReflectionToStringBuilder builder = new ReflectionToStringBuilder(p, new RecursiveToStringStyle());
235         final String expected = baseStr + (builder.isForceAccessible() ? "[age=33,job=" + jobStr + "[title=Manager],name=John Doe,smoker=false]"
236                 : "[age=<null>,job=<null>,name=<null>,smoker=<null>]");
237         assertEquals(expected, builder.toString());
238     }
239 
240     @Test
241     void testPrimitiveWrapperArray() {
242         assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(new Object[] { null, base, new Character[] { '3', '6' } }).toString());
243         assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(new Object[] { null, base, new Long[] { 3L, 6L } }).toString());
244         assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(new Object[] { null, base, new Integer[] { 3, 6 } }).toString());
245         assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(new Object[] { null, base, new Short[] { 3, 6 } }).toString());
246         assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(new Object[] { null, base, new Byte[] { 3, 6 } }).toString());
247         assertEquals(baseStr + "[{<null>,5,{3.0,6.0}}]", new ToStringBuilder(base).append(new Object[] { null, base, new Float[] { 3f, 6f } }).toString());
248         assertEquals(baseStr + "[{<null>,5,{3.0,6.0}}]", new ToStringBuilder(base).append(new Object[] { null, base, new Double[] { 3d, 6d } }).toString());
249         assertEquals(baseStr + "[{<null>,5,{true,false}}]",
250                 new ToStringBuilder(base).append(new Object[] { null, base, new Boolean[] { true, false } }).toString());
251     }
252 
253     /**
254      * A shared (acyclic) reference is detailed once per top-level call; the second occurrence
255      * is rendered in the abbreviated {@code Object.toString()} format instead of re-traversed.
256      */
257     @Test
258     void testSharedReferenceDetailedOnce() {
259         final ArrayList<Object> shared = new ArrayList<>(Collections.singletonList("x"));
260         final String sharedStr = "java.util.ArrayList@" + Integer.toHexString(System.identityHashCode(shared));
261         assertEquals(baseStr + "[a=" + sharedStr + "{x},b=" + sharedStr + "]",
262                 new ToStringBuilder(base).append("a", shared, true).append("b", shared, true).toString());
263     }
264 
265     /**
266      * A deep reference diamond (each level holding two references to the same child) must be
267      * traversed in linear time and produce linear output. Without the per-call visited set,
268      * this 40-level diamond would require ~2^40 traversals.
269      */
270     @Test
271     void testSharedReferenceDiamondIsLinear() {
272         List<Object> child = new ArrayList<>(Collections.singletonList("leaf"));
273         for (int i = 0; i < 40; i++) {
274             final List<Object> parent = new ArrayList<>();
275             parent.add(child);
276             parent.add(child);
277             child = parent;
278         }
279         final String s = new ToStringBuilder(base).append("g", child, true).toString();
280         assertTrue(s.contains("leaf"), "The graph content must still be rendered");
281         assertTrue(s.length() < 100_000, "Output must be linear in graph size, was " + s.length());
282     }
283 }