From 5e891932445a6ddaab7f0164da30bbb44a895b7f Mon Sep 17 00:00:00 2001 From: JFronny Date: Sat, 20 Apr 2024 15:49:30 +0200 Subject: [PATCH] feat(serialize-xml): port tests --- .../serialize/xml/NativeXmlWriter.java | 18 +- .../xml/test/NativeXmlWriterTest.java | 368 ++++++++++++++++++ 2 files changed, 379 insertions(+), 7 deletions(-) create mode 100644 commons-serialize-xml/src/test/java/io/gitlab/jfronny/commons/serialize/xml/test/NativeXmlWriterTest.java diff --git a/commons-serialize-xml/src/main/java/io/gitlab/jfronny/commons/serialize/xml/NativeXmlWriter.java b/commons-serialize-xml/src/main/java/io/gitlab/jfronny/commons/serialize/xml/NativeXmlWriter.java index a9b9c4f..6ad344b 100644 --- a/commons-serialize-xml/src/main/java/io/gitlab/jfronny/commons/serialize/xml/NativeXmlWriter.java +++ b/commons-serialize-xml/src/main/java/io/gitlab/jfronny/commons/serialize/xml/NativeXmlWriter.java @@ -29,8 +29,8 @@ public class NativeXmlWriter implements Closeable, Flushable { private String deferredText = null; private boolean wasText = false; - private boolean lenient; - private boolean escapeNonAscii; + private boolean lenient = false; + private boolean escapeNonAscii = true; public NativeXmlWriter(Writer out) { this.out = Objects.requireNonNull(out, "out == null"); newline = indent = ""; @@ -100,6 +100,9 @@ public class NativeXmlWriter implements Closeable, Flushable { public NativeXmlWriter endTag() throws IOException { int context = peek(); + if (context == DANGLING_NAME) { + throw new IllegalStateException("Dangling name."); + } if (context != TAG_HEAD && context != TAG_BODY) { throw new IllegalStateException("Nesting problem."); } @@ -204,10 +207,10 @@ public class NativeXmlWriter implements Closeable, Flushable { public NativeXmlWriter attributeName(String name) throws IOException { Objects.requireNonNull(name, "name == null"); - wasText = false; if (peek() != TAG_HEAD) { throw new IllegalStateException("Nesting problem."); } + wasText = false; replaceTop(DANGLING_NAME); out.write(' '); name(name); @@ -216,10 +219,10 @@ public class NativeXmlWriter implements Closeable, Flushable { public NativeXmlWriter attributeValue(String value) throws IOException { value = value == null ? "null" : value; - wasText = false; if (peek() != DANGLING_NAME) { throw new IllegalStateException("Nesting problem."); } + wasText = false; replaceTop(TAG_HEAD); out.write('='); out.write('"'); @@ -229,12 +232,13 @@ public class NativeXmlWriter implements Closeable, Flushable { } public NativeXmlWriter text(String text) throws IOException { - if (peek() == TAG_HEAD && !text.contains("\n") && deferredComments.isEmpty()) { + text = text == null ? "null" : text; + if (peek() == TAG_HEAD && !text.contains("\n") && deferredText == null && deferredComments.isEmpty()) { deferredText = text; + wasText = true; return this; } - text = text == null ? "" : text; - if (wasText) comment(""); + if (wasText && deferredComments.isEmpty()) deferredComments.add(""); beforeValue(); escapeText(text, true); wasText = true; diff --git a/commons-serialize-xml/src/test/java/io/gitlab/jfronny/commons/serialize/xml/test/NativeXmlWriterTest.java b/commons-serialize-xml/src/test/java/io/gitlab/jfronny/commons/serialize/xml/test/NativeXmlWriterTest.java new file mode 100644 index 0000000..de8f971 --- /dev/null +++ b/commons-serialize-xml/src/test/java/io/gitlab/jfronny/commons/serialize/xml/test/NativeXmlWriterTest.java @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gitlab.jfronny.commons.serialize.xml.test; + +import io.gitlab.jfronny.commons.serialize.xml.NativeXmlWriter; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +@SuppressWarnings("resource") +public final class NativeXmlWriterTest { + @Test + public void testWriteComments() throws IOException { + String expectedJson = """ + + + + true + + + + + + false + + + + + + """; + + StringWriter sw = new StringWriter(); + NativeXmlWriter jw = new NativeXmlWriter(sw); + jw.setLenient(true); + jw.setIndent(" "); + + jw.comment("comment at file head") + .beginTag("root") + .comment("comment directly after context") + .beginTag("a") + .text("true") + .endTag() + .comment("comment before context") + .beginTag("inner") + .attributeName("att") + .comment("comment directly after attribute name") + .attributeValue("value") + .beginTag("b") + .attributeName("att") + .attributeValue("value") + .comment("comment after attribute value") + .text("false") + .endTag() + .beginTag("cnt") + .comment("only comment inside tag") + .endTag() + .endTag() + .comment("comment before context end") + .endTag() + .comment("comment behind the object"); + + jw.close(); + assertThat(sw.toString()).isEqualTo(expectedJson); + sw.close(); + } + + @Test + public void testDefaultStrictness() throws IOException { + NativeXmlWriter jsonWriter = new NativeXmlWriter(new StringWriter()); + assertThat(jsonWriter.isLenient()).isEqualTo(false); + jsonWriter.text("false"); + jsonWriter.close(); + } + + // for NativeXmlWriter.setLenient + @Test + public void testSetLenientTrue() throws IOException { + NativeXmlWriter jsonWriter = new NativeXmlWriter(new StringWriter()); + jsonWriter.setLenient(true); + assertThat(jsonWriter.isLenient()).isEqualTo(true); + jsonWriter.text("false"); + jsonWriter.close(); + } + + // for NativeXmlWriter.setLenient + @Test + public void testSetLenientFalse() throws IOException { + NativeXmlWriter jsonWriter = new NativeXmlWriter(new StringWriter()); + jsonWriter.setLenient(false); + assertThat(jsonWriter.isLenient()).isEqualTo(false); + jsonWriter.text("false"); + jsonWriter.close(); + } + + @Test + public void testEmptyTag() throws IOException { + StringWriter sw = new StringWriter(); + NativeXmlWriter jw = new NativeXmlWriter(sw); + jw.beginTag("empty").endTag(); + jw.close(); + assertThat(sw.toString()).isEqualTo(""); + } + + @Test + public void testTopLevelValueTypes() throws IOException { + StringWriter string5 = new StringWriter(); + NativeXmlWriter writert = new NativeXmlWriter(string5); + writert.text("a"); + writert.close(); + assertThat(string5.toString()).isEqualTo("a"); + } + + @Test + public void testNameWithoutValue() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.beginTag("tag"); + jsonWriter.attributeName("a"); + try { + jsonWriter.endTag(); + fail(); + } catch (IllegalStateException expected) { + assertThat(expected).hasMessageThat().isEqualTo("Dangling name."); + } + } + + @Test + public void testValueWithoutName() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.beginTag("tag"); + try { + jsonWriter.attributeValue("a"); + fail(); + } catch (IllegalStateException expected) { + assertThat(expected).hasMessageThat().isEqualTo("Nesting problem."); + } + } + + @Test + public void testMultipleTopLevelValuesStrict() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.setLenient(false); + jsonWriter.beginTag("tag").endTag(); + + IllegalStateException expected = + assertThrows(IllegalStateException.class, () -> jsonWriter.beginTag("tag")); + assertThat(expected).hasMessageThat().isEqualTo("XML must have only one top-level value."); + } + + @Test + public void testMultipleTopLevelValuesLenient() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter writer = new NativeXmlWriter(stringWriter); + writer.setLenient(true); + writer.beginTag("tag"); + writer.endTag(); + writer.beginTag("tag2"); + writer.endTag(); + writer.close(); + assertThat(stringWriter.toString()).isEqualTo(""); + } + + @Test + public void testNullName() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.beginTag("tag"); + try { + jsonWriter.attributeName(null); + fail(); + } catch (NullPointerException expected) { + } + } + + @Test + public void testNullStringValue() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.beginTag("tag"); + jsonWriter.attributeName("a"); + jsonWriter.attributeValue(null); + jsonWriter.endTag(); + assertThat(stringWriter.toString()).isEqualTo(""); + } + + @Test + public void testUnicodeLineBreaksEscaped() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.beginTag("body"); + jsonWriter.text("\u2028 \u2029"); + jsonWriter.endTag(); + // JSON specification does not require that they are escaped, but Gson escapes them for + // compatibility with JavaScript where they are considered line breaks + assertThat(stringWriter.toString()).isEqualTo("
 
"); + } + + @Test + public void testDeepNesting() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + for (int i = 0; i < 20; i++) { + jsonWriter.beginTag("a"); + } + for (int i = 0; i < 20; i++) { + jsonWriter.endTag(); + } + assertThat(stringWriter.toString()) + .isEqualTo( + "" + + "" + + "" + + "" + + ""); + } + + @Test + public void testRepeatedName() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.beginTag("tag"); + jsonWriter.attributeName("a"); + jsonWriter.attributeValue("true"); + jsonWriter.attributeName("a"); + jsonWriter.attributeValue("false"); + jsonWriter.beginTag("a").text("true").endTag(); + jsonWriter.beginTag("a").text("false").endTag(); + jsonWriter.endTag(); + // NativeXmlWriter doesn't attempt to detect duplicate names + assertThat(stringWriter.toString()).isEqualTo("truefalse"); + } + + @Test + public void testPrettyPrintArray() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter jsonWriter = new NativeXmlWriter(stringWriter); + jsonWriter.setIndent(" "); + + jsonWriter.beginTag("tag"); + jsonWriter.attribute("a", "b"); + jsonWriter.text("true"); + jsonWriter.text("false"); + jsonWriter.text("5.0"); + jsonWriter.text(null); + jsonWriter.beginTag("object"); + jsonWriter.attributeName("a").attributeValue("6.0"); + jsonWriter.beginTag("b").text("7.0").endTag(); + jsonWriter.endTag(); + jsonWriter.beginTag("tag"); + jsonWriter.text("8.0"); + jsonWriter.text("9.0"); + jsonWriter.endTag(); + jsonWriter.endTag(); + + String expected = + """ + + true + + false + + 5.0 + + null + + 7.0 + + + 8.0 + + 9.0 + + """; + assertThat(stringWriter.toString()).isEqualTo(expected); + } + + @Test + public void testClosedWriterThrowsOnStructure() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter writer = new NativeXmlWriter(stringWriter); + writer.beginTag("tag"); + writer.endTag(); + writer.close(); + try { + writer.beginTag("tag"); + fail(); + } catch (IllegalStateException expected) { + } + try { + writer.endTag(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testClosedWriterThrowsOnName() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter writer = new NativeXmlWriter(stringWriter); + writer.beginTag("tag"); + writer.endTag(); + writer.close(); + try { + writer.attributeName("a"); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testClosedWriterThrowsOnValue() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter writer = new NativeXmlWriter(stringWriter); + writer.beginTag("tag"); + writer.endTag(); + writer.close(); + try { + writer.attributeValue("a"); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testClosedWriterThrowsOnFlush() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter writer = new NativeXmlWriter(stringWriter); + writer.beginTag("tag"); + writer.endTag(); + writer.close(); + try { + writer.flush(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testWriterCloseIsIdempotent() throws IOException { + StringWriter stringWriter = new StringWriter(); + NativeXmlWriter writer = new NativeXmlWriter(stringWriter); + writer.beginTag("tag"); + writer.endTag(); + writer.close(); + writer.close(); + } +}