From 5a7e8456e10c8688509531548902ff7579366ecc Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 8 Apr 2022 17:35:27 +1200 Subject: [PATCH 1/4] #2641 - Provide a decent default toString() implementation for entity beans --- .../java/io/ebean/bean/BeanCollection.java | 2 +- .../main/java/io/ebean/bean/EntityBean.java | 6 +- .../java/io/ebean/bean/ToStringAware.java | 12 ++ .../java/io/ebean/bean/ToStringBuilder.java | 153 ++++++++++++++++++ .../main/java/io/ebean/common/BeanList.java | 10 +- .../main/java/io/ebean/common/BeanMap.java | 14 ++ .../main/java/io/ebean/common/BeanSet.java | 10 +- .../io/ebean/bean/ToStringBuilderTest.java | 124 ++++++++++++++ .../main/resources/META-INF/ebean-version.mf | 2 +- 9 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 ebean-api/src/main/java/io/ebean/bean/ToStringAware.java create mode 100644 ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java create mode 100644 ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java diff --git a/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java b/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java index 6271d40514..96a37bb4a3 100644 --- a/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java +++ b/ebean-api/src/main/java/io/ebean/bean/BeanCollection.java @@ -18,7 +18,7 @@ * java.util.Collection. The reason being that java.util.Map is not a * Collection. I realise this makes this name confusing so I apologise for that. */ -public interface BeanCollection extends Serializable { +public interface BeanCollection extends Serializable, ToStringAware { enum ModifyListenMode { /** diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBean.java b/ebean-api/src/main/java/io/ebean/bean/EntityBean.java index ee54991ad6..e126218295 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBean.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBean.java @@ -11,7 +11,7 @@ * general application consumption. *

*/ -public interface EntityBean extends Serializable { +public interface EntityBean extends Serializable, ToStringAware { /** * Return all the property names in defined order. @@ -109,4 +109,8 @@ default Object _ebean_getFieldIntercept(int fieldIndex) { throw new NotEnhancedException(); } + @Override + default void toString(ToStringBuilder builder) { + throw new NotEnhancedException(); + } } diff --git a/ebean-api/src/main/java/io/ebean/bean/ToStringAware.java b/ebean-api/src/main/java/io/ebean/bean/ToStringAware.java new file mode 100644 index 0000000000..bb86d9629a --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/bean/ToStringAware.java @@ -0,0 +1,12 @@ +package io.ebean.bean; + +/** + * A type that can participate in building toString content with ToStringBuilder. + */ +public interface ToStringAware { + + /** + * Append to the ToStringBuilder. + */ + void toString(ToStringBuilder builder); +} diff --git a/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java b/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java new file mode 100644 index 0000000000..30894b9f6e --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java @@ -0,0 +1,153 @@ +package io.ebean.bean; + +import java.util.Collection; +import java.util.IdentityHashMap; + +/** + * Helps build toString content taking into account recursion. + *

+ * That is, it detects and handles the case where there are relationships that recurse + * and would otherwise become an infinite loop (e.g. bidirectional parent child). + */ +public final class ToStringBuilder { + + /** + * The max number of objects that we allow before stopping content being appended. + */ + private static final int MAX = 100; + + /** + * Max length of content in string form added for any given value. + */ + private static final int TRIM_LENGTH = 500; + + /** + * The max total content after which we stop content being appended. + */ + private static final int MAX_TOTAL_CONTENT = 2000; + + private final IdentityHashMap id = new IdentityHashMap<>(); + private final StringBuilder sb = new StringBuilder(50); + private boolean first = true; + private int counter; + + @Override + public String toString() { + return sb.toString(); + } + + /** + * Set of an object being added. + */ + public void start(Object bean) { + if (counter == 0) { + id.putIfAbsent(bean, 0); + } + if (counter <= MAX) { + sb.append(bean.getClass().getSimpleName()).append("@").append(counter).append("("); + } + } + + /** + * Add a property as name value pair. + */ + public void add(String name, Object value) { + if (value != null && counter <= MAX) { + key(name); + value(value); + } + } + + /** + * Add raw content. + */ + public void addRaw(String content) { + sb.append(content); + } + + /** + * End of an object. + */ + public void end() { + if (counter <= MAX) { + sb.append(")"); + } + } + + private void key(String name) { + if (counter > MAX) { + return; + } + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(name).append(":"); + } + + private void value(Object value) { + if (counter > MAX) { + return; + } + if (value instanceof ToStringAware) { + if (push(value)) { + ((ToStringAware) value).toString(this); + } + } else if (value instanceof Collection) { + addCollection((Collection) value); + } else { + String content = String.valueOf(value); + if (content.length() > TRIM_LENGTH) { + content = content.substring(0, TRIM_LENGTH) + " "; + } + sb.append(content); + if (sb.length() >= MAX_TOTAL_CONTENT) { + sb.append(" ..."); + counter += MAX; + } + } + } + + /** + * Add a collection of values. + */ + public void addCollection(Collection c) { + if (c == null || c.isEmpty()) { + sb.append("[]"); + return; + } + int collectionPos = 0; + sb.append("["); + for (Object o : c) { + if (collectionPos++ > 0) { + sb.append(", "); + } + value(o); + if (counter > MAX) { + return; + } + } + sb.append("]"); + } + + private boolean push(Object bean) { + if (counter > MAX) { + return false; + } + if (counter == MAX) { + sb.append(" ..."); + counter++; + return false; + } + Integer idx = id.putIfAbsent(bean, counter++); + if (idx != null) { + --counter; + sb.append(bean.getClass().getSimpleName()).append("@").append(idx); + return false; + } + first = true; + return true; + } + +} diff --git a/ebean-api/src/main/java/io/ebean/common/BeanList.java b/ebean-api/src/main/java/io/ebean/common/BeanList.java index ba0adca990..0305b4a5d2 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanList.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanList.java @@ -1,9 +1,6 @@ package io.ebean.common; -import io.ebean.bean.BeanCollection; -import io.ebean.bean.BeanCollectionAdd; -import io.ebean.bean.BeanCollectionLoader; -import io.ebean.bean.EntityBean; +import io.ebean.bean.*; import java.io.Serializable; import java.util.ArrayList; @@ -47,6 +44,11 @@ public BeanList(BeanCollectionLoader loader, EntityBean ownerBean, String proper super(loader, ownerBean, propertyName); } + @Override + public void toString(ToStringBuilder builder) { + builder.addCollection(list); + } + @Override public void reset(EntityBean ownerBean, String propertyName) { this.ownerBean = ownerBean; diff --git a/ebean-api/src/main/java/io/ebean/common/BeanMap.java b/ebean-api/src/main/java/io/ebean/common/BeanMap.java index 643febc14c..25f51b7aa2 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanMap.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanMap.java @@ -3,6 +3,7 @@ import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; +import io.ebean.bean.ToStringBuilder; import java.util.Collection; import java.util.Collections; @@ -40,6 +41,19 @@ public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String pr super(ebeanServer, ownerBean, propertyName); } + @Override + public void toString(ToStringBuilder builder) { + if (map == null || map.isEmpty()) { + builder.addRaw("{}"); + } else { + builder.addRaw("{"); + for (Entry entry : map.entrySet()) { + builder.add(String.valueOf(entry.getKey()), entry.getValue()); + } + builder.addRaw("}"); + } + } + @Override public void reset(EntityBean ownerBean, String propertyName) { this.ownerBean = ownerBean; diff --git a/ebean-api/src/main/java/io/ebean/common/BeanSet.java b/ebean-api/src/main/java/io/ebean/common/BeanSet.java index 0c6f0c5088..e82e82bbbc 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanSet.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanSet.java @@ -1,9 +1,6 @@ package io.ebean.common; -import io.ebean.bean.BeanCollection; -import io.ebean.bean.BeanCollectionAdd; -import io.ebean.bean.BeanCollectionLoader; -import io.ebean.bean.EntityBean; +import io.ebean.bean.*; import java.io.Serializable; import java.util.Collection; @@ -41,6 +38,11 @@ public BeanSet(BeanCollectionLoader loader, EntityBean ownerBean, String propert super(loader, ownerBean, propertyName); } + @Override + public void toString(ToStringBuilder builder) { + builder.addCollection(set); + } + @Override public void reset(EntityBean ownerBean, String propertyName) { this.ownerBean = ownerBean; diff --git a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java new file mode 100644 index 0000000000..1a251e4bdc --- /dev/null +++ b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java @@ -0,0 +1,124 @@ +package io.ebean.bean; + +import io.ebean.common.BeanList; +import io.ebean.common.BeanMap; +import io.ebean.common.BeanSet; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; + +class ToStringBuilderTest { + + @Test + void basic() { + ToStringBuilder builder = new ToStringBuilder(); + builder.start(new Object()); + builder.end(); + assertThat(builder.toString()).isEqualTo("Object@0()"); + } + + @Test + void fields() { + ToStringBuilder builder = new ToStringBuilder(); + builder.start(new Object()); + builder.add("a", 1); + builder.add("b", "B"); + builder.end(); + assertThat(builder.toString()).isEqualTo("Object@0(a:1, b:B)"); + } + + @Test + void flatBean() { + Recurse instance0 = new Recurse(42, "java"); + assertThat(instance0.toString()).isEqualTo("Recurse@0(id:42, nm:java)"); + } + + @Test + void recursive_expect_reference() { + Recurse instance0 = new Recurse(42, "java"); + instance0.other = instance0; + + assertThat(instance0.toString()).isEqualTo("Recurse@0(id:42, nm:java, other:Recurse@0)"); + } + + @Test + void notRecursive() { + Recurse instance0 = new Recurse(42, "java"); + instance0.other = new Recurse(43, "jvm"); + assertThat(instance0.toString()).isEqualTo("Recurse@0(id:42, nm:java, other:Recurse@1(id:43, nm:jvm))"); + } + + @Test + void beanList_null_empty() { + assertThat(toStringFor(new BeanList(null))).isEqualTo("[]"); + assertThat(toStringFor(new BeanList(Collections.emptyList()))).isEqualTo("[]"); + } + + @Test + void beanSet_null_empty() { + assertThat(toStringFor(new BeanSet(null))).isEqualTo("[]"); + assertThat(toStringFor(new BeanSet(Collections.emptySet()))).isEqualTo("[]"); + } + + @Test + void beanMap_null_empty() { + assertThat(toStringFor(new BeanMap(null))).isEqualTo("{}"); + assertThat(toStringFor(new BeanMap(Collections.emptyMap()))).isEqualTo("{}"); + } + + @Test + void beanList_some() { + BeanList list = new BeanList<>(List.of(new Recurse(1, "a"), new Recurse(2, "b"))); + assertThat(toStringFor(list)).isEqualTo("[Recurse@1(id:1, nm:a), Recurse@2(id:2, nm:b)]"); + } + + @Test + void beanSet_some() { + BeanSet list = new BeanSet<>(new LinkedHashSet<>(List.of(new Recurse(1, "a"), new Recurse(2, "b")))); + assertThat(toStringFor(list)).isEqualTo("[Recurse@1(id:1, nm:a), Recurse@2(id:2, nm:b)]"); + } + + @Test + void beanMap_some() { + Map under = new LinkedHashMap<>(); + under.put("a", new Recurse(1, "a")); + under.put("b", new Recurse(2, "b")); + BeanMap list = new BeanMap<>(under); + assertThat(toStringFor(list)).isEqualTo("{a:Recurse@1(id:1, nm:a), b:Recurse@2(id:2, nm:b)}"); + } + + private String toStringFor(ToStringAware aware) { + ToStringBuilder builder = new ToStringBuilder(); + aware.toString(builder); + return builder.toString(); + } + + static class Recurse implements ToStringAware { + + final int id; + final String nm; + Recurse other; + + Recurse(int id, String nm) { + this.id = id; + this.nm = nm; + } + + public String toString() { + ToStringBuilder builder = new ToStringBuilder(); + toString(builder); + return builder.toString(); + } + + @Override + public void toString(ToStringBuilder builder) { + builder.start(this); + builder.add("id", id); + builder.add("nm", nm); + builder.add("other", other); + builder.end(); + } + } +} diff --git a/ebean-core/src/main/resources/META-INF/ebean-version.mf b/ebean-core/src/main/resources/META-INF/ebean-version.mf index 6bcacf1d63..2eb6e1e8e6 100644 --- a/ebean-core/src/main/resources/META-INF/ebean-version.mf +++ b/ebean-core/src/main/resources/META-INF/ebean-version.mf @@ -1 +1 @@ -ebean-version: 129 +ebean-version: 133 From 183b601be8e26d5214bad4712341e46d81e85069 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 8 Apr 2022 18:53:57 +1200 Subject: [PATCH 2/4] #2641 - toString() - Change BeanCollection toString() implementations, suppress unloaded BeanCollections --- .../java/io/ebean/bean/ToStringBuilder.java | 10 +++++++++- .../src/main/java/io/ebean/common/BeanList.java | 11 ++--------- .../src/main/java/io/ebean/common/BeanMap.java | 17 +++++------------ .../src/main/java/io/ebean/common/BeanSet.java | 11 ++--------- .../TestElementCollectionBasicMap.java | 2 +- .../TestElementCollectionBasicSet.java | 2 +- .../TestElementCollectionEmbeddedList.java | 2 +- .../TestElementCollectionEmbeddedMap.java | 2 +- .../TestElementCollectionEnumSet.java | 2 +- pom.xml | 4 ++-- 10 files changed, 25 insertions(+), 38 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java b/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java index 30894b9f6e..2d57ab944c 100644 --- a/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java +++ b/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java @@ -53,6 +53,12 @@ public void start(Object bean) { */ public void add(String name, Object value) { if (value != null && counter <= MAX) { + if (value instanceof BeanCollection) { + if (((BeanCollection)value).isReference()) { + // suppress unloaded bean collections + return; + } + } key(name); value(value); } @@ -91,7 +97,9 @@ private void value(Object value) { return; } if (value instanceof ToStringAware) { - if (push(value)) { + if (value instanceof BeanCollection) { + ((ToStringAware) value).toString(this); + } else if (push(value)) { ((ToStringAware) value).toString(this); } } else if (value instanceof Collection) { diff --git a/ebean-api/src/main/java/io/ebean/common/BeanList.java b/ebean-api/src/main/java/io/ebean/common/BeanList.java index 0305b4a5d2..0a04eb4724 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanList.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanList.java @@ -193,18 +193,11 @@ public boolean isReference() { @Override public String toString() { - StringBuilder sb = new StringBuilder(50); - sb.append("BeanList "); - if (isReadOnly()) { - sb.append("readOnly "); - } if (list == null) { - sb.append("deferred "); + return "BeanList"; } else { - sb.append("size[").append(list.size()).append("] "); - sb.append("list").append(list); + return list.toString(); } - return sb.toString(); } /** diff --git a/ebean-api/src/main/java/io/ebean/common/BeanMap.java b/ebean-api/src/main/java/io/ebean/common/BeanMap.java index 25f51b7aa2..454642d553 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanMap.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanMap.java @@ -197,27 +197,20 @@ public Collection getActualEntries() { @Override public String toString() { - StringBuilder sb = new StringBuilder(50); - sb.append("BeanMap "); - if (isReadOnly()) { - sb.append("readOnly "); - } if (map == null) { - sb.append("deferred "); + return "BeanMap"; } else { - sb.append("size[").append(map.size()).append("]"); - sb.append(" map").append(map); + return map.toString(); } - return sb.toString(); } /** - * Equal if obj is a Map and equal in a Map sense. + * Equal if object is a Map and equal in a Map sense. */ @Override - public boolean equals(Object obj) { + public boolean equals(Object object) { init(); - return map.equals(obj); + return map.equals(object); } @Override diff --git a/ebean-api/src/main/java/io/ebean/common/BeanSet.java b/ebean-api/src/main/java/io/ebean/common/BeanSet.java index e82e82bbbc..683b2c1f9c 100644 --- a/ebean-api/src/main/java/io/ebean/common/BeanSet.java +++ b/ebean-api/src/main/java/io/ebean/common/BeanSet.java @@ -171,18 +171,11 @@ public Collection getActualEntries() { @Override public String toString() { - StringBuilder sb = new StringBuilder(50); - sb.append("BeanSet "); - if (isReadOnly()) { - sb.append("readOnly "); - } if (set == null) { - sb.append("deferred "); + return "BeanSet"; } else { - sb.append("size[").append(set.size()).append("]"); - sb.append(" set").append(set); + return set.toString(); } - return sb.toString(); } /** diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java index 0bf01e2e28..0e5b018530 100644 --- a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicMap.java @@ -192,6 +192,6 @@ public void json() { final EcmPerson fromJson = DB.json().toBean(EcmPerson.class, asJson); assertThat(fromJson.getName()).isEqualTo("Fiona021"); assertThat(fromJson.getPhoneNumbers()).hasSize(2); - assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanMap size[2] map{home=021 1234, work=021 4321}"); + assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("{home=021 1234, work=021 4321}"); } } diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java index 36bb4c28fc..8860f69584 100644 --- a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java @@ -183,6 +183,6 @@ void json() { final EcsPerson fromJson = DB.json().toBean(EcsPerson.class, asJson); assertThat(fromJson.getName()).isEqualTo("Fiona021"); assertThat(fromJson.getPhoneNumbers()).hasSize(2); - assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanSet size[2] set[021 1234, 021 4321]"); + assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("[021 1234, 021 4321]"); } } diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedList.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedList.java index 508c84156f..a3ec36ab72 100644 --- a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedList.java +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedList.java @@ -190,6 +190,6 @@ public void json() { final EcblPerson fromJson = DB.json().toBean(EcblPerson.class, asJson); assertThat(fromJson.getName()).isEqualTo("Fiona64021"); assertThat(fromJson.getPhoneNumbers()).hasSize(2); - assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanList size[2] list[64-021-1234, 64-021-4321]"); + assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("[64-021-1234, 64-021-4321]"); } } diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedMap.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedMap.java index 0c9ab75a0a..bc3cacff1f 100644 --- a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedMap.java +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedMap.java @@ -181,6 +181,6 @@ public void json() { final EcbmPerson fromJson = DB.json().toBean(EcbmPerson.class, asJson); assertThat(fromJson.getName()).isEqualTo("Fiona64021"); assertThat(fromJson.getPhoneNumbers()).hasSize(2); - assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanMap size[2] map{home=64-021-1234, work=64-021-4321}"); + assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("{home=64-021-1234, work=64-021-4321}"); } } diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEnumSet.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEnumSet.java index 27d856347f..9482671f1a 100644 --- a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEnumSet.java +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEnumSet.java @@ -45,6 +45,6 @@ public void json() { final EcEnumPerson fromJson = DB.json().toBean(EcEnumPerson.class, asJson); assertThat(fromJson.getName()).isEqualTo("Enum Person"); assertThat(fromJson.getTags()).hasSize(2); - assertThat(fromJson.getTags().toString()).isEqualTo("BeanSet size[2] set[BLUE, RED]"); + assertThat(fromJson.getTags().toString()).isEqualTo("[BLUE, RED]"); } } diff --git a/pom.xml b/pom.xml index 01f6861528..dc48a5429e 100644 --- a/pom.xml +++ b/pom.xml @@ -46,8 +46,8 @@ 13.0.0 4.5 7.5 - 13.2.0 - 13.2.0 + 13.2.1-RC1 + 13.2.1-RC1 false From be2466003dd09faaa62a7cddd7e931d548143df1 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 8 Apr 2022 22:30:00 +1200 Subject: [PATCH 3/4] #2641 - toString() - addCollection() firstElement change --- .../java/io/ebean/bean/ToStringBuilder.java | 6 ++-- .../io/ebean/bean/ToStringBuilderTest.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java b/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java index 2d57ab944c..1dd87495af 100644 --- a/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java +++ b/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java @@ -125,10 +125,12 @@ public void addCollection(Collection c) { sb.append("[]"); return; } - int collectionPos = 0; + boolean firstElement = true; sb.append("["); for (Object o : c) { - if (collectionPos++ > 0) { + if (firstElement) { + firstElement = false; + } else { sb.append(", "); } value(o); diff --git a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java index 1a251e4bdc..b00fc30bb8 100644 --- a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java +++ b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java @@ -29,6 +29,34 @@ void fields() { assertThat(builder.toString()).isEqualTo("Object@0(a:1, b:B)"); } + @Test + void add_referenceBeanCollection_expect_nothingAdded() { + ToStringBuilder builder = new ToStringBuilder(); + builder.add("ref", new BeanList<>(null)); + assertThat(builder.toString()).isEqualTo(""); + } + + @Test + void addCollection_some() { + ToStringBuilder builder = new ToStringBuilder(); + builder.addCollection( new BeanList<>(List.of("A","B"))); + assertThat(builder.toString()).isEqualTo("[A, B]"); + } + + @Test + void addCollection_null() { + ToStringBuilder builder = new ToStringBuilder(); + builder.addCollection(null); + assertThat(builder.toString()).isEqualTo("[]"); + } + + @Test + void addCollection_empty() { + ToStringBuilder builder = new ToStringBuilder(); + builder.addCollection(Collections.emptyList()); + assertThat(builder.toString()).isEqualTo("[]"); + } + @Test void flatBean() { Recurse instance0 = new Recurse(42, "java"); From 6d6e417c948816022c28469e1a808b63413dd972 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 11 Apr 2022 12:27:09 +1200 Subject: [PATCH 4/4] #2641 - toString() - add tests for max beans and max content --- .../io/ebean/bean/ToStringBuilderTest.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java index b00fc30bb8..99f125ee58 100644 --- a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java +++ b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java @@ -29,6 +29,55 @@ void fields() { assertThat(builder.toString()).isEqualTo("Object@0(a:1, b:B)"); } + @Test + void max100Beans_expect_noMoreContentAfterMax100() { + + String loop100 = addLoopForMaxBeans(100); + String loop101 = addLoopForMaxBeans(101); + String loop900 = addLoopForMaxBeans(900); + + assertThat(loop100).isEqualTo(loop101); + assertThat(loop101).isEqualTo(loop900); + } + + private String addLoopForMaxBeans(int loopMax) { + ToStringBuilder builder = new ToStringBuilder(); + builder.start(new Object()); + for (int i = 0; i <= loopMax; i++) { + addForMax(builder, i); + } + builder.end(); + return builder.toString(); + } + + private void addForMax(ToStringBuilder builder, int i) { + builder.add("c", new B(i)); + } + + @Test + void maxContent_expect_noMoreContentAfterMax2000() { + String loop23 = addContentLoop(23); + String loop24 = addContentLoop(24); + String loop25 = addContentLoop(25); + + assertThat(loop23).isEqualTo(loop24); + assertThat(loop23).isEqualTo(loop25); + } + + private String addContentLoop(int loopMax) { + ToStringBuilder builder = new ToStringBuilder(); + builder.start(new Object()); + for (int i = 0; i <= loopMax; i++) { + addForMaxContent(builder, i); + } + builder.end(); + return builder.toString(); + } + + private void addForMaxContent(ToStringBuilder builder, int i) { + builder.add("someContentThatAddsUp", new Recurse(i, "SomeContentThatAddsUp_SomeContentThatAddsUp!!")); + } + @Test void add_referenceBeanCollection_expect_nothingAdded() { ToStringBuilder builder = new ToStringBuilder(); @@ -123,7 +172,7 @@ private String toStringFor(ToStringAware aware) { return builder.toString(); } - static class Recurse implements ToStringAware { + static final class Recurse implements ToStringAware { final int id; final String nm; @@ -149,4 +198,26 @@ public void toString(ToStringBuilder builder) { builder.end(); } } + + static final class B implements ToStringAware { + + final int id; + + B(int id) { + this.id = id; + } + + public String toString() { + ToStringBuilder builder = new ToStringBuilder(); + toString(builder); + return builder.toString(); + } + + @Override + public void toString(ToStringBuilder builder) { + builder.start(this); + builder.add("b", id); + builder.end(); + } + } }