text: package com.moandjiezana.toml;
  
  import static org.junit.Assert.*;
  
  import java.io.BufferedReader;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileReader;
  import java.io.IOException;
  import java.io.StringWriter;
  import java.io.UnsupportedEncodingException;
  import java.lang.annotation.RetentionPolicy;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.net.URI;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.LinkedHashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.TimeZone;
  
  import org.hamcrest.Matchers;
  import org.junit.Rule;
  import org.junit.Test;
  import org.junit.rules.ExpectedException;
  import org.junit.rules.TemporaryFolder;
  
  @SuppressWarnings("unused")
  public class TomlWriterTest {
  
    @Rule
    public TemporaryFolder testDirectory = new TemporaryFolder();
    
    @Rule
    public ExpectedException expectedException = ExpectedException.none();
  
    @Test
    public void should_write_primitive_types() {
      class TestClass {
        public String aString = "hello";
        int anInt = 4;
        protected float aFloat = 1.23f;
        private double aDouble = -5.43;
        final boolean aBoolean = false;
        static final int aFinalInt = 1; // Should be skipped
        Date aDate;
      }
  
      TestClass o = new TestClass();
  
      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Africa/Johannesburg"));
      calendar.set(2015, Calendar.JULY, 1, 11, 5, 30);
      calendar.set(Calendar.MILLISECOND, 0);
      o.aDate = calendar.getTime();
      
      String output = new TomlWriter().write(o);
      String expected = "aString = \"hello\"\n" +
          "anInt = 4\n" +
          "aFloat = 1.23\n" +
          "aDouble = -5.43\n" +
          "aBoolean = false\n" +
          "aDate = 2015-07-01T09:05:30Z\n";
  
      assertEquals(expected, output);
    }
  
    class SubChild {
      int anInt;
    }
    class Child {
      SubChild subChild;
      int anInt;
    }
    class Parent {
      Map<String, Object> aMap;
      Child child;
      boolean aBoolean;
    }
  
    private Parent buildNestedMap() {
      Parent parent = new Parent();
      parent.aMap = new LinkedHashMap<String, Object>();
      parent.aMap.put("foo", 1);
      parent.aMap.put("bar", "value1");
      parent.aMap.put("baz.x", true);
      parent.child = new Child();
      parent.child.anInt = 2;
      parent.child.subChild = new SubChild();
      parent.child.subChild.anInt = 4;
      parent.aBoolean = true;
  
      return parent;
    }
  
    @Test
    public void should_write_nested_map_with_default_indentation_policy() {
      String output = new TomlWriter().write(buildNestedMap());
      String expected = "aBoolean = true\n\n" +
          "[aMap]\n" +
          "foo = 1\n" +
          "bar = \"value1\"\n" +
          "\"baz.x\" = true\n\n" +
          "[child]\n" +
          "anInt = 2\n\n" +
          "[child.subChild]\n" +
          "anInt = 4\n";
      assertEquals(expected, output);
    }
  
    @Test
    public void should_follow_indentation_policy_of_indented_values() {
      String output = new TomlWriter.Builder().
        indentValuesBy(2).
        build().
        write(buildNestedMap());
      String expected = "aBoolean = true\n\n" +
          "[aMap]\n" +
          "  foo = 1\n" +
          "  bar = \"value1\"\n" +
          "  \"baz.x\" = true\n\n" +
          "[child]\n" +
          "  anInt = 2\n\n" +
          "[child.subChild]\n" +
          "  anInt = 4\n";
      assertEquals(expected, output);
    }
  
    @Test
    public void should_follow_indentation_policy_of_indented_tables() {
      String output = new TomlWriter.Builder().
        indentTablesBy(2).
        build().
        write(buildNestedMap());
      String expected = "aBoolean = true\n\n" +
          "[aMap]\n" +
          "foo = 1\n" +
          "bar = \"value1\"\n" +
          "\"baz.x\" = true\n\n" +
          "[child]\n" +
          "anInt = 2\n\n" +
          "  [child.subChild]\n" +
          "  anInt = 4\n";
      assertEquals(expected, output);
    }
  
    @Test
    public void should_follow_indentation_policy_of_indented_tables_and_values() {
      String output = new TomlWriter.Builder().
        indentValuesBy(2).
        indentTablesBy(2).
        build().
        write(buildNestedMap());
      String expected = "aBoolean = true\n\n" +
          "[aMap]\n" +
          "  foo = 1\n" +
          "  bar = \"value1\"\n" +
          "  \"baz.x\" = true\n\n" +
          "[child]\n" +
          "  anInt = 2\n\n" +
          "  [child.subChild]\n" +
          "    anInt = 4\n";
      assertEquals(expected, output);
    }
  
    @Test
    public void should_write_array_of_tables_from_object() {
      class Table {
        int anInt;
  
        Table(int anInt) {
          this.anInt = anInt;
        }
      }
      class Config {
        Table[] table = new Table[]{new Table(1), new Table(2)};
        List<Table> table2 = Arrays.asList(new Table(3), new Table(4));
      }
      Config config = new Config();
  
      String output = new TomlWriter().write(config);
      String expected = "[[table]]\n" +
          "anInt = 1\n\n" +
          "[[table]]\n" +
          "anInt = 2\n" +
          "[[table2]]\n" +
          "anInt = 3\n\n" +
          "[[table2]]\n" +
          "anInt = 4\n";
      assertEquals(expected, output);
    }
    
    @Test
    public void should_write_array_of_tables_from_map() throws Exception {
      List<Map<String, Object>> maps = new ArrayList<Map<String,Object>>();
      
      HashMap<String, Object> item1 = new HashMap<String, Object>();
      item1.put("anInt", 1L);
      HashMap<String, Object> item2 = new HashMap<String, Object>();
      item2.put("anInt", 2L);
  
      maps.add(item1);
      maps.add(item2);
      
      Map<String, Object> input = new HashMap<String, Object>();
      input.put("maps", maps);
      
      String output = new TomlWriter().write(input);
      
      String expected = "[[maps]]\n" +
          "anInt = 1\n\n" +
          "[[maps]]\n" +
          "anInt = 2\n";
      assertEquals(expected, output);
    }
  
    @Test
    public void should_write_array_of_array() {
      class ArrayTest {
        int[][] array = {{1, 2, 3}, {4, 5, 6}};
      }
      ArrayTest arrayTest = new ArrayTest();
  
      String output = new TomlWriter.Builder().padArrayDelimitersBy(1).build().write(arrayTest);
      String expected = "array = [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]\n";
      assertEquals(expected, output);
    }
  
    @Test
    public void should_write_list() {
      class ListTest {
        List<Integer> aList = new LinkedList<Integer>();
      }
      ListTest o = new ListTest();
      o.aList.add(1);
      o.aList.add(2);
  
      assertEquals("aList = [ 1, 2 ]\n", new TomlWriter.Builder().padArrayDelimitersBy(1).build().write(o));
    }
  
    @Test
    public void should_handle_zero_length_arrays_and_lists() {
      class TestClass {
        List<Integer> aList = new LinkedList<Integer>();
        Float[] anArray = new Float[0];
      }
      assertEquals("aList = []\nanArray = []\n", new TomlWriter().write(new TestClass()));
    }
  
    @Test
    public void should_reject_heterogeneous_arrays() {
      class BadArray {
        Object[] array = new Object[2];
      }
      BadArray badArray = new BadArray();
      badArray.array[0] = new Integer(1);
      badArray.array[1] = "oops";
  
      expectedException.expect(IllegalStateException.class);
      expectedException.expectMessage(Matchers.startsWith("array"));
      
      new TomlWriter().write(badArray);
    }
  
    @Test
    public void should_reject_nested_heterogeneous_array() {
      class BadArray {
        Map<String, Object> aMap = new HashMap<String, Object>();
      }
      
      BadArray badArray = new BadArray();
      badArray.aMap.put("array", new Object[] { Integer.valueOf(1), "oops" });
  
      expectedException.expect(IllegalStateException.class);
      expectedException.expectMessage("aMap.array");
  
      new TomlWriter().write(badArray);
    }
  
    @Test
    public void should_elide_empty_intermediate_tables() {
      class C {
        int anInt = 1;
      }
      class B {
        C c = new C();
      }
      class A {
        B b = new B();
      }
  
      assertEquals("[b.c]\nanInt = 1\n", new TomlWriter().write(new A()));
    }
    
    @Test
    public void should_write_map() throws Exception {
      assertEquals("a = 1\n", new TomlWriter().write(new Toml().read("a = 1").toMap()));
    }
  
    class Base {
      protected int anInt = 2;
    }
    class Impl extends Base {
      boolean aBoolean = true;
    }
  
    @Test
    public void should_write_classes_with_inheritance() {
      Impl impl = new Impl();
      String expected = "aBoolean = true\nanInt = 2\n";
      assertEquals(expected, new TomlWriter().write(impl));
    }
  
    @Test
    public void should_write_strings_to_toml_utf8() throws UnsupportedEncodingException {
      class Utf8Test {
        String input;
      }
  
      Utf8Test utf8Test = new Utf8Test();
      utf8Test.input = " é foo \u20AC \b \t \n \f \r \" \\ ";
      assertEquals("input = \" é foo € \\b \\t \\n \\f \\r \\\" \\\\ \"\n", new TomlWriter().write(utf8Test));
  
      // Check unicode code points greater than 0XFFFF
      utf8Test.input = " \uD801\uDC28 \uD840\uDC0B ";
      assertEquals("input = \" 𐐨 𠀋 \"\n", new TomlWriter().write(utf8Test));
    }
  
    @Test
    public void should_quote_keys() {
      Map<String, Integer> aMap = new LinkedHashMap<String, Integer>();
      aMap.put("a.b", 1);
      aMap.put("5€", 2);
      aMap.put("c$d", 3);
      aMap.put("e/f", 4);
  
      String expected = "\"a.b\" = 1\n" +
          "\"5€\" = 2\n" +
          "\"c$d\" = 3\n" +
          "\"e/f\" = 4\n";
      assertEquals(expected, new TomlWriter().write(aMap));
    }
    
    @Test
    public void should_quote_keys_in_object() throws Exception {
      class A$ {
        Double µµ = 5.3;
      }
      
      class A {
        int €5 = 5;
        String français = "langue";
        A$ a$ = new A$();
      }
      
      assertEquals("\"€5\" = 5\n\"français\" = \"langue\"\n\n[\"a$\"]\n\"µµ\" = 5.3\n", new TomlWriter().write(new A()));
    }
    
    @Test
    public void should_handle_urls() throws Exception {
      class WithUrl {
        URL url;
        URI uri;
      }
      
      WithUrl from = new WithUrl();
      from.url = new URL("https://github.com");
      from.uri = new URI("https://bitbucket.com");
      
      String expected = "url = \"https://github.com\"\n"
        + "uri = \"https://bitbucket.com\"\n";
      
      assertEquals(expected, new TomlWriter().write(from));
    }
  
    @Test
    public void should_handle_enum() throws Exception {
      class WithEnum {
        RetentionPolicy retentionPolicy = RetentionPolicy.RUNTIME;
      }
      
      assertEquals("retentionPolicy = \"RUNTIME\"\n", new TomlWriter().write(new WithEnum()));
    }
    
    @Test
    public void should_handle_char() throws Exception {
      class WithChar {
        char c = 'a';
      }
      
      assertEquals("c = \"a\"\n", new TomlWriter().write(new WithChar()));
    }
    
    @Test
    public void should_handle_big_numbers() throws Exception {
      class WithBigNumbers {
        BigInteger bigInt = BigInteger.valueOf(1);
        BigDecimal bigDecimal = BigDecimal.valueOf(2.8);
      }
      
      String expected = "bigInt = 1\n"
        + "bigDecimal = 2.8\n";
      
      assertEquals(expected, new TomlWriter().write(new WithBigNumbers()));
    }
    
    @Test
    public void should_handle_wrappers() throws Exception {
      class WithWrappers {
        Character c = Character.valueOf('b');
        Long l = Long.valueOf(2);
        Double d = Double.valueOf(3.4);
      }
      
      String expected = "c = \"b\"\n"
        + "l = 2\n"
        + "d = 3.4\n";
      
      assertEquals(expected, new TomlWriter().write(new WithWrappers()));
    }
    
    @Test
    public void should_use_specified_time_zone() throws Exception {
      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
      calendar.set(2015, Calendar.JULY, 1, 11, 5, 30);
      calendar.set(Calendar.MILLISECOND, 0);
      
      Map<String, Date> o = new HashMap<String, Date>();
      
      o.put("sast", calendar.getTime());
      
      TomlWriter writer = new TomlWriter.Builder().
        timeZone(TimeZone.getTimeZone("Africa/Johannesburg")).
        build();
      
      assertEquals("sast = 2015-07-01T13:05:30+02:00\n", writer.write(o));
    }
    
    @Test
    public void should_show_fractional_seconds() throws Exception {
      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
      calendar.set(2015, Calendar.JULY, 1, 11, 5, 30);
      calendar.set(Calendar.MILLISECOND, 345);
      
      Map<String, Date> o = new HashMap<String, Date>();
      
      o.put("date", calendar.getTime());
      
      TomlWriter writer = new TomlWriter.Builder().
        showFractionalSeconds().
        build();
      
      assertEquals("date = 2015-07-01T11:05:30.345Z\n", writer.write(o));
    }
    
    @Test
    public void should_show_fractional_seconds_in_specified_time_zone() throws Exception {
      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
      calendar.set(2015, Calendar.JULY, 1, 11, 5, 30);
      calendar.set(Calendar.MILLISECOND, 345);
      
      Map<String, Date> o = new LinkedHashMap<String, Date>();
      
      o.put("date", calendar.getTime());
      calendar.set(Calendar.MINUTE, 37);
      o.put("date2", calendar.getTime());
      
      TomlWriter writer = new TomlWriter.Builder().
        timeZone(TimeZone.getTimeZone("Africa/Johannesburg")).
        showFractionalSeconds().
        build();
      
      String expected = "date = 2015-07-01T13:05:30.345+02:00\n"
        + "date2 = 2015-07-01T13:37:30.345+02:00\n";
      
      assertEquals(expected, writer.write(o));
    }
  
    private static class SimpleTestClass {
      int a = 1;
    }
    
    private static class TransientClass {
      int a = 2;
      transient int b = 3;
    }
  
    @Test
    public void should_write_to_writer() throws IOException {
      StringWriter output = new StringWriter();
      new TomlWriter().write(new SimpleTestClass(), output);
  
      assertEquals("a = 1\n", output.toString());
    }
  
    @Test
    public void should_write_to_outputstream() throws IOException {
      ByteArrayOutputStream output = new ByteArrayOutputStream();
      new TomlWriter().write(new SimpleTestClass(), output);
  
      assertEquals("a = 1\n", output.toString());
    }
  
    @Test
    public void should_write_to_file() throws IOException {
      File output = testDirectory.newFile();
      new TomlWriter().write(new SimpleTestClass(), output);
  
      assertEquals("a = 1\n", readFile(output));
    }
    
    @Test
    public void should_skip_transient_fields() throws Exception {
      String toml = new TomlWriter().write(new TransientClass());
      
      assertEquals("a = 2\n", toml);
    }
    
    @Test(expected = IllegalArgumentException.class)
    public void should_refuse_to_write_string_fragment() {
      new TomlWriter().write("fragment");
    }
  
    @Test(expected = IllegalArgumentException.class)
    public void should_refuse_to_write_boolean_fragment() {
      new TomlWriter().write(true);
    }
  
    @Test(expected = IllegalArgumentException.class)
    public void should_refuse_to_write_number_fragment() {
      new TomlWriter().write(42);
    }
  
    @Test(expected = IllegalArgumentException.class)
    public void should_refuse_to_write_date_fragment() {
      new TomlWriter().write(new Date());
    }
  
    @Test(expected = IllegalArgumentException.class)
    public void should_refuse_to_write_array_fragment() {
      new TomlWriter().write(new int[2]);
    }
  
    @Test(expected = IllegalArgumentException.class)
    public void should_refuse_to_write_table_array_fragment() {
      new TomlWriter().write(new SimpleTestClass[2]);
    }
  
    @Test(expected=IllegalArgumentException.class)
    public void should_not_write_list() throws Exception {
      new TomlWriter().write(Arrays.asList("a"));
    }
  
    private String readFile(File input) throws IOException {
      BufferedReader bufferedReader = new BufferedReader(new FileReader(input));
  
      try {
        StringBuilder w = new StringBuilder();
        String line = bufferedReader.readLine();
        while (line != null) {
          w.append(line).append('\n');
          line = bufferedReader.readLine();
        }
  
        return w.toString();
      } finally {
        bufferedReader.close();
      }
    }
  }
  