blob: 939edb74be794dc0d374fa738f84e87077f463a3 [file] [log] [blame]
/*
* 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.ignite.internal.tostring;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ignite.internal.testframework.IgniteAbstractTest;
import org.apache.ignite.lang.IgniteInternalException;
import org.apache.ignite.lang.IgniteSystemProperties;
import org.junit.jupiter.api.Test;
import static org.apache.ignite.internal.tostring.IgniteToStringBuilder.identity;
import static org.apache.ignite.lang.IgniteSystemProperties.IGNITE_TO_STRING_COLLECTION_LIMIT;
import static org.apache.ignite.lang.IgniteSystemProperties.IGNITE_TO_STRING_MAX_LENGTH;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Tests for {@link IgniteToStringBuilder}.
*/
public class IgniteToStringBuilderSelfTest extends IgniteAbstractTest {
/**
*
*/
@Test
public void testToString() {
TestClass1 obj = new TestClass1();
assertEquals(obj.toStringManual(), obj.toStringAutomatic());
}
/**
*
*/
@Test
public void testToStringWithAdditions() {
TestClass1 obj = new TestClass1();
String manual = obj.toStringWithAdditionalManual();
String automatic = obj.toStringWithAdditionalAutomatic();
assertEquals(manual, automatic);
}
/**
*
*/
@Test
public void testToStringCheckSimpleListRecursionPrevention() {
ArrayList<Object> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
list2.add(list1);
list1.add(list2);
assertEquals("ArrayList [size=1]", IgniteToStringBuilder.toString(ArrayList.class, list1));
assertEquals("ArrayList [size=1]", IgniteToStringBuilder.toString(ArrayList.class, list2));
}
/**
*
*/
@Test
public void testToStringCheckSimpleMapRecursionPrevention() {
HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
map1.put("2", map2);
map2.put("1", map1);
assertEquals("HashMap []", IgniteToStringBuilder.toString(HashMap.class, map1));
assertEquals("HashMap []", IgniteToStringBuilder.toString(HashMap.class, map2));
}
/**
*
*/
@Test
public void testToStringCheckListAdvancedRecursionPrevention() {
ArrayList<Object> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
list2.add(list1);
list1.add(list2);
final String hash1 = identity(list1);
final String hash2 = identity(list2);
assertEquals("ArrayList" + hash1 + " [size=1, name=ArrayList [ArrayList" + hash1 + "]]",
IgniteToStringBuilder.toString(ArrayList.class, list1, "name", list2));
assertEquals("ArrayList" + hash2 + " [size=1, name=ArrayList [ArrayList" + hash2 + "]]",
IgniteToStringBuilder.toString(ArrayList.class, list2, "name", list1));
}
/**
*
*/
@Test
public void testToStringCheckMapAdvancedRecursionPrevention() {
HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
map1.put("2", map2);
map2.put("1", map1);
final String hash1 = identity(map1);
final String hash2 = identity(map2);
assertEquals("HashMap" + hash1 + " [name=HashMap {1=HashMap" + hash1 + "}]",
IgniteToStringBuilder.toString(HashMap.class, map1, "name", map2));
assertEquals("HashMap" + hash2 + " [name=HashMap {2=HashMap" + hash2 + "}]",
IgniteToStringBuilder.toString(HashMap.class, map2, "name", map1));
}
/**
* @throws Exception If failed.
*/
@Test
public void testToStringCheckObjectRecursionPrevention() throws Exception {
Node n1 = new Node();
Node n2 = new Node();
Node n3 = new Node();
Node n4 = new Node();
n1.name = "n1";
n2.name = "n2";
n3.name = "n3";
n4.name = "n4";
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n3;
n1.nodes = new Node[4];
n2.nodes = n1.nodes;
n3.nodes = n1.nodes;
n4.nodes = n1.nodes;
n1.nodes[0] = n1;
n1.nodes[1] = n2;
n1.nodes[2] = n3;
n1.nodes[3] = n4;
String expN1 = n1.toString();
String expN2 = n2.toString();
String expN3 = n3.toString();
String expN4 = n4.toString();
CyclicBarrier bar = new CyclicBarrier(4);
Executor pool = Executors.newFixedThreadPool(4);
CompletableFuture<String> fut1 = runAsync(new BarrierCallable(bar, n1, expN1), pool);
CompletableFuture<String> fut2 = runAsync(new BarrierCallable(bar, n2, expN2), pool);
CompletableFuture<String> fut3 = runAsync(new BarrierCallable(bar, n3, expN3), pool);
CompletableFuture<String> fut4 = runAsync(new BarrierCallable(bar, n4, expN4), pool);
fut1.get(3_000, TimeUnit.MILLISECONDS);
fut2.get(3_000, TimeUnit.MILLISECONDS);
fut3.get(3_000, TimeUnit.MILLISECONDS);
fut4.get(3_000, TimeUnit.MILLISECONDS);
}
/**
* @param callable Callable.
* @return Completable future.
*/
private static <U> CompletableFuture<U> runAsync(Callable<U> callable, Executor pool) {
return CompletableFuture.supplyAsync(() -> {
try {
return callable.call();
}
catch (Throwable th) {
throw new IgniteInternalException(th);
}
}, pool);
}
/**
* JUnit.
*/
@Test
public void testToStringPerformance() {
TestClass1 obj = new TestClass1();
// Warm up.
obj.toStringAutomatic();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++)
obj.toStringManual();
logger().info("Manual toString() took: " + (System.currentTimeMillis() - start) + "ms");
start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++)
obj.toStringAutomatic();
logger().info("Automatic toString() took: " + (System.currentTimeMillis() - start) + "ms");
}
/**
* Test array print.
*
* @param v value to get array class and fill array.
* @param limit value of IGNITE_TO_STRING_COLLECTION_LIMIT.
*/
private <T, V> void testArr(V v, int limit) {
T[] arrOf = (T[])Array.newInstance(v.getClass(), limit + 1);
Arrays.fill(arrOf, v);
T[] arr = Arrays.copyOf(arrOf, limit);
checkArrayOverflow(arrOf, arr, limit);
}
/**
* Test array print.
*/
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
public void testArrLimitWithRecursion() {
int limit = IgniteSystemProperties.getInteger(IGNITE_TO_STRING_COLLECTION_LIMIT, 100);
ArrayList[] arrOf = new ArrayList[limit + 1];
Arrays.fill(arrOf, new ArrayList());
ArrayList[] arr = Arrays.copyOf(arrOf, limit);
arrOf[0].add(arrOf);
arr[0].add(arr);
checkArrayOverflow(arrOf, arr, limit);
}
/**
* @param arrOf Array.
* @param arr Array copy.
* @param limit Array limit.
*/
private void checkArrayOverflow(Object[] arrOf, Object[] arr, int limit) {
String arrStr = IgniteToStringBuilder.arrayToString(arr);
String arrOfStr = IgniteToStringBuilder.arrayToString(arrOf);
// Simulate overflow
StringBuilder resultSB = new StringBuilder(arrStr);
resultSB.deleteCharAt(resultSB.length() - 1);
resultSB.append("... and ").append(arrOf.length - limit).append(" more]");
arrStr = resultSB.toString();
assertEquals(arrStr, arrOfStr, "Collection limit error in array of type " +
arrOf.getClass().getName() + " error, normal arr: <" + arrStr + ">, overflowed arr: <" + arrOfStr + ">");
}
/**
*
*/
@Test
public void testToStringCollectionLimits() {
int limit = IgniteSystemProperties.getInteger(IGNITE_TO_STRING_COLLECTION_LIMIT, 100);
Object[] vals = new Object[] {
Byte.MIN_VALUE, Boolean.TRUE, Short.MIN_VALUE, Integer.MIN_VALUE, Long.MIN_VALUE,
Float.MIN_VALUE, Double.MIN_VALUE, Character.MIN_VALUE, new TestClass1()};
for (Object val : vals)
testArr(val, limit);
//noinspection ZeroLengthArrayAllocation
int[] intArr1 = new int[0];
assertEquals("[]", IgniteToStringBuilder.arrayToString(intArr1));
assertEquals("null", IgniteToStringBuilder.arrayToString(null));
int[] intArr2 = {1, 2, 3};
assertEquals("[1, 2, 3]", IgniteToStringBuilder.arrayToString(intArr2));
Object[] intArr3 = {2, 3, 4};
assertEquals("[2, 3, 4]", IgniteToStringBuilder.arrayToString(intArr3));
byte[] byteArr = new byte[1];
byteArr[0] = 1;
assertEquals(Arrays.toString(byteArr), IgniteToStringBuilder.arrayToString(byteArr));
byteArr = Arrays.copyOf(byteArr, 101);
assertTrue(IgniteToStringBuilder.arrayToString(byteArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
boolean[] boolArr = new boolean[1];
boolArr[0] = true;
assertEquals(Arrays.toString(boolArr), IgniteToStringBuilder.arrayToString(boolArr));
boolArr = Arrays.copyOf(boolArr, 101);
assertTrue(IgniteToStringBuilder.arrayToString(boolArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
short[] shortArr = new short[1];
shortArr[0] = 100;
assertEquals(Arrays.toString(shortArr), IgniteToStringBuilder.arrayToString(shortArr));
shortArr = Arrays.copyOf(shortArr, 101);
assertTrue(IgniteToStringBuilder.arrayToString(shortArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
int[] intArr = new int[1];
intArr[0] = 10000;
assertEquals(Arrays.toString(intArr), IgniteToStringBuilder.arrayToString(intArr));
intArr = Arrays.copyOf(intArr, 101);
assertTrue(IgniteToStringBuilder.arrayToString(intArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
long[] longArr = new long[1];
longArr[0] = 10000000;
assertEquals(Arrays.toString(longArr), IgniteToStringBuilder.arrayToString(longArr));
longArr = Arrays.copyOf(longArr, 101);
assertTrue(
IgniteToStringBuilder.arrayToString(longArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
float[] floatArr = new float[1];
floatArr[0] = 1.f;
assertEquals(Arrays.toString(floatArr), IgniteToStringBuilder.arrayToString(floatArr));
floatArr = Arrays.copyOf(floatArr, 101);
assertTrue(IgniteToStringBuilder.arrayToString(floatArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
double[] doubleArr = new double[1];
doubleArr[0] = 1.;
assertEquals(Arrays.toString(doubleArr), IgniteToStringBuilder.arrayToString(doubleArr));
doubleArr = Arrays.copyOf(doubleArr, 101);
assertTrue(IgniteToStringBuilder.arrayToString(doubleArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
char[] cArr = new char[1];
cArr[0] = 'a';
assertEquals(Arrays.toString(cArr), IgniteToStringBuilder.arrayToString(cArr));
cArr = Arrays.copyOf(cArr, 101);
assertTrue(IgniteToStringBuilder.arrayToString(cArr).contains("... and 1 more"),
"Can't find \"... and 1 more\" in overflowed array string!");
Map<String, String> strMap = new TreeMap<>();
List<String> strList = new ArrayList<>(limit + 1);
TestClass1 testCls = new TestClass1();
testCls.strMap = strMap;
testCls.strListIncl = strList;
for (int i = 0; i < limit; i++) {
strMap.put("k" + i, "v");
strList.add("e");
}
checkColAndMap(testCls);
}
/**
*
*/
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
public void testToStringColAndMapLimitWithRecursion() {
int limit = IgniteSystemProperties.getInteger(IGNITE_TO_STRING_COLLECTION_LIMIT, 100);
Map strMap = new TreeMap<>();
List strList = new ArrayList<>(limit + 1);
TestClass1 testClass = new TestClass1();
testClass.strMap = strMap;
testClass.strListIncl = strList;
Map m = new TreeMap();
m.put("m", strMap);
List l = new ArrayList();
l.add(strList);
strMap.put("k0", m);
strList.add(l);
for (int i = 1; i < limit; i++) {
strMap.put("k" + i, "v");
strList.add("e");
}
checkColAndMap(testClass);
}
/**
* @param testCls Class with collection and map included in toString().
*/
private void checkColAndMap(TestClass1 testCls) {
String testClsStr = IgniteToStringBuilder.toString(TestClass1.class, testCls);
testCls.strMap.put("kz", "v"); // important to add last element in TreeMap here
testCls.strListIncl.add("e");
String testClsStrOf = IgniteToStringBuilder.toString(TestClass1.class, testCls);
String testClsStrOfR = testClsStrOf.replaceAll("... and 1 more", "");
assertEquals(testClsStr.length(), testClsStrOfR.length(),
"Collection limit error in Map or List, normal: <" + testClsStr + ">, overflowed: <" + testClsStrOf + ">");
}
/**
*
*/
@Test
public void testToStringSizeLimits() {
int limit = IgniteSystemProperties.getInteger(IGNITE_TO_STRING_MAX_LENGTH, 10_000);
int tailLen = limit / 10 * 2;
StringBuilder sb = new StringBuilder(limit + 10);
sb.append("a".repeat(Math.max(0, limit - 100)));
String actual = IgniteToStringBuilder.toString(TestClass2.class, new TestClass2(sb.toString()));
String exp = "TestClass2 [str=" + sb + ", nullArr=null]";
assertEquals(exp, actual);
sb.append("b".repeat(110));
actual = IgniteToStringBuilder.toString(TestClass2.class, new TestClass2(sb.toString()));
exp = "TestClass2 [str=" + sb + ", nullArr=null]";
assertEquals(exp.substring(0, limit - tailLen), actual.substring(0, limit - tailLen));
assertEquals(exp.substring(exp.length() - tailLen), actual.substring(actual.length() - tailLen));
assertTrue(actual.contains("... and"));
assertTrue(actual.contains("skipped ..."));
}
/**
*
*/
@Test
public void testHierarchy() {
Wrapper w = new Wrapper();
Parent p = w.p;
String hash = identity(p);
assertEquals("Wrapper [p=Child [b=0, pb=Parent[] [null], super=Parent [a=0, pa=Parent[] [null]]]]",
w.toString());
p.pa[0] = p;
assertEquals("Wrapper [p=Child" + hash + " [b=0, pb=Parent[] [null]," +
" super=Parent [a=0, pa=Parent[] [Child" + hash + "]]]]", w.toString());
((Child)p).pb[0] = p;
assertEquals("Wrapper [p=Child" + hash + " [b=0, pb=Parent[] [Child" + hash
+ "], super=Parent [a=0, pa=Parent[] [Child" + hash + "]]]]", w.toString());
}
/**
* Verifies that {@link IgniteToStringBuilder} doesn't fail while iterating over concurrently modified collection.
*
* @throws Exception If failed.
*/
@Test
public void testToStringCheckConcurrentModificationExceptionFromList() throws Exception {
ClassWithList classWithList = new ClassWithList();
CountDownLatch modificationStartedLatch = new CountDownLatch(1);
AtomicBoolean finished = new AtomicBoolean(false);
final CompletableFuture<Void> finishFut = CompletableFuture.runAsync(() -> {
List<SlowToStringObject> list = classWithList.list;
for (int i = 0; i < 100; i++)
list.add(new SlowToStringObject());
Random rnd = new Random();
while (!finished.get()) {
if (rnd.nextBoolean() && list.size() > 1)
list.remove(list.size() / 2);
else
list.add(list.size() / 2, new SlowToStringObject());
if (modificationStartedLatch.getCount() > 0)
modificationStartedLatch.countDown();
}
});
modificationStartedLatch.await();
String s = null;
try {
s = classWithList.toString();
}
finally {
finished.set(true);
finishFut.get();
assertNotNull(s);
assertTrue(s.contains("concurrent modification"));
}
}
/**
* Verifies that {@link IgniteToStringBuilder} doesn't fail while iterating over concurrently modified map.
*
* @throws Exception If failed.
*/
@Test
public void testToStringCheckConcurrentModificationExceptionFromMap() throws Exception {
ClassWithMap classWithMap = new ClassWithMap();
CountDownLatch modificationStartedLatch = new CountDownLatch(1);
AtomicBoolean finished = new AtomicBoolean(false);
CompletableFuture<Void> finishFut = CompletableFuture.runAsync(() -> {
Map<Integer, SlowToStringObject> map = classWithMap.map;
for (int i = 0; i < 100; i++)
map.put(i, new SlowToStringObject());
Random rnd = new Random();
while (!finished.get()) {
if (rnd.nextBoolean() && map.size() > 1)
map.remove(map.size() / 2);
else
map.put(map.size() / 2, new SlowToStringObject());
if (modificationStartedLatch.getCount() > 0)
modificationStartedLatch.countDown();
}
});
modificationStartedLatch.await();
String s = null;
try {
s = classWithMap.toString();
}
finally {
finished.set(true);
finishFut.get();
assertNotNull(s);
assertTrue(s.contains("concurrent modification"));
}
}
/**
* Test verifies that when RuntimeException is thrown from toString method of some class
* IgniteToString builder doesn't fail but finishes building toString representation.
*/
@Test
public void testRuntimeExceptionCaught() {
WrapperForFaultyToStringClass wr = new WrapperForFaultyToStringClass(
new ClassWithFaultyToString[] {new ClassWithFaultyToString()});
String strRep = wr.toString();
//field before faulty field was written successfully to string representation
assertTrue(strRep.contains("id=12345"));
//message from RuntimeException was written to string representation
assertTrue(strRep.contains("toString failed"));
//field after faulty field was written successfully to string representation
assertTrue(strRep.contains("str=str"));
}
/**
* Test class.
*/
@SuppressWarnings("InstanceVariableMayNotBeInitialized")
private static class Node {
/**
*
*/
@IgniteToStringInclude
String name;
/**
*
*/
@IgniteToStringInclude
Node next;
/**
*
*/
@IgniteToStringInclude
Node[] nodes;
/** {@inheritDoc} */
@Override public String toString() {
return IgniteToStringBuilder.toString(Node.class, this);
}
}
/**
* Test class.
*/
private static class BarrierCallable implements Callable<String> {
/**
*
*/
CyclicBarrier bar;
/**
*
*/
Object obj;
/** Expected value of {@code toString()} method. */
String exp;
/**
*
*/
private BarrierCallable(CyclicBarrier bar, Object obj, String exp) {
this.bar = bar;
this.obj = obj;
this.exp = exp;
}
/** {@inheritDoc} */
@Override public String call() throws Exception {
for (int i = 0; i < 10; i++) {
bar.await();
assertEquals(exp, obj.toString());
}
return null;
}
}
/**
* Class containing another class with faulty toString implementation
* to force IgniteToStringBuilder to call faulty toString.
*/
@SuppressWarnings({"FieldMayBeFinal", "unused"})
private static class WrapperForFaultyToStringClass {
/**
*
*/
@IgniteToStringInclude
private int id = 12345;
/**
*
*/
@SuppressWarnings("unused")
@IgniteToStringInclude
private ClassWithFaultyToString[] arr;
/**
*
*/
@SuppressWarnings("unused")
@IgniteToStringInclude
private String str = "str";
/**
*
*/
WrapperForFaultyToStringClass(ClassWithFaultyToString[] arr) {
this.arr = arr;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(WrapperForFaultyToStringClass.class, this);
}
}
/**
* Class throwing a RuntimeException from a {@link ClassWithFaultyToString#toString()} method.
*/
private static class ClassWithFaultyToString {
/** {@inheritDoc} */
@Override public String toString() {
throw new RuntimeException("toString failed");
}
}
/**
* Test class.
*/
@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "FieldMayBeFinal", "unused", "FieldMayBeStatic"})
private static class TestClass1 {
/**
*
*/
@IgniteToStringOrder(0)
private String id = "1234567890";
/**
*
*/
private int intVar;
/**
*
*/
@IgniteToStringInclude(sensitive = true)
private long longVar;
/**
*
*/
@IgniteToStringOrder(1)
private UUID uuidVar = UUID.randomUUID();
/**
*
*/
private boolean boolVar;
/**
*
*/
private byte byteVar;
/**
*
*/
private String name = "qwertyuiopasdfghjklzxcvbnm";
/**
*
*/
private final Integer finalInt = 2;
/**
*
*/
private List<String> strList;
/**
*
*/
@IgniteToStringInclude
private Map<String, String> strMap;
/**
*
*/
@IgniteToStringInclude
private List<String> strListIncl;
/**
*
*/
private Object obj = new Object();
/**
*
*/
private ReadWriteLock lock;
/**
* @return Manual string.
*/
String toStringManual() {
StringBuilder buf = new StringBuilder();
buf.append(getClass().getSimpleName()).append(" [");
buf.append("id=").append(id).append(", ");
buf.append("uuidVar=").append(uuidVar).append(", ");
buf.append("intVar=").append(intVar).append(", ");
if (S.includeSensitive())
buf.append("longVar=").append(longVar).append(", ");
buf.append("boolVar=").append(boolVar).append(", ");
buf.append("byteVar=").append(byteVar).append(", ");
buf.append("name=").append(name).append(", ");
buf.append("finalInt=").append(finalInt).append(", ");
buf.append("strMap=").append(strMap).append(", ");
buf.append("strListIncl=").append(strListIncl);
buf.append("]");
return buf.toString();
}
/**
* @return Automatic string.
*/
String toStringAutomatic() {
return S.toString(TestClass1.class, this);
}
/**
* @return Automatic string with additional parameters.
*/
String toStringWithAdditionalAutomatic() {
return S.toString(TestClass1.class, this, "newParam1", 1, false, "newParam2", 2, true);
}
/**
* @return Manual string with additional parameters.
*/
String toStringWithAdditionalManual() {
StringBuilder s = new StringBuilder(toStringManual());
s.setLength(s.length() - 1);
s.append(", newParam1=").append(1);
if (S.includeSensitive())
s.append(", newParam2=").append(2);
s.append(']');
return s.toString();
}
}
/**
*
*/
@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "FieldMayBeFinal", "unused"})
private static class TestClass2 {
/**
*
*/
@SuppressWarnings("unused")
@IgniteToStringInclude
private String str;
/**
*
*/
@IgniteToStringInclude
private Object[] nullArr;
/**
* @param str String.
*/
TestClass2(String str) {
this.str = str;
}
}
/**
*
*/
@SuppressWarnings("FieldMayBeFinal")
private static class ClassWithList {
/**
*
*/
@IgniteToStringInclude
private List<SlowToStringObject> list = new LinkedList<>();
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(ClassWithList.class, this);
}
}
/**
*
*/
@SuppressWarnings("FieldMayBeFinal")
private static class ClassWithMap {
/**
*
*/
@IgniteToStringInclude
private Map<Integer, SlowToStringObject> map = new HashMap<>();
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(ClassWithMap.class, this);
}
}
/**
* Class sleeps a short quanta of time to increase chances of data race
* between {@link IgniteToStringBuilder} iterating over collection user thread concurrently modifying it.
*/
private static class SlowToStringObject {
/** {@inheritDoc} */
@Override public String toString() {
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
return super.toString();
}
}
/**
*
*/
@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "MismatchedReadAndWriteOfArray", "unused", "FieldMayBeFinal"})
private static class Parent {
/**
*
*/
private int a;
/**
*
*/
@IgniteToStringInclude
private Parent[] pa = new Parent[1];
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(Parent.class, this);
}
}
/**
*
*/
@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "MismatchedReadAndWriteOfArray", "unused", "FieldMayBeFinal"})
private static class Child extends Parent {
/**
*
*/
private int b;
/**
*
*/
@IgniteToStringInclude
private Parent[] pb = new Parent[1];
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(Child.class, this, super.toString());
}
}
/**
*
*/
private static class Wrapper {
/**
*
*/
@IgniteToStringInclude
Parent p = new Child();
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(Wrapper.class, this);
}
}
}