content: /*
   * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License").
   * You may not use this file except in compliance with the License.
   * A copy of the License is located at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * or in the "license" file accompanying this file. This file 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 com.amazon.ion.impl;
  
  import static com.amazon.ion.SystemSymbols.ION_1_0;
  import static com.amazon.ion.system.IonWriterBuilder.InitialIvmHandling.ENSURE;
  import static com.amazon.ion.system.IonWriterBuilder.InitialIvmHandling.SUPPRESS;
  import static com.amazon.ion.system.IonWriterBuilder.IvmMinimizing.DISTANT;
  import static java.lang.String.format;
  
  import com.amazon.ion.IonBinaryWriter;
  import com.amazon.ion.IonDatagram;
  import com.amazon.ion.IonReader;
  import com.amazon.ion.IonSequence;
  import com.amazon.ion.IonStruct;
  import com.amazon.ion.IonWriter;
  import com.amazon.ion.SymbolTable;
  import com.amazon.ion.SystemSymbols;
  import com.amazon.ion.system.IonTextWriterBuilder;
  import com.amazon.ion.system.IonTextWriterBuilder.LstMinimizing;
  import com.amazon.ion.system.IonWriterBuilder.IvmMinimizing;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import org.junit.Test;
  
  
  public class TextWriterTest
      extends OutputStreamWriterTestCase
  {
      private IonTextWriterBuilder options;
  
      @Override
      protected IonWriter makeWriter(OutputStream out, SymbolTable... imports)
          throws Exception
      {
          myOutputForm = OutputForm.TEXT;
  
          if (options != null) return options.withImports(imports).build(out);
  
          return system().newTextWriter(out, imports);
      }
  
      protected String outputString()
          throws Exception
      {
          byte[] utf8Bytes = outputByteArray();
          return _Private_Utils.utf8(utf8Bytes);
      }
  
      @Test
      public void testNotWritingSymtab()
          throws Exception
      {
          iw = makeWriter();
          iw.writeSymbol("holla");
          String ionText = outputString();
  
          assertEquals("holla", ionText);
      }
  
  
      @Test
      public void testEnsureInitialIvm()
          throws Exception
      {
          options = IonTextWriterBuilder.standard();
          iw = makeWriter();
          iw.writeNull();
          String ionText = outputString();
          assertEquals("null", ionText);
  
          options.setInitialIvmHandling(ENSURE);
          iw = makeWriter();
          iw.writeNull();
          ionText = outputString();
          assertEquals(ION_1_0 + " null", ionText);
  
          iw = makeWriter();
          iw.writeSymbol(SystemSymbols.ION_1_0);
          iw.writeNull();
          ionText = outputString();
          assertEquals(ION_1_0 + " null", ionText);
      }
  
  
      @Test
      public void testIvmMinimizing()
          throws Exception
      {
          options = IonTextWriterBuilder.standard();
          iw = makeWriter();
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol("foo");
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol("bar");
          iw.writeSymbol(ION_1_0);
  
          String ionText = outputString();
          assertEquals(ION_1_0 + " " + ION_1_0 + " foo " +
                       ION_1_0 + " " + ION_1_0 + " " + ION_1_0 + " bar " +
                       ION_1_0,
                       ionText);
  
          options.setIvmMinimizing(IvmMinimizing.ADJACENT);
          iw = makeWriter();
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol("foo");
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol("bar");
          iw.writeSymbol(ION_1_0);
  
          ionText = outputString();
          assertEquals(ION_1_0 + " foo " + ION_1_0 + " bar " + ION_1_0,
                       ionText);
      }
  
      @Test
      public void testLstMinimizing()
          throws Exception
      {
          SymbolTable fred1 = Symtabs.register("fred",   1, catalog());
  
          IonBinaryWriter binaryWriter = system().newBinaryWriter(fred1);
          binaryWriter.writeSymbol("fred_1");
          binaryWriter.writeSymbol("ginger");
          binaryWriter.finish();
          byte[] binaryData = binaryWriter.getBytes();
  
          options = IonTextWriterBuilder.standard();
  
          // TODO User reader still transfers local symtabs!
          IonReader binaryReader = system().newReader(binaryData);
          iw = makeWriter();
          iw.writeValues(binaryReader);
  
          String ionText = outputString();
          assertEquals(// TODO "$ion_1_0 " +
                       "$ion_symbol_table::{imports:[{name:\"fred\",version:1,max_id:2}],"
                       +                   "symbols:[\"ginger\"]} " +
                       "fred_1 ginger",
                       ionText);
  
          options.setLstMinimizing(LstMinimizing.LOCALS);
          binaryReader = system().newReader(binaryData);
          iw = makeWriter();
          iw.writeValues(binaryReader);
          ionText = outputString();
          assertEquals(// TODO "$ion_1_0 " +
                       "$ion_symbol_table::{imports:[{name:\"fred\",version:1,max_id:2}]} " +
                       "fred_1 ginger",
                       ionText);
  
          options.setLstMinimizing(LstMinimizing.EVERYTHING);
          binaryReader = system().newReader(binaryData);
          iw = makeWriter();
          iw.writeValues(binaryReader);
          ionText = outputString();
          assertEquals(// TODO "$ion_1_0 " +
                       "$ion_1_0 fred_1 ginger",
                       ionText);
  
          options.setInitialIvmHandling(SUPPRESS);
          binaryReader = system().newReader(binaryData);
          iw = makeWriter();
          iw.writeValues(binaryReader);
          ionText = outputString();
          assertEquals("fred_1 ginger",
                       ionText);
      }
  
  
      @Test
      public void testC1ControlCodes()
          throws Exception
      {
          options = IonTextWriterBuilder.standard();
          options.setInitialIvmHandling(SUPPRESS);
  
          iw = makeWriter();
          iw.writeString("\u0080 through \u009f"); // Note Java Unicode escapes!
  
          assertEquals("\"\\x80 through \\x9f\"", outputString());
      }
  
  
      private void expectRendering(String expected, IonDatagram original)
          throws Exception
      {
          iw = makeWriter();
          original.writeTo(iw);
  
          assertEquals(original, reload());
  
          String actual = outputString();
          assertEquals(expected, actual);
      }
  
      @Test
      public void testWritingLongStrings()
          throws Exception
      {
          options = IonTextWriterBuilder.standard();
          options.setInitialIvmHandling(SUPPRESS);
          options.setLongStringThreshold(5);
  
          // This is tricky because we must avoid triple-quoting multiple
          // long strings in such a way that they'd get concatenated together!
          IonDatagram dg = system().newDatagram();
          dg.add().newNullString();
          dg.add().newString("hello");
          dg.add().newString("hello\nnurse");
          dg.add().newString("goodbye").addTypeAnnotation("a");
          dg.add().newString("what's\nup\ndoc");
  
          expectRendering("null.string \"hello\" '''hello\nnurse''' a::'''goodbye''' \"what's\\nup\\ndoc\"",
                          dg);
  
          dg.clear();
          dg.add().newString("looong");
          IonSequence seq = dg.add().newEmptySexp();
          seq.add().newString("looong");
          seq.add().newString("hello");
          seq.add().newString("hello\nnurse");
          seq.add().newString("goodbye").addTypeAnnotation("a");
          seq.add().newString("what's\nup\ndoc");
  
          expectRendering("'''looong''' ('''looong''' \"hello\" '''hello\nnurse''' a::'''goodbye''' \"what's\\nup\\ndoc\")",
                          dg);
  
          dg.clear();
          dg.add().newString("looong");
          seq = dg.add().newEmptyList();
          seq.add().newString("looong");
          seq.add().newString("hello");
          seq.add().newString("hello\nnurse");
          seq.add().newString("what's\nup\ndoc");
  
          expectRendering("'''looong''' ['''looong''',\"hello\",'''hello\n" +
                          "nurse''','''what\\'s\n" +
                          "up\n" +
                          "doc''']",
                          dg);
  
          options.setLongStringThreshold(0);
          expectRendering("\"looong\" [\"looong\",\"hello\",\"hello\\nnurse\",\"what's\\nup\\ndoc\"]",
              dg);
  
          options.setLongStringThreshold(5);
          dg.clear();
          dg.add().newString("looong");
          IonStruct struct = dg.add().newEmptyStruct();
          struct.add("a").newString("looong");
          struct.add("b").newString("hello");
          struct.add("c").newString("hello\nnurse");
          struct.add("d").newString("what's\nup\ndoc");
  
          expectRendering(
              "'''looong''' {a:'''looong''',b:\"hello\",c:'''hello\n" +
              "nurse''',d:'''what\\'s\n" +
              "up\n" +
              "doc'''}",
              dg
          );
  
          options.withPrettyPrinting();
          expectRendering(
              // TODO amazon-ion/ion-java/issues/57 determine if these really should be platform independent newlines
              format(
                  "%n" +
                  "'''looong'''%n" +
                  "{%n" +
                          "  a:'''looong''',%n" +
                  "  b:\"hello\",%n" +
                  "  c:'''hello\n" +
                          "nurse''',%n" +
                          "  d:'''what\\'s\n" +
                          "up\n" +
                          "doc'''%n" +
                          "}"
              ),
              dg
          );
      }
  
      @Test
      public void testJsonLongStrings()
          throws Exception
      {
          options = IonTextWriterBuilder.json();
          options.setLongStringThreshold(5);
  
          IonDatagram dg = system().newDatagram();
          dg.add().newString("hello");
          dg.add().newString("hello!");
          dg.add().newString("goodbye");
          dg.add().newString("what's\nup\ndoc");
  
          expectRendering("\"hello\" '''hello!''' \"goodbye\" '''what\\'s\nup\ndoc'''",
                          dg);
      }
  
      @Test
      public void testJsonSystemMinimization()
          throws Exception
      {
          SymbolTable fred1 = Symtabs.register("fred",   1, catalog());
  
          options = IonTextWriterBuilder.json();
          iw = makeWriter(fred1);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol("fred_1");
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol("fred_1");
  
          assertEquals("\"fred_1\" \"fred_1\"", outputString());
      }
  
      @Test
      public void testWritingTopLevelValuesOnNewLinesWithoutPrettyPrint()
      {
          IonTextWriterBuilder writerBuilder = IonTextWriterBuilder.standard()
                  .withInitialIvmHandling(SUPPRESS)
                  .withWriteTopLevelValuesOnNewLines(true)
                  .withNewLineType(IonTextWriterBuilder.NewLineType.LF);
  
          IonDatagram dg = system().newDatagram();
          dg.add().newString("Foo");
          dg.add().newSymbol("Bar");
          dg.add().newSexp(new int[]{1, 2, 3});
          dg.add().newList(new int[]{4, 5, 6});
          IonStruct struct = dg.add().newEmptyStruct();
          struct.add("def").newInt(42);
          struct.addTypeAnnotation("abc");
  
          StringBuilder sb = new StringBuilder();
          IonWriter writer = writerBuilder.build(sb);
          dg.writeTo(writer);
          assertEquals("\"Foo\"\nBar\n(1 2 3)\n[4,5,6]\nabc::{def:42}", sb.toString());
      }
  
      @Test
      public void testWritingTopLevelValuesOnNewLinesShouldHaveNoEffectWithPrettyPrint()
      {
          // Setting top-level newlines to false should have no effect when pretty printing.
          IonTextWriterBuilder writerBuilder = IonTextWriterBuilder.standard()
                  .withInitialIvmHandling(SUPPRESS)
                  .withWriteTopLevelValuesOnNewLines(false)
                  .withPrettyPrinting()
                  .withNewLineType(IonTextWriterBuilder.NewLineType.LF);
  
          IonDatagram dg = system().newDatagram();
          dg.add().newString("Foo");
          dg.add().newSymbol("Bar");
          dg.add().newSexp(new int[]{1, 2, 3});
          dg.add().newList(new int[]{4, 5, 6});
  
          StringBuilder sb = new StringBuilder();
          IonWriter writer = writerBuilder.build(sb);
          dg.writeTo(writer);
          assertEquals("\n\"Foo\"\nBar\n(\n  1\n  2\n  3\n)\n[\n  4,\n  5,\n  6\n]", sb.toString());
      }
      
      @Test
      public void testNewLineTypesWithPrettyPrinting()
      {
          for (IonTextWriterBuilder.NewLineType nlt : IonTextWriterBuilder.NewLineType.values()) {
              String expected = String.format("%s\"Foo\"%<sBar%<s(%<s  1%<s  2%<s  3%<s)%<s[%<s  4,%<s  5,%<s  6%<s]", nlt.getCharSequence());
  
              IonTextWriterBuilder writerBuilder = IonTextWriterBuilder.standard()
                      .withInitialIvmHandling(SUPPRESS)
                      .withWriteTopLevelValuesOnNewLines(false)
                      .withPrettyPrinting()
                      .withNewLineType(nlt);
  
              IonDatagram dg = system().newDatagram();
              dg.add().newString("Foo");
              dg.add().newSymbol("Bar");
              dg.add().newSexp(new int[]{1, 2, 3});
              dg.add().newList(new int[]{4, 5, 6});
  
              StringBuilder sb = new StringBuilder();
              IonWriter writer = writerBuilder.build(sb);
              dg.writeTo(writer);
              assertEquals(expected, sb.toString());
          }
      }
  
      @Test
      public void testNewLineTypesWithStandardPrinting()
      {
          for (IonTextWriterBuilder.NewLineType nlt : IonTextWriterBuilder.NewLineType.values()) {
              String expected = String.format("\"Foo\"%sBar%<s(1 2 3)%<s[4,5,6]", nlt.getCharSequence());
  
              IonTextWriterBuilder writerBuilder = IonTextWriterBuilder.standard()
                      .withInitialIvmHandling(SUPPRESS)
                      .withWriteTopLevelValuesOnNewLines(true)
                      .withNewLineType(nlt);
  
              IonDatagram dg = system().newDatagram();
              dg.add().newString("Foo");
              dg.add().newSymbol("Bar");
              dg.add().newSexp(new int[]{1, 2, 3});
              dg.add().newList(new int[]{4, 5, 6});
  
              StringBuilder sb = new StringBuilder();
              IonWriter writer = writerBuilder.build(sb);
              dg.writeTo(writer);
              assertEquals(expected, sb.toString());
          }
      }
  
      @Test
      public void testWritingLongClobs()
          throws Exception
      {
          options = IonTextWriterBuilder.standard();
          options.setInitialIvmHandling(SUPPRESS);
          options.setLongStringThreshold(4);
  
  
          IonDatagram dg = system().newDatagram();
          dg.add().newClob(new byte[]{'a', '"', '\'', '\n'});
  
          expectRendering("{{\"a\\\"'\\n\"}}", dg);
  
          dg.clear();
          dg.add().newClob(new byte[]{'a', '"', '\'', '\n', 'c'});
          expectRendering("{{'''a\"\\'\n" +
                          "c'''}}",
                          dg);
      }
  
      @Test
      public void testWritingJsonLongClobs()
          throws Exception
      {
          options = IonTextWriterBuilder.json();
          options.setInitialIvmHandling(SUPPRESS);
          options.setLongStringThreshold(4);
  
          // Cannot call expectRendering as the input to reload() would be Json rather than Ion
          // test 1
          IonDatagram dg = system().newDatagram();
          dg.add().newClob(new byte[]{'a', '"', '\'', '\n'});
  
          // expectRendering
          iw = makeWriter();
          dg.writeTo(iw);
  
          IonDatagram reloaded = loader().load("{{\"a\\\"'\\n\"}}");
          assertEquals(dg, reloaded);
  
          String actual = outputString();
          assertEquals("\"a\\\"'\\n\"", actual);
  
          // test 2
          dg.clear();
          dg.add().newClob(new byte[]{'a', '"', '\'', '\n', 'c', 0x7F});
  
          // expectRendering
          iw = makeWriter();
          dg.writeTo(iw);
  
          reloaded = loader().load("{{'''a\"\\'\nc\\x7f'''}}");
          assertEquals(dg, reloaded);
  
          actual = outputString();
          assertEquals("\"a\\\"'\\nc\\u007f\"", actual);
      }
  
      @Test
      public void testWritingFloatNanToJson()
              throws Exception
      {
          options = IonTextWriterBuilder.json();
          options.setInitialIvmHandling(SUPPRESS);
  
          IonDatagram dg = system().newDatagram();
          dg.add().newFloat(Double.NaN);
  
          iw = makeWriter();
          dg.writeTo(iw);
  
          String actual = outputString();
          assertEquals("null", actual);
      }
  
      @Test
      public void testWritingFloatPositiveInfinityToJson()
              throws Exception
      {
          options = IonTextWriterBuilder.json();
          options.setInitialIvmHandling(SUPPRESS);
  
          IonDatagram dg = system().newDatagram();
          dg.add().newFloat(Double.POSITIVE_INFINITY);
  
          iw = makeWriter();
          dg.writeTo(iw);
  
          String actual = outputString();
          assertEquals("null", actual);
      }
  
      @Test
      public void testWritingFloatNegativeInfinityToJson()
              throws Exception
      {
          options = IonTextWriterBuilder.json();
          options.setInitialIvmHandling(SUPPRESS);
  
          IonDatagram dg = system().newDatagram();
          dg.add().newFloat(Double.NEGATIVE_INFINITY);
  
          iw = makeWriter();
          dg.writeTo(iw);
  
          String actual = outputString();
          assertEquals("null", actual);
      }
  
      @Test
      public void testSuppressInitialIvm()
          throws Exception
      {
          iw = makeWriter();
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeNull();
          iw.writeSymbol(ION_1_0);
          iw.writeNull();
  
          assertEquals(ION_1_0 + " " + ION_1_0 + " null " + ION_1_0 + " null",
                       outputString());
  
          options = IonTextWriterBuilder.standard().withInitialIvmHandling(SUPPRESS);
  
          iw = makeWriter();
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeNull();
          iw.writeSymbol(ION_1_0);
          iw.writeNull();
  
          assertEquals("null " + ION_1_0 + " null", outputString());
      }
  
      @Test
      public void testMinimizeDistantIvm()
          throws Exception
      {
          options = IonTextWriterBuilder.standard().withIvmMinimizing(DISTANT);
  
          iw = makeWriter();
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeNull();
          iw.writeSymbol(ION_1_0);
          iw.writeSymbol(ION_1_0);
          iw.writeNull();
  
          assertEquals(ION_1_0 + " null null", outputString());
      }
  
      @Test @Override
      public void testWritingLob()
          throws Exception
      {
          super.testWritingLob();
  
          options = IonTextWriterBuilder.standard();
          super.testWritingLob();
  
          options = IonTextWriterBuilder.pretty();
          super.testWritingLob();
  
          options.setLongStringThreshold(2);
          super.testWritingLob();
      }
  
      @Test @Override
      public void testFinishDoesReset()
          throws Exception
      {
          super.testFinishDoesReset();
  
          options = IonTextWriterBuilder.standard().withIvmMinimizing(DISTANT);
          super.testFinishDoesReset();
      }
  
      @Test @Override
      public void testAnnotationNotSetToIvmOnStartOfStream()
          throws Exception
      {
          options = IonTextWriterBuilder.standard().withInitialIvmHandling(ENSURE);
  
          super.testAnnotationNotSetToIvmOnStartOfStream();
      }
  
      @Test(expected = IllegalArgumentException.class)
      public void testNegativeMaximumTimestampDigitsFails() throws IOException  {
          try (IonWriter writer = IonTextWriterBuilder.standard().withMaximumTimestampPrecisionDigits(-1).build(new StringBuilder())) {}
      }
  
      @Override
      protected void checkFlushedAfterTopLevelValueWritten()
      {
          checkFlushed(true);
          myOutputStreamWrapper.flushed = false;
      }
  }
  