diff --git a/src/main/java/com/moandjiezana/toml/TomlParser.java b/src/main/java/com/moandjiezana/toml/TomlParser.java index 0a27744..377d058 100644 --- a/src/main/java/com/moandjiezana/toml/TomlParser.java +++ b/src/main/java/com/moandjiezana/toml/TomlParser.java @@ -153,7 +153,7 @@ class TomlParser extends BaseParser { @SuppressNode Rule Comment() { - return Sequence('#', ZeroOrMore(TestNot(NewLine()), ANY), FirstOf(NewLine(), EOI)); + return Sequence(ZeroOrMore(Whitespace()), '#', ZeroOrMore(TestNot(NewLine()), ANY), FirstOf(NewLine(), EOI)); } boolean addTableArray(String name) { @@ -178,7 +178,12 @@ class TomlParser extends BaseParser { boolean pushList() { ArrayList list = new ArrayList(); while (peek() != ArrayList.class) { - list.add(0, pop()); + Object listItem = pop(); + if (list.isEmpty() || list.get(0).getClass().isAssignableFrom(listItem.getClass()) || listItem.getClass().isAssignableFrom(list.get(0).getClass())) { + list.add(0, listItem); + } else { + results().errors.append("Attempt to create mixed array!"); + } } poke(list); diff --git a/src/test/java/com/moandjiezana/toml/BurntSushiTest.java b/src/test/java/com/moandjiezana/toml/BurntSushiTest.java new file mode 100644 index 0000000..bdfd454 --- /dev/null +++ b/src/test/java/com/moandjiezana/toml/BurntSushiTest.java @@ -0,0 +1,184 @@ +package com.moandjiezana.toml; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class BurntSushiTest { + + private InputStream inputToml; + private InputStreamReader expectedJsonReader; + + private static final Gson TEST_GSON = new GsonBuilder() + .registerTypeAdapter(Boolean.class, serialize(Boolean.class)) + .registerTypeAdapter(String.class, serialize(String.class)) + .registerTypeAdapter(Date.class, new JsonSerializer() { + @Override + public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { + DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC")); + return context.serialize(new Value("datetime", iso8601Format.format(src))); + } + }) + .registerTypeHierarchyAdapter(Number.class, new JsonSerializer() { + @Override + public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) { + String number = src.toString(); + String type = number.contains(".") ? "float" : "integer"; + + return context.serialize(new Value(type, number)); + } + }) + .registerTypeHierarchyAdapter(List.class, new JsonSerializer>() { + @Override + public JsonElement serialize(List src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray array = new JsonArray(); + for (Object o : src) { + array.add(context.serialize(o)); + } + + if (!src.isEmpty() && src.get(0) instanceof Map) { + return array; + } + + JsonObject object = new JsonObject(); + object.add("type", new JsonPrimitive("array")); + object.add("value", array); + + return object; + } + }) + .registerTypeAdapter(Value.class, new JsonSerializer() { + @Override + public JsonElement serialize(Value src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + object.add("type", new JsonPrimitive(src.type)); + object.add("value", new JsonPrimitive(src.value)); + + return object; + } + }) + .create(); + + @Test + public void comments_everywhere() throws Exception { + inputToml = getClass().getResourceAsStream("burntsushi/valid/comments_everywhere.toml"); + expectedJsonReader = new InputStreamReader(getClass().getResourceAsStream("burntsushi/valid/comments_everywhere.json")); + JsonElement expectedJson = new Gson().fromJson(expectedJsonReader, JsonElement.class); + + JsonElement actual = new Toml().parse(inputToml).to(JsonElement.class, TEST_GSON); + + Assert.assertEquals(expectedJson, actual); + } + + @Test + @Ignore + public void key_special_chars() throws Exception { + runValidTest("key-special-chars"); + } + + @Test + public void table_array_implicit() throws Exception { + runValidTest("table-array-implicit"); + } + + @Test + public void array_mixed_types_ints_and_floats() throws Exception { + runInvalidTest("array-mixed-types-ints-and-floats"); + } + + @After + public void after() throws IOException { + inputToml.close(); + if (expectedJsonReader != null) { + expectedJsonReader.close(); + } + } + + private void runValidTest(String testName) { + inputToml = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml"); + expectedJsonReader = new InputStreamReader(getClass().getResourceAsStream("burntsushi/valid/" + testName + ".json")); + JsonElement expectedJson = new Gson().fromJson(expectedJsonReader, JsonElement.class); + + Toml toml = new Toml().parse(inputToml); + JsonElement actual = toml.to(JsonElement.class, TEST_GSON); + + Assert.assertEquals(expectedJson, actual); + } + + private void runInvalidTest(String testName) { + inputToml = getClass().getResourceAsStream("burntsushi/invalid/" + testName + ".toml"); + + try { + new Toml().parse(inputToml); + Assert.fail("Should have rejected invalid input!"); + } catch (IllegalStateException e) { + // success + } + } + + private static class Value { + public final String type; + public final String value; + + public Value(String type, String value) { + this.type = type; + this.value = value; + } + } + + private static JsonSerializer serialize(final Class aClass) { + return new JsonSerializer() { + @Override + public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(new Value(toTomlType(aClass), src.toString())); + } + }; + } + + private static String toTomlType(Class aClass) { + if (aClass == String.class) { + return "string"; + } + + if (aClass == Float.class || aClass == Double.class) { + return "float"; + } + + if (Number.class.isAssignableFrom(aClass)) { + return "integer"; + } + + if (aClass == Date.class) { + return "datetime"; + } + + if (aClass == Boolean.class) { + return "bool"; + } + + return "array"; + } +} diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/invalid/array-mixed-types-ints-and-floats.toml b/src/test/resources/com/moandjiezana/toml/burntsushi/invalid/array-mixed-types-ints-and-floats.toml new file mode 100644 index 0000000..51ebe80 --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/invalid/array-mixed-types-ints-and-floats.toml @@ -0,0 +1 @@ +ints-and-floats = [1, 1.0] diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/comments_everywhere.json b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/comments_everywhere.json new file mode 100644 index 0000000..e69a2e9 --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/comments_everywhere.json @@ -0,0 +1,12 @@ +{ + "group": { + "answer": {"type": "integer", "value": "42"}, + "more": { + "type": "array", + "value": [ + {"type": "integer", "value": "42"}, + {"type": "integer", "value": "42"} + ] + } + } +} diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/comments_everywhere.toml b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/comments_everywhere.toml new file mode 100644 index 0000000..3dca74c --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/comments_everywhere.toml @@ -0,0 +1,24 @@ +# Top comment. + # Top comment. +# Top comment. + +# [no-extraneous-groups-please] + +[group] # Comment +answer = 42 # Comment +# no-extraneous-keys-please = 999 +# Inbetween comment. +more = [ # Comment + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. + 42, 42, # Comments within arrays are fun. + # What about multiple # comments? + # Can you handle it? + # + # Evil. +# Evil. +# ] Did I fool you? +] # Hopefully not. diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-implicit.json b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-implicit.json new file mode 100644 index 0000000..32e4640 --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-implicit.json @@ -0,0 +1,7 @@ +{ + "albums": { + "songs": [ + {"name": {"type": "string", "value": "Glory Days"}} + ] + } +} diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-implicit.toml b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-implicit.toml new file mode 100644 index 0000000..3157ac9 --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-implicit.toml @@ -0,0 +1,2 @@ +[[albums.songs]] +name = "Glory Days"