From 4d7a3d135c3658823700b68fdfc45d5aa7bb8b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20P=C3=B6hler?= Date: Tue, 26 Apr 2022 11:58:12 +0200 Subject: [PATCH] FIX: StackOverflowError when using recursive relation within Map (cherry picked from commit d06bc13b4e1e421546f4997900d5fdc6aa980257) --- .../java/io/ebean/bean/ToStringBuilder.java | 27 ++++++++ .../io/ebean/bean/ToStringBuilderTest.java | 64 +++++++++++++++++++ 2 files changed, 91 insertions(+) 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 1dd87495a..70e759724 100644 --- a/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java +++ b/ebean-api/src/main/java/io/ebean/bean/ToStringBuilder.java @@ -1,7 +1,10 @@ package io.ebean.bean; +import io.ebean.common.BeanMap; + import java.util.Collection; import java.util.IdentityHashMap; +import java.util.Map; /** * Helps build toString content taking into account recursion. @@ -104,6 +107,8 @@ public final class ToStringBuilder { } } else if (value instanceof Collection) { addCollection((Collection) value); + } else if (value instanceof Map) { + addMap(((Map) value)); } else { String content = String.valueOf(value); if (content.length() > TRIM_LENGTH) { @@ -117,6 +122,28 @@ public final class ToStringBuilder { } } + public void addMap(Map map) { + if (map == null || map.isEmpty()) { + sb.append("{}"); + } else { + boolean firstElement = true; + sb.append("{"); + for (Map.Entry entry : map.entrySet()) { + if (firstElement) { + firstElement = false; + } else { + sb.append(", "); + } + sb.append(entry.getKey()).append(":"); + value(entry.getValue()); + if (counter > MAX) { + return; + } + } + sb.append("}"); + } + } + /** * Add a collection of values. */ 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 99f125ee5..c049ec9b0 100644 --- a/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java +++ b/ebean-api/src/test/java/io/ebean/bean/ToStringBuilderTest.java @@ -166,12 +166,76 @@ class ToStringBuilderTest { assertThat(toStringFor(list)).isEqualTo("{a:Recurse@1(id:1, nm:a), b:Recurse@2(id:2, nm:b)}"); } + @Test + void map_recurse() { + final ParamStore paramStore = new ParamStore(1); + + paramStore.map.put("aaa", new ParamValue(1, paramStore)); + paramStore.map.put("bbb", new ParamValue(2, paramStore)); + + assertThat(toStringFor(paramStore)) + .isEqualTo("ParamStore@0(id:1, map:{aaa:ParamValue@1(id:1, pm:ParamStore@0), bbb:ParamValue@2(id:2, pm:ParamStore@0)})"); + } + private String toStringFor(ToStringAware aware) { ToStringBuilder builder = new ToStringBuilder(); aware.toString(builder); return builder.toString(); } + static final class ParamValue implements ToStringAware { + + final int id; + + final ParamStore parentModel; + + ParamValue(final int id, final ParamStore parentModel) { + this.id = id; + this.parentModel = parentModel; + } + + 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("pm", parentModel); + builder.end(); + } + } + + static final class ParamStore implements ToStringAware { + + final int id; + + final Map map = new HashMap<>(); + + + ParamStore(final 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("id", id); + builder.add("map", map); + builder.end(); + } + } + + static final class Recurse implements ToStringAware { final int id;