From 8e266ec5db4f375d37426e68ca3e9085f281f158 Mon Sep 17 00:00:00 2001 From: Paul King Date: Fri, 28 Aug 2026 14:01:39 +1000 Subject: [PATCH] Text renderer: materialize CharSequence values as String #130 --- .../jupyter/kernel/display/common/Text.java | 20 ++-- .../kernel/display/common/TextTest.java | 92 +++++++++++++++++++ 2 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 jjava-jupyter/src/test/java/org/dflib/jjava/jupyter/kernel/display/common/TextTest.java diff --git a/jjava-jupyter/src/main/java/org/dflib/jjava/jupyter/kernel/display/common/Text.java b/jjava-jupyter/src/main/java/org/dflib/jjava/jupyter/kernel/display/common/Text.java index 3403739..281931c 100644 --- a/jjava-jupyter/src/main/java/org/dflib/jjava/jupyter/kernel/display/common/Text.java +++ b/jjava-jupyter/src/main/java/org/dflib/jjava/jupyter/kernel/display/common/Text.java @@ -22,13 +22,17 @@ public static void registerAll(Renderer renderer) { } public static void renderCharSequence(CharSequence data, RenderContext context) { - context.renderIfRequested(JS, () -> data); - context.renderIfRequested(PLAIN, () -> data); - context.renderIfRequested(MARKDOWN, () -> data); - context.renderIfRequested(LATEX, () -> data); - context.renderIfRequested(HTML, () -> data); - context.renderIfRequested(CSS, () -> data); - context.renderIfRequested(SVG, () -> data); - context.renderIfRequested(JSON, () -> data); + // materialize as String: the stored value is later serialized by Gson, which + // handles Strings but turns other CharSequence implementations into a reflective + // field dump (or fails on JDK-internal types such as CharBuffer) + String text = String.valueOf(data); + context.renderIfRequested(JS, () -> text); + context.renderIfRequested(PLAIN, () -> text); + context.renderIfRequested(MARKDOWN, () -> text); + context.renderIfRequested(LATEX, () -> text); + context.renderIfRequested(HTML, () -> text); + context.renderIfRequested(CSS, () -> text); + context.renderIfRequested(SVG, () -> text); + context.renderIfRequested(JSON, () -> text); } } diff --git a/jjava-jupyter/src/test/java/org/dflib/jjava/jupyter/kernel/display/common/TextTest.java b/jjava-jupyter/src/test/java/org/dflib/jjava/jupyter/kernel/display/common/TextTest.java new file mode 100644 index 0000000..b2e391d --- /dev/null +++ b/jjava-jupyter/src/test/java/org/dflib/jjava/jupyter/kernel/display/common/TextTest.java @@ -0,0 +1,92 @@ +package org.dflib.jjava.jupyter.kernel.display.common; + +import org.dflib.jjava.jupyter.kernel.display.DisplayData; +import org.dflib.jjava.jupyter.kernel.display.Renderer; +import org.dflib.jjava.jupyter.kernel.display.mime.MIMEType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.CharBuffer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TextTest { + + private Renderer renderer; + + @BeforeEach + public void setUp() { + this.renderer = new Renderer(); + Text.registerAll(this.renderer); + } + + @Test + public void rendersStringAsIs() { + DisplayData data = this.renderer.render("hello"); + + assertEquals("hello", data.getData(MIMEType.TEXT_PLAIN)); + } + + @Test + public void materializesStringBuilder() { + DisplayData data = this.renderer.render(new StringBuilder("hello")); + + assertEquals("hello", data.getData(MIMEType.TEXT_PLAIN)); + } + + @Test + public void materializesCharBuffer() { + // CharBuffer is a CharSequence whose implementation classes Gson cannot + // serialize reflectively on modern JDKs (JsonIOException) + DisplayData data = this.renderer.render(CharBuffer.wrap("hello")); + + assertEquals("hello", data.getData(MIMEType.TEXT_PLAIN)); + } + + @Test + public void materializesCustomCharSequence() { + DisplayData data = this.renderer.render(new CustomText("hello")); + + assertEquals("hello", data.getData(MIMEType.TEXT_PLAIN)); + } + + @Test + public void materializesForAlternateMimeTypes() { + DisplayData data = this.renderer.renderAs(new CustomText("hi"), MIMEType.TEXT_HTML.toString()); + + assertEquals("hi", data.getData(MIMEType.TEXT_HTML)); + } + + /** + * Stand-in for library-provided CharSequence implementations (e.g. + * commons-text TextStringBuilder, Groovy GString) that Gson would + * otherwise serialize as a reflective field dump. + */ + private static class CustomText implements CharSequence { + private final String value; + + CustomText(String value) { + this.value = value; + } + + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int index) { + return value.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return value.subSequence(start, end); + } + + @Override + public String toString() { + return value; + } + } +}