From 0f198e5a5ed6f5d17b6013978c6e016b35b37ac0 Mon Sep 17 00:00:00 2001 From: "moandji.ezana" Date: Sun, 25 Jan 2015 13:07:54 +0200 Subject: [PATCH] Arrays handle mixed string types. Removed Parboiled dependency. --- CHANGELOG.md | 1 + pom.xml | 5 - .../com/moandjiezana/toml/ArrayConverter.java | 118 +++++++++++++++--- .../toml/ValueConverterUtils.java | 13 -- .../com/moandjiezana/toml/ValueParser.java | 57 --------- .../java/com/moandjiezana/toml/ArrayTest.java | 16 +++ 6 files changed, 119 insertions(+), 91 deletions(-) delete mode 100644 src/main/java/com/moandjiezana/toml/ValueParser.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ddd84..35366d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## NEXT * Fixed short-form Unicode escapes * Fixed exponent handling +* Dropped dependency on Parboiled * Updated Gson to 2.3.1 ## 0.3.1 / 2014-12-16 diff --git a/pom.xml b/pom.xml index cd45a96..1835672 100644 --- a/pom.xml +++ b/pom.xml @@ -30,11 +30,6 @@ 4.12 test - - org.parboiled - parboiled-java - 1.1.6 - com.google.code.gson gson diff --git a/src/main/java/com/moandjiezana/toml/ArrayConverter.java b/src/main/java/com/moandjiezana/toml/ArrayConverter.java index 725278b..014600d 100644 --- a/src/main/java/com/moandjiezana/toml/ArrayConverter.java +++ b/src/main/java/com/moandjiezana/toml/ArrayConverter.java @@ -1,18 +1,16 @@ package com.moandjiezana.toml; import static com.moandjiezana.toml.ValueConverterUtils.INVALID; -import static com.moandjiezana.toml.ValueConverterUtils.parse; -import static com.moandjiezana.toml.ValueConverterUtils.parser; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; class ArrayConverter implements ValueConverter { static final ArrayConverter ARRAY_PARSER = new ArrayConverter(); - private static final List INVALID_ARRAY = new ArrayList(); - private static final ValueConverters VALUE_ANALYSIS = new ValueConverters(); + private static final ValueConverters VALUE_CONVERTERS = new ValueConverters(); @Override public boolean canConvert(String s) { @@ -21,37 +19,105 @@ class ArrayConverter implements ValueConverter { @Override public Object convert(String s) { - List tokens = parse(parser().Array(), s); - List values = convertList(tokens); + return convert(s, new AtomicInteger(1), true); + } + + public Object convert(String s, AtomicInteger sharedIndex, boolean topLevel) { + char[] chars = s.toCharArray(); + List arrayItems = new ArrayList(); + boolean terminated = false; + StringType stringType = StringType.NONE; + StringBuilder current = new StringBuilder(); + + for (int i = 1; i < chars.length; i++, sharedIndex.incrementAndGet()) { + char c = chars[i]; - if (values == INVALID_ARRAY) { + if (terminated && !topLevel) { + break; + } + + if (terminated) { + if (c == '#') { + break; + } + if (!Character.isWhitespace(c)) { + return INVALID; + } + continue; + } + + if (stringType == StringType.NONE) { + if (c == ',') { + if (current.toString().trim().length() > 0) { + arrayItems.add(current.toString()); + } + current = new StringBuilder(); + continue; + } + + if (c == '[') { + arrayItems.add(convert(s.substring(i), sharedIndex, false)); + i = sharedIndex.get(); + continue; + } + + if (c == ']') { + terminated = true; + if (current.toString().trim().length() > 0) { + arrayItems.add(current.toString()); + } + current = new StringBuilder(); + continue; + } + } + + if (c == '"' && chars[i - 1] != '\\' && !stringType.accepts(c)) { + if (chars.length > i + 2 && chars[i + 1] == c && chars[i + 2] == c) { + stringType = stringType.flip(StringType.MULTILINE); + } else { + stringType = stringType.flip(StringType.BASIC); + } + } + + if (c == '\'' && !stringType.accepts(c)) { + if (chars.length > i + 2 && chars[i + 1] == c && chars[i + 2] == c) { + stringType = stringType.flip(StringType.MULTILINE_LITERAL); + } else { + stringType = stringType.flip(StringType.LITERAL); + } + } + + current.append(c); + } + + if (!terminated) { return INVALID; } - - return values; + + return convertList(arrayItems); } - private List convertList(List tokens) { + private Object convertList(List tokens) { ArrayList nestedList = new ArrayList(); for (Object token : tokens) { if (token instanceof String) { - Object converted = VALUE_ANALYSIS.convert(((String) token).trim()); + Object converted = VALUE_CONVERTERS.convert(((String) token).trim()); if (converted == INVALID) { - return INVALID_ARRAY; + return INVALID; } if (isHomogenousArray(converted, nestedList)) { nestedList.add(converted); } else { - return INVALID_ARRAY; + return INVALID; } } else if (token instanceof List) { @SuppressWarnings("unchecked") - List convertedList = convertList((List) token); - if (convertedList != INVALID_ARRAY && isHomogenousArray(convertedList, nestedList)) { + List convertedList = (List) token; + if (isHomogenousArray(convertedList, nestedList)) { nestedList.add(convertedList); } else { - return INVALID_ARRAY; + return INVALID; } } } @@ -62,6 +128,26 @@ class ArrayConverter implements ValueConverter { private boolean isHomogenousArray(Object o, List values) { return values.isEmpty() || values.get(0).getClass().isAssignableFrom(o.getClass()) || o.getClass().isAssignableFrom(values.get(0).getClass()); } + + private static enum StringType { + NONE, BASIC, LITERAL, MULTILINE, MULTILINE_LITERAL; + + StringType flip(StringType to) { + return this == NONE ? to : NONE; + } + + boolean accepts(char c) { + if (this == BASIC || this == MULTILINE) { + return c != '"'; + } + + if (this == LITERAL || this == MULTILINE_LITERAL) { + return c != '\''; + } + + return false; + } + } private ArrayConverter() {} } diff --git a/src/main/java/com/moandjiezana/toml/ValueConverterUtils.java b/src/main/java/com/moandjiezana/toml/ValueConverterUtils.java index e2f7b31..a50efb8 100644 --- a/src/main/java/com/moandjiezana/toml/ValueConverterUtils.java +++ b/src/main/java/com/moandjiezana/toml/ValueConverterUtils.java @@ -1,22 +1,9 @@ package com.moandjiezana.toml; -import java.util.List; - -import org.parboiled.Parboiled; -import org.parboiled.Rule; -import org.parboiled.parserunners.BasicParseRunner; class ValueConverterUtils { static final Object INVALID = new Object(); - static ValueParser parser() { - return Parboiled.createParser(ValueParser.class); - } - - static List parse(Rule rule, String s) { - return new BasicParseRunner>(rule).run(s).resultValue; - } - static boolean isComment(String line) { if (line == null || line.isEmpty()) { return true; diff --git a/src/main/java/com/moandjiezana/toml/ValueParser.java b/src/main/java/com/moandjiezana/toml/ValueParser.java deleted file mode 100644 index 295c616..0000000 --- a/src/main/java/com/moandjiezana/toml/ValueParser.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.moandjiezana.toml; - -import java.util.ArrayList; -import java.util.List; - -import org.parboiled.BaseParser; -import org.parboiled.Rule; -import org.parboiled.annotations.BuildParseTree; - -@BuildParseTree -class ValueParser extends BaseParser> { - - public Rule Array() { - return FirstOf(EmptyArray(), Sequence('[', startList(), OneOrMore(FirstOf(NonEmptyArray(), ' ', ',')), ']', endList())); - } - - Rule NonEmptyArray() { - return FirstOf(Array(), OneOrMore(TestNot(']'), FirstOf(StringToken(), Array(), ',', ' ', OtherValue()))); - } - - Rule StringToken() { - return Sequence(Sequence('"', ZeroOrMore(Sequence(TestNot('"'), ANY)), '"'), pushToken(match())); - } - - Rule EmptyArray() { - return Sequence('[', ']', startList(), endList()); - } - - Rule OtherValue() { - return Sequence(ZeroOrMore(NoneOf("],")), pushToken(match())); - } - - boolean startList() { - ArrayList newTokens = new ArrayList(); - - if (!getContext().getValueStack().isEmpty()) { - peek().add(newTokens); - } - push(newTokens); - - return true; - } - - boolean endList() { - if (getContext().getValueStack().size() > 1) { - pop(); - } - - return true; - } - - boolean pushToken(String s) { - peek().add(s); - - return true; - } -} \ No newline at end of file diff --git a/src/test/java/com/moandjiezana/toml/ArrayTest.java b/src/test/java/com/moandjiezana/toml/ArrayTest.java index 0c95a61..92c3e4e 100644 --- a/src/test/java/com/moandjiezana/toml/ArrayTest.java +++ b/src/test/java/com/moandjiezana/toml/ArrayTest.java @@ -1,7 +1,9 @@ package com.moandjiezana.toml; import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.File; @@ -57,6 +59,20 @@ public class ArrayTest { assertEquals(asList(1L, 2L, 3L), toml.getList("key", Long.class)); } + @Test + public void should_support_mixed_string_types() throws Exception { + Toml toml = new Toml().parse("key = [\"a\", 'b', \"\"\"c\"\"\", '''d''']"); + + assertThat(toml.getList("key", String.class), contains("a", "b", "c", "d")); + } + + @Test + public void should_support_array_terminator_in_strings() throws Exception { + Toml toml = new Toml().parse("key = [\"a]\", 'b]', \"\"\"c]\"\"\", '''d]''']"); + + assertThat(toml.getList("key", String.class), contains("a]", "b]", "c]", "d]")); + } + private File file(String file) { return Utils.file(getClass(), file); }