From 5ef243990e46dafd25581a8a0d00a89f7b9d7e9e Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 31 Jan 2017 19:27:03 +0000 Subject: [PATCH 1/5] Ensure multiline output/strings end in NL --- jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java index 633507df591..78fa7a406cd 100644 --- a/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java @@ -159,13 +159,17 @@ public static void write(OutputStream output, JsonValue jValue) { /** Write out a JSON value - pass a JSON Object to get legal exchangeable JSON */ public static void write(IndentedWriter output, JsonValue jValue) { + int rowStart = output.getRow(); JsonWriter w = new JsonWriter(output) ; w.startOutput() ; jValue.visit(w) ; w.finishOutput() ; + // If multiline, make sure we end on a new line. + if ( ! output.inFlatMode() && output.getRow() > rowStart ) + output.ensureStartOfLine(); } - /** Write out a JSON value to - pass a JSON Object to get legal exchangeable JSON */ + /** Write out a JSON value - pass a JSON Object to get legal exchangeable JSON */ public static void write(JsonValue jValue) { write(IndentedWriter.stdout, jValue) ; } From 1085c89c934e2a583363438635f5ec14958168db Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 31 Jan 2017 19:27:48 +0000 Subject: [PATCH 2/5] Add pair(,) --- .../apache/jena/atlas/json/JsonBuilder.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java index d5f1ff1882c..7d2713f497b 100644 --- a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java @@ -116,6 +116,42 @@ public JsonBuilder finishArray() { return this ; } + public JsonBuilder pair(String key, JsonValue value) { + key(key); + value(value); + return this ; + } + + public JsonBuilder pair(String key, boolean value) { + key(key); + value(value); + return this ; + } + + public JsonBuilder pair(String key, BigDecimal value) { + key(key); + value(value); + return this ; + } + + public JsonBuilder pair(String key, double value) { + key(key); + value(value); + return this ; + } + + public JsonBuilder pair(String key, long value) { + key(key); + value(value); + return this ; + } + + public JsonBuilder pair(String key, String value) { + key(key); + value(value); + return this ; + } + public JsonBuilder key(String key) { State state = stack.peek() ; if ( state != State.OBJECT ) From 470bfc38d94547507a0e2038050a7f224cff85df Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 31 Jan 2017 19:28:33 +0000 Subject: [PATCH 3/5] return 'this' for chaining style --- .../apache/jena/atlas/json/io/JSWriter.java | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/io/JSWriter.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/io/JSWriter.java index 10f1da88acc..521cfbc066e 100644 --- a/jena-arq/src/main/java/org/apache/jena/atlas/json/io/JSWriter.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/io/JSWriter.java @@ -70,13 +70,14 @@ public void startOutput() {} // (object or array). Deque stack = new ArrayDeque<>() ; - public void startObject() { + public JSWriter startObject() { startCompound() ; out.print(ObjectStart) ; out.incIndent() ; + return this; } - public void finishObject() { + public JSWriter finishObject() { out.decIndent() ; if ( isFirst() ) out.print(ObjectFinish) ; @@ -85,9 +86,10 @@ public void finishObject() { out.println(ObjectFinish) ; } finishCompound() ; + return this; } - public void key(String key) { + public JSWriter key(String key) { if ( isFirst() ) { out.println() ; setNotFirst() ; @@ -96,69 +98,80 @@ public void key(String key) { value(key) ; out.print(ObjectPairSep) ; // Ready to start the pair value. + return this; } // "Pair" is the name used in the JSON spec. - public void pair(String key, String value) { + public JSWriter pair(String key, String value) { key(key) ; value(value) ; + return this; } - public void pair(String key, boolean val) { + public JSWriter pair(String key, boolean val) { key(key) ; value(val) ; + return this; } - public void pair(String key, long val) { + public JSWriter pair(String key, long val) { key(key) ; value(val) ; + return this; } - public void pair(String key, Number val) { + public JSWriter pair(String key, Number val) { key(key) ; value(val) ; + return this; } - public void startArray() { + public JSWriter startArray() { startCompound() ; out.print(ArrayStart) ; - // Messy with objects out.incIndent() ; + return this; } - public void finishArray() { + public JSWriter finishArray() { // out.decIndent() ; out.print(ArrayFinish) ; // Leave on same line. finishCompound() ; + return this; } - public void arrayElement(String str) { + public JSWriter arrayElement(String str) { arrayElementProcess() ; value(str) ; + return this; } - private void arrayElementProcess() { + private JSWriter arrayElementProcess() { if ( isFirst() ) setNotFirst() ; else out.print(ArraySep) ; + return this; } - public void arrayElement(boolean b) { + public JSWriter arrayElement(boolean b) { arrayElementProcess() ; value(b) ; + return this; } - public void arrayElement(long integer) { + public JSWriter arrayElement(long integer) { arrayElementProcess() ; value(integer) ; + return this; } /** * Useful if you are manually creating arrays and so need to print array * separators yourself */ - public void arraySep() { + public JSWriter arraySep() { out.print(ArraySep) ; + return this; } public static String outputQuotedString(String string) { From 92111800a4dc37ab4d4cb3bbf0789fa679bb521a Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 31 Jan 2017 19:29:14 +0000 Subject: [PATCH 4/5] Remove blank line --- .../org/apache/jena/atlas/json/io/parser/JSONParserBase.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/io/parser/JSONParserBase.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/io/parser/JSONParserBase.java index 6d5ade13767..4208c2ef9c7 100644 --- a/jena-arq/src/main/java/org/apache/jena/atlas/json/io/parser/JSONParserBase.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/io/parser/JSONParserBase.java @@ -25,7 +25,6 @@ import org.apache.jena.riot.tokens.TokenType ; import org.apache.jena.riot.tokens.Tokenizer ; - class JSONParserBase { protected boolean VERBOSE = true ; From e2f53c31174ec79461049a9b6830d22c94859be7 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Thu, 2 Feb 2017 09:49:37 +0000 Subject: [PATCH 5/5] JSON.copy, JSON.buildObject --- .../java/org/apache/jena/atlas/json/JSON.java | 31 +++++++++- .../apache/jena/atlas/json/JsonBuilder.java | 60 ++++++++++++++++++- .../org/apache/jena/atlas/json/TS_JSON.java | 1 + .../org/apache/jena/atlas/json/TestJson.java | 3 +- .../apache/jena/atlas/json/TestJsonAPI.java | 46 ++++++++++++++ .../jena/atlas/json/TestJsonBuilder.java | 47 +++++++++++++-- .../apache/jena/atlas/json/TestJsonExt.java | 6 +- 7 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonAPI.java diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java index 78fa7a406cd..2ddc9126cff 100644 --- a/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/JSON.java @@ -19,6 +19,7 @@ package org.apache.jena.atlas.json; import java.io.* ; +import java.util.function.Consumer; import org.apache.jena.atlas.io.IO ; import org.apache.jena.atlas.io.IndentedLineBuffer ; @@ -84,9 +85,8 @@ public static JsonValue readAny(String filename) { IO.exception("IOException: " + filename, ex) ; return null ; } - } - + // Hide the reader versions - not encouraged due to charset problems. private static JsonObject _parse(Reader r) { @@ -173,4 +173,31 @@ public static void write(IndentedWriter output, JsonValue jValue) { public static void write(JsonValue jValue) { write(IndentedWriter.stdout, jValue) ; } + + // General functions for working with JSON + + /** Create a safe copy of a {@link JsonValue}. + *

+ * If the JsonValue is a structure (object or array), copy the structure recursively. + *

+ * If the JsonValue is a primitive (string, number, boolean or null), + * it is immutable so return the same object. + */ + public static JsonValue copy(JsonValue arg) { + return JsonBuilder.copy(arg); + } + + /** Build a JsonObject. The outer object is created and then the {@code setup} function called to fill in the contents. + *

+     * buildObject(builder->{
+     *     builder.pair("key", 1234);
+     * });
+     * 
+ * + * @param setup + * @return JsonObject + */ + public static JsonObject buildObject(Consumer setup) { + return JsonBuilder.buildObject(setup); + } } diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java index 7d2713f497b..8183a9cacfa 100644 --- a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonBuilder.java @@ -22,6 +22,7 @@ import java.util.ArrayDeque ; import java.util.Deque ; import java.util.Objects; +import java.util.function.Consumer; import org.apache.jena.atlas.logging.Log ; @@ -47,8 +48,65 @@ private static enum State { public static JsonBuilder create() { return new JsonBuilder() ; } + /** Create a builder from a {@link JsonValue}. + *

If the argument is an object or array, use it to initailize the builder. + *

If the argument is a JSON primitive (string, number, boolean or null), + *

Otherwise thrown {@link IllegalArgumentException}. + */ + public static JsonBuilder createFrom(JsonValue arg) { + if ( arg.isObject() ) { + JsonObject obj = arg.getAsObject() ; + JsonBuilder builder = JsonBuilder.create() ; + builder.startObject() ; + obj.forEach((k,v) -> builder.key(k).value(copy(v))) ; + builder.finishObject() ; + return builder ; + } + if ( arg.isArray() ) { + JsonArray array = arg.getAsArray() ; + JsonBuilder builder = JsonBuilder.create() ; + builder.startArray() ; + array.forEach((a)->builder.value(copy(a))) ; + builder.finishArray() ; + return builder ; + } + throw new IllegalArgumentException("Not a JSON object or JSON array; "+arg); + } + + + /** Create a safe copy of a {@link JsonValue}. + *

+ * If the JsonValue is a structure (object or array), copy the structure recursively. + *

+ * If the JsonValue is a primitive (string, number, boolean or null), + * it is immutable so return the same object. + */ + public static JsonValue copy(JsonValue arg) { + if ( ! arg.isArray() && ! arg.isObject() ) + return arg; + return createFrom(arg).build(); + } + + // An unlikely-to-be-used label to help check object alignment + private static String LABEL = "%|%object%|%" ; + + /** Build a JsonObject. The outer object is created and then the {@code setup} function called to fill in the contents. + *

+     * buildObject(builder->{
+     *     builder.pair("key", 1234);
+     * });
+     * 
+ * + * @param setup + * @return JsonObject + */ + public static JsonObject buildObject(Consumer setup) { + JsonBuilder b = JsonBuilder.create().startObject(LABEL) ; + setup.accept(b); + return b.finishObject(LABEL).build().getAsObject() ; + } + public JsonBuilder() { - } public JsonValue build() { diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/json/TS_JSON.java b/jena-arq/src/test/java/org/apache/jena/atlas/json/TS_JSON.java index c2f2cb4042e..4338d37e3ce 100644 --- a/jena-arq/src/test/java/org/apache/jena/atlas/json/TS_JSON.java +++ b/jena-arq/src/test/java/org/apache/jena/atlas/json/TS_JSON.java @@ -27,6 +27,7 @@ , TestJsonExt.class , TestJsonWriter.class , TestJsonBuilder.class + , TestJsonAPI.class }) public class TS_JSON {} diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJson.java b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJson.java index 3b6398d94af..31169dd2b36 100644 --- a/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJson.java +++ b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJson.java @@ -19,10 +19,9 @@ package org.apache.jena.atlas.json; import static org.apache.jena.atlas.json.LibJsonTest.read ; -import org.apache.jena.atlas.junit.BaseTest ; import org.junit.Test ; -public class TestJson extends BaseTest +public class TestJson { @Test public void js_value_1() { diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonAPI.java b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonAPI.java new file mode 100644 index 00000000000..36b70acd8c7 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonAPI.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.atlas.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; + +import org.junit.Test ; + + +public class TestJsonAPI +{ + @Test public void jsonAPI_01() { + JsonObject obj = JSON.parse("{ key1: 'str1' , key2: [ 1 , 2 ] }") ; + assertEquals(2, obj.size()); + } + + @Test public void jsonAPI_02() { + JsonObject obj = JSON.parse("{ key1: 'str1' , key2: [ 1 , 2 ] }") ; + JsonObject obj2 = (JsonObject)JSON.copy(obj); + assertNotSame(obj, obj2); + assertEquals(obj, obj2); + } + + @Test public void jsonAPI_03() { + JsonValue jv1 = JSON.parseAny("2") ; + JsonValue jv2 = JSON.copy(jv1); + assertSame(jv1, jv2); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonBuilder.java b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonBuilder.java index 505e0fab09a..a43fcf38bf7 100644 --- a/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonBuilder.java +++ b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonBuilder.java @@ -22,10 +22,6 @@ import org.junit.Test ; public class TestJsonBuilder extends BaseTest{ - // The Jena JSON parser is more livberal than strict JSON to make embedding easier. - // * Keys do not need quotes - // * Strings can use '' - @Test public void jsonBuild01() { JsonValue x = JSON.parseAny("{ }") ; JsonBuilder builder = new JsonBuilder() ; @@ -79,8 +75,32 @@ public class TestJsonBuilder extends BaseTest{ JsonValue v = builder.build() ; assertEquals(x,v) ; } - + @Test public void jsonBuild06() { + JsonValue x = JSON.parseAny("{ a: 'B'}") ; + JsonBuilder builder = new JsonBuilder() ; + builder.startObject().pair("a", "B").finishObject() ; + JsonValue v = builder.build() ; + assertEquals(x,v) ; + } + + @Test public void jsonBuild07() { + JsonValue x = JSON.parseAny("{ a: 123}") ; + JsonBuilder builder = new JsonBuilder() ; + builder.startObject().pair("a", 123).finishObject() ; + JsonValue v = builder.build() ; + assertEquals(x,v) ; + } + + @Test public void jsonBuild08() { + JsonValue x = JSON.parseAny("{ a: true}") ; + JsonBuilder builder = new JsonBuilder() ; + JsonValue jv = new JsonBoolean(true); + builder.startObject().pair("a", jv).finishObject() ; + JsonValue v = builder.build() ; + assertEquals(x,v) ; + } + @Test(expected=JsonException.class) public void jsonBuildErr00() { JsonBuilder builder = new JsonBuilder() ; @@ -107,5 +127,20 @@ public void jsonBuildErr03() { builder.startObject("A") ; builder.finishObject("B") ; } - + + @Test + public void jsonBuildObject_01() { + JsonObject obj = JsonBuilder.buildObject(b->{}); + assertTrue(obj.entrySet().isEmpty()); + } + + @Test + public void jsonBuildObject_02() { + JsonValue x = JSON.parseAny("{ key1: 'value1', key2: 'value2' }") ; + JsonObject obj = JsonBuilder.buildObject(b->{ + b.pair("key1", "value1") + .pair("key2", "value2"); + }); + assertEquals(x, obj); + } } diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonExt.java b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonExt.java index 06b5adf4e31..52f135ce7ff 100644 --- a/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonExt.java +++ b/jena-arq/src/test/java/org/apache/jena/atlas/json/TestJsonExt.java @@ -26,9 +26,13 @@ import org.apache.jena.atlas.junit.BaseTest ; import org.junit.Test ; -/** Test that are of extension of JSON */ +/** Tests that are of extensions of JSON */ public class TestJsonExt extends BaseTest { + // The Jena JSON parser is more liberal than strict JSON to make embedding easier. + // * Keys do not need quotes + // * Strings can use '' + // -------- Non-standard things. @Test public void js_value_ext_1()