From e7d7de7ae5d5a0b7e6d59e095d4ee9bae829ad68 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 31 May 2015 21:08:42 -0700 Subject: [PATCH 01/24] Implement serialization. This provides functionality to convert populated Toml instances and arbitrary objects into TOML. --- README.md | 28 ++ .../moandjiezana/toml/ArraySerializer.java | 69 +++++ .../moandjiezana/toml/BooleanSerializer.java | 25 ++ .../com/moandjiezana/toml/DateSerializer.java | 29 ++ .../com/moandjiezana/toml/MapSerializer.java | 98 ++++++ .../moandjiezana/toml/NumberSerializer.java | 25 ++ .../moandjiezana/toml/ObjectSerializer.java | 76 +++++ .../toml/PrimitiveArraySerializer.java | 28 ++ .../com/moandjiezana/toml/Serializer.java | 11 + .../moandjiezana/toml/SerializerContext.java | 50 ++++ .../com/moandjiezana/toml/Serializers.java | 38 +++ .../moandjiezana/toml/StringSerializer.java | 56 ++++ .../toml/TableArraySerializer.java | 23 ++ src/main/java/com/moandjiezana/toml/Toml.java | 42 ++- .../com/moandjiezana/toml/SerializerTest.java | 281 ++++++++++++++++++ 15 files changed, 863 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/moandjiezana/toml/ArraySerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/BooleanSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/DateSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/MapSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/NumberSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/ObjectSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/Serializer.java create mode 100644 src/main/java/com/moandjiezana/toml/SerializerContext.java create mode 100644 src/main/java/com/moandjiezana/toml/Serializers.java create mode 100644 src/main/java/com/moandjiezana/toml/StringSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/TableArraySerializer.java create mode 100644 src/test/java/com/moandjiezana/toml/SerializerTest.java diff --git a/README.md b/README.md index b64de95..5f42ed0 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,34 @@ Long tableD = toml.getLong("table.d"); // returns null, not 5, because of shallo Long arrayD = toml.getLong("array[0].d"); // returns 3 ``` +### Serialization + +You can serialize a `Toml` or any arbitrary object to a TOML string. + +Once you have populated a `Toml` via `Toml.parse()`, you can serialize it back to TOML. + +```java +Toml toml = new Toml().parse("a=1"); +String tomlString = toml.serialize(); +``` + +Or you can serialize any object. + +```java +class AClass { + int anInt = 1; + int[] anArray = { 2, 3 }; +} + +String tomlString = Toml.serializeFrom(new AClass()); + +/* +yields: +anInt = 1 +anArray = [ 2, 3 ] +*/ +``` + ### Limitations Date precision is limited to milliseconds. diff --git a/src/main/java/com/moandjiezana/toml/ArraySerializer.java b/src/main/java/com/moandjiezana/toml/ArraySerializer.java new file mode 100644 index 0000000..c396b3a --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/ArraySerializer.java @@ -0,0 +1,69 @@ +package com.moandjiezana.toml; + + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; + +abstract class ArraySerializer implements Serializer { + 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) { + Serializer serializer = Serializers.findSerializerFor(first); + return serializer.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(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; + } +} diff --git a/src/main/java/com/moandjiezana/toml/BooleanSerializer.java b/src/main/java/com/moandjiezana/toml/BooleanSerializer.java new file mode 100644 index 0000000..022ac20 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/BooleanSerializer.java @@ -0,0 +1,25 @@ +package com.moandjiezana.toml; + +class BooleanSerializer implements Serializer { + static final Serializer BOOLEAN_SERIALIZER = new BooleanSerializer(); + + @Override + public boolean canSerialize(Object value) { + return Boolean.class.isInstance(value); + } + + @Override + public void serialize(Object value, SerializerContext context) { + context.serialized.append(value.toString()); + } + + @Override + public boolean isPrimitiveType() { + return true; + } + + @Override + public boolean isTable() { + return false; + } +} diff --git a/src/main/java/com/moandjiezana/toml/DateSerializer.java b/src/main/java/com/moandjiezana/toml/DateSerializer.java new file mode 100644 index 0000000..f9935b8 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/DateSerializer.java @@ -0,0 +1,29 @@ +package com.moandjiezana.toml; + +import java.text.SimpleDateFormat; +import java.util.Date; + +class DateSerializer implements Serializer { + static final Serializer DATE_SERIALIZER = new DateSerializer(); + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ssXXX"); + + @Override + public boolean canSerialize(Object value) { + return value instanceof Date; + } + + @Override + public void serialize(Object value, SerializerContext context) { + context.serialized.append(dateFormat.format(value)); + } + + @Override + public boolean isPrimitiveType() { + return true; + } + + @Override + public boolean isTable() { + return false; + } +} diff --git a/src/main/java/com/moandjiezana/toml/MapSerializer.java b/src/main/java/com/moandjiezana/toml/MapSerializer.java new file mode 100644 index 0000000..bd7b30e --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/MapSerializer.java @@ -0,0 +1,98 @@ +package com.moandjiezana.toml; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.moandjiezana.toml.PrimitiveArraySerializer.PRIMITIVE_ARRAY_SERIALIZER; +import static com.moandjiezana.toml.TableArraySerializer.TABLE_ARRAY_SERIALIZER; + +class MapSerializer implements Serializer { + static final Serializer MAP_SERIALIZER = new MapSerializer(); + + private static final Pattern requiredQuotingPattern = Pattern.compile("^.*[^A-Za-z\\d_-].*$"); + + @Override + public boolean canSerialize(Object value) { + return value instanceof Map; + } + + @Override + public void serialize(Object value, SerializerContext context) { + Map from = (Map) value; + + if (hasPrimitiveValues(from)) { + context.serializeKey(); + } + + // 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; + } + + Serializer serializer = Serializers.findSerializerFor(fromValue); + if (serializer.isPrimitiveType()) { + context.indent(); + context.serialized.append(quoteKey(key)).append(" = "); + serializer.serialize(fromValue, context); + context.serialized.append('\n'); + } else if (serializer == PRIMITIVE_ARRAY_SERIALIZER) { + context.serialized.append(quoteKey(key)).append(" = "); + serializer.serialize(fromValue, context); + context.serialized.append('\n'); + } + } + + // Now render (sub)tables and arrays of tables + for (Object key : from.keySet()) { + Object fromValue = from.get(key); + if (fromValue == null) { + continue; + } + + Serializer serializer = Serializers.findSerializerFor(fromValue); + if (serializer.isTable() || serializer == TABLE_ARRAY_SERIALIZER) { + serializer.serialize(fromValue, context.extend(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) { + for (Object key : values.keySet()) { + Object fromValue = values.get(key); + if (fromValue == null) { + continue; + } + + Serializer serializer = Serializers.findSerializerFor(fromValue); + if (serializer.isPrimitiveType() || serializer == PRIMITIVE_ARRAY_SERIALIZER) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/com/moandjiezana/toml/NumberSerializer.java b/src/main/java/com/moandjiezana/toml/NumberSerializer.java new file mode 100644 index 0000000..78eab87 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/NumberSerializer.java @@ -0,0 +1,25 @@ +package com.moandjiezana.toml; + +class NumberSerializer implements Serializer { + static final Serializer NUMBER_SERIALIZER = new NumberSerializer(); + + @Override + public boolean canSerialize(Object value) { + return Number.class.isInstance(value); + } + + @Override + public void serialize(Object value, SerializerContext context) { + context.serialized.append(value.toString()); + } + + @Override + public boolean isPrimitiveType() { + return true; + } + + @Override + public boolean isTable() { + return false; + } +} diff --git a/src/main/java/com/moandjiezana/toml/ObjectSerializer.java b/src/main/java/com/moandjiezana/toml/ObjectSerializer.java new file mode 100644 index 0000000..ffe106c --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/ObjectSerializer.java @@ -0,0 +1,76 @@ +package com.moandjiezana.toml; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Predicate; + +import static com.moandjiezana.toml.MapSerializer.MAP_SERIALIZER; + +class ObjectSerializer implements Serializer { + static final Serializer OBJECT_SERIALIZER = new ObjectSerializer(); + + @Override + public boolean canSerialize(Object value) { + return true; + } + + @Override + public void serialize(Object value, SerializerContext context) { + Map to = new LinkedHashMap(); + Set fields = getFieldsForClass(value.getClass()); + for (Field field : fields) { + to.put(field.getName(), getFieldValue(field, value)); + } + + MAP_SERIALIZER.serialize(to, context); + } + + @Override + public boolean isPrimitiveType() { + return false; + } + + @Override + public boolean isTable() { + return true; + } + + static private Set getFieldsForClass(Class cls) { + Set fields = new LinkedHashSet(Arrays.asList(cls.getDeclaredFields())); + + getSuperClassFields(cls.getSuperclass(), fields); + + // Skip final fields + fields.removeIf(new Predicate() { + @Override + public boolean test(Field field) { + return Modifier.isFinal(field.getModifiers()); + } + }); + + return fields; + } + + static private void getSuperClassFields(Class cls, Set 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; + } +} diff --git a/src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java b/src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java new file mode 100644 index 0000000..79be5ab --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java @@ -0,0 +1,28 @@ +package com.moandjiezana.toml; + +import java.util.Collection; + +class PrimitiveArraySerializer extends ArraySerializer { + static final Serializer PRIMITIVE_ARRAY_SERIALIZER = new PrimitiveArraySerializer(); + + @Override + public boolean canSerialize(Object value) { + return isArrayish(value) && isArrayOfPrimitive(value); + } + + @Override + public void serialize(Object value, SerializerContext context) { + Collection values = normalize(value); + + context.serialized.append("[ "); + boolean first = true; + for (Object elem : values) { + if (!first) { + context.serialized.append(", "); + } + Serializers.serialize(elem, context); + first = false; + } + context.serialized.append(" ]"); + } +} diff --git a/src/main/java/com/moandjiezana/toml/Serializer.java b/src/main/java/com/moandjiezana/toml/Serializer.java new file mode 100644 index 0000000..c0ef999 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/Serializer.java @@ -0,0 +1,11 @@ +package com.moandjiezana.toml; + +interface Serializer { + boolean canSerialize(Object value); + + void serialize(Object value, SerializerContext context); + + boolean isPrimitiveType(); + + boolean isTable(); +} diff --git a/src/main/java/com/moandjiezana/toml/SerializerContext.java b/src/main/java/com/moandjiezana/toml/SerializerContext.java new file mode 100644 index 0000000..a83434b --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/SerializerContext.java @@ -0,0 +1,50 @@ +package com.moandjiezana.toml; + +class SerializerContext { + private String key = ""; + private boolean isArrayOfTable = false; + StringBuilder serialized = new StringBuilder(); + + SerializerContext(String key, StringBuilder serialized) { + this.key = key; + this.serialized = serialized; + } + + SerializerContext() { + } + + SerializerContext extend(String newKey) { + String fullKey = key + (key.isEmpty() ? newKey : "." + newKey); + + return new SerializerContext(fullKey, serialized); + } + + SerializerContext extend() { + return new SerializerContext(key, serialized); + } + + void serializeKey() { + if (key.isEmpty()) { + return; + } + + if (serialized.length() > 0) { + serialized.append('\n'); + } + + if (isArrayOfTable) { + serialized.append("[[").append(key).append("]]\n"); + } else { + serialized.append('[').append(key).append("]\n"); + } + } + + void indent() { + serialized.append(key.isEmpty() ? "" : " "); + } + + SerializerContext setIsArrayOfTable(boolean isArrayOfTable) { + this.isArrayOfTable = isArrayOfTable; + return this; + } +} diff --git a/src/main/java/com/moandjiezana/toml/Serializers.java b/src/main/java/com/moandjiezana/toml/Serializers.java new file mode 100644 index 0000000..fb49757 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/Serializers.java @@ -0,0 +1,38 @@ +package com.moandjiezana.toml; + +import static com.moandjiezana.toml.BooleanSerializer.BOOLEAN_SERIALIZER; +import static com.moandjiezana.toml.DateSerializer.DATE_SERIALIZER; +import static com.moandjiezana.toml.MapSerializer.MAP_SERIALIZER; +import static com.moandjiezana.toml.NumberSerializer.NUMBER_SERIALIZER; +import static com.moandjiezana.toml.ObjectSerializer.OBJECT_SERIALIZER; +import static com.moandjiezana.toml.PrimitiveArraySerializer.PRIMITIVE_ARRAY_SERIALIZER; +import static com.moandjiezana.toml.StringSerializer.STRING_SERIALIZER; +import static com.moandjiezana.toml.TableArraySerializer.TABLE_ARRAY_SERIALIZER; + +abstract class Serializers { + private static final Serializer[] SERIALIZERS = { + STRING_SERIALIZER, NUMBER_SERIALIZER, BOOLEAN_SERIALIZER, DATE_SERIALIZER, + MAP_SERIALIZER, PRIMITIVE_ARRAY_SERIALIZER, TABLE_ARRAY_SERIALIZER + }; + + static Serializer findSerializerFor(Object value) { + for (Serializer serializer : SERIALIZERS) { + if (serializer.canSerialize(value)) { + return serializer; + } + } + + return OBJECT_SERIALIZER; + } + + static String serialize(Object value) { + SerializerContext context = new SerializerContext(); + serialize(value, context); + + return context.serialized.toString(); + } + + static void serialize(Object value, SerializerContext context) { + findSerializerFor(value).serialize(value, context); + } +} diff --git a/src/main/java/com/moandjiezana/toml/StringSerializer.java b/src/main/java/com/moandjiezana/toml/StringSerializer.java new file mode 100644 index 0000000..d3eee57 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/StringSerializer.java @@ -0,0 +1,56 @@ +package com.moandjiezana.toml; + +class StringSerializer implements Serializer { + static final Serializer STRING_SERIALIZER = new StringSerializer(); + + 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 canSerialize(Object value) { + return value.getClass().isAssignableFrom(String.class); + } + + @Override + public void serialize(Object value, SerializerContext context) { + context.serialized.append('"'); + escapeUnicode(value.toString(), context.serialized); + context.serialized.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++; + } + } + } +} diff --git a/src/main/java/com/moandjiezana/toml/TableArraySerializer.java b/src/main/java/com/moandjiezana/toml/TableArraySerializer.java new file mode 100644 index 0000000..d2c2912 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/TableArraySerializer.java @@ -0,0 +1,23 @@ +package com.moandjiezana.toml; + +import java.util.Collection; + +class TableArraySerializer extends ArraySerializer { + static final Serializer TABLE_ARRAY_SERIALIZER = new TableArraySerializer(); + + @Override + public boolean canSerialize(Object value) { + return isArrayish(value) && !isArrayOfPrimitive(value); + } + + @Override + public void serialize(Object value, SerializerContext context) { + Collection values = normalize(value); + + SerializerContext subContext = context.extend().setIsArrayOfTable(true); + + for (Object elem : values) { + Serializers.serialize(elem, subContext); + } + } +} diff --git a/src/main/java/com/moandjiezana/toml/Toml.java b/src/main/java/com/moandjiezana/toml/Toml.java index fd14171..659ad87 100644 --- a/src/main/java/com/moandjiezana/toml/Toml.java +++ b/src/main/java/com/moandjiezana/toml/Toml.java @@ -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.Collections; -import java.util.Date; -import java.util.HashMap; -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.*; + /** *

Provides access to the keys and tables in a TOML data source.

* @@ -282,6 +269,29 @@ public class Toml { return DEFAULT_GSON.fromJson(json, targetClass); } + /** + * Serializes the values of this Toml instance into TOML. + * + * @return a string containing the TOML representation of this Toml instance. + */ + public String serialize() { + return Serializers.serialize(values); + } + + /** + * Serializes an Object into TOML. + * + * The input 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 serialized to TOML tables, and {@link List}s and Array to TOML arrays. + * + * @param from the object to be serialized + * @return a string containing the TOML representation of the given Object + */ + public static String serializeFrom(Object from) { + return Serializers.serialize(from); + } + @SuppressWarnings("unchecked") private Object get(String key) { if (values.containsKey(key)) { diff --git a/src/test/java/com/moandjiezana/toml/SerializerTest.java b/src/test/java/com/moandjiezana/toml/SerializerTest.java new file mode 100644 index 0000000..444d0d4 --- /dev/null +++ b/src/test/java/com/moandjiezana/toml/SerializerTest.java @@ -0,0 +1,281 @@ +package com.moandjiezana.toml; + +import org.junit.Test; + +import java.io.UnsupportedEncodingException; +import java.util.*; + +import static org.junit.Assert.assertEquals; + +public class SerializerTest { + @Test + public void serializesPrimitiveTypes() { + 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; + + String theDate = "2015-05-31T08:44:03-07:00"; + Toml dateToml = new Toml().parse("a_date = " + theDate); + o.aDate = dateToml.getDate("a_date"); + + String serialized = Toml.serializeFrom(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, serialized); + } + + @Test + public void serializesNestedMap() { + class SubChild { + int anInt; + } + class Child { + SubChild subChild; + int anInt; + } + class Parent { + Map aMap; + Child child; + boolean aBoolean; + } + + Parent parent = new Parent(); + parent.aMap = new LinkedHashMap(); + 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; + + String serialized = Toml.serializeFrom(parent); + 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, serialized); + } + + @Test + public void serializesArrayOfPrimitive() { + class ArrayTest { + int[] array = {1, 2, 3}; + } + + ArrayTest arrayTest = new ArrayTest(); + String serialized = Toml.serializeFrom(arrayTest); + String expected = "array = [ 1, 2, 3 ]\n"; + assertEquals(expected, serialized); + } + + @Test + public void serializesArrayOfTables() { + 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 serialized = Toml.serializeFrom(config); + String expected = "[[table]]\n" + + " anInt = 1\n\n" + + "[[table]]\n" + + " anInt = 2\n"; + assertEquals(expected, serialized); + } + + @Test + public void serializesArrayOfArray() { + class ArrayTest { + int[][] array = {{1, 2, 3}, {4, 5, 6}}; + } + ArrayTest arrayTest = new ArrayTest(); + + String serialized = Toml.serializeFrom(arrayTest); + String expected = "array = [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]\n"; + assertEquals(expected, serialized); + } + + @Test + public void serializesList() { + class ListTest { + List aList = new LinkedList(); + } + ListTest o = new ListTest(); + o.aList.add(1); + o.aList.add(2); + + assertEquals("aList = [ 1, 2 ]\n", Toml.serializeFrom(o)); + } + + @Test + public void handlesZeroLengthArraysAndLists() { + class TestClass { + List aList = new LinkedList(); + Float[] anArray = new Float[0]; + } + assertEquals("", Toml.serializeFrom(new TestClass())); + } + + @Test + public void elidesEmptyIntermediateTables() { + class C { + int anInt = 1; + } + class B { + C c = new C(); + } + class A { + B b = new B(); + } + + assertEquals("[b.c]\n anInt = 1\n", Toml.serializeFrom(new A())); + } + + @Test + public void serializesNestedArraysOfTables() { + class Physical { + String color; + String shape; + } + class Variety { + String name; + } + class Fruit { + Physical physical; + Variety[] variety; + String name; + } + class Basket { + Fruit[] fruit; + } + + Basket basket = new Basket(); + basket.fruit = new Fruit[2]; + + basket.fruit[0] = new Fruit(); + basket.fruit[0].name = "apple"; + basket.fruit[0].physical = new Physical(); + basket.fruit[0].physical.color = "red"; + basket.fruit[0].physical.shape = "round"; + basket.fruit[0].variety = new Variety[2]; + basket.fruit[0].variety[0] = new Variety(); + basket.fruit[0].variety[0].name = "red delicious"; + basket.fruit[0].variety[1] = new Variety(); + basket.fruit[0].variety[1].name = "granny smith"; + + basket.fruit[1] = new Fruit(); + basket.fruit[1].name = "banana"; + basket.fruit[1].variety = new Variety[1]; + basket.fruit[1].variety[0] = new Variety(); + basket.fruit[1].variety[0].name = "plantain"; + + String expected = "[[fruit]]\n" + + " name = \"apple\"\n" + + "\n" + + "[fruit.physical]\n" + + " color = \"red\"\n" + + " shape = \"round\"\n" + + "\n" + + "[[fruit.variety]]\n" + + " name = \"red delicious\"\n" + + "\n" + + "[[fruit.variety]]\n" + + " name = \"granny smith\"\n" + + "\n" + + "[[fruit]]\n" + + " name = \"banana\"\n" + + "\n" + + "[[fruit.variety]]\n" + + " name = \"plantain\"" + + "\n"; + + + String serialized = Toml.serializeFrom(basket); + assertEquals(expected, serialized); + } + + @Test + public void serializesClassesWithInheritance() { + class Parent { + protected int anInt = 2; + } + class Child extends Parent { + boolean aBoolean = true; + } + + Child child = new Child(); + String expected = "aBoolean = true\nanInt = 2\n"; + assertEquals(expected, Toml.serializeFrom(child)); + } + + @Test + public void emptyTomlSerializesToEmptyString() { + Toml toml = new Toml(); + assertEquals("", toml.serialize()); + } + + @Test + public void serializesStringsToTomlUtf8() throws UnsupportedEncodingException { + String input = " é foo € \b \t \n \f \r \" \\ "; + assertEquals("\" \\u00E9 foo \\u20AC \\b \\t \\n \\f \\r \\\" \\ \"", Toml.serializeFrom(input)); + + // Check unicode code points greater than 0XFFFF + input = " \uD801\uDC28 \uD840\uDC0B "; + assertEquals("\" \\U00010428 \\U0002000B \"", Toml.serializeFrom(input)); + } + + @Test + public void quotesKeys() { + Map aMap = new LinkedHashMap(); + 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, Toml.serializeFrom(aMap)); + } + + @Test + public void serializesFromToml() { + String tomlString = "a = 1\n"; + Toml toml = new Toml().parse(tomlString); + assertEquals(tomlString, toml.serialize()); + } +} \ No newline at end of file From bb0d9b11466d050799ca68b7c06d65f557cbab31 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Wed, 24 Jun 2015 21:55:37 -0700 Subject: [PATCH 02/24] Add serialization to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddc865b..feaba59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Added +* Serialization of objects and Toml instances to TOML. * Support for underscores in numbers (the feature branch had accidentally not been merged! :( ) * Set Toml#entrySet() cf. Reflection section in README * Overloaded getters that take a default value. Thanks to __[udiabon](https://github.com/udiabon)__. From 9f717384a4130cba4d4ec0297a8ff6c1bcc965e4 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Wed, 24 Jun 2015 22:14:42 -0700 Subject: [PATCH 03/24] Don't use java.util.function.Predicate. It is a Java-8 addition. --- .../com/moandjiezana/toml/ObjectSerializer.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/ObjectSerializer.java b/src/main/java/com/moandjiezana/toml/ObjectSerializer.java index ffe106c..07da595 100644 --- a/src/main/java/com/moandjiezana/toml/ObjectSerializer.java +++ b/src/main/java/com/moandjiezana/toml/ObjectSerializer.java @@ -3,7 +3,6 @@ package com.moandjiezana.toml; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; -import java.util.function.Predicate; import static com.moandjiezana.toml.MapSerializer.MAP_SERIALIZER; @@ -42,14 +41,14 @@ class ObjectSerializer implements Serializer { getSuperClassFields(cls.getSuperclass(), fields); // Skip final fields - fields.removeIf(new Predicate() { - @Override - public boolean test(Field field) { - return Modifier.isFinal(field.getModifiers()); + Set prunedFields = new LinkedHashSet(); + for (Field field : fields) { + if (!Modifier.isFinal(field.getModifiers())) { + prunedFields.add(field); } - }); + } - return fields; + return prunedFields; } static private void getSuperClassFields(Class cls, Set fields) { From 64afb09d967bc50b42be61e2fb4331dcb98f025a Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Wed, 24 Jun 2015 23:35:19 -0700 Subject: [PATCH 04/24] Java-6 style date/tz formatting --- src/main/java/com/moandjiezana/toml/DateSerializer.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/moandjiezana/toml/DateSerializer.java b/src/main/java/com/moandjiezana/toml/DateSerializer.java index f9935b8..bf6bf77 100644 --- a/src/main/java/com/moandjiezana/toml/DateSerializer.java +++ b/src/main/java/com/moandjiezana/toml/DateSerializer.java @@ -1,11 +1,14 @@ package com.moandjiezana.toml; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; class DateSerializer implements Serializer { static final Serializer DATE_SERIALIZER = new DateSerializer(); - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ssXXX"); + private static final Calendar calendar = new GregorianCalendar(); + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss"); @Override public boolean canSerialize(Object value) { @@ -15,6 +18,8 @@ class DateSerializer implements Serializer { @Override public void serialize(Object value, SerializerContext context) { context.serialized.append(dateFormat.format(value)); + int tzOffset = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000); + context.serialized.append(String.format("%+03d:%02d", tzOffset / 60, tzOffset % 60)); } @Override From 4ed37407327bf0c56f546c3a2f076e25e8efb575 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Thu, 25 Jun 2015 07:56:04 -0700 Subject: [PATCH 05/24] Fix Date serialization testing. The test can be run in any time zone, so always test with a Date instance from the local time zone where the test is being run. --- .../com/moandjiezana/toml/SerializerTest.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/moandjiezana/toml/SerializerTest.java b/src/test/java/com/moandjiezana/toml/SerializerTest.java index 444d0d4..e15451c 100644 --- a/src/test/java/com/moandjiezana/toml/SerializerTest.java +++ b/src/test/java/com/moandjiezana/toml/SerializerTest.java @@ -3,6 +3,7 @@ package com.moandjiezana.toml; import org.junit.Test; import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; import java.util.*; import static org.junit.Assert.assertEquals; @@ -27,9 +28,8 @@ public class SerializerTest { o.aDouble = -5.43; o.aBoolean = false; - String theDate = "2015-05-31T08:44:03-07:00"; - Toml dateToml = new Toml().parse("a_date = " + theDate); - o.aDate = dateToml.getDate("a_date"); + o.aDate = new Date(); + String theDate = formatDate(o.aDate); String serialized = Toml.serializeFrom(o); String expected = "aString = \"hello\"\n" + @@ -42,6 +42,18 @@ public class SerializerTest { assertEquals(expected, serialized); } + private String formatDate(Date date) { + // Copying the date formatting code from DateSerializer isn't optimal, but + // I can't see any other way to check date serialization - 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; + } + @Test public void serializesNestedMap() { class SubChild { From 1216a009cb53b7420413a8c54723d8a943ea01be Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Thu, 25 Jun 2015 08:01:34 -0700 Subject: [PATCH 06/24] 4 -> 2 spaces --- .../moandjiezana/toml/ObjectSerializer.java | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/ObjectSerializer.java b/src/main/java/com/moandjiezana/toml/ObjectSerializer.java index 07da595..0540c38 100644 --- a/src/main/java/com/moandjiezana/toml/ObjectSerializer.java +++ b/src/main/java/com/moandjiezana/toml/ObjectSerializer.java @@ -7,69 +7,69 @@ import java.util.*; import static com.moandjiezana.toml.MapSerializer.MAP_SERIALIZER; class ObjectSerializer implements Serializer { - static final Serializer OBJECT_SERIALIZER = new ObjectSerializer(); + static final Serializer OBJECT_SERIALIZER = new ObjectSerializer(); - @Override - public boolean canSerialize(Object value) { - return true; + @Override + public boolean canSerialize(Object value) { + return true; + } + + @Override + public void serialize(Object value, SerializerContext context) { + Map to = new LinkedHashMap(); + Set fields = getFieldsForClass(value.getClass()); + for (Field field : fields) { + to.put(field.getName(), getFieldValue(field, value)); } - @Override - public void serialize(Object value, SerializerContext context) { - Map to = new LinkedHashMap(); - Set fields = getFieldsForClass(value.getClass()); - for (Field field : fields) { - to.put(field.getName(), getFieldValue(field, value)); - } + MAP_SERIALIZER.serialize(to, context); + } - MAP_SERIALIZER.serialize(to, context); + @Override + public boolean isPrimitiveType() { + return false; + } + + @Override + public boolean isTable() { + return true; + } + + static private Set getFieldsForClass(Class cls) { + Set fields = new LinkedHashSet(Arrays.asList(cls.getDeclaredFields())); + + getSuperClassFields(cls.getSuperclass(), fields); + + // Skip final fields + Set prunedFields = new LinkedHashSet(); + for (Field field : fields) { + if (!Modifier.isFinal(field.getModifiers())) { + prunedFields.add(field); + } } - @Override - public boolean isPrimitiveType() { - return false; + return prunedFields; + } + + static private void getSuperClassFields(Class cls, Set fields) { + if (cls == Object.class) { + return; } - @Override - public boolean isTable() { - return true; + 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); - static private Set getFieldsForClass(Class cls) { - Set fields = new LinkedHashSet(Arrays.asList(cls.getDeclaredFields())); - - getSuperClassFields(cls.getSuperclass(), fields); - - // Skip final fields - Set prunedFields = new LinkedHashSet(); - for (Field field : fields) { - if (!Modifier.isFinal(field.getModifiers())) { - prunedFields.add(field); - } - } - - return prunedFields; - } - - static private void getSuperClassFields(Class cls, Set 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; - } + return value; + } } From b729e450abb972d319154845c50f015a05103f36 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Thu, 25 Jun 2015 09:21:36 -0700 Subject: [PATCH 07/24] Fix thread-safety issue in DateSerializer. --- src/main/java/com/moandjiezana/toml/DateSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/moandjiezana/toml/DateSerializer.java b/src/main/java/com/moandjiezana/toml/DateSerializer.java index bf6bf77..0da2ba9 100644 --- a/src/main/java/com/moandjiezana/toml/DateSerializer.java +++ b/src/main/java/com/moandjiezana/toml/DateSerializer.java @@ -8,7 +8,6 @@ import java.util.GregorianCalendar; class DateSerializer implements Serializer { static final Serializer DATE_SERIALIZER = new DateSerializer(); private static final Calendar calendar = new GregorianCalendar(); - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss"); @Override public boolean canSerialize(Object value) { @@ -17,6 +16,7 @@ class DateSerializer implements Serializer { @Override public void serialize(Object value, SerializerContext context) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss"); context.serialized.append(dateFormat.format(value)); int tzOffset = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000); context.serialized.append(String.format("%+03d:%02d", tzOffset / 60, tzOffset % 60)); From ca6e04392653162c6ad61ad86076c01cc5e37cff Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Thu, 25 Jun 2015 09:31:36 -0700 Subject: [PATCH 08/24] Change bouncy caps to underscores in test names. --- .../com/moandjiezana/toml/SerializerTest.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/test/java/com/moandjiezana/toml/SerializerTest.java b/src/test/java/com/moandjiezana/toml/SerializerTest.java index e15451c..2a78d62 100644 --- a/src/test/java/com/moandjiezana/toml/SerializerTest.java +++ b/src/test/java/com/moandjiezana/toml/SerializerTest.java @@ -10,7 +10,7 @@ import static org.junit.Assert.assertEquals; public class SerializerTest { @Test - public void serializesPrimitiveTypes() { + public void should_serialize_primitive_types() { class TestClass { public String aString; int anInt; @@ -55,7 +55,7 @@ public class SerializerTest { } @Test - public void serializesNestedMap() { + public void should_serialize_nested_map() { class SubChild { int anInt; } @@ -94,7 +94,7 @@ public class SerializerTest { } @Test - public void serializesArrayOfPrimitive() { + public void should_serialize_array_of_primitive() { class ArrayTest { int[] array = {1, 2, 3}; } @@ -106,7 +106,7 @@ public class SerializerTest { } @Test - public void serializesArrayOfTables() { + public void should_serialize_array_of_tables() { class Table { int anInt; @@ -129,7 +129,7 @@ public class SerializerTest { } @Test - public void serializesArrayOfArray() { + public void should_serialize_array_of_array() { class ArrayTest { int[][] array = {{1, 2, 3}, {4, 5, 6}}; } @@ -141,7 +141,7 @@ public class SerializerTest { } @Test - public void serializesList() { + public void should_serialize_list() { class ListTest { List aList = new LinkedList(); } @@ -153,7 +153,7 @@ public class SerializerTest { } @Test - public void handlesZeroLengthArraysAndLists() { + public void should_handle_zero_length_arrays_and_lists() { class TestClass { List aList = new LinkedList(); Float[] anArray = new Float[0]; @@ -162,7 +162,7 @@ public class SerializerTest { } @Test - public void elidesEmptyIntermediateTables() { + public void should_elide_empty_intermediate_tables() { class C { int anInt = 1; } @@ -177,7 +177,7 @@ public class SerializerTest { } @Test - public void serializesNestedArraysOfTables() { + public void should_serialize_nested_arrays_of_tables() { class Physical { String color; String shape; @@ -240,7 +240,7 @@ public class SerializerTest { } @Test - public void serializesClassesWithInheritance() { + public void should_serialize_classes_with_inheritance() { class Parent { protected int anInt = 2; } @@ -254,13 +254,13 @@ public class SerializerTest { } @Test - public void emptyTomlSerializesToEmptyString() { + public void should_serialize_empty_toml_to_empty_string() { Toml toml = new Toml(); assertEquals("", toml.serialize()); } @Test - public void serializesStringsToTomlUtf8() throws UnsupportedEncodingException { + public void should_serialize_strings_to_toml_utf8() throws UnsupportedEncodingException { String input = " é foo € \b \t \n \f \r \" \\ "; assertEquals("\" \\u00E9 foo \\u20AC \\b \\t \\n \\f \\r \\\" \\ \"", Toml.serializeFrom(input)); @@ -270,7 +270,7 @@ public class SerializerTest { } @Test - public void quotesKeys() { + public void should_quote_keys() { Map aMap = new LinkedHashMap(); aMap.put("a.b", 1); aMap.put("5€", 2); @@ -285,7 +285,7 @@ public class SerializerTest { } @Test - public void serializesFromToml() { + public void should_serialize_from_toml() { String tomlString = "a = 1\n"; Toml toml = new Toml().parse(tomlString); assertEquals(tomlString, toml.serialize()); From c076099ddd3dadcee5f694f3ce7fcc22771fa7b0 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sat, 27 Jun 2015 12:50:15 -0700 Subject: [PATCH 09/24] Use 'write' rather than 'serialize'. Also refactor ValueWriter implementations into ValueConverter implementations, where possible. --- ...ySerializer.java => ArrayValueWriter.java} | 6 +- .../moandjiezana/toml/BooleanConverter.java | 22 ++++++- .../moandjiezana/toml/BooleanSerializer.java | 25 ------- .../moandjiezana/toml/BooleanValueWriter.java | 25 +++++++ .../com/moandjiezana/toml/DateConverter.java | 31 ++++++++- ...teSerializer.java => DateValueWriter.java} | 12 ++-- ...MapSerializer.java => MapValueWriter.java} | 42 ++++++------ .../moandjiezana/toml/NumberConverter.java | 23 ++++++- .../moandjiezana/toml/NumberSerializer.java | 25 ------- .../moandjiezana/toml/NumberValueWriter.java | 25 +++++++ ...Serializer.java => ObjectValueWriter.java} | 12 ++-- .../toml/PrimitiveArraySerializer.java | 28 -------- .../toml/PrimitiveArrayValueWriter.java | 28 ++++++++ .../com/moandjiezana/toml/Serializer.java | 11 ---- .../moandjiezana/toml/SerializerContext.java | 50 -------------- .../com/moandjiezana/toml/Serializers.java | 38 ----------- .../moandjiezana/toml/StringConverter.java | 55 +++++++++++++++- ...Serializer.java => StringValueWriter.java} | 14 ++-- .../toml/TableArraySerializer.java | 23 ------- .../toml/TableArrayValueWriter.java | 23 +++++++ src/main/java/com/moandjiezana/toml/Toml.java | 29 +++----- .../com/moandjiezana/toml/ValueWriter.java | 11 ++++ .../com/moandjiezana/toml/ValueWriters.java | 41 ++++++++++++ .../com/moandjiezana/toml/WriterContext.java | 50 ++++++++++++++ ...rializerTest.java => ValueWriterTest.java} | 66 +++++++++---------- 25 files changed, 412 insertions(+), 303 deletions(-) rename src/main/java/com/moandjiezana/toml/{ArraySerializer.java => ArrayValueWriter.java} (88%) delete mode 100644 src/main/java/com/moandjiezana/toml/BooleanSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/BooleanValueWriter.java rename src/main/java/com/moandjiezana/toml/{DateSerializer.java => DateValueWriter.java} (62%) rename src/main/java/com/moandjiezana/toml/{MapSerializer.java => MapValueWriter.java} (54%) delete mode 100644 src/main/java/com/moandjiezana/toml/NumberSerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/NumberValueWriter.java rename src/main/java/com/moandjiezana/toml/{ObjectSerializer.java => ObjectValueWriter.java} (82%) delete mode 100644 src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java delete mode 100644 src/main/java/com/moandjiezana/toml/Serializer.java delete mode 100644 src/main/java/com/moandjiezana/toml/SerializerContext.java delete mode 100644 src/main/java/com/moandjiezana/toml/Serializers.java rename src/main/java/com/moandjiezana/toml/{StringSerializer.java => StringValueWriter.java} (79%) delete mode 100644 src/main/java/com/moandjiezana/toml/TableArraySerializer.java create mode 100644 src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java create mode 100644 src/main/java/com/moandjiezana/toml/ValueWriter.java create mode 100644 src/main/java/com/moandjiezana/toml/ValueWriters.java create mode 100644 src/main/java/com/moandjiezana/toml/WriterContext.java rename src/test/java/com/moandjiezana/toml/{SerializerTest.java => ValueWriterTest.java} (77%) diff --git a/src/main/java/com/moandjiezana/toml/ArraySerializer.java b/src/main/java/com/moandjiezana/toml/ArrayValueWriter.java similarity index 88% rename from src/main/java/com/moandjiezana/toml/ArraySerializer.java rename to src/main/java/com/moandjiezana/toml/ArrayValueWriter.java index c396b3a..f474ab9 100644 --- a/src/main/java/com/moandjiezana/toml/ArraySerializer.java +++ b/src/main/java/com/moandjiezana/toml/ArrayValueWriter.java @@ -5,7 +5,7 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; -abstract class ArraySerializer implements Serializer { +abstract class ArrayValueWriter implements ValueWriter { static protected boolean isArrayish(Object value) { return value instanceof Collection || value.getClass().isArray(); } @@ -23,8 +23,8 @@ abstract class ArraySerializer implements Serializer { static boolean isArrayOfPrimitive(Object array) { Object first = peek(array); if (first != null) { - Serializer serializer = Serializers.findSerializerFor(first); - return serializer.isPrimitiveType() || isArrayish(first); + ValueWriter valueWriter = ValueWriters.findWriterFor(first); + return valueWriter.isPrimitiveType() || isArrayish(first); } return false; diff --git a/src/main/java/com/moandjiezana/toml/BooleanConverter.java b/src/main/java/com/moandjiezana/toml/BooleanConverter.java index 82f73d2..68bc7b0 100644 --- a/src/main/java/com/moandjiezana/toml/BooleanConverter.java +++ b/src/main/java/com/moandjiezana/toml/BooleanConverter.java @@ -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,25 @@ 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() {} } diff --git a/src/main/java/com/moandjiezana/toml/BooleanSerializer.java b/src/main/java/com/moandjiezana/toml/BooleanSerializer.java deleted file mode 100644 index 022ac20..0000000 --- a/src/main/java/com/moandjiezana/toml/BooleanSerializer.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.moandjiezana.toml; - -class BooleanSerializer implements Serializer { - static final Serializer BOOLEAN_SERIALIZER = new BooleanSerializer(); - - @Override - public boolean canSerialize(Object value) { - return Boolean.class.isInstance(value); - } - - @Override - public void serialize(Object value, SerializerContext context) { - context.serialized.append(value.toString()); - } - - @Override - public boolean isPrimitiveType() { - return true; - } - - @Override - public boolean isTable() { - return false; - } -} diff --git a/src/main/java/com/moandjiezana/toml/BooleanValueWriter.java b/src/main/java/com/moandjiezana/toml/BooleanValueWriter.java new file mode 100644 index 0000000..7a230bb --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/BooleanValueWriter.java @@ -0,0 +1,25 @@ +package com.moandjiezana.toml; + +class BooleanValueWriter implements ValueWriter { + static final ValueWriter BOOLEAN_VALUE_WRITER = new BooleanValueWriter(); + + @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; + } +} diff --git a/src/main/java/com/moandjiezana/toml/DateConverter.java b/src/main/java/com/moandjiezana/toml/DateConverter.java index 2063d46..07761b0 100644 --- a/src/main/java/com/moandjiezana/toml/DateConverter.java +++ b/src/main/java/com/moandjiezana/toml/DateConverter.java @@ -1,14 +1,18 @@ package com.moandjiezana.toml; import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; 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}))(.*)"); + private static final Calendar calendar = new GregorianCalendar(); @Override public boolean canConvert(String s) { @@ -79,6 +83,29 @@ class DateConverter implements ValueConverter { return errors; } } - + + @Override + public boolean canWrite(Object value) { + return value instanceof Date; + } + + @Override + public void write(Object value, WriterContext context) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss"); + context.output.append(dateFormat.format(value)); + 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() {} } diff --git a/src/main/java/com/moandjiezana/toml/DateSerializer.java b/src/main/java/com/moandjiezana/toml/DateValueWriter.java similarity index 62% rename from src/main/java/com/moandjiezana/toml/DateSerializer.java rename to src/main/java/com/moandjiezana/toml/DateValueWriter.java index 0da2ba9..dd7b1cd 100644 --- a/src/main/java/com/moandjiezana/toml/DateSerializer.java +++ b/src/main/java/com/moandjiezana/toml/DateValueWriter.java @@ -5,21 +5,21 @@ import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; -class DateSerializer implements Serializer { - static final Serializer DATE_SERIALIZER = new DateSerializer(); +class DateValueWriter implements ValueWriter { + static final ValueWriter DATE_VALUE_WRITER = new DateValueWriter(); private static final Calendar calendar = new GregorianCalendar(); @Override - public boolean canSerialize(Object value) { + public boolean canWrite(Object value) { return value instanceof Date; } @Override - public void serialize(Object value, SerializerContext context) { + public void write(Object value, WriterContext context) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss"); - context.serialized.append(dateFormat.format(value)); + context.output.append(dateFormat.format(value)); int tzOffset = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000); - context.serialized.append(String.format("%+03d:%02d", tzOffset / 60, tzOffset % 60)); + context.output.append(String.format("%+03d:%02d", tzOffset / 60, tzOffset % 60)); } @Override diff --git a/src/main/java/com/moandjiezana/toml/MapSerializer.java b/src/main/java/com/moandjiezana/toml/MapValueWriter.java similarity index 54% rename from src/main/java/com/moandjiezana/toml/MapSerializer.java rename to src/main/java/com/moandjiezana/toml/MapValueWriter.java index bd7b30e..717d18f 100644 --- a/src/main/java/com/moandjiezana/toml/MapSerializer.java +++ b/src/main/java/com/moandjiezana/toml/MapValueWriter.java @@ -4,25 +4,25 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.moandjiezana.toml.PrimitiveArraySerializer.PRIMITIVE_ARRAY_SERIALIZER; -import static com.moandjiezana.toml.TableArraySerializer.TABLE_ARRAY_SERIALIZER; +import static com.moandjiezana.toml.PrimitiveArrayValueWriter.PRIMITIVE_ARRAY_VALUE_WRITER; +import static com.moandjiezana.toml.TableArrayValueWriter.TABLE_ARRAY_VALUE_WRITER; -class MapSerializer implements Serializer { - static final Serializer MAP_SERIALIZER = new MapSerializer(); +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 canSerialize(Object value) { + public boolean canWrite(Object value) { return value instanceof Map; } @Override - public void serialize(Object value, SerializerContext context) { + public void write(Object value, WriterContext context) { Map from = (Map) value; if (hasPrimitiveValues(from)) { - context.serializeKey(); + context.writeKey(); } // Render primitive types and arrays of primitive first so they are @@ -33,16 +33,16 @@ class MapSerializer implements Serializer { continue; } - Serializer serializer = Serializers.findSerializerFor(fromValue); - if (serializer.isPrimitiveType()) { + ValueWriter valueWriter = ValueWriters.findWriterFor(fromValue); + if (valueWriter.isPrimitiveType()) { context.indent(); - context.serialized.append(quoteKey(key)).append(" = "); - serializer.serialize(fromValue, context); - context.serialized.append('\n'); - } else if (serializer == PRIMITIVE_ARRAY_SERIALIZER) { - context.serialized.append(quoteKey(key)).append(" = "); - serializer.serialize(fromValue, context); - context.serialized.append('\n'); + context.output.append(quoteKey(key)).append(" = "); + valueWriter.write(fromValue, context); + context.output.append('\n'); + } else if (valueWriter == PRIMITIVE_ARRAY_VALUE_WRITER) { + context.output.append(quoteKey(key)).append(" = "); + valueWriter.write(fromValue, context); + context.output.append('\n'); } } @@ -53,9 +53,9 @@ class MapSerializer implements Serializer { continue; } - Serializer serializer = Serializers.findSerializerFor(fromValue); - if (serializer.isTable() || serializer == TABLE_ARRAY_SERIALIZER) { - serializer.serialize(fromValue, context.extend(quoteKey(key))); + ValueWriter valueWriter = ValueWriters.findWriterFor(fromValue); + if (valueWriter.isTable() || valueWriter == TABLE_ARRAY_VALUE_WRITER) { + valueWriter.write(fromValue, context.extend(quoteKey(key))); } } } @@ -87,8 +87,8 @@ class MapSerializer implements Serializer { continue; } - Serializer serializer = Serializers.findSerializerFor(fromValue); - if (serializer.isPrimitiveType() || serializer == PRIMITIVE_ARRAY_SERIALIZER) { + ValueWriter valueWriter = ValueWriters.findWriterFor(fromValue); + if (valueWriter.isPrimitiveType() || valueWriter == PRIMITIVE_ARRAY_VALUE_WRITER) { return true; } } diff --git a/src/main/java/com/moandjiezana/toml/NumberConverter.java b/src/main/java/com/moandjiezana/toml/NumberConverter.java index a65578c..74a2edf 100644 --- a/src/main/java/com/moandjiezana/toml/NumberConverter.java +++ b/src/main/java/com/moandjiezana/toml/NumberConverter.java @@ -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,25 @@ 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; + } + } diff --git a/src/main/java/com/moandjiezana/toml/NumberSerializer.java b/src/main/java/com/moandjiezana/toml/NumberSerializer.java deleted file mode 100644 index 78eab87..0000000 --- a/src/main/java/com/moandjiezana/toml/NumberSerializer.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.moandjiezana.toml; - -class NumberSerializer implements Serializer { - static final Serializer NUMBER_SERIALIZER = new NumberSerializer(); - - @Override - public boolean canSerialize(Object value) { - return Number.class.isInstance(value); - } - - @Override - public void serialize(Object value, SerializerContext context) { - context.serialized.append(value.toString()); - } - - @Override - public boolean isPrimitiveType() { - return true; - } - - @Override - public boolean isTable() { - return false; - } -} diff --git a/src/main/java/com/moandjiezana/toml/NumberValueWriter.java b/src/main/java/com/moandjiezana/toml/NumberValueWriter.java new file mode 100644 index 0000000..48c0bad --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/NumberValueWriter.java @@ -0,0 +1,25 @@ +package com.moandjiezana.toml; + +class NumberValueWriter implements ValueWriter { + static final ValueWriter NUMBER_VALUE_WRITER = new NumberValueWriter(); + + @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; + } +} diff --git a/src/main/java/com/moandjiezana/toml/ObjectSerializer.java b/src/main/java/com/moandjiezana/toml/ObjectValueWriter.java similarity index 82% rename from src/main/java/com/moandjiezana/toml/ObjectSerializer.java rename to src/main/java/com/moandjiezana/toml/ObjectValueWriter.java index 0540c38..1c37e6a 100644 --- a/src/main/java/com/moandjiezana/toml/ObjectSerializer.java +++ b/src/main/java/com/moandjiezana/toml/ObjectValueWriter.java @@ -4,25 +4,25 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; -import static com.moandjiezana.toml.MapSerializer.MAP_SERIALIZER; +import static com.moandjiezana.toml.MapValueWriter.MAP_VALUE_WRITER; -class ObjectSerializer implements Serializer { - static final Serializer OBJECT_SERIALIZER = new ObjectSerializer(); +class ObjectValueWriter implements ValueWriter { + static final ValueWriter OBJECT_VALUE_WRITER = new ObjectValueWriter(); @Override - public boolean canSerialize(Object value) { + public boolean canWrite(Object value) { return true; } @Override - public void serialize(Object value, SerializerContext context) { + public void write(Object value, WriterContext context) { Map to = new LinkedHashMap(); Set fields = getFieldsForClass(value.getClass()); for (Field field : fields) { to.put(field.getName(), getFieldValue(field, value)); } - MAP_SERIALIZER.serialize(to, context); + MAP_VALUE_WRITER.write(to, context); } @Override diff --git a/src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java b/src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java deleted file mode 100644 index 79be5ab..0000000 --- a/src/main/java/com/moandjiezana/toml/PrimitiveArraySerializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.moandjiezana.toml; - -import java.util.Collection; - -class PrimitiveArraySerializer extends ArraySerializer { - static final Serializer PRIMITIVE_ARRAY_SERIALIZER = new PrimitiveArraySerializer(); - - @Override - public boolean canSerialize(Object value) { - return isArrayish(value) && isArrayOfPrimitive(value); - } - - @Override - public void serialize(Object value, SerializerContext context) { - Collection values = normalize(value); - - context.serialized.append("[ "); - boolean first = true; - for (Object elem : values) { - if (!first) { - context.serialized.append(", "); - } - Serializers.serialize(elem, context); - first = false; - } - context.serialized.append(" ]"); - } -} diff --git a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java new file mode 100644 index 0000000..e0e3aa7 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java @@ -0,0 +1,28 @@ +package com.moandjiezana.toml; + +import java.util.Collection; + +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("[ "); + boolean first = true; + for (Object elem : values) { + if (!first) { + context.output.append(", "); + } + ValueWriters.write(elem, context); + first = false; + } + context.output.append(" ]"); + } +} diff --git a/src/main/java/com/moandjiezana/toml/Serializer.java b/src/main/java/com/moandjiezana/toml/Serializer.java deleted file mode 100644 index c0ef999..0000000 --- a/src/main/java/com/moandjiezana/toml/Serializer.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.moandjiezana.toml; - -interface Serializer { - boolean canSerialize(Object value); - - void serialize(Object value, SerializerContext context); - - boolean isPrimitiveType(); - - boolean isTable(); -} diff --git a/src/main/java/com/moandjiezana/toml/SerializerContext.java b/src/main/java/com/moandjiezana/toml/SerializerContext.java deleted file mode 100644 index a83434b..0000000 --- a/src/main/java/com/moandjiezana/toml/SerializerContext.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.moandjiezana.toml; - -class SerializerContext { - private String key = ""; - private boolean isArrayOfTable = false; - StringBuilder serialized = new StringBuilder(); - - SerializerContext(String key, StringBuilder serialized) { - this.key = key; - this.serialized = serialized; - } - - SerializerContext() { - } - - SerializerContext extend(String newKey) { - String fullKey = key + (key.isEmpty() ? newKey : "." + newKey); - - return new SerializerContext(fullKey, serialized); - } - - SerializerContext extend() { - return new SerializerContext(key, serialized); - } - - void serializeKey() { - if (key.isEmpty()) { - return; - } - - if (serialized.length() > 0) { - serialized.append('\n'); - } - - if (isArrayOfTable) { - serialized.append("[[").append(key).append("]]\n"); - } else { - serialized.append('[').append(key).append("]\n"); - } - } - - void indent() { - serialized.append(key.isEmpty() ? "" : " "); - } - - SerializerContext setIsArrayOfTable(boolean isArrayOfTable) { - this.isArrayOfTable = isArrayOfTable; - return this; - } -} diff --git a/src/main/java/com/moandjiezana/toml/Serializers.java b/src/main/java/com/moandjiezana/toml/Serializers.java deleted file mode 100644 index fb49757..0000000 --- a/src/main/java/com/moandjiezana/toml/Serializers.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.moandjiezana.toml; - -import static com.moandjiezana.toml.BooleanSerializer.BOOLEAN_SERIALIZER; -import static com.moandjiezana.toml.DateSerializer.DATE_SERIALIZER; -import static com.moandjiezana.toml.MapSerializer.MAP_SERIALIZER; -import static com.moandjiezana.toml.NumberSerializer.NUMBER_SERIALIZER; -import static com.moandjiezana.toml.ObjectSerializer.OBJECT_SERIALIZER; -import static com.moandjiezana.toml.PrimitiveArraySerializer.PRIMITIVE_ARRAY_SERIALIZER; -import static com.moandjiezana.toml.StringSerializer.STRING_SERIALIZER; -import static com.moandjiezana.toml.TableArraySerializer.TABLE_ARRAY_SERIALIZER; - -abstract class Serializers { - private static final Serializer[] SERIALIZERS = { - STRING_SERIALIZER, NUMBER_SERIALIZER, BOOLEAN_SERIALIZER, DATE_SERIALIZER, - MAP_SERIALIZER, PRIMITIVE_ARRAY_SERIALIZER, TABLE_ARRAY_SERIALIZER - }; - - static Serializer findSerializerFor(Object value) { - for (Serializer serializer : SERIALIZERS) { - if (serializer.canSerialize(value)) { - return serializer; - } - } - - return OBJECT_SERIALIZER; - } - - static String serialize(Object value) { - SerializerContext context = new SerializerContext(); - serialize(value, context); - - return context.serialized.toString(); - } - - static void serialize(Object value, SerializerContext context) { - findSerializerFor(value).serialize(value, context); - } -} diff --git a/src/main/java/com/moandjiezana/toml/StringConverter.java b/src/main/java/com/moandjiezana/toml/StringConverter.java index 8100e44..774e9c3 100644 --- a/src/main/java/com/moandjiezana/toml/StringConverter.java +++ b/src/main/java/com/moandjiezana/toml/StringConverter.java @@ -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,45 @@ 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() {} } diff --git a/src/main/java/com/moandjiezana/toml/StringSerializer.java b/src/main/java/com/moandjiezana/toml/StringValueWriter.java similarity index 79% rename from src/main/java/com/moandjiezana/toml/StringSerializer.java rename to src/main/java/com/moandjiezana/toml/StringValueWriter.java index d3eee57..acdd79b 100644 --- a/src/main/java/com/moandjiezana/toml/StringSerializer.java +++ b/src/main/java/com/moandjiezana/toml/StringValueWriter.java @@ -1,7 +1,7 @@ package com.moandjiezana.toml; -class StringSerializer implements Serializer { - static final Serializer STRING_SERIALIZER = new StringSerializer(); +class StringValueWriter implements ValueWriter { + static final ValueWriter STRING_VALUE_WRITER = new StringValueWriter(); static private final String[] specialCharacterEscapes = new String[93]; @@ -16,15 +16,15 @@ class StringSerializer implements Serializer { } @Override - public boolean canSerialize(Object value) { + public boolean canWrite(Object value) { return value.getClass().isAssignableFrom(String.class); } @Override - public void serialize(Object value, SerializerContext context) { - context.serialized.append('"'); - escapeUnicode(value.toString(), context.serialized); - context.serialized.append('"'); + public void write(Object value, WriterContext context) { + context.output.append('"'); + escapeUnicode(value.toString(), context.output); + context.output.append('"'); } @Override diff --git a/src/main/java/com/moandjiezana/toml/TableArraySerializer.java b/src/main/java/com/moandjiezana/toml/TableArraySerializer.java deleted file mode 100644 index d2c2912..0000000 --- a/src/main/java/com/moandjiezana/toml/TableArraySerializer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.moandjiezana.toml; - -import java.util.Collection; - -class TableArraySerializer extends ArraySerializer { - static final Serializer TABLE_ARRAY_SERIALIZER = new TableArraySerializer(); - - @Override - public boolean canSerialize(Object value) { - return isArrayish(value) && !isArrayOfPrimitive(value); - } - - @Override - public void serialize(Object value, SerializerContext context) { - Collection values = normalize(value); - - SerializerContext subContext = context.extend().setIsArrayOfTable(true); - - for (Object elem : values) { - Serializers.serialize(elem, subContext); - } - } -} diff --git a/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java new file mode 100644 index 0000000..9fdd9b6 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java @@ -0,0 +1,23 @@ +package com.moandjiezana.toml; + +import java.util.Collection; + +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.extend().setIsArrayOfTable(true); + + for (Object elem : values) { + ValueWriters.write(elem, subContext); + } + } +} diff --git a/src/main/java/com/moandjiezana/toml/Toml.java b/src/main/java/com/moandjiezana/toml/Toml.java index d5efe3f..715b206 100644 --- a/src/main/java/com/moandjiezana/toml/Toml.java +++ b/src/main/java/com/moandjiezana/toml/Toml.java @@ -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.*; + /** *

Provides access to the keys and tables in a TOML data source.

* @@ -378,7 +365,7 @@ public class Toml { * @return a string containing the TOML representation of this Toml instance. */ public String serialize() { - return Serializers.serialize(values); + return ValueWriters.write(values); } /** @@ -386,13 +373,13 @@ public class Toml { * * The input 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 serialized to TOML tables, and {@link List}s and Array to TOML arrays. + * are output to TOML tables, and {@link List}s and Array to TOML arrays. * - * @param from the object to be serialized + * @param from the object to be written * @return a string containing the TOML representation of the given Object */ - public static String serializeFrom(Object from) { - return Serializers.serialize(from); + public static String write(Object from) { + return ValueWriters.write(from); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/moandjiezana/toml/ValueWriter.java b/src/main/java/com/moandjiezana/toml/ValueWriter.java new file mode 100644 index 0000000..6edae56 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/ValueWriter.java @@ -0,0 +1,11 @@ +package com.moandjiezana.toml; + +interface ValueWriter { + boolean canWrite(Object value); + + void write(Object value, WriterContext context); + + boolean isPrimitiveType(); + + boolean isTable(); +} diff --git a/src/main/java/com/moandjiezana/toml/ValueWriters.java b/src/main/java/com/moandjiezana/toml/ValueWriters.java new file mode 100644 index 0000000..9ffe6ba --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/ValueWriters.java @@ -0,0 +1,41 @@ +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 ValueWriter findWriterFor(Object value) { + for (ValueWriter valueWriter : VALUE_WRITERs) { + if (valueWriter.canWrite(value)) { + return valueWriter; + } + } + + return OBJECT_VALUE_WRITER; + } + + static String write(Object value) { + WriterContext context = new WriterContext(); + write(value, context); + + return context.output.toString(); + } + + static 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 + }; +} diff --git a/src/main/java/com/moandjiezana/toml/WriterContext.java b/src/main/java/com/moandjiezana/toml/WriterContext.java new file mode 100644 index 0000000..e07e828 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/WriterContext.java @@ -0,0 +1,50 @@ +package com.moandjiezana.toml; + +class WriterContext { + private String key = ""; + private boolean isArrayOfTable = false; + StringBuilder output = new StringBuilder(); + + WriterContext(String key, StringBuilder output) { + this.key = key; + this.output = output; + } + + WriterContext() { + } + + WriterContext extend(String newKey) { + String fullKey = key + (key.isEmpty() ? newKey : "." + newKey); + + return new WriterContext(fullKey, output); + } + + WriterContext extend() { + return new WriterContext(key, output); + } + + void writeKey() { + if (key.isEmpty()) { + return; + } + + if (output.length() > 0) { + output.append('\n'); + } + + if (isArrayOfTable) { + output.append("[[").append(key).append("]]\n"); + } else { + output.append('[').append(key).append("]\n"); + } + } + + void indent() { + output.append(key.isEmpty() ? "" : " "); + } + + WriterContext setIsArrayOfTable(boolean isArrayOfTable) { + this.isArrayOfTable = isArrayOfTable; + return this; + } +} diff --git a/src/test/java/com/moandjiezana/toml/SerializerTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java similarity index 77% rename from src/test/java/com/moandjiezana/toml/SerializerTest.java rename to src/test/java/com/moandjiezana/toml/ValueWriterTest.java index 2a78d62..8319e66 100644 --- a/src/test/java/com/moandjiezana/toml/SerializerTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -8,9 +8,9 @@ import java.util.*; import static org.junit.Assert.assertEquals; -public class SerializerTest { +public class ValueWriterTest { @Test - public void should_serialize_primitive_types() { + public void should_write_primitive_types() { class TestClass { public String aString; int anInt; @@ -31,7 +31,7 @@ public class SerializerTest { o.aDate = new Date(); String theDate = formatDate(o.aDate); - String serialized = Toml.serializeFrom(o); + String output = Toml.write(o); String expected = "aString = \"hello\"\n" + "anInt = 4\n" + "aFloat = 1.23\n" + @@ -39,12 +39,12 @@ public class SerializerTest { "aBoolean = false\n" + "aDate = " + theDate + "\n"; - assertEquals(expected, serialized); + assertEquals(expected, output); } private String formatDate(Date date) { - // Copying the date formatting code from DateSerializer isn't optimal, but - // I can't see any other way to check date serialization - the test gets + // 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(); @@ -55,7 +55,7 @@ public class SerializerTest { } @Test - public void should_serialize_nested_map() { + public void should_write_nested_map() { class SubChild { int anInt; } @@ -80,7 +80,7 @@ public class SerializerTest { parent.child.subChild.anInt = 4; parent.aBoolean = true; - String serialized = Toml.serializeFrom(parent); + String output = Toml.write(parent); String expected = "aBoolean = true\n\n" + "[aMap]\n" + " foo = 1\n" + @@ -90,23 +90,23 @@ public class SerializerTest { " anInt = 2\n\n" + "[child.subChild]\n" + " anInt = 4\n"; - assertEquals(expected, serialized); + assertEquals(expected, output); } @Test - public void should_serialize_array_of_primitive() { + public void should_write_array_of_primitive() { class ArrayTest { int[] array = {1, 2, 3}; } ArrayTest arrayTest = new ArrayTest(); - String serialized = Toml.serializeFrom(arrayTest); + String output = Toml.write(arrayTest); String expected = "array = [ 1, 2, 3 ]\n"; - assertEquals(expected, serialized); + assertEquals(expected, output); } @Test - public void should_serialize_array_of_tables() { + public void should_write_array_of_tables() { class Table { int anInt; @@ -120,28 +120,28 @@ public class SerializerTest { Config config = new Config(); config.table = new Table[]{new Table(1), new Table(2)}; - String serialized = Toml.serializeFrom(config); + String output = Toml.write(config); String expected = "[[table]]\n" + " anInt = 1\n\n" + "[[table]]\n" + " anInt = 2\n"; - assertEquals(expected, serialized); + assertEquals(expected, output); } @Test - public void should_serialize_array_of_array() { + public void should_write_array_of_array() { class ArrayTest { int[][] array = {{1, 2, 3}, {4, 5, 6}}; } ArrayTest arrayTest = new ArrayTest(); - String serialized = Toml.serializeFrom(arrayTest); + String output = Toml.write(arrayTest); String expected = "array = [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]\n"; - assertEquals(expected, serialized); + assertEquals(expected, output); } @Test - public void should_serialize_list() { + public void should_write_list() { class ListTest { List aList = new LinkedList(); } @@ -149,7 +149,7 @@ public class SerializerTest { o.aList.add(1); o.aList.add(2); - assertEquals("aList = [ 1, 2 ]\n", Toml.serializeFrom(o)); + assertEquals("aList = [ 1, 2 ]\n", Toml.write(o)); } @Test @@ -158,7 +158,7 @@ public class SerializerTest { List aList = new LinkedList(); Float[] anArray = new Float[0]; } - assertEquals("", Toml.serializeFrom(new TestClass())); + assertEquals("", Toml.write(new TestClass())); } @Test @@ -173,11 +173,11 @@ public class SerializerTest { B b = new B(); } - assertEquals("[b.c]\n anInt = 1\n", Toml.serializeFrom(new A())); + assertEquals("[b.c]\n anInt = 1\n", Toml.write(new A())); } @Test - public void should_serialize_nested_arrays_of_tables() { + public void should_write_nested_arrays_of_tables() { class Physical { String color; String shape; @@ -235,12 +235,12 @@ public class SerializerTest { "\n"; - String serialized = Toml.serializeFrom(basket); - assertEquals(expected, serialized); + String output = Toml.write(basket); + assertEquals(expected, output); } @Test - public void should_serialize_classes_with_inheritance() { + public void should_write_classes_with_inheritance() { class Parent { protected int anInt = 2; } @@ -250,23 +250,23 @@ public class SerializerTest { Child child = new Child(); String expected = "aBoolean = true\nanInt = 2\n"; - assertEquals(expected, Toml.serializeFrom(child)); + assertEquals(expected, Toml.write(child)); } @Test - public void should_serialize_empty_toml_to_empty_string() { + public void should_write_empty_toml_to_empty_string() { Toml toml = new Toml(); assertEquals("", toml.serialize()); } @Test - public void should_serialize_strings_to_toml_utf8() throws UnsupportedEncodingException { + 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 \\\" \\ \"", Toml.serializeFrom(input)); + assertEquals("\" \\u00E9 foo \\u20AC \\b \\t \\n \\f \\r \\\" \\ \"", Toml.write(input)); // Check unicode code points greater than 0XFFFF input = " \uD801\uDC28 \uD840\uDC0B "; - assertEquals("\" \\U00010428 \\U0002000B \"", Toml.serializeFrom(input)); + assertEquals("\" \\U00010428 \\U0002000B \"", Toml.write(input)); } @Test @@ -281,11 +281,11 @@ public class SerializerTest { "\"5€\" = 2\n" + "\"c$d\" = 3\n" + "\"e/f\" = 4\n"; - assertEquals(expected, Toml.serializeFrom(aMap)); + assertEquals(expected, Toml.write(aMap)); } @Test - public void should_serialize_from_toml() { + public void should_write_from_toml() { String tomlString = "a = 1\n"; Toml toml = new Toml().parse(tomlString); assertEquals(tomlString, toml.serialize()); From acc952a572deb83aba45393ff0c546b08b63e9f3 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sat, 27 Jun 2015 13:11:52 -0700 Subject: [PATCH 10/24] Remove Toml.serialize(). --- src/main/java/com/moandjiezana/toml/Toml.java | 9 --------- .../java/com/moandjiezana/toml/ValueWriterTest.java | 13 ------------- 2 files changed, 22 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/Toml.java b/src/main/java/com/moandjiezana/toml/Toml.java index 715b206..ac778f8 100644 --- a/src/main/java/com/moandjiezana/toml/Toml.java +++ b/src/main/java/com/moandjiezana/toml/Toml.java @@ -359,15 +359,6 @@ public class Toml { } } - /** - * Serializes the values of this Toml instance into TOML. - * - * @return a string containing the TOML representation of this Toml instance. - */ - public String serialize() { - return ValueWriters.write(values); - } - /** * Serializes an Object into TOML. * diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index 8319e66..be18db7 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -253,12 +253,6 @@ public class ValueWriterTest { assertEquals(expected, Toml.write(child)); } - @Test - public void should_write_empty_toml_to_empty_string() { - Toml toml = new Toml(); - assertEquals("", toml.serialize()); - } - @Test public void should_write_strings_to_toml_utf8() throws UnsupportedEncodingException { String input = " é foo € \b \t \n \f \r \" \\ "; @@ -283,11 +277,4 @@ public class ValueWriterTest { "\"e/f\" = 4\n"; assertEquals(expected, Toml.write(aMap)); } - - @Test - public void should_write_from_toml() { - String tomlString = "a = 1\n"; - Toml toml = new Toml().parse(tomlString); - assertEquals(tomlString, toml.serialize()); - } } \ No newline at end of file From ec30be79017a838cc19da4500190df35f5c5c981 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sat, 27 Jun 2015 14:19:40 -0700 Subject: [PATCH 11/24] Move Toml.write() to TomlWriter. Add TomlWriter methods for writing to File, Writer, and OutputStream targets. --- src/main/java/com/moandjiezana/toml/Toml.java | 14 ---- .../com/moandjiezana/toml/TomlWriter.java | 74 ++++++++++++++++++ .../moandjiezana/toml/ValueWriterTest.java | 75 +++++++++++++++---- 3 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/moandjiezana/toml/TomlWriter.java diff --git a/src/main/java/com/moandjiezana/toml/Toml.java b/src/main/java/com/moandjiezana/toml/Toml.java index ac778f8..f6af1ff 100644 --- a/src/main/java/com/moandjiezana/toml/Toml.java +++ b/src/main/java/com/moandjiezana/toml/Toml.java @@ -359,20 +359,6 @@ public class Toml { } } - /** - * Serializes an Object into TOML. - * - * The input 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. - * - * @param from the object to be written - * @return a string containing the TOML representation of the given Object - */ - public static String write(Object from) { - return ValueWriters.write(from); - } - @SuppressWarnings("unchecked") private Object get(String key) { if (values.containsKey(key)) { diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java new file mode 100644 index 0000000..d959dcd --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -0,0 +1,74 @@ +package com.moandjiezana.toml; + +import java.io.*; +import java.util.List; +import java.util.Map; + +/** + *

Converts Objects to TOML

+ * + *

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.

+ * + *

Example usage:

+ *

+ * class AClass {
+ *   int anInt = 1;
+ *   int[] anArray = { 2, 3 };
+ * }
+ *
+ * String tomlString = new TomlWriter().write(new AClass());
+ * 
+ */ +public class TomlWriter { + /** + * 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 ValueWriters.write(from); + } + + /** + * 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(); + } +} diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index be18db7..48a0a06 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -1,14 +1,20 @@ package com.moandjiezana.toml; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; -import java.io.UnsupportedEncodingException; +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 { @@ -31,7 +37,7 @@ public class ValueWriterTest { o.aDate = new Date(); String theDate = formatDate(o.aDate); - String output = Toml.write(o); + String output = new TomlWriter().write(o); String expected = "aString = \"hello\"\n" + "anInt = 4\n" + "aFloat = 1.23\n" + @@ -80,7 +86,7 @@ public class ValueWriterTest { parent.child.subChild.anInt = 4; parent.aBoolean = true; - String output = Toml.write(parent); + String output = new TomlWriter().write(parent); String expected = "aBoolean = true\n\n" + "[aMap]\n" + " foo = 1\n" + @@ -100,7 +106,7 @@ public class ValueWriterTest { } ArrayTest arrayTest = new ArrayTest(); - String output = Toml.write(arrayTest); + String output = new TomlWriter().write(arrayTest); String expected = "array = [ 1, 2, 3 ]\n"; assertEquals(expected, output); } @@ -120,7 +126,7 @@ public class ValueWriterTest { Config config = new Config(); config.table = new Table[]{new Table(1), new Table(2)}; - String output = Toml.write(config); + String output = new TomlWriter().write(config); String expected = "[[table]]\n" + " anInt = 1\n\n" + "[[table]]\n" + @@ -135,7 +141,7 @@ public class ValueWriterTest { } ArrayTest arrayTest = new ArrayTest(); - String output = Toml.write(arrayTest); + String output = new TomlWriter().write(arrayTest); String expected = "array = [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]\n"; assertEquals(expected, output); } @@ -149,7 +155,7 @@ public class ValueWriterTest { o.aList.add(1); o.aList.add(2); - assertEquals("aList = [ 1, 2 ]\n", Toml.write(o)); + assertEquals("aList = [ 1, 2 ]\n", new TomlWriter().write(o)); } @Test @@ -158,7 +164,7 @@ public class ValueWriterTest { List aList = new LinkedList(); Float[] anArray = new Float[0]; } - assertEquals("", Toml.write(new TestClass())); + assertEquals("", new TomlWriter().write(new TestClass())); } @Test @@ -173,7 +179,7 @@ public class ValueWriterTest { B b = new B(); } - assertEquals("[b.c]\n anInt = 1\n", Toml.write(new A())); + assertEquals("[b.c]\n anInt = 1\n", new TomlWriter().write(new A())); } @Test @@ -235,7 +241,7 @@ public class ValueWriterTest { "\n"; - String output = Toml.write(basket); + String output = new TomlWriter().write(basket); assertEquals(expected, output); } @@ -250,17 +256,17 @@ public class ValueWriterTest { Child child = new Child(); String expected = "aBoolean = true\nanInt = 2\n"; - assertEquals(expected, Toml.write(child)); + assertEquals(expected, new TomlWriter().write(child)); } @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 \\\" \\ \"", Toml.write(input)); + 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 \"", Toml.write(input)); + assertEquals("\" \\U00010428 \\U0002000B \"", new TomlWriter().write(input)); } @Test @@ -275,6 +281,47 @@ public class ValueWriterTest { "\"5€\" = 2\n" + "\"c$d\" = 3\n" + "\"e/f\" = 4\n"; - assertEquals(expected, Toml.write(aMap)); + 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(); } } \ No newline at end of file From bcc52f5cb05a9dd405243f080ed075667bee4859 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sat, 27 Jun 2015 14:23:52 -0700 Subject: [PATCH 12/24] Use new 'write' vocabulary in the docs. --- CHANGELOG.md | 2 +- README.md | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feaba59..02695e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Added -* Serialization of objects and Toml instances to TOML. +* Support for writing objects to TOML format. * Support for underscores in numbers (the feature branch had accidentally not been merged! :( ) * Set Toml#entrySet() cf. Reflection section in README * Overloaded getters that take a default value. Thanks to __[udiabon](https://github.com/udiabon)__. diff --git a/README.md b/README.md index 54bc415..e2187f2 100644 --- a/README.md +++ b/README.md @@ -233,18 +233,9 @@ toml.containsTable("a"); // false toml.containsTableArray("a"); // false ``` -### Serialization +### Converting Objects To TOML -You can serialize a `Toml` or any arbitrary object to a TOML string. - -Once you have populated a `Toml` via `Toml.parse()`, you can serialize it back to TOML. - -```java -Toml toml = new Toml().parse("a=1"); -String tomlString = toml.serialize(); -``` - -Or you can serialize any object. +You can write any arbitrary object to a TOML `String`, `File`, `Writer`, or `OutputStream`. ```java class AClass { @@ -252,7 +243,7 @@ class AClass { int[] anArray = { 2, 3 }; } -String tomlString = Toml.serializeFrom(new AClass()); +String tomlString = new TomlWriter().write(new AClass()); /* yields: @@ -261,6 +252,8 @@ anArray = [ 2, 3 ] */ ``` +See the `TomlWriter` class for more details. + ### Limitations Date precision is limited to milliseconds. From 8b3652a309e3be599fb2bb4fd90ef29a785ec74c Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sat, 27 Jun 2015 14:27:38 -0700 Subject: [PATCH 13/24] Remove classes merged into Converters. --- .../moandjiezana/toml/BooleanValueWriter.java | 25 --------- .../moandjiezana/toml/DateValueWriter.java | 34 ----------- .../moandjiezana/toml/NumberValueWriter.java | 25 --------- .../moandjiezana/toml/StringValueWriter.java | 56 ------------------- 4 files changed, 140 deletions(-) delete mode 100644 src/main/java/com/moandjiezana/toml/BooleanValueWriter.java delete mode 100644 src/main/java/com/moandjiezana/toml/DateValueWriter.java delete mode 100644 src/main/java/com/moandjiezana/toml/NumberValueWriter.java delete mode 100644 src/main/java/com/moandjiezana/toml/StringValueWriter.java diff --git a/src/main/java/com/moandjiezana/toml/BooleanValueWriter.java b/src/main/java/com/moandjiezana/toml/BooleanValueWriter.java deleted file mode 100644 index 7a230bb..0000000 --- a/src/main/java/com/moandjiezana/toml/BooleanValueWriter.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.moandjiezana.toml; - -class BooleanValueWriter implements ValueWriter { - static final ValueWriter BOOLEAN_VALUE_WRITER = new BooleanValueWriter(); - - @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; - } -} diff --git a/src/main/java/com/moandjiezana/toml/DateValueWriter.java b/src/main/java/com/moandjiezana/toml/DateValueWriter.java deleted file mode 100644 index dd7b1cd..0000000 --- a/src/main/java/com/moandjiezana/toml/DateValueWriter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.moandjiezana.toml; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; - -class DateValueWriter implements ValueWriter { - static final ValueWriter DATE_VALUE_WRITER = new DateValueWriter(); - private static final Calendar calendar = new GregorianCalendar(); - - @Override - public boolean canWrite(Object value) { - return value instanceof Date; - } - - @Override - public void write(Object value, WriterContext context) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss"); - context.output.append(dateFormat.format(value)); - 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; - } -} diff --git a/src/main/java/com/moandjiezana/toml/NumberValueWriter.java b/src/main/java/com/moandjiezana/toml/NumberValueWriter.java deleted file mode 100644 index 48c0bad..0000000 --- a/src/main/java/com/moandjiezana/toml/NumberValueWriter.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.moandjiezana.toml; - -class NumberValueWriter implements ValueWriter { - static final ValueWriter NUMBER_VALUE_WRITER = new NumberValueWriter(); - - @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; - } -} diff --git a/src/main/java/com/moandjiezana/toml/StringValueWriter.java b/src/main/java/com/moandjiezana/toml/StringValueWriter.java deleted file mode 100644 index acdd79b..0000000 --- a/src/main/java/com/moandjiezana/toml/StringValueWriter.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.moandjiezana.toml; - -class StringValueWriter implements ValueWriter { - static final ValueWriter STRING_VALUE_WRITER = new StringValueWriter(); - - 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 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++; - } - } - } -} From 24a6503d19288c5cbd18ac3e714570817962515a Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sat, 27 Jun 2015 14:35:26 -0700 Subject: [PATCH 14/24] Conform to the project's singleton pattern. --- .../java/com/moandjiezana/toml/ArrayValueWriter.java | 4 +++- src/main/java/com/moandjiezana/toml/MapValueWriter.java | 9 ++++++--- .../java/com/moandjiezana/toml/ObjectValueWriter.java | 2 ++ .../com/moandjiezana/toml/PrimitiveArrayValueWriter.java | 4 +++- .../com/moandjiezana/toml/TableArrayValueWriter.java | 6 +++++- src/main/java/com/moandjiezana/toml/TomlWriter.java | 4 +++- src/main/java/com/moandjiezana/toml/ValueWriters.java | 8 +++++--- 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/ArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/ArrayValueWriter.java index f474ab9..c2449a7 100644 --- a/src/main/java/com/moandjiezana/toml/ArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/ArrayValueWriter.java @@ -5,6 +5,8 @@ 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(); @@ -23,7 +25,7 @@ abstract class ArrayValueWriter implements ValueWriter { static boolean isArrayOfPrimitive(Object array) { Object first = peek(array); if (first != null) { - ValueWriter valueWriter = ValueWriters.findWriterFor(first); + ValueWriter valueWriter = WRITERS.findWriterFor(first); return valueWriter.isPrimitiveType() || isArrayish(first); } diff --git a/src/main/java/com/moandjiezana/toml/MapValueWriter.java b/src/main/java/com/moandjiezana/toml/MapValueWriter.java index 717d18f..586c32b 100644 --- a/src/main/java/com/moandjiezana/toml/MapValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/MapValueWriter.java @@ -6,6 +6,7 @@ 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(); @@ -33,7 +34,7 @@ class MapValueWriter implements ValueWriter { continue; } - ValueWriter valueWriter = ValueWriters.findWriterFor(fromValue); + ValueWriter valueWriter = WRITERS.findWriterFor(fromValue); if (valueWriter.isPrimitiveType()) { context.indent(); context.output.append(quoteKey(key)).append(" = "); @@ -53,7 +54,7 @@ class MapValueWriter implements ValueWriter { continue; } - ValueWriter valueWriter = ValueWriters.findWriterFor(fromValue); + ValueWriter valueWriter = WRITERS.findWriterFor(fromValue); if (valueWriter.isTable() || valueWriter == TABLE_ARRAY_VALUE_WRITER) { valueWriter.write(fromValue, context.extend(quoteKey(key))); } @@ -87,7 +88,7 @@ class MapValueWriter implements ValueWriter { continue; } - ValueWriter valueWriter = ValueWriters.findWriterFor(fromValue); + ValueWriter valueWriter = WRITERS.findWriterFor(fromValue); if (valueWriter.isPrimitiveType() || valueWriter == PRIMITIVE_ARRAY_VALUE_WRITER) { return true; } @@ -95,4 +96,6 @@ class MapValueWriter implements ValueWriter { return false; } + + private MapValueWriter() {} } diff --git a/src/main/java/com/moandjiezana/toml/ObjectValueWriter.java b/src/main/java/com/moandjiezana/toml/ObjectValueWriter.java index 1c37e6a..369e187 100644 --- a/src/main/java/com/moandjiezana/toml/ObjectValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/ObjectValueWriter.java @@ -72,4 +72,6 @@ class ObjectValueWriter implements ValueWriter { return value; } + + private ObjectValueWriter() {} } diff --git a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java index e0e3aa7..0f823e6 100644 --- a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java @@ -20,9 +20,11 @@ class PrimitiveArrayValueWriter extends ArrayValueWriter { if (!first) { context.output.append(", "); } - ValueWriters.write(elem, context); + ValueWriters.WRITERS.write(elem, context); first = false; } context.output.append(" ]"); } + + private PrimitiveArrayValueWriter() {} } diff --git a/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java index 9fdd9b6..acdff1e 100644 --- a/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java @@ -2,6 +2,8 @@ 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(); @@ -17,7 +19,9 @@ class TableArrayValueWriter extends ArrayValueWriter { WriterContext subContext = context.extend().setIsArrayOfTable(true); for (Object elem : values) { - ValueWriters.write(elem, subContext); + WRITERS.write(elem, subContext); } } + + private TableArrayValueWriter() {} } diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java index d959dcd..651d6df 100644 --- a/src/main/java/com/moandjiezana/toml/TomlWriter.java +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -4,6 +4,8 @@ import java.io.*; import java.util.List; import java.util.Map; +import static com.moandjiezana.toml.ValueWriters.WRITERS; + /** *

Converts Objects to TOML

* @@ -34,7 +36,7 @@ public class TomlWriter { * @return a string containing the TOML representation of the given Object */ public String write(Object from) { - return ValueWriters.write(from); + return WRITERS.write(from); } /** diff --git a/src/main/java/com/moandjiezana/toml/ValueWriters.java b/src/main/java/com/moandjiezana/toml/ValueWriters.java index 9ffe6ba..4fe7695 100644 --- a/src/main/java/com/moandjiezana/toml/ValueWriters.java +++ b/src/main/java/com/moandjiezana/toml/ValueWriters.java @@ -11,7 +11,9 @@ import static com.moandjiezana.toml.TableArrayValueWriter.TABLE_ARRAY_VALUE_WRIT class ValueWriters { - static ValueWriter findWriterFor(Object value) { + static final ValueWriters WRITERS = new ValueWriters(); + + ValueWriter findWriterFor(Object value) { for (ValueWriter valueWriter : VALUE_WRITERs) { if (valueWriter.canWrite(value)) { return valueWriter; @@ -21,14 +23,14 @@ class ValueWriters { return OBJECT_VALUE_WRITER; } - static String write(Object value) { + String write(Object value) { WriterContext context = new WriterContext(); write(value, context); return context.output.toString(); } - static void write(Object value, WriterContext context) { + void write(Object value, WriterContext context) { findWriterFor(value).write(value, context); } From 72941c146d6251154d858c4ece00ff631522a8d4 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 10:30:35 -0700 Subject: [PATCH 15/24] Add writer indentation policy controls. Set the default indentation to 0. --- .../com/moandjiezana/toml/MapValueWriter.java | 2 +- .../toml/TableArrayValueWriter.java | 2 +- .../com/moandjiezana/toml/TomlWriter.java | 22 +++- .../com/moandjiezana/toml/ValueWriters.java | 4 +- .../com/moandjiezana/toml/WriterContext.java | 46 ++++++-- .../toml/WriterIndentationPolicy.java | 41 +++++++ .../moandjiezana/toml/ValueWriterTest.java | 105 +++++++++++++----- 7 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/moandjiezana/toml/WriterIndentationPolicy.java diff --git a/src/main/java/com/moandjiezana/toml/MapValueWriter.java b/src/main/java/com/moandjiezana/toml/MapValueWriter.java index 586c32b..d59bc65 100644 --- a/src/main/java/com/moandjiezana/toml/MapValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/MapValueWriter.java @@ -56,7 +56,7 @@ class MapValueWriter implements ValueWriter { ValueWriter valueWriter = WRITERS.findWriterFor(fromValue); if (valueWriter.isTable() || valueWriter == TABLE_ARRAY_VALUE_WRITER) { - valueWriter.write(fromValue, context.extend(quoteKey(key))); + valueWriter.write(fromValue, context.pushTable(quoteKey(key))); } } } diff --git a/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java index acdff1e..d92ab17 100644 --- a/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java @@ -16,7 +16,7 @@ class TableArrayValueWriter extends ArrayValueWriter { public void write(Object value, WriterContext context) { Collection values = normalize(value); - WriterContext subContext = context.extend().setIsArrayOfTable(true); + WriterContext subContext = context.pushTableFromArray(); for (Object elem : values) { WRITERS.write(elem, subContext); diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java index 651d6df..28acc70 100644 --- a/src/main/java/com/moandjiezana/toml/TomlWriter.java +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -24,6 +24,9 @@ import static com.moandjiezana.toml.ValueWriters.WRITERS; * */ public class TomlWriter { + + private WriterIndentationPolicy indentationPolicy = new WriterIndentationPolicy(); + /** * Creates a TomlWriter instance. */ @@ -36,7 +39,7 @@ public class TomlWriter { * @return a string containing the TOML representation of the given Object */ public String write(Object from) { - return WRITERS.write(from); + return WRITERS.write(from, this); } /** @@ -73,4 +76,21 @@ public class TomlWriter { 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; + } } diff --git a/src/main/java/com/moandjiezana/toml/ValueWriters.java b/src/main/java/com/moandjiezana/toml/ValueWriters.java index 4fe7695..aba1b99 100644 --- a/src/main/java/com/moandjiezana/toml/ValueWriters.java +++ b/src/main/java/com/moandjiezana/toml/ValueWriters.java @@ -23,8 +23,8 @@ class ValueWriters { return OBJECT_VALUE_WRITER; } - String write(Object value) { - WriterContext context = new WriterContext(); + String write(Object value, TomlWriter tomlWriter) { + WriterContext context = new WriterContext(tomlWriter); write(value, context); return context.output.toString(); diff --git a/src/main/java/com/moandjiezana/toml/WriterContext.java b/src/main/java/com/moandjiezana/toml/WriterContext.java index e07e828..ba6edb6 100644 --- a/src/main/java/com/moandjiezana/toml/WriterContext.java +++ b/src/main/java/com/moandjiezana/toml/WriterContext.java @@ -1,26 +1,43 @@ package com.moandjiezana.toml; +import java.util.Arrays; + class WriterContext { private String key = ""; + private String currentTableIndent = ""; + private String currentFieldIndent = ""; private boolean isArrayOfTable = false; + private final TomlWriter tomlWriter; StringBuilder output = new StringBuilder(); - WriterContext(String key, StringBuilder output) { + 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() { + WriterContext(TomlWriter tomlWriter) { + this.tomlWriter = tomlWriter; } - WriterContext extend(String newKey) { + WriterContext pushTable(String newKey) { + String newIndent = ""; + if (!key.isEmpty()) { + newIndent = growIndent(tomlWriter.getIndentationPolicy()); + } + String fullKey = key + (key.isEmpty() ? newKey : "." + newKey); - return new WriterContext(fullKey, output); + return new WriterContext(fullKey, newIndent, output, tomlWriter); } - WriterContext extend() { - return new WriterContext(key, output); + WriterContext pushTableFromArray() { + WriterContext subContext = new WriterContext(key, currentTableIndent, output, tomlWriter); + subContext.setIsArrayOfTable(true); + + return subContext; } void writeKey() { @@ -32,6 +49,8 @@ class WriterContext { output.append('\n'); } + output.append(currentTableIndent); + if (isArrayOfTable) { output.append("[[").append(key).append("]]\n"); } else { @@ -40,11 +59,24 @@ class WriterContext { } void indent() { - output.append(key.isEmpty() ? "" : " "); + 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); + } } diff --git a/src/main/java/com/moandjiezana/toml/WriterIndentationPolicy.java b/src/main/java/com/moandjiezana/toml/WriterIndentationPolicy.java new file mode 100644 index 0000000..b8587c5 --- /dev/null +++ b/src/main/java/com/moandjiezana/toml/WriterIndentationPolicy.java @@ -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; + } +} diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index 48a0a06..9545e55 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -60,21 +60,20 @@ public class ValueWriterTest { return dateString; } - @Test - public void should_write_nested_map() { - class SubChild { - int anInt; - } - class Child { - SubChild subChild; - int anInt; - } - class Parent { - Map aMap; - Child child; - boolean aBoolean; - } + class SubChild { + int anInt; + } + class Child { + SubChild subChild; + int anInt; + } + class Parent { + Map aMap; + Child child; + boolean aBoolean; + } + private Parent buildNestedMap() { Parent parent = new Parent(); parent.aMap = new LinkedHashMap(); parent.aMap.put("foo", 1); @@ -86,7 +85,29 @@ public class ValueWriterTest { parent.child.subChild.anInt = 4; parent.aBoolean = true; - String output = new TomlWriter().write(parent); + return parent; + } + + @Test + public void should_write_nested_map() { + 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" + @@ -99,6 +120,40 @@ public class ValueWriterTest { 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_primitive() { class ArrayTest { @@ -128,9 +183,9 @@ public class ValueWriterTest { String output = new TomlWriter().write(config); String expected = "[[table]]\n" + - " anInt = 1\n\n" + + "anInt = 1\n\n" + "[[table]]\n" + - " anInt = 2\n"; + "anInt = 2\n"; assertEquals(expected, output); } @@ -179,7 +234,7 @@ public class ValueWriterTest { B b = new B(); } - assertEquals("[b.c]\n anInt = 1\n", new TomlWriter().write(new A())); + assertEquals("[b.c]\nanInt = 1\n", new TomlWriter().write(new A())); } @Test @@ -221,23 +276,23 @@ public class ValueWriterTest { basket.fruit[1].variety[0].name = "plantain"; String expected = "[[fruit]]\n" + - " name = \"apple\"\n" + + "name = \"apple\"\n" + "\n" + "[fruit.physical]\n" + - " color = \"red\"\n" + - " shape = \"round\"\n" + + "color = \"red\"\n" + + "shape = \"round\"\n" + "\n" + "[[fruit.variety]]\n" + - " name = \"red delicious\"\n" + + "name = \"red delicious\"\n" + "\n" + "[[fruit.variety]]\n" + - " name = \"granny smith\"\n" + + "name = \"granny smith\"\n" + "\n" + "[[fruit]]\n" + - " name = \"banana\"\n" + + "name = \"banana\"\n" + "\n" + "[[fruit.variety]]\n" + - " name = \"plantain\"" + + "name = \"plantain\"" + "\n"; From ecbd9f048d5eff49058d576b7a58a810a50a1edd Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 11:26:49 -0700 Subject: [PATCH 16/24] Add a way to control spacing around array brackets. --- .../toml/PrimitiveArrayValueWriter.java | 12 +++++-- .../com/moandjiezana/toml/TomlWriter.java | 32 +++++++++++++++++++ .../com/moandjiezana/toml/WriterContext.java | 4 +++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java index 0f823e6..13d346c 100644 --- a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java @@ -14,7 +14,11 @@ class PrimitiveArrayValueWriter extends ArrayValueWriter { public void write(Object value, WriterContext context) { Collection values = normalize(value); - context.output.append("[ "); + context.output.append('['); + if (context.getTomlWriter().wantSpaceyArrays()) { + context.output.append(' '); + } + boolean first = true; for (Object elem : values) { if (!first) { @@ -23,7 +27,11 @@ class PrimitiveArrayValueWriter extends ArrayValueWriter { ValueWriters.WRITERS.write(elem, context); first = false; } - context.output.append(" ]"); + + if (context.getTomlWriter().wantSpaceyArrays()) { + context.output.append(' '); + } + context.output.append(']'); } private PrimitiveArrayValueWriter() {} diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java index 28acc70..abb703b 100644 --- a/src/main/java/com/moandjiezana/toml/TomlWriter.java +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -26,6 +26,7 @@ import static com.moandjiezana.toml.ValueWriters.WRITERS; public class TomlWriter { private WriterIndentationPolicy indentationPolicy = new WriterIndentationPolicy(); + private boolean wantSpaceyArraysValue = true; /** * Creates a TomlWriter instance. @@ -93,4 +94,35 @@ public class TomlWriter { this.indentationPolicy = indentationPolicy; return this; } + + /** + *

Control spacing around array brackets in the TOML output.

+ * + *

Spacey arrays = true (default):

+ * + *

+   *   a = [ 1, 2, 3 ]
+   * 
+ * + *

Spacey arrays = false:

+ * + *

+   *   a = [1, 2, 3]
+   * 
+ * + * @param value spacey arrays setting + * @return this TomlWriter instance + */ + public TomlWriter wantSpaceyArrays(boolean value) { + this.wantSpaceyArraysValue = value; + return this; + } + + /** + * Get the current array whitespace policy + * @return the current policy + */ + public boolean wantSpaceyArrays() { + return wantSpaceyArraysValue; + } } diff --git a/src/main/java/com/moandjiezana/toml/WriterContext.java b/src/main/java/com/moandjiezana/toml/WriterContext.java index ba6edb6..e7622c7 100644 --- a/src/main/java/com/moandjiezana/toml/WriterContext.java +++ b/src/main/java/com/moandjiezana/toml/WriterContext.java @@ -79,4 +79,8 @@ class WriterContext { return new String(chars); } + + public TomlWriter getTomlWriter() { + return tomlWriter; + } } From 3d90260d32aee1695e87b42390aff82ebe295d0c Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 11:52:40 -0700 Subject: [PATCH 17/24] Add methods to control date formatting and time zones. --- .../com/moandjiezana/toml/DateConverter.java | 21 ++++++-- .../com/moandjiezana/toml/TomlWriter.java | 49 +++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/DateConverter.java b/src/main/java/com/moandjiezana/toml/DateConverter.java index 07761b0..19c7448 100644 --- a/src/main/java/com/moandjiezana/toml/DateConverter.java +++ b/src/main/java/com/moandjiezana/toml/DateConverter.java @@ -1,9 +1,9 @@ package com.moandjiezana.toml; +import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,7 +12,6 @@ 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}))(.*)"); - private static final Calendar calendar = new GregorianCalendar(); @Override public boolean canConvert(String s) { @@ -91,10 +90,22 @@ class DateConverter implements ValueConverter, ValueWriter { @Override public void write(Object value, WriterContext context) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:m:ss"); + 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)); - 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)); + + 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 diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java index abb703b..8b2324a 100644 --- a/src/main/java/com/moandjiezana/toml/TomlWriter.java +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -1,8 +1,11 @@ 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; @@ -27,6 +30,8 @@ public class TomlWriter { private WriterIndentationPolicy indentationPolicy = new WriterIndentationPolicy(); private boolean wantSpaceyArraysValue = true; + private GregorianCalendar calendar = new GregorianCalendar(); + private DateFormat customDateFormat = null; /** * Creates a TomlWriter instance. @@ -125,4 +130,48 @@ public class TomlWriter { public boolean wantSpaceyArrays() { return wantSpaceyArraysValue; } + + /** + * 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; + } } From 77f7b922be95f027537160dcc8a67454e16b0852 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 13:35:34 -0700 Subject: [PATCH 18/24] Fix escaping of back slashes. --- src/main/java/com/moandjiezana/toml/StringConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/moandjiezana/toml/StringConverter.java b/src/main/java/com/moandjiezana/toml/StringConverter.java index 774e9c3..bcc2576 100644 --- a/src/main/java/com/moandjiezana/toml/StringConverter.java +++ b/src/main/java/com/moandjiezana/toml/StringConverter.java @@ -18,7 +18,7 @@ class StringConverter implements ValueConverter, ValueWriter { specialCharacterEscapes[0x0C] = "\\f"; specialCharacterEscapes[0x0D] = "\\r"; specialCharacterEscapes[0x22] = "\\\""; - specialCharacterEscapes[0x5C] = "\\"; + specialCharacterEscapes[0x5C] = "\\\\"; } @Override From 1a0d38bc72d27b2210ddd58f50adae68747b92d2 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 13:37:47 -0700 Subject: [PATCH 19/24] Improve wording for array whitespace controls --- .../toml/PrimitiveArrayValueWriter.java | 4 ++-- .../com/moandjiezana/toml/TomlWriter.java | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java index 13d346c..c4231fd 100644 --- a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java @@ -15,7 +15,7 @@ class PrimitiveArrayValueWriter extends ArrayValueWriter { Collection values = normalize(value); context.output.append('['); - if (context.getTomlWriter().wantSpaceyArrays()) { + if (!context.getTomlWriter().wantTerseArrays()) { context.output.append(' '); } @@ -28,7 +28,7 @@ class PrimitiveArrayValueWriter extends ArrayValueWriter { first = false; } - if (context.getTomlWriter().wantSpaceyArrays()) { + if (!context.getTomlWriter().wantTerseArrays()) { context.output.append(' '); } context.output.append(']'); diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java index 8b2324a..dcbb95e 100644 --- a/src/main/java/com/moandjiezana/toml/TomlWriter.java +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -29,7 +29,7 @@ import static com.moandjiezana.toml.ValueWriters.WRITERS; public class TomlWriter { private WriterIndentationPolicy indentationPolicy = new WriterIndentationPolicy(); - private boolean wantSpaceyArraysValue = true; + private boolean wantTerseArraysValue = true; private GregorianCalendar calendar = new GregorianCalendar(); private DateFormat customDateFormat = null; @@ -101,25 +101,25 @@ public class TomlWriter { } /** - *

Control spacing around array brackets in the TOML output.

+ *

Control whitespace in arrays in the TOML output.

* - *

Spacey arrays = true (default):

+ *

Terse arrays = false (default):

* *

    *   a = [ 1, 2, 3 ]
    * 
* - *

Spacey arrays = false:

+ *

Terse arrays = true:

* *

-   *   a = [1, 2, 3]
+   *   a = [1,2,3]
    * 
* - * @param value spacey arrays setting + * @param value terse arrays setting * @return this TomlWriter instance */ - public TomlWriter wantSpaceyArrays(boolean value) { - this.wantSpaceyArraysValue = value; + public TomlWriter wantTerseArrays(boolean value) { + this.wantTerseArraysValue = value; return this; } @@ -127,8 +127,8 @@ public class TomlWriter { * Get the current array whitespace policy * @return the current policy */ - public boolean wantSpaceyArrays() { - return wantSpaceyArraysValue; + public boolean wantTerseArrays() { + return wantTerseArraysValue; } /** From b550e638c7f366698cfbbd5ebe336702f3b3e89c Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 13:54:29 -0700 Subject: [PATCH 20/24] Run burntsushi tests on TomlWriter, where possible. --- .../com/moandjiezana/toml/MapValueWriter.java | 4 +- .../com/moandjiezana/toml/TomlWriter.java | 2 +- .../toml/BurntSushiValidTest.java | 145 +++++++++++++++--- .../moandjiezana/toml/ValueWriterTest.java | 2 +- .../valid/table-array-nest-modified.json | 18 +++ .../valid/table-array-nest-modified.toml | 17 ++ 6 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.json create mode 100644 src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.toml diff --git a/src/main/java/com/moandjiezana/toml/MapValueWriter.java b/src/main/java/com/moandjiezana/toml/MapValueWriter.java index d59bc65..9d6f5e4 100644 --- a/src/main/java/com/moandjiezana/toml/MapValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/MapValueWriter.java @@ -22,7 +22,7 @@ class MapValueWriter implements ValueWriter { public void write(Object value, WriterContext context) { Map from = (Map) value; - if (hasPrimitiveValues(from)) { + if (hasPrimitiveValues(from, context)) { context.writeKey(); } @@ -81,7 +81,7 @@ class MapValueWriter implements ValueWriter { return stringKey; } - private static boolean hasPrimitiveValues(Map values) { + private static boolean hasPrimitiveValues(Map values, WriterContext context) { for (Object key : values.keySet()) { Object fromValue = values.get(key); if (fromValue == null) { diff --git a/src/main/java/com/moandjiezana/toml/TomlWriter.java b/src/main/java/com/moandjiezana/toml/TomlWriter.java index dcbb95e..bddccff 100644 --- a/src/main/java/com/moandjiezana/toml/TomlWriter.java +++ b/src/main/java/com/moandjiezana/toml/TomlWriter.java @@ -29,7 +29,7 @@ import static com.moandjiezana.toml.ValueWriters.WRITERS; public class TomlWriter { private WriterIndentationPolicy indentationPolicy = new WriterIndentationPolicy(); - private boolean wantTerseArraysValue = true; + private boolean wantTerseArraysValue = false; private GregorianCalendar calendar = new GregorianCalendar(); private DateFormat customDateFormat = null; diff --git a/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java b/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java index 2539a31..735cc5d 100644 --- a/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java +++ b/src/test/java/com/moandjiezana/toml/BurntSushiValidTest.java @@ -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 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 enrichJson(JsonObject jsonObject) { + Map enriched = new LinkedHashMap(); + for (Map.Entry 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 tables = new LinkedList(); + 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 enriched = new LinkedList(); + 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)) diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index 9545e55..482fe3d 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -317,7 +317,7 @@ public class ValueWriterTest { @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)); + 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 "; diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.json b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.json new file mode 100644 index 0000000..c117afa --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.json @@ -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"}} + ] + } + ] +} diff --git a/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.toml b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.toml new file mode 100644 index 0000000..ce3cae1 --- /dev/null +++ b/src/test/resources/com/moandjiezana/toml/burntsushi/valid/table-array-nest-modified.toml @@ -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" From f3dca7d24fa12550d130f02aee54e21da1cee32a Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 14:09:20 -0700 Subject: [PATCH 21/24] Remove extraneous tests. The burntsushi tests provide adequate coverage of these. --- .../moandjiezana/toml/ValueWriterTest.java | 77 +------------------ 1 file changed, 1 insertion(+), 76 deletions(-) diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index 482fe3d..b7bb224 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -89,7 +89,7 @@ public class ValueWriterTest { } @Test - public void should_write_nested_map() { + public void should_write_nested_map_with_default_indentation_policy() { String output = new TomlWriter().write(buildNestedMap()); String expected = "aBoolean = true\n\n" + "[aMap]\n" + @@ -154,18 +154,6 @@ public class ValueWriterTest { assertEquals(expected, output); } - @Test - public void should_write_array_of_primitive() { - class ArrayTest { - int[] array = {1, 2, 3}; - } - - ArrayTest arrayTest = new ArrayTest(); - String output = new TomlWriter().write(arrayTest); - String expected = "array = [ 1, 2, 3 ]\n"; - assertEquals(expected, output); - } - @Test public void should_write_array_of_tables() { class Table { @@ -237,69 +225,6 @@ public class ValueWriterTest { assertEquals("[b.c]\nanInt = 1\n", new TomlWriter().write(new A())); } - @Test - public void should_write_nested_arrays_of_tables() { - class Physical { - String color; - String shape; - } - class Variety { - String name; - } - class Fruit { - Physical physical; - Variety[] variety; - String name; - } - class Basket { - Fruit[] fruit; - } - - Basket basket = new Basket(); - basket.fruit = new Fruit[2]; - - basket.fruit[0] = new Fruit(); - basket.fruit[0].name = "apple"; - basket.fruit[0].physical = new Physical(); - basket.fruit[0].physical.color = "red"; - basket.fruit[0].physical.shape = "round"; - basket.fruit[0].variety = new Variety[2]; - basket.fruit[0].variety[0] = new Variety(); - basket.fruit[0].variety[0].name = "red delicious"; - basket.fruit[0].variety[1] = new Variety(); - basket.fruit[0].variety[1].name = "granny smith"; - - basket.fruit[1] = new Fruit(); - basket.fruit[1].name = "banana"; - basket.fruit[1].variety = new Variety[1]; - basket.fruit[1].variety[0] = new Variety(); - basket.fruit[1].variety[0].name = "plantain"; - - String expected = "[[fruit]]\n" + - "name = \"apple\"\n" + - "\n" + - "[fruit.physical]\n" + - "color = \"red\"\n" + - "shape = \"round\"\n" + - "\n" + - "[[fruit.variety]]\n" + - "name = \"red delicious\"\n" + - "\n" + - "[[fruit.variety]]\n" + - "name = \"granny smith\"\n" + - "\n" + - "[[fruit]]\n" + - "name = \"banana\"\n" + - "\n" + - "[[fruit.variety]]\n" + - "name = \"plantain\"" + - "\n"; - - - String output = new TomlWriter().write(basket); - assertEquals(expected, output); - } - @Test public void should_write_classes_with_inheritance() { class Parent { From 7ce548e616f2b80a96226537cf573b4052e47d38 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 14:15:38 -0700 Subject: [PATCH 22/24] Note writer options in the readme. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index e2187f2..a6b8132 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,15 @@ 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 From 99c48231c2d6420f686eb9c3f2f86c938ca0d6b3 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 14:35:39 -0700 Subject: [PATCH 23/24] Refuse to write heterogeneous arrays. --- .../moandjiezana/toml/BooleanConverter.java | 5 ++++ .../com/moandjiezana/toml/DateConverter.java | 5 ++++ .../com/moandjiezana/toml/MapValueWriter.java | 1 + .../moandjiezana/toml/NumberConverter.java | 4 +++ .../toml/PrimitiveArrayValueWriter.java | 26 ++++++++++++++++--- .../moandjiezana/toml/StringConverter.java | 5 ++++ .../toml/TableArrayValueWriter.java | 5 ++++ .../com/moandjiezana/toml/WriterContext.java | 10 +++++++ .../moandjiezana/toml/ValueWriterTest.java | 12 +++++++++ 9 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/moandjiezana/toml/BooleanConverter.java b/src/main/java/com/moandjiezana/toml/BooleanConverter.java index 68bc7b0..88bf0f0 100644 --- a/src/main/java/com/moandjiezana/toml/BooleanConverter.java +++ b/src/main/java/com/moandjiezana/toml/BooleanConverter.java @@ -45,4 +45,9 @@ class BooleanConverter implements ValueConverter, ValueWriter { } private BooleanConverter() {} + + @Override + public String toString() { + return "boolean"; + } } diff --git a/src/main/java/com/moandjiezana/toml/DateConverter.java b/src/main/java/com/moandjiezana/toml/DateConverter.java index 19c7448..1e43a43 100644 --- a/src/main/java/com/moandjiezana/toml/DateConverter.java +++ b/src/main/java/com/moandjiezana/toml/DateConverter.java @@ -119,4 +119,9 @@ class DateConverter implements ValueConverter, ValueWriter { } private DateConverter() {} + + @Override + public String toString() { + return "datetime"; + } } diff --git a/src/main/java/com/moandjiezana/toml/MapValueWriter.java b/src/main/java/com/moandjiezana/toml/MapValueWriter.java index 9d6f5e4..8bdceaf 100644 --- a/src/main/java/com/moandjiezana/toml/MapValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/MapValueWriter.java @@ -41,6 +41,7 @@ class MapValueWriter implements ValueWriter { 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'); diff --git a/src/main/java/com/moandjiezana/toml/NumberConverter.java b/src/main/java/com/moandjiezana/toml/NumberConverter.java index 74a2edf..efea438 100644 --- a/src/main/java/com/moandjiezana/toml/NumberConverter.java +++ b/src/main/java/com/moandjiezana/toml/NumberConverter.java @@ -103,4 +103,8 @@ class NumberConverter implements ValueConverter, ValueWriter { return false; } + @Override + public String toString() { + return "number"; + } } diff --git a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java index c4231fd..81ad1f4 100644 --- a/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/PrimitiveArrayValueWriter.java @@ -2,6 +2,8 @@ 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(); @@ -20,12 +22,25 @@ class PrimitiveArrayValueWriter extends ArrayValueWriter { } boolean first = true; + ValueWriter firstWriter = null; + for (Object elem : values) { - if (!first) { + 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(", "); } - ValueWriters.WRITERS.write(elem, context); - first = false; + + WRITERS.write(elem, context); } if (!context.getTomlWriter().wantTerseArrays()) { @@ -35,4 +50,9 @@ class PrimitiveArrayValueWriter extends ArrayValueWriter { } private PrimitiveArrayValueWriter() {} + + @Override + public String toString() { + return "primitive-array"; + } } diff --git a/src/main/java/com/moandjiezana/toml/StringConverter.java b/src/main/java/com/moandjiezana/toml/StringConverter.java index bcc2576..409aafd 100644 --- a/src/main/java/com/moandjiezana/toml/StringConverter.java +++ b/src/main/java/com/moandjiezana/toml/StringConverter.java @@ -129,4 +129,9 @@ class StringConverter implements ValueConverter, ValueWriter { } private StringConverter() {} + + @Override + public String toString() { + return "string"; + } } diff --git a/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java index d92ab17..c8b3499 100644 --- a/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java +++ b/src/main/java/com/moandjiezana/toml/TableArrayValueWriter.java @@ -24,4 +24,9 @@ class TableArrayValueWriter extends ArrayValueWriter { } private TableArrayValueWriter() {} + + @Override + public String toString() { + return "table-array"; + } } diff --git a/src/main/java/com/moandjiezana/toml/WriterContext.java b/src/main/java/com/moandjiezana/toml/WriterContext.java index e7622c7..61095f9 100644 --- a/src/main/java/com/moandjiezana/toml/WriterContext.java +++ b/src/main/java/com/moandjiezana/toml/WriterContext.java @@ -6,6 +6,7 @@ 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(); @@ -83,4 +84,13 @@ class WriterContext { public TomlWriter getTomlWriter() { return tomlWriter; } + + public WriterContext setArrayKey(String arrayKey) { + this.arrayKey = arrayKey; + return this; + } + + public String getContextPath() { + return key.isEmpty() ? arrayKey : key + "." + arrayKey; + } } diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index b7bb224..2361d3e 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -210,6 +210,18 @@ public class ValueWriterTest { 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 { From a879385f4c5a62b2d14f1e3ef5c671fbf59c5240 Mon Sep 17 00:00:00 2001 From: Jonathan Wood Date: Sun, 28 Jun 2015 20:18:27 -0700 Subject: [PATCH 24/24] Tweak test for openjdk-6. --- .../moandjiezana/toml/ValueWriterTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java index 2361d3e..4ec21d1 100644 --- a/src/test/java/com/moandjiezana/toml/ValueWriterTest.java +++ b/src/test/java/com/moandjiezana/toml/ValueWriterTest.java @@ -237,18 +237,18 @@ public class ValueWriterTest { 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() { - class Parent { - protected int anInt = 2; - } - class Child extends Parent { - boolean aBoolean = true; - } - - Child child = new Child(); + Impl impl = new Impl(); String expected = "aBoolean = true\nanInt = 2\n"; - assertEquals(expected, new TomlWriter().write(child)); + assertEquals(expected, new TomlWriter().write(impl)); } @Test @@ -316,4 +316,4 @@ public class ValueWriterTest { return w.toString(); } -} \ No newline at end of file +}