diff --git a/datastructures-api/src/main/java/org/xbib/datastructures/api/Builder.java b/datastructures-api/src/main/java/org/xbib/datastructures/api/Builder.java index 970e089..9eac9e6 100644 --- a/datastructures-api/src/main/java/org/xbib/datastructures/api/Builder.java +++ b/datastructures-api/src/main/java/org/xbib/datastructures/api/Builder.java @@ -16,7 +16,7 @@ public interface Builder { Builder buildMap(Map map) throws IOException; - Builder buildCollection(Collection collection) throws IOException; + Builder buildCollection(Collection collection) throws IOException; Builder buildKey(CharSequence key) throws IOException; @@ -24,7 +24,13 @@ public interface Builder { Builder buildNull() throws IOException; - default Builder buildIfNotNull(CharSequence key, Object value) throws IOException { + default Builder field(CharSequence key, Object value) throws IOException { + buildKey(key); + buildValue(value); + return this; + } + + default Builder fieldIfNotNull(CharSequence key, Object value) throws IOException { if (value != null){ buildKey(key); buildValue(value); @@ -32,5 +38,25 @@ public interface Builder { return this; } + default Builder collection(CharSequence key, Collection value) throws IOException { + buildKey(key); + buildValue(value); + return this; + } + + default Builder beginMap(CharSequence key) throws IOException { + buildKey(key); + beginMap(); + return this; + } + + default Builder beginCollection(CharSequence key) throws IOException { + buildKey(key); + beginCollection(); + return this; + } + + Builder copy(Builder builder) throws IOException; + String build(); } diff --git a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java index b28c122..74f0141 100644 --- a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java +++ b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java @@ -1,13 +1,23 @@ package org.xbib.datastructures.json.tiny; -import org.xbib.datastructures.api.*; - +import org.xbib.datastructures.api.Builder; +import org.xbib.datastructures.api.ByteSizeValue; +import org.xbib.datastructures.api.DataStructure; +import org.xbib.datastructures.api.Generator; +import org.xbib.datastructures.api.Node; +import org.xbib.datastructures.api.Parser; +import org.xbib.datastructures.api.TimeValue; +import java.io.IOException; +import java.io.StringReader; import java.io.StringWriter; import java.time.Instant; +import java.util.Map; import java.util.function.Consumer; public class Json implements DataStructure { + private static final Json INSTANCE = new Json(); + private final char separator; private Node root; @@ -25,6 +35,15 @@ public class Json implements DataStructure { this.separator = separator; } + @SuppressWarnings("unchecked") + public static Map toMap(String yaml) throws IOException { + return (Map) INSTANCE.createParser().parse(new StringReader(yaml)).get(); + } + + public static String toString(Map map) throws IOException { + return INSTANCE.createBuilder().buildMap(map).build(); + } + @Override public Parser createParser() { return new StreamParser(); diff --git a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java index aac0550..0d31dfb 100644 --- a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java +++ b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java @@ -3,11 +3,8 @@ package org.xbib.datastructures.json.tiny; import org.xbib.datastructures.api.Builder; import org.xbib.datastructures.api.ByteSizeValue; import org.xbib.datastructures.api.TimeValue; - import java.io.IOException; -import java.io.StringWriter; import java.io.UncheckedIOException; -import java.io.Writer; import java.time.Instant; import java.util.Collection; import java.util.Map; @@ -15,31 +12,31 @@ import java.util.Objects; public class JsonBuilder implements Builder { - private final Writer writer; + private final Appendable appendable; private State state; public JsonBuilder() { - this(new StringWriter()); + this(new StringBuilder()); } - public JsonBuilder(Writer writer) { - this.writer = writer; - this.state = new State(null, 0, Structure.MAP, true); + public JsonBuilder(Appendable appendable) { + this.appendable = appendable; + this.state = new State(null, 0, Structure.DOCSTART, true); } public static JsonBuilder builder() { return new JsonBuilder(); } - public static JsonBuilder builder(Writer writer) { - return new JsonBuilder(writer); + public static JsonBuilder builder(Appendable appendable) { + return new JsonBuilder(appendable); } @Override public Builder beginCollection() throws IOException { this.state = new State(state, state.level + 1, Structure.COLLECTION, true); - writer.write('['); + appendable.append('['); return this; } @@ -48,7 +45,7 @@ public class JsonBuilder implements Builder { if (state.structure != Structure.COLLECTION) { throw new JsonException("no array to close"); } - writer.write(']'); + appendable.append(']'); this.state = state != null ? state.parent : null; return this; } @@ -56,25 +53,30 @@ public class JsonBuilder implements Builder { @Override public Builder beginMap() throws IOException { this.state = new State(state, state.level + 1, Structure.MAP, true); - writer.write('{'); + appendable.append('{'); return this; } @Override public Builder endMap() throws IOException { - if (state.structure != Structure.MAP) { + if (state.structure != Structure.MAP && state.structure != Structure.KEY) { throw new JsonException("no object to close"); } - writer.write('}'); + appendable.append('}'); this.state = state != null ? state.parent : null; return this; } - @Override public Builder buildMap(Map map) throws IOException { Objects.requireNonNull(map); - beginMap(); + if (state.structure == Structure.COLLECTION) { + beginArrayValue(map); + } + boolean wrap = state.structure != Structure.MAP; + if (wrap) { + beginMap(); + } map.forEach((k, v) -> { try { buildKey(k); @@ -83,12 +85,17 @@ public class JsonBuilder implements Builder { throw new UncheckedIOException(e); } }); - endMap(); + if (wrap) { + endMap(); + } + if (state.structure == Structure.COLLECTION) { + endArrayValue(map); + } return this; } @Override - public Builder buildCollection(Collection collection) throws IOException { + public Builder buildCollection(Collection collection) throws IOException { Objects.requireNonNull(collection); beginCollection(); collection.forEach(v -> { @@ -105,7 +112,7 @@ public class JsonBuilder implements Builder { @SuppressWarnings("unchecked") @Override public Builder buildValue(Object object) throws IOException { - if (state.structure == Structure.MAP) { + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { beginValue(object); } else if (state.structure == Structure.COLLECTION) { beginArrayValue(object); @@ -142,7 +149,7 @@ public class JsonBuilder implements Builder { } else { throw new IllegalArgumentException("unable to write object class " + object.getClass()); } - if (state.structure == Structure.MAP) { + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { endValue(object); } else if (state.structure == Structure.COLLECTION) { endArrayValue(object); @@ -152,42 +159,68 @@ public class JsonBuilder implements Builder { @Override public Builder buildKey(CharSequence string) throws IOException { - if (state.structure == Structure.MAP) { + if (state.structure == Structure.COLLECTION) { + beginArrayValue(string); + } + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { beginKey(string != null ? string.toString() : null); } buildString(string, true); - if (state.structure == Structure.MAP) { + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { endKey(string != null ? string.toString() : null); } + state.structure = Structure.KEY; return this; } @Override public Builder buildNull() throws IOException { - if (state.structure == Structure.MAP) { + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { beginValue(null); } else if (state.structure == Structure.COLLECTION) { beginArrayValue(null); } buildString("null", false); + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { + endValue(null); + } else if (state.structure == Structure.COLLECTION) { + endArrayValue(null); + } + return this; + } + + @Override + public Builder copy(Builder builder) throws IOException { + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { + beginValue(null); + } else if (state.structure == Structure.COLLECTION) { + beginArrayValue(null); + } + appendable.append(builder.build()); + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { + endValue(null); + } + if (state.structure == Structure.COLLECTION) { + endArrayValue(null); + } return this; } @Override public String build() { - return writer.toString(); + return appendable.toString(); } private void beginKey(String k) throws IOException { if (state.first) { state.first = false; } else { - writer.write(","); + appendable.append(","); } } private void endKey(String k) throws IOException { - writer.write(":"); + appendable.append(":"); } private void beginValue(Object v) { @@ -200,7 +233,7 @@ public class JsonBuilder implements Builder { if (state.first) { state.first = false; } else { - writer.write(","); + appendable.append(","); } } @@ -220,10 +253,10 @@ public class JsonBuilder implements Builder { } private void buildString(CharSequence string, boolean escape) throws IOException { - writer.write(escape ? escapeString(string) : string.toString()); + appendable.append(escape ? escapeString(string) : string); } - private String escapeString(CharSequence string) { + private CharSequence escapeString(CharSequence string) { StringBuilder sb = new StringBuilder(); sb.append('"'); int start = 0; @@ -232,17 +265,17 @@ public class JsonBuilder implements Builder { char c = string.charAt(i); if (c == '"' || c < 32 || c >= 127 || c == '\\') { if (start < i) { - sb.append(string.toString(), start, i - start); + sb.append(string, start, i - start); } start = i; sb.append(escapeCharacter(c)); } } if (start < l) { - sb.append(string.toString(), start, l - start); + sb.append(string, start, l - start); } sb.append('"'); - return sb.toString(); + return sb; } private static String escapeCharacter(char c) { @@ -264,7 +297,7 @@ public class JsonBuilder implements Builder { return "\\u0000".substring(0, 6 - hex.length()) + hex; } - private enum Structure { MAP, COLLECTION }; + private enum Structure { DOCSTART, MAP, KEY, COLLECTION }; private static class State { State parent; diff --git a/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java b/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java index 1cdc9ed..79c1a33 100644 --- a/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java +++ b/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java @@ -6,6 +6,7 @@ import org.xbib.datastructures.json.tiny.StreamParser; import java.io.IOException; import java.io.StringReader; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -108,4 +109,108 @@ public class JsonBuilderTest { jsonBuilder.buildMap(Map.of("a", "b")); assertEquals("{\"a\":\"b\"}", jsonBuilder.build()); } + + @Test + public void testKeyValue() throws Exception { + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginMap(); + jsonBuilder.buildKey("a"); + jsonBuilder.buildValue("b"); + // test comma separation + jsonBuilder.buildKey("c"); + jsonBuilder.buildValue("d"); + jsonBuilder.endMap(); + assertEquals("{\"a\":\"b\",\"c\":\"d\"}", jsonBuilder.build()); + } + + @Test + public void testMapBuild() throws Exception { + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginMap(); + jsonBuilder.buildKey("map"); + // buildMap is wrapped with '{' and '}' + jsonBuilder.buildMap(Map.of("a", "b")); + jsonBuilder.endMap(); + assertEquals("{\"map\":{\"a\":\"b\"}}", jsonBuilder.build()); + } + + @Test + public void testBeginMapBuild() throws Exception { + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginMap(); + jsonBuilder.beginMap("map"); + // buildMap is not wrapped with '{' and '}' + jsonBuilder.buildMap(Map.of("a", "b")); + jsonBuilder.endMap(); + jsonBuilder.endMap(); + assertEquals("{\"map\":{\"a\":\"b\"}}", jsonBuilder.build()); + } + + @Test + public void testMapOfCollections() throws Exception { + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginMap(); + jsonBuilder.beginMap("map"); + jsonBuilder.collection("a", Arrays.asList("b", "c")); + // test comma separation + jsonBuilder.collection("d", Arrays.asList("e", "f")); + jsonBuilder.endMap(); + jsonBuilder.endMap(); + assertEquals("{\"map\":{\"a\":[\"b\",\"c\"],\"d\":[\"e\",\"f\"]}}", jsonBuilder.build()); + } + + @Test + public void testMapOfEmptyCollections() throws Exception { + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginMap(); + jsonBuilder.beginMap("map"); + jsonBuilder.collection("a", List.of()); + // test comma separation + jsonBuilder.collection("b", List.of()); + jsonBuilder.endMap(); + jsonBuilder.endMap(); + assertEquals("{\"map\":{\"a\":[],\"b\":[]}}", jsonBuilder.build()); + } + + @Test + public void testCollectionOfMaps() throws Exception { + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginMap(); + jsonBuilder.beginCollection("collection"); + jsonBuilder.buildMap(Map.of("a", "b")); + // test comma separation + jsonBuilder.buildMap(Map.of("c", "d")); + jsonBuilder.endCollection(); + jsonBuilder.endMap(); + assertEquals("{\"collection\":[{\"a\":\"b\"},{\"c\":\"d\"}]}", jsonBuilder.build()); + } + + @Test + public void testCollectionOfEmptyMaps() throws Exception { + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginMap(); + jsonBuilder.beginCollection("collection"); + jsonBuilder.buildMap(Map.of()); + // test comma separation + jsonBuilder.buildMap(Map.of()); + jsonBuilder.endCollection(); + jsonBuilder.endMap(); + assertEquals("{\"collection\":[{},{}]}", jsonBuilder.build()); + } + + @Test + public void testCopy() throws Exception { + JsonBuilder jsonBuilder1 = new JsonBuilder(); + jsonBuilder1.buildMap(Map.of("a", "b")); + JsonBuilder jsonBuilder2 = new JsonBuilder(); + jsonBuilder2.buildMap(Map.of("c", "d")); + JsonBuilder jsonBuilder = new JsonBuilder(); + jsonBuilder.beginCollection(); + jsonBuilder.copy(jsonBuilder1); + // test comma separation + jsonBuilder.copy(jsonBuilder2); + jsonBuilder.endCollection(); + assertEquals("[{\"a\":\"b\"},{\"c\":\"d\"}]", jsonBuilder.build()); + } + } diff --git a/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/Yaml.java b/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/Yaml.java index c62ad8e..2692fa7 100644 --- a/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/Yaml.java +++ b/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/Yaml.java @@ -3,12 +3,17 @@ package org.xbib.datastructures.yaml.tiny; import org.xbib.datastructures.api.*; import org.xbib.datastructures.api.Builder; +import java.io.IOException; +import java.io.StringReader; import java.io.StringWriter; import java.time.Instant; +import java.util.Map; import java.util.function.Consumer; public class Yaml implements DataStructure { + private static final Yaml INSTANCE = new Yaml(); +; private final char separator; private Node root; @@ -26,6 +31,15 @@ public class Yaml implements DataStructure { this.separator = separator; } + @SuppressWarnings("unchecked") + public static Map toMap(String yaml) throws IOException { + return (Map) INSTANCE.createParser().parse(new StringReader(yaml)).get(); + } + + public static String toString(Map map) throws IOException { + return INSTANCE.createBuilder().buildMap(map).build(); + } + @Override public Parser createParser() { return new YamlParser(); diff --git a/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/YamlBuilder.java b/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/YamlBuilder.java index b5d97dd..cc6eefe 100644 --- a/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/YamlBuilder.java +++ b/datastructures-yaml-tiny/src/main/java/org/xbib/datastructures/yaml/tiny/YamlBuilder.java @@ -4,9 +4,7 @@ import org.xbib.datastructures.api.Builder; import org.xbib.datastructures.api.ByteSizeValue; import org.xbib.datastructures.api.TimeValue; import java.io.IOException; -import java.io.StringWriter; import java.io.UncheckedIOException; -import java.io.Writer; import java.time.Instant; import java.util.Collection; import java.util.Map; @@ -14,22 +12,22 @@ import java.util.Objects; public class YamlBuilder implements Builder { - public final Writer writer; + public final Appendable appendable; private final int indent; private State state; public YamlBuilder() { - this(new StringWriter()); + this(new StringBuilder()); } - public YamlBuilder(Writer writer) { - this(writer, 2); + public YamlBuilder(Appendable appendable) { + this(appendable, 2); } - public YamlBuilder(Writer writer, int indent) { - this.writer = writer; + public YamlBuilder(Appendable appendable, int indent) { + this.appendable = appendable; this.indent = indent; this.state = new State(null, 0, Structure.MAP, false); } @@ -38,8 +36,8 @@ public class YamlBuilder implements Builder { return new YamlBuilder(); } - public static YamlBuilder builder(Writer writer) { - return new YamlBuilder(writer); + public static YamlBuilder builder(Appendable appendable) { + return new YamlBuilder(appendable); } @Override @@ -89,7 +87,7 @@ public class YamlBuilder implements Builder { } @Override - public Builder buildCollection(Collection collection) { + public Builder buildCollection(Collection collection) { Objects.requireNonNull(collection); beginCollection(); collection.forEach(v -> { @@ -170,9 +168,16 @@ public class YamlBuilder implements Builder { return this; } + @Override + public Builder copy(Builder builder) throws IOException { + // TODO: no correct indent yet for copied yaml + buildValue(builder.build()); + return this; + } + @Override public String build() { - return writer.toString(); + return appendable.toString(); } private void buildNumber(Number number) throws IOException { @@ -188,7 +193,8 @@ public class YamlBuilder implements Builder { } private void buildString(CharSequence string, boolean escape) throws IOException { - String value = escape ? escapeString(string) : string.toString(); + CharSequence charSequence = escape ? escapeString(string) : string; + String value = charSequence.toString(); if (!((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) && value.matches(".*[?\\-#:>|$%&{}\\[\\]]+.*|[ ]+")) { if (value.contains("\"")) { @@ -197,7 +203,7 @@ public class YamlBuilder implements Builder { value = "\"" + value + "\""; } } - writer.write(value); + appendable.append(value); } private void beginKey(String k) throws IOException { @@ -205,11 +211,11 @@ public class YamlBuilder implements Builder { state.parent.item = false; return; } - writer.write(" ".repeat((state.level - 1) * indent)); + appendable.append(" ".repeat((state.level - 1) * indent)); } private void endKey(String k) throws IOException { - writer.write(": "); + appendable.append(": "); } private void beginValue(Object v) throws IOException { @@ -236,8 +242,8 @@ public class YamlBuilder implements Builder { if (v instanceof Collection) { return; } - writer.write(" ".repeat((state.level - 1) * indent)); - writer.write("- "); + appendable.append(" ".repeat((state.level - 1) * indent)); + appendable.append("- "); state.item = true; } @@ -252,10 +258,10 @@ public class YamlBuilder implements Builder { } private void writeLn() throws IOException{ - writer.write(System.lineSeparator()); + appendable.append(System.lineSeparator()); } - private String escapeString(CharSequence string) { + private CharSequence escapeString(CharSequence string) { StringBuilder sb = new StringBuilder(); int start = 0; int l = string.length(); @@ -263,16 +269,16 @@ public class YamlBuilder implements Builder { char c = string.charAt(i); if (c == '"' || c < 32 || c >= 127 || c == '\\') { if (start < i) { - sb.append(string.toString(), start, i - start); + sb.append(string, start, i - start); } start = i; sb.append(escapeCharacter(c)); } } if (start < l) { - sb.append(string.toString(), start, l - start); + sb.append(string, start, l - start); } - return sb.toString(); + return sb; } private static String escapeCharacter(char c) {