[java] directly append strings while writing JSON output
diff --git a/java/src/org/openqa/selenium/json/JsonOutput.java b/java/src/org/openqa/selenium/json/JsonOutput.java index e62a785..e24fc86 100644 --- a/java/src/org/openqa/selenium/json/JsonOutput.java +++ b/java/src/org/openqa/selenium/json/JsonOutput.java
@@ -28,11 +28,13 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -42,6 +44,7 @@ import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.openqa.selenium.internal.Require; import org.openqa.selenium.logging.LogLevelMapping; @@ -106,7 +109,7 @@ public class JsonOutput implements Closeable { private final Map<Predicate<Class<?>>, DepthAwareConsumer> converters; private final Appendable appendable; - private final Consumer<String> appender; + private final Consumer<CharSequence> appender; private final Deque<Node> stack; private String indent = ""; private String lineSeparator = "\n"; @@ -432,8 +435,8 @@ public void close() { } } - private JsonOutput append(String text) { - stack.getFirst().write(text); + private JsonOutput append(CharSequence... fragments) { + stack.getFirst().write(fragments); return this; } @@ -441,26 +444,36 @@ private JsonOutput append(String text) { * Return a quoted JSON string representing the specified Java object. * * @param obj Java object to be represented - * @return quoted JSON string + * @return the quoted JSON string, might be fragmented into chucks */ - private String asString(Object obj) { - StringBuilder toReturn = new StringBuilder("\""); + private CharSequence[] asString(Object obj) { + List<CharSequence> quoted = new ArrayList<>(3); + quoted.add("\""); - String.valueOf(obj) - .chars() - .forEach( - i -> { - String escaped = ESCAPES.get(i); - if (escaped != null) { - toReturn.append(escaped); - } else { - toReturn.append((char) i); - } - }); + String value = String.valueOf(obj); + int n = value.length(); + int last = 0; - toReturn.append('"'); + for (int i = 0; i < n; i++) { + String escaped = ESCAPES.get(Integer.valueOf(value.charAt(i))); - return toReturn.toString(); + if (escaped != null) { + // the text until we need to write an escaped char + quoted.add(value.subSequence(last, i)); + last = i + 1; + + // the escaped char + quoted.add(escaped); + } + } + + // the text after the last escaped char, might be the complete string e.g. when processing huge + // base64 encoded data + quoted.add(value.subSequence(last, n)); + + quoted.add("\""); + + return quoted.toArray(CharSequence[]::new); } /** @@ -570,9 +583,9 @@ private abstract class Node { * a comma and the defined line separator (either {@literal <newline>} or empty string) to * delimit a new object property or array item. * - * @param text text to be appended to the output + * @param fragments text to be appended to the output, might be fragmented into multiple parts */ - public void write(String text) { + public void write(CharSequence... fragments) { if (isEmpty) { isEmpty = false; } else { @@ -580,7 +593,10 @@ public void write(String text) { } appender.accept(indent); - appender.accept(text); + + for (int i = 0; i < fragments.length; i++) { + appender.accept(fragments[i]); + } } } @@ -590,16 +606,16 @@ private class Root extends Node { /** * Write the specified text to the appender of this JSON output object. * - * @param text text to be appended to the output + * @param fragments text to be appended to the output, might be fragmented into multiple parts * @throws JsonException if this {@link JsonOutput} has already been used. */ @Override - public void write(String text) { + public void write(CharSequence... fragments) { if (!isEmpty) { throw new JsonException("Only allowed to write one value to a json stream"); } - super.write(text); + super.write(fragments); } } @@ -629,17 +645,21 @@ public void name(String name) { /** * Write the value of a JSON property to the appender of this JSON output object. * - * @param text JSON object property value + * @param fragments JSON object property value, might be fragmented into multiple parts * @throws JsonException if not expecting a JSON property value */ @Override - public void write(String text) { + public void write(CharSequence... fragments) { if (isNameNext) { - throw new JsonException("Unexpected attempt to write value before name: " + text); + throw new JsonException( + "Unexpected attempt to write value before name: " + + Stream.of(fragments).collect(Collectors.joining())); } isNameNext = true; - appender.accept(text); + for (int i = 0; i < fragments.length; i++) { + appender.accept(fragments[i]); + } } }