mirror of
https://github.com/plexusorg/toml4j.git
synced 2024-12-29 11:42:15 +00:00
Merge branch 'wip' of git://github.com/dilecti/toml4j into writer
This commit is contained in:
commit
121db7ef05
21 changed files with 1374 additions and 43 deletions
|
@ -9,6 +9,7 @@
|
|||
|
||||
### Added
|
||||
|
||||
* Support for writing objects to TOML format.
|
||||
* Support for underscores in numbers (the feature branch had accidentally not been merged! :( )
|
||||
* Set<Map.Entry> Toml#entrySet() cf. Reflection section in README
|
||||
* Overloaded getters that take a default value. Thanks to __[udiabon](https://github.com/udiabon)__.
|
||||
|
|
29
README.md
29
README.md
|
@ -233,6 +233,35 @@ toml.containsTable("a"); // false
|
|||
toml.containsTableArray("a"); // false
|
||||
```
|
||||
|
||||
### Converting Objects To TOML
|
||||
|
||||
You can write any arbitrary object to a TOML `String`, `File`, `Writer`, or `OutputStream`.
|
||||
|
||||
```java
|
||||
class AClass {
|
||||
int anInt = 1;
|
||||
int[] anArray = { 2, 3 };
|
||||
}
|
||||
|
||||
String tomlString = new TomlWriter().write(new AClass());
|
||||
|
||||
/*
|
||||
yields:
|
||||
anInt = 1
|
||||
anArray = [ 2, 3 ]
|
||||
*/
|
||||
```
|
||||
|
||||
You can exert some control over formatting. For example, you can change the default indentation, white space, and time zone:
|
||||
|
||||
```java
|
||||
new TomlWriter().
|
||||
wantTerseArrays(true).
|
||||
setIndentationPolicy(new WriterIndentationPolicy().setTableIndent(2)).
|
||||
setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
```
|
||||
|
||||
See the `TomlWriter` class for more details.
|
||||
|
||||
### Limitations
|
||||
|
||||
|
|
71
src/main/java/com/moandjiezana/toml/ArrayValueWriter.java
Normal file
71
src/main/java/com/moandjiezana/toml/ArrayValueWriter.java
Normal file
|
@ -0,0 +1,71 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import static com.moandjiezana.toml.ValueWriters.WRITERS;
|
||||
|
||||
abstract class ArrayValueWriter implements ValueWriter {
|
||||
static protected boolean isArrayish(Object value) {
|
||||
return value instanceof Collection || value.getClass().isArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean isArrayOfPrimitive(Object array) {
|
||||
Object first = peek(array);
|
||||
if (first != null) {
|
||||
ValueWriter valueWriter = WRITERS.findWriterFor(first);
|
||||
return valueWriter.isPrimitiveType() || isArrayish(first);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Collection normalize(Object value) {
|
||||
Collection collection;
|
||||
|
||||
if (value.getClass().isArray()) {
|
||||
// Arrays.asList() interprets an array as a single element,
|
||||
// so convert it to a list by hand
|
||||
collection = new ArrayList<Object>(Array.getLength(value));
|
||||
for (int i = 0; i < Array.getLength(value); i++) {
|
||||
Object elem = Array.get(value, i);
|
||||
collection.add(elem);
|
||||
}
|
||||
} else {
|
||||
collection = (Collection) value;
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Object peek(Object value) {
|
||||
if (value.getClass().isArray()) {
|
||||
if (Array.getLength(value) > 0) {
|
||||
return Array.get(value, 0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Collection collection = (Collection) value;
|
||||
if (collection.size() > 0) {
|
||||
return collection.iterator().next();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package com.moandjiezana.toml;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
class BooleanConverter implements ValueConverter {
|
||||
class BooleanConverter implements ValueConverter, ValueWriter {
|
||||
|
||||
static final BooleanConverter BOOLEAN_PARSER = new BooleanConverter();
|
||||
|
||||
|
@ -24,5 +24,30 @@ class BooleanConverter implements ValueConverter {
|
|||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return Boolean.class.isInstance(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
context.output.append(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private BooleanConverter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "boolean";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class DateConverter implements ValueConverter {
|
||||
class DateConverter implements ValueConverter, ValueWriter {
|
||||
|
||||
static final DateConverter DATE_PARSER = new DateConverter();
|
||||
private static final Pattern DATE_REGEX = Pattern.compile("(\\d{4}-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9])(\\.\\d*)?(Z|(?:[+\\-]\\d{2}:\\d{2}))(.*)");
|
||||
|
@ -79,6 +82,46 @@ class DateConverter implements ValueConverter {
|
|||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return value instanceof Date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
DateFormat dateFormat;
|
||||
DateFormat customDateFormat = context.getTomlWriter().getDateFormat();
|
||||
if (customDateFormat != null) {
|
||||
dateFormat = customDateFormat;
|
||||
} else {
|
||||
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss");
|
||||
}
|
||||
dateFormat.setTimeZone(context.getTomlWriter().getTimeZone());
|
||||
|
||||
context.output.append(dateFormat.format(value));
|
||||
|
||||
if (customDateFormat == null) {
|
||||
Calendar calendar = context.getTomlWriter().getCalendar();
|
||||
int tzOffset = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000);
|
||||
context.output.append(String.format("%+03d:%02d", tzOffset / 60, tzOffset % 60));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private DateConverter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "datetime";
|
||||
}
|
||||
}
|
||||
|
|
102
src/main/java/com/moandjiezana/toml/MapValueWriter.java
Normal file
102
src/main/java/com/moandjiezana/toml/MapValueWriter.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.moandjiezana.toml.PrimitiveArrayValueWriter.PRIMITIVE_ARRAY_VALUE_WRITER;
|
||||
import static com.moandjiezana.toml.TableArrayValueWriter.TABLE_ARRAY_VALUE_WRITER;
|
||||
import static com.moandjiezana.toml.ValueWriters.WRITERS;
|
||||
|
||||
class MapValueWriter implements ValueWriter {
|
||||
static final ValueWriter MAP_VALUE_WRITER = new MapValueWriter();
|
||||
|
||||
private static final Pattern requiredQuotingPattern = Pattern.compile("^.*[^A-Za-z\\d_-].*$");
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return value instanceof Map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
Map from = (Map) value;
|
||||
|
||||
if (hasPrimitiveValues(from, context)) {
|
||||
context.writeKey();
|
||||
}
|
||||
|
||||
// Render primitive types and arrays of primitive first so they are
|
||||
// grouped under the same table (if there is one)
|
||||
for (Object key : from.keySet()) {
|
||||
Object fromValue = from.get(key);
|
||||
if (fromValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ValueWriter valueWriter = WRITERS.findWriterFor(fromValue);
|
||||
if (valueWriter.isPrimitiveType()) {
|
||||
context.indent();
|
||||
context.output.append(quoteKey(key)).append(" = ");
|
||||
valueWriter.write(fromValue, context);
|
||||
context.output.append('\n');
|
||||
} else if (valueWriter == PRIMITIVE_ARRAY_VALUE_WRITER) {
|
||||
context.setArrayKey(key.toString());
|
||||
context.output.append(quoteKey(key)).append(" = ");
|
||||
valueWriter.write(fromValue, context);
|
||||
context.output.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Now render (sub)tables and arrays of tables
|
||||
for (Object key : from.keySet()) {
|
||||
Object fromValue = from.get(key);
|
||||
if (fromValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ValueWriter valueWriter = WRITERS.findWriterFor(fromValue);
|
||||
if (valueWriter.isTable() || valueWriter == TABLE_ARRAY_VALUE_WRITER) {
|
||||
valueWriter.write(fromValue, context.pushTable(quoteKey(key)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String quoteKey(Object key) {
|
||||
String stringKey = key.toString();
|
||||
Matcher matcher = requiredQuotingPattern.matcher(stringKey);
|
||||
if (matcher.matches()) {
|
||||
stringKey = "\"" + stringKey + "\"";
|
||||
}
|
||||
|
||||
return stringKey;
|
||||
}
|
||||
|
||||
private static boolean hasPrimitiveValues(Map values, WriterContext context) {
|
||||
for (Object key : values.keySet()) {
|
||||
Object fromValue = values.get(key);
|
||||
if (fromValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ValueWriter valueWriter = WRITERS.findWriterFor(fromValue);
|
||||
if (valueWriter.isPrimitiveType() || valueWriter == PRIMITIVE_ARRAY_VALUE_WRITER) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private MapValueWriter() {}
|
||||
}
|
|
@ -2,7 +2,7 @@ package com.moandjiezana.toml;
|
|||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class NumberConverter implements ValueConverter {
|
||||
class NumberConverter implements ValueConverter, ValueWriter {
|
||||
static final NumberConverter NUMBER_PARSER = new NumberConverter();
|
||||
|
||||
@Override
|
||||
|
@ -82,4 +82,29 @@ class NumberConverter implements ValueConverter {
|
|||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return Number.class.isInstance(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
context.output.append(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "number";
|
||||
}
|
||||
}
|
||||
|
|
77
src/main/java/com/moandjiezana/toml/ObjectValueWriter.java
Normal file
77
src/main/java/com/moandjiezana/toml/ObjectValueWriter.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import static com.moandjiezana.toml.MapValueWriter.MAP_VALUE_WRITER;
|
||||
|
||||
class ObjectValueWriter implements ValueWriter {
|
||||
static final ValueWriter OBJECT_VALUE_WRITER = new ObjectValueWriter();
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
Map<String, Object> to = new LinkedHashMap<String, Object>();
|
||||
Set<Field> fields = getFieldsForClass(value.getClass());
|
||||
for (Field field : fields) {
|
||||
to.put(field.getName(), getFieldValue(field, value));
|
||||
}
|
||||
|
||||
MAP_VALUE_WRITER.write(to, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static private Set<Field> getFieldsForClass(Class cls) {
|
||||
Set<Field> fields = new LinkedHashSet<Field>(Arrays.asList(cls.getDeclaredFields()));
|
||||
|
||||
getSuperClassFields(cls.getSuperclass(), fields);
|
||||
|
||||
// Skip final fields
|
||||
Set<Field> prunedFields = new LinkedHashSet<Field>();
|
||||
for (Field field : fields) {
|
||||
if (!Modifier.isFinal(field.getModifiers())) {
|
||||
prunedFields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
return prunedFields;
|
||||
}
|
||||
|
||||
static private void getSuperClassFields(Class cls, Set<Field> fields) {
|
||||
if (cls == Object.class) {
|
||||
return;
|
||||
}
|
||||
|
||||
fields.addAll(Arrays.asList(cls.getDeclaredFields()));
|
||||
getSuperClassFields(cls.getSuperclass(), fields);
|
||||
}
|
||||
|
||||
static private Object getFieldValue(Field field, Object o) {
|
||||
boolean isAccessible = field.isAccessible();
|
||||
field.setAccessible(true);
|
||||
Object value = null;
|
||||
try {
|
||||
value = field.get(o);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
field.setAccessible(isAccessible);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private ObjectValueWriter() {}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static com.moandjiezana.toml.ValueWriters.WRITERS;
|
||||
|
||||
class PrimitiveArrayValueWriter extends ArrayValueWriter {
|
||||
static final ValueWriter PRIMITIVE_ARRAY_VALUE_WRITER = new PrimitiveArrayValueWriter();
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return isArrayish(value) && isArrayOfPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
Collection values = normalize(value);
|
||||
|
||||
context.output.append('[');
|
||||
if (!context.getTomlWriter().wantTerseArrays()) {
|
||||
context.output.append(' ');
|
||||
}
|
||||
|
||||
boolean first = true;
|
||||
ValueWriter firstWriter = null;
|
||||
|
||||
for (Object elem : values) {
|
||||
if (first) {
|
||||
firstWriter = WRITERS.findWriterFor(elem);
|
||||
first = false;
|
||||
} else {
|
||||
ValueWriter writer = WRITERS.findWriterFor(elem);
|
||||
if (writer != firstWriter) {
|
||||
throw new IllegalStateException(
|
||||
context.getContextPath() +
|
||||
": cannot write a heterogeneous array; first element was of type " + firstWriter +
|
||||
" but found " + writer
|
||||
);
|
||||
}
|
||||
context.output.append(", ");
|
||||
}
|
||||
|
||||
WRITERS.write(elem, context);
|
||||
}
|
||||
|
||||
if (!context.getTomlWriter().wantTerseArrays()) {
|
||||
context.output.append(' ');
|
||||
}
|
||||
context.output.append(']');
|
||||
}
|
||||
|
||||
private PrimitiveArrayValueWriter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "primitive-array";
|
||||
}
|
||||
}
|
|
@ -4,11 +4,23 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class StringConverter implements ValueConverter {
|
||||
class StringConverter implements ValueConverter, ValueWriter {
|
||||
|
||||
static final StringConverter STRING_PARSER = new StringConverter();
|
||||
private static final Pattern UNICODE_REGEX = Pattern.compile("\\\\[uU](.{4})");
|
||||
|
||||
static private final String[] specialCharacterEscapes = new String[93];
|
||||
|
||||
static {
|
||||
specialCharacterEscapes[0x08] = "\\b";
|
||||
specialCharacterEscapes[0x09] = "\\t";
|
||||
specialCharacterEscapes[0x0A] = "\\n";
|
||||
specialCharacterEscapes[0x0C] = "\\f";
|
||||
specialCharacterEscapes[0x0D] = "\\r";
|
||||
specialCharacterEscapes[0x22] = "\\\"";
|
||||
specialCharacterEscapes[0x5C] = "\\\\";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canConvert(String s) {
|
||||
return s.startsWith("\"");
|
||||
|
@ -76,6 +88,50 @@ class StringConverter implements ValueConverter {
|
|||
.replace("\\b", "\b")
|
||||
.replace("\\f", "\f");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return value.getClass().isAssignableFrom(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
context.output.append('"');
|
||||
escapeUnicode(value.toString(), context.output);
|
||||
context.output.append('"');
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimitiveType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void escapeUnicode(String in, StringBuilder out) {
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
int codePoint = in.codePointAt(i);
|
||||
if (codePoint < specialCharacterEscapes.length && specialCharacterEscapes[codePoint] != null) {
|
||||
out.append(specialCharacterEscapes[codePoint]);
|
||||
} else if (codePoint > 0x1f && codePoint < 0x7f) {
|
||||
out.append(Character.toChars(codePoint));
|
||||
} else if (codePoint <= 0xFFFF) {
|
||||
out.append(String.format("\\u%04X", codePoint));
|
||||
} else {
|
||||
out.append(String.format("\\U%08X", codePoint));
|
||||
// Skip the low surrogate, which will be the next in the code point sequence.
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StringConverter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static com.moandjiezana.toml.ValueWriters.WRITERS;
|
||||
|
||||
class TableArrayValueWriter extends ArrayValueWriter {
|
||||
static final ValueWriter TABLE_ARRAY_VALUE_WRITER = new TableArrayValueWriter();
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Object value) {
|
||||
return isArrayish(value) && !isArrayOfPrimitive(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object value, WriterContext context) {
|
||||
Collection values = normalize(value);
|
||||
|
||||
WriterContext subContext = context.pushTableFromArray();
|
||||
|
||||
for (Object elem : values) {
|
||||
WRITERS.write(elem, subContext);
|
||||
}
|
||||
}
|
||||
|
||||
private TableArrayValueWriter() {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "table-array";
|
||||
}
|
||||
}
|
|
@ -1,24 +1,11 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Provides access to the keys and tables in a TOML data source.</p>
|
||||
*
|
||||
|
|
177
src/main/java/com/moandjiezana/toml/TomlWriter.java
Normal file
177
src/main/java/com/moandjiezana/toml/TomlWriter.java
Normal file
|
@ -0,0 +1,177 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.DateFormat;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static com.moandjiezana.toml.ValueWriters.WRITERS;
|
||||
|
||||
/**
|
||||
* <p>Converts Objects to TOML</p>
|
||||
*
|
||||
* <p>An input Object can comprise arbitrarily nested combinations of Java primitive types,
|
||||
* other {@link Object}s, {@link Map}s, {@link List}s, and Arrays. {@link Object}s and {@link Map}s
|
||||
* are output to TOML tables, and {@link List}s and Array to TOML arrays.</p>
|
||||
*
|
||||
* <p>Example usage:</p>
|
||||
* <pre><code>
|
||||
* class AClass {
|
||||
* int anInt = 1;
|
||||
* int[] anArray = { 2, 3 };
|
||||
* }
|
||||
*
|
||||
* String tomlString = new TomlWriter().write(new AClass());
|
||||
* </code></pre>
|
||||
*/
|
||||
public class TomlWriter {
|
||||
|
||||
private WriterIndentationPolicy indentationPolicy = new WriterIndentationPolicy();
|
||||
private boolean wantTerseArraysValue = false;
|
||||
private GregorianCalendar calendar = new GregorianCalendar();
|
||||
private DateFormat customDateFormat = null;
|
||||
|
||||
/**
|
||||
* Creates a TomlWriter instance.
|
||||
*/
|
||||
public TomlWriter() {}
|
||||
|
||||
/**
|
||||
* Write an Object into TOML String.
|
||||
*
|
||||
* @param from the object to be written
|
||||
* @return a string containing the TOML representation of the given Object
|
||||
*/
|
||||
public String write(Object from) {
|
||||
return WRITERS.write(from, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an Object in TOML to a {@link Writer}.
|
||||
*
|
||||
* @param from the object to be written
|
||||
* @param target the Writer to which TOML will be written
|
||||
* @throws IOException if target.write() fails
|
||||
*/
|
||||
public void write(Object from, Writer target) throws IOException {
|
||||
target.write(write(from));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an Object in TOML to a {@link OutputStream}.
|
||||
*
|
||||
* @param from the object to be written
|
||||
* @param target the OutputStream to which the TOML will be written
|
||||
* @throws IOException if target.write() fails
|
||||
*/
|
||||
public void write(Object from, OutputStream target) throws IOException {
|
||||
target.write(write(from).getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an Object in TOML to a {@link File}.
|
||||
*
|
||||
* @param from the object to be written
|
||||
* @param target the File to which the TOML will be written
|
||||
* @throws IOException if any file operations fail
|
||||
*/
|
||||
public void write(Object from, File target) throws IOException {
|
||||
FileWriter writer = new FileWriter(target);
|
||||
writer.write(write(from));
|
||||
writer.close();
|
||||
}
|
||||
|
||||
public WriterIndentationPolicy getIndentationPolicy() {
|
||||
return indentationPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link WriterIndentationPolicy} for this writer.
|
||||
*
|
||||
* If unset, the default policy (no indentation) is used.
|
||||
*
|
||||
* @param indentationPolicy the new policy
|
||||
* @return this TomlWriter instance
|
||||
*/
|
||||
public TomlWriter setIndentationPolicy(WriterIndentationPolicy indentationPolicy) {
|
||||
this.indentationPolicy = indentationPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Control whitespace in arrays in the TOML output.</p>
|
||||
*
|
||||
* <p>Terse arrays = false (default):</p>
|
||||
*
|
||||
* <pre><code>
|
||||
* a = [ 1, 2, 3 ]
|
||||
* </code></pre>
|
||||
*
|
||||
* <p>Terse arrays = true:</p>
|
||||
*
|
||||
* <pre><code>
|
||||
* a = [1,2,3]
|
||||
* </code></pre>
|
||||
*
|
||||
* @param value terse arrays setting
|
||||
* @return this TomlWriter instance
|
||||
*/
|
||||
public TomlWriter wantTerseArrays(boolean value) {
|
||||
this.wantTerseArraysValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current array whitespace policy
|
||||
* @return the current policy
|
||||
*/
|
||||
public boolean wantTerseArrays() {
|
||||
return wantTerseArraysValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link TimeZone} used when formatting datetimes.
|
||||
*
|
||||
* If unset, datetimes will be rendered in the current time zone.
|
||||
*
|
||||
* @param timeZone custom TimeZone.
|
||||
* @return this TomlWriter instance
|
||||
*/
|
||||
public TomlWriter setTimeZone(TimeZone timeZone) {
|
||||
calendar = new GregorianCalendar(timeZone);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link TimeZone} in use for this TomlWriter.
|
||||
*
|
||||
* @return the currently set TimeZone.
|
||||
*/
|
||||
public TimeZone getTimeZone() {
|
||||
return calendar.getTimeZone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default date format.
|
||||
*
|
||||
* If a time zone was set with {@link #setTimeZone(TimeZone)}, it will be applied before formatting
|
||||
* datetimes.
|
||||
*
|
||||
* @param customDateFormat a custom DateFormat
|
||||
* @return this TomlWriter instance
|
||||
*/
|
||||
public TomlWriter setDateFormat(DateFormat customDateFormat) {
|
||||
this.customDateFormat = customDateFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DateFormat getDateFormat() {
|
||||
return customDateFormat;
|
||||
}
|
||||
|
||||
GregorianCalendar getCalendar() {
|
||||
return calendar;
|
||||
}
|
||||
}
|
11
src/main/java/com/moandjiezana/toml/ValueWriter.java
Normal file
11
src/main/java/com/moandjiezana/toml/ValueWriter.java
Normal file
|
@ -0,0 +1,11 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
interface ValueWriter {
|
||||
boolean canWrite(Object value);
|
||||
|
||||
void write(Object value, WriterContext context);
|
||||
|
||||
boolean isPrimitiveType();
|
||||
|
||||
boolean isTable();
|
||||
}
|
43
src/main/java/com/moandjiezana/toml/ValueWriters.java
Normal file
43
src/main/java/com/moandjiezana/toml/ValueWriters.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static com.moandjiezana.toml.BooleanConverter.BOOLEAN_PARSER;
|
||||
import static com.moandjiezana.toml.DateConverter.DATE_PARSER;
|
||||
import static com.moandjiezana.toml.MapValueWriter.MAP_VALUE_WRITER;
|
||||
import static com.moandjiezana.toml.NumberConverter.NUMBER_PARSER;
|
||||
import static com.moandjiezana.toml.ObjectValueWriter.OBJECT_VALUE_WRITER;
|
||||
import static com.moandjiezana.toml.PrimitiveArrayValueWriter.PRIMITIVE_ARRAY_VALUE_WRITER;
|
||||
import static com.moandjiezana.toml.StringConverter.STRING_PARSER;
|
||||
import static com.moandjiezana.toml.TableArrayValueWriter.TABLE_ARRAY_VALUE_WRITER;
|
||||
|
||||
class ValueWriters {
|
||||
|
||||
static final ValueWriters WRITERS = new ValueWriters();
|
||||
|
||||
ValueWriter findWriterFor(Object value) {
|
||||
for (ValueWriter valueWriter : VALUE_WRITERs) {
|
||||
if (valueWriter.canWrite(value)) {
|
||||
return valueWriter;
|
||||
}
|
||||
}
|
||||
|
||||
return OBJECT_VALUE_WRITER;
|
||||
}
|
||||
|
||||
String write(Object value, TomlWriter tomlWriter) {
|
||||
WriterContext context = new WriterContext(tomlWriter);
|
||||
write(value, context);
|
||||
|
||||
return context.output.toString();
|
||||
}
|
||||
|
||||
void write(Object value, WriterContext context) {
|
||||
findWriterFor(value).write(value, context);
|
||||
}
|
||||
|
||||
private ValueWriters() {}
|
||||
|
||||
private static final ValueWriter[] VALUE_WRITERs = {
|
||||
STRING_PARSER, NUMBER_PARSER, BOOLEAN_PARSER, DATE_PARSER,
|
||||
MAP_VALUE_WRITER, PRIMITIVE_ARRAY_VALUE_WRITER, TABLE_ARRAY_VALUE_WRITER
|
||||
};
|
||||
}
|
96
src/main/java/com/moandjiezana/toml/WriterContext.java
Normal file
96
src/main/java/com/moandjiezana/toml/WriterContext.java
Normal file
|
@ -0,0 +1,96 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class WriterContext {
|
||||
private String key = "";
|
||||
private String currentTableIndent = "";
|
||||
private String currentFieldIndent = "";
|
||||
private String arrayKey = null;
|
||||
private boolean isArrayOfTable = false;
|
||||
private final TomlWriter tomlWriter;
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
WriterContext(String key, String tableIndent, StringBuilder output, TomlWriter tomlWriter) {
|
||||
this.key = key;
|
||||
this.currentTableIndent = tableIndent;
|
||||
this.currentFieldIndent = tableIndent + fillStringWithSpaces(tomlWriter.getIndentationPolicy().getKeyValueIndent());
|
||||
this.output = output;
|
||||
this.tomlWriter = tomlWriter;
|
||||
}
|
||||
|
||||
WriterContext(TomlWriter tomlWriter) {
|
||||
this.tomlWriter = tomlWriter;
|
||||
}
|
||||
|
||||
WriterContext pushTable(String newKey) {
|
||||
String newIndent = "";
|
||||
if (!key.isEmpty()) {
|
||||
newIndent = growIndent(tomlWriter.getIndentationPolicy());
|
||||
}
|
||||
|
||||
String fullKey = key + (key.isEmpty() ? newKey : "." + newKey);
|
||||
|
||||
return new WriterContext(fullKey, newIndent, output, tomlWriter);
|
||||
}
|
||||
|
||||
WriterContext pushTableFromArray() {
|
||||
WriterContext subContext = new WriterContext(key, currentTableIndent, output, tomlWriter);
|
||||
subContext.setIsArrayOfTable(true);
|
||||
|
||||
return subContext;
|
||||
}
|
||||
|
||||
void writeKey() {
|
||||
if (key.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.length() > 0) {
|
||||
output.append('\n');
|
||||
}
|
||||
|
||||
output.append(currentTableIndent);
|
||||
|
||||
if (isArrayOfTable) {
|
||||
output.append("[[").append(key).append("]]\n");
|
||||
} else {
|
||||
output.append('[').append(key).append("]\n");
|
||||
}
|
||||
}
|
||||
|
||||
void indent() {
|
||||
if (!key.isEmpty()) {
|
||||
output.append(currentFieldIndent);
|
||||
}
|
||||
}
|
||||
|
||||
WriterContext setIsArrayOfTable(boolean isArrayOfTable) {
|
||||
this.isArrayOfTable = isArrayOfTable;
|
||||
return this;
|
||||
}
|
||||
|
||||
private String growIndent(WriterIndentationPolicy indentationPolicy) {
|
||||
return currentTableIndent + fillStringWithSpaces(indentationPolicy.getTableIndent());
|
||||
}
|
||||
|
||||
private String fillStringWithSpaces(int count) {
|
||||
char[] chars = new char[count];
|
||||
Arrays.fill(chars, ' ');
|
||||
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
public TomlWriter getTomlWriter() {
|
||||
return tomlWriter;
|
||||
}
|
||||
|
||||
public WriterContext setArrayKey(String arrayKey) {
|
||||
this.arrayKey = arrayKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getContextPath() {
|
||||
return key.isEmpty() ? arrayKey : key + "." + arrayKey;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
/**
|
||||
* Controls how a {@link TomlWriter} indents tables and key/value pairs.
|
||||
*
|
||||
* The default policy is to not indent.
|
||||
*/
|
||||
public class WriterIndentationPolicy {
|
||||
private int tableIndent = 0;
|
||||
private int keyValueIndent = 0;
|
||||
|
||||
public int getTableIndent() {
|
||||
return tableIndent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of spaces a nested table name is indented.
|
||||
*
|
||||
* @param tableIndent number of spaces to indent
|
||||
* @return this WriterIndentationPolicy instance
|
||||
*/
|
||||
public WriterIndentationPolicy setTableIndent(int tableIndent) {
|
||||
this.tableIndent = tableIndent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getKeyValueIndent() {
|
||||
return keyValueIndent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of spaces key/value pairs within a table are indented.
|
||||
*
|
||||
* @param keyValueIndent number of spaces to indent
|
||||
* @return this WriterIndentationPolicy instance
|
||||
*/
|
||||
public WriterIndentationPolicy setKeyValueIndent(int keyValueIndent) {
|
||||
this.keyValueIndent = keyValueIndent;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import com.google.gson.*;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -8,24 +10,11 @@ import java.io.InputStreamReader;
|
|||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
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 java.util.*;
|
||||
|
||||
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;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class BurntSushiValidTest {
|
||||
|
||||
|
@ -43,11 +32,13 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void arrays_hetergeneous() throws Exception {
|
||||
run("arrays-hetergeneous");
|
||||
runEncoder("arrays-hetergeneous");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arrays_nested() throws Exception {
|
||||
run("arrays-nested");
|
||||
runEncoder("arrays-nested");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -68,21 +59,25 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void datetime() throws Exception {
|
||||
run("datetime");
|
||||
runEncoder("datetime");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty() throws Exception {
|
||||
run("empty");
|
||||
runEncoder("empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void example() throws Exception {
|
||||
run("example");
|
||||
runEncoder("example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void float_() throws Exception {
|
||||
run("float");
|
||||
runEncoder("float");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -93,16 +88,19 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void implicit_and_explicit_before() throws Exception {
|
||||
run("implicit-and-explicit-before");
|
||||
runEncoder("implicit-and-explicit-before");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void implicit_groups() throws Exception {
|
||||
run("implicit-groups");
|
||||
runEncoder("implicit-groups");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void integer() throws Exception {
|
||||
run("integer");
|
||||
runEncoder("integer");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -128,6 +126,7 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void key_special_chars_modified() throws Exception {
|
||||
run("key-special-chars-modified");
|
||||
runEncoder("key-special-chars-modified");
|
||||
}
|
||||
|
||||
@Test @Ignore
|
||||
|
@ -143,11 +142,13 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void long_float() throws Exception {
|
||||
run("long-float");
|
||||
runEncoder("long-float");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void long_integer() throws Exception {
|
||||
run("long-integer");
|
||||
runEncoder("long-integer");
|
||||
}
|
||||
|
||||
@Test @Ignore
|
||||
|
@ -173,6 +174,7 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void string_empty() throws Exception {
|
||||
run("string-empty");
|
||||
runEncoder("string-empty");
|
||||
}
|
||||
|
||||
@Test @Ignore
|
||||
|
@ -183,11 +185,13 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void string_escapes_modified() throws Exception {
|
||||
run("string-escapes-modified");
|
||||
runEncoder("string-escapes-modified");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void string_simple() throws Exception {
|
||||
run("string-simple");
|
||||
runEncoder("string-simple");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -198,11 +202,13 @@ public class BurntSushiValidTest {
|
|||
@Test
|
||||
public void table_array_implicit() throws Exception {
|
||||
run("table-array-implicit");
|
||||
runEncoder("table-array-implicit");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void table_array_many() throws Exception {
|
||||
run("table-array-many");
|
||||
runEncoder("table-array-many");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -210,9 +216,17 @@ public class BurntSushiValidTest {
|
|||
run("table-array-nest");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void table_array_nest_modified() throws Exception {
|
||||
// Same test, but with stray spaces in the expected TOML removed
|
||||
runEncoder("table-array-nest-modified",
|
||||
new TomlWriter().setIndentationPolicy(new WriterIndentationPolicy().setTableIndent(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void table_array_one() throws Exception {
|
||||
run("table-array-one");
|
||||
runEncoder("table-array-one");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -246,7 +260,9 @@ public class BurntSushiValidTest {
|
|||
}
|
||||
|
||||
private void run(String testName) {
|
||||
InputStream inputToml = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml");
|
||||
InputStream inputTomlStream = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml");
|
||||
// InputStream inputToml = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml");
|
||||
String inputToml = convertStreamToString(inputTomlStream);
|
||||
Reader expectedJsonReader = new InputStreamReader(getClass().getResourceAsStream("burntsushi/valid/" + testName + ".json"));
|
||||
JsonElement expectedJson = GSON.fromJson(expectedJsonReader, JsonElement.class);
|
||||
|
||||
|
@ -254,16 +270,103 @@ public class BurntSushiValidTest {
|
|||
JsonElement actual = TEST_GSON.toJsonTree(toml).getAsJsonObject().get("values");
|
||||
|
||||
assertEquals(expectedJson, actual);
|
||||
|
||||
|
||||
try {
|
||||
inputToml.close();
|
||||
inputTomlStream.close();
|
||||
} catch (IOException e) {}
|
||||
|
||||
try {
|
||||
expectedJsonReader.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
|
||||
static String convertStreamToString(java.io.InputStream is) {
|
||||
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
|
||||
private void runEncoder(String testName) {
|
||||
runEncoder(testName,
|
||||
new TomlWriter().
|
||||
wantTerseArrays(true).
|
||||
setTimeZone(TimeZone.getTimeZone("UTC")).
|
||||
setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")));
|
||||
}
|
||||
|
||||
private void runEncoder(String testName, TomlWriter tomlWriter) {
|
||||
InputStream inputTomlStream = getClass().getResourceAsStream("burntsushi/valid/" + testName + ".toml");
|
||||
String expectedToml = convertStreamToString(inputTomlStream);
|
||||
|
||||
Reader inputJsonReader = new InputStreamReader(getClass().getResourceAsStream("burntsushi/valid/" + testName + ".json"));
|
||||
JsonElement jsonInput = GSON.fromJson(inputJsonReader, JsonElement.class);
|
||||
Map<String, Object> enriched = enrichJson(jsonInput.getAsJsonObject());
|
||||
|
||||
String encoded = tomlWriter.write(enriched);
|
||||
assertEquals(expectedToml, encoded);
|
||||
}
|
||||
|
||||
// Enrich toml-test JSON trees into native Java types, suiteable
|
||||
// for consumption by TomlWriter.
|
||||
private Map<String, Object> enrichJson(JsonObject jsonObject) {
|
||||
Map<String, Object> enriched = new LinkedHashMap<String, Object>();
|
||||
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
|
||||
enriched.put(entry.getKey(), enrichJsonElement(entry.getValue()));
|
||||
}
|
||||
|
||||
return enriched;
|
||||
}
|
||||
|
||||
Object enrichJsonElement(JsonElement jsonElement) {
|
||||
if (jsonElement.isJsonObject()) {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
if (jsonObject.has("type") && jsonObject.has("value")) {
|
||||
return enrichPrimitive(jsonObject);
|
||||
}
|
||||
return enrichJson(jsonElement.getAsJsonObject());
|
||||
} else if (jsonElement.isJsonArray()) {
|
||||
List<Object> tables = new LinkedList<Object>();
|
||||
for (JsonElement arrayElement : jsonElement.getAsJsonArray()) {
|
||||
tables.add(enrichJsonElement(arrayElement));
|
||||
}
|
||||
|
||||
return tables;
|
||||
}
|
||||
|
||||
throw new AssertionError("received unexpected JsonElement: " + jsonElement);
|
||||
}
|
||||
|
||||
private Object enrichPrimitive(JsonObject jsonObject) {
|
||||
String type = jsonObject.getAsJsonPrimitive("type").getAsString();
|
||||
if ("bool".equals(type)) {
|
||||
return jsonObject.getAsJsonPrimitive("value").getAsBoolean();
|
||||
} else if ("integer".equals(type)) {
|
||||
return jsonObject.getAsJsonPrimitive("value").getAsBigInteger();
|
||||
} else if ("float".equals(type)) {
|
||||
return jsonObject.getAsJsonPrimitive("value").getAsDouble();
|
||||
} else if ("string".equals(type)) {
|
||||
return jsonObject.getAsJsonPrimitive("value").getAsString();
|
||||
} else if ("datetime".equals(type)) {
|
||||
DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String dateString = jsonObject.getAsJsonPrimitive("value").getAsString();
|
||||
try {
|
||||
return iso8601Format.parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
throw new AssertionError("failed to parse datetime '" + dateString + "': " + e.getMessage());
|
||||
}
|
||||
} else if ("array".equals(type)) {
|
||||
JsonArray jsonArray = jsonObject.getAsJsonArray("value");
|
||||
List<Object> enriched = new LinkedList<Object>();
|
||||
for (JsonElement arrayElement : jsonArray) {
|
||||
enriched.add(enrichJsonElement(arrayElement));
|
||||
}
|
||||
|
||||
return enriched;
|
||||
}
|
||||
|
||||
throw new AssertionError("enrichPrimitive: received unknown type " + type);
|
||||
}
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final Gson TEST_GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(Boolean.class, serialize(Boolean.class))
|
||||
|
|
319
src/test/java/com/moandjiezana/toml/ValueWriterTest.java
Normal file
319
src/test/java/com/moandjiezana/toml/ValueWriterTest.java
Normal file
|
@ -0,0 +1,319 @@
|
|||
package com.moandjiezana.toml;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ValueWriterTest {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder testDirectory = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void should_write_primitive_types() {
|
||||
class TestClass {
|
||||
public String aString;
|
||||
int anInt;
|
||||
protected float aFloat;
|
||||
private double aDouble;
|
||||
boolean aBoolean;
|
||||
final int aFinalInt = 1; // Should be skipped
|
||||
Date aDate;
|
||||
}
|
||||
|
||||
TestClass o = new TestClass();
|
||||
o.aString = "hello";
|
||||
o.anInt = 4;
|
||||
o.aFloat = 1.23f;
|
||||
o.aDouble = -5.43;
|
||||
o.aBoolean = false;
|
||||
|
||||
o.aDate = new Date();
|
||||
String theDate = formatDate(o.aDate);
|
||||
|
||||
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 = " + theDate + "\n";
|
||||
|
||||
assertEquals(expected, output);
|
||||
}
|
||||
|
||||
private String formatDate(Date date) {
|
||||
// Copying the date formatting code from DateValueWriter isn't optimal, but
|
||||
// I can't see any other way to check date formatting - the test gets
|
||||
// run in multiple time zones, so we can't just hard-code a time zone.
|
||||
String dateString = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss").format(date);
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
int tzOffset = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000);
|
||||
dateString += String.format("%+03d:%02d", tzOffset / 60, tzOffset % 60);
|
||||
|
||||
return dateString;
|
||||
}
|
||||
|
||||
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().
|
||||
setIndentationPolicy(new WriterIndentationPolicy().setKeyValueIndent(2)).
|
||||
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().
|
||||
setIndentationPolicy(new WriterIndentationPolicy().setTableIndent(2)).
|
||||
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().
|
||||
setIndentationPolicy(new WriterIndentationPolicy().setTableIndent(2).setKeyValueIndent(2)).
|
||||
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() {
|
||||
class Table {
|
||||
int anInt;
|
||||
|
||||
Table(int anInt) {
|
||||
this.anInt = anInt;
|
||||
}
|
||||
}
|
||||
class Config {
|
||||
Table[] table;
|
||||
}
|
||||
Config config = new Config();
|
||||
config.table = new Table[]{new Table(1), new Table(2)};
|
||||
|
||||
String output = new TomlWriter().write(config);
|
||||
String expected = "[[table]]\n" +
|
||||
"anInt = 1\n\n" +
|
||||
"[[table]]\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().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().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("", new TomlWriter().write(new TestClass()));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
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";
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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 {
|
||||
String input = " é foo € \b \t \n \f \r \" \\ ";
|
||||
assertEquals("\" \\u00E9 foo \\u20AC \\b \\t \\n \\f \\r \\\" \\\\ \"", new TomlWriter().write(input));
|
||||
|
||||
// Check unicode code points greater than 0XFFFF
|
||||
input = " \uD801\uDC28 \uD840\uDC0B ";
|
||||
assertEquals("\" \\U00010428 \\U0002000B \"", new TomlWriter().write(input));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
private static class SimpleTestClass {
|
||||
int a = 1;
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
private String readFile(File input) throws IOException {
|
||||
BufferedReader bufferedReader = new BufferedReader(new FileReader(input));
|
||||
|
||||
StringBuilder w = new StringBuilder();
|
||||
String line = bufferedReader.readLine();
|
||||
while (line != null) {
|
||||
w.append(line).append('\n');
|
||||
line = bufferedReader.readLine();
|
||||
}
|
||||
|
||||
return w.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"albums": [
|
||||
{
|
||||
"name": {"type": "string", "value": "Born to Run"},
|
||||
"songs": [
|
||||
{"name": {"type": "string", "value": "Jungleland"}},
|
||||
{"name": {"type": "string", "value": "Meeting Across the River"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": {"type": "string", "value": "Born in the USA"},
|
||||
"songs": [
|
||||
{"name": {"type": "string", "value": "Glory Days"}},
|
||||
{"name": {"type": "string", "value": "Dancing in the Dark"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
[[albums]]
|
||||
name = "Born to Run"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Jungleland"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Meeting Across the River"
|
||||
|
||||
[[albums]]
|
||||
name = "Born in the USA"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Glory Days"
|
||||
|
||||
[[albums.songs]]
|
||||
name = "Dancing in the Dark"
|
Loading…
Reference in a new issue