Arrays handle mixed string types. Removed Parboiled dependency.

This commit is contained in:
moandji.ezana 2015-01-25 13:07:54 +02:00
parent 84de939b7c
commit 0f198e5a5e
6 changed files with 119 additions and 91 deletions

View file

@ -3,6 +3,7 @@
## NEXT ## NEXT
* Fixed short-form Unicode escapes * Fixed short-form Unicode escapes
* Fixed exponent handling * Fixed exponent handling
* Dropped dependency on Parboiled
* Updated Gson to 2.3.1 * Updated Gson to 2.3.1
## 0.3.1 / 2014-12-16 ## 0.3.1 / 2014-12-16

View file

@ -30,11 +30,6 @@
<version>4.12</version> <version>4.12</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.parboiled</groupId>
<artifactId>parboiled-java</artifactId>
<version>1.1.6</version>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>

View file

@ -1,18 +1,16 @@
package com.moandjiezana.toml; package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
class ArrayConverter implements ValueConverter { class ArrayConverter implements ValueConverter {
static final ArrayConverter ARRAY_PARSER = new ArrayConverter(); static final ArrayConverter ARRAY_PARSER = new ArrayConverter();
private static final List<Object> INVALID_ARRAY = new ArrayList<Object>(); private static final ValueConverters VALUE_CONVERTERS = new ValueConverters();
private static final ValueConverters VALUE_ANALYSIS = new ValueConverters();
@Override @Override
public boolean canConvert(String s) { public boolean canConvert(String s) {
@ -21,37 +19,105 @@ class ArrayConverter implements ValueConverter {
@Override @Override
public Object convert(String s) { public Object convert(String s) {
List<Object> tokens = parse(parser().Array(), s); return convert(s, new AtomicInteger(1), true);
List<Object> values = convertList(tokens); }
public Object convert(String s, AtomicInteger sharedIndex, boolean topLevel) {
char[] chars = s.toCharArray();
List<Object> arrayItems = new ArrayList<Object>();
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 INVALID;
} }
return values; return convertList(arrayItems);
} }
private List<Object> convertList(List<Object> tokens) { private Object convertList(List<Object> tokens) {
ArrayList<Object> nestedList = new ArrayList<Object>(); ArrayList<Object> nestedList = new ArrayList<Object>();
for (Object token : tokens) { for (Object token : tokens) {
if (token instanceof String) { if (token instanceof String) {
Object converted = VALUE_ANALYSIS.convert(((String) token).trim()); Object converted = VALUE_CONVERTERS.convert(((String) token).trim());
if (converted == INVALID) { if (converted == INVALID) {
return INVALID_ARRAY; return INVALID;
} }
if (isHomogenousArray(converted, nestedList)) { if (isHomogenousArray(converted, nestedList)) {
nestedList.add(converted); nestedList.add(converted);
} else { } else {
return INVALID_ARRAY; return INVALID;
} }
} else if (token instanceof List) { } else if (token instanceof List) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Object> convertedList = convertList((List<Object>) token); List<Object> convertedList = (List<Object>) token;
if (convertedList != INVALID_ARRAY && isHomogenousArray(convertedList, nestedList)) { if (isHomogenousArray(convertedList, nestedList)) {
nestedList.add(convertedList); nestedList.add(convertedList);
} else { } else {
return INVALID_ARRAY; return INVALID;
} }
} }
} }
@ -62,6 +128,26 @@ class ArrayConverter implements ValueConverter {
private boolean isHomogenousArray(Object o, List<?> values) { private boolean isHomogenousArray(Object o, List<?> values) {
return values.isEmpty() || values.get(0).getClass().isAssignableFrom(o.getClass()) || o.getClass().isAssignableFrom(values.get(0).getClass()); 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() {} private ArrayConverter() {}
} }

View file

@ -1,22 +1,9 @@
package com.moandjiezana.toml; package com.moandjiezana.toml;
import java.util.List;
import org.parboiled.Parboiled;
import org.parboiled.Rule;
import org.parboiled.parserunners.BasicParseRunner;
class ValueConverterUtils { class ValueConverterUtils {
static final Object INVALID = new Object(); static final Object INVALID = new Object();
static ValueParser parser() {
return Parboiled.createParser(ValueParser.class);
}
static <T> List<T> parse(Rule rule, String s) {
return new BasicParseRunner<List<T>>(rule).run(s).resultValue;
}
static boolean isComment(String line) { static boolean isComment(String line) {
if (line == null || line.isEmpty()) { if (line == null || line.isEmpty()) {
return true; return true;

View file

@ -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<List<Object>> {
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<Object> newTokens = new ArrayList<Object>();
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;
}
}

View file

@ -1,7 +1,9 @@
package com.moandjiezana.toml; package com.moandjiezana.toml;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.File; import java.io.File;
@ -57,6 +59,20 @@ public class ArrayTest {
assertEquals(asList(1L, 2L, 3L), toml.getList("key", Long.class)); 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) { private File file(String file) {
return Utils.file(getClass(), file); return Utils.file(getClass(), file);
} }