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();
+ }
+ }
}