You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by pa...@apache.org on 2017/11/24 22:11:08 UTC
[sling-org-apache-sling-commons-johnzon] 01/01: SLING-7267: Update
commons johnzon to johnzon 1.1.4 and make it work with java7.
This is an automated email from the ASF dual-hosted git repository.
pauls pushed a commit to branch SLING-7267
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-johnzon.git
commit 62ab59b4b2af5ea9989943a67440a75898c0e9ee
Author: Karl Pauls <ka...@gmail.com>
AuthorDate: Fri Nov 24 23:10:46 2017 +0100
SLING-7267: Update commons johnzon to johnzon 1.1.4 and make it work with java7.
---
pom.xml | 37 +-
src/main/java/java/util/function/Function.java | 23 +
src/main/java/java/util/stream/Stream.java | 22 +
src/main/java/javax/json/Json.java | 359 +++++++
src/main/java/javax/json/JsonArray.java | 153 +++
src/main/java/javax/json/JsonArrayBuilder.java | 94 ++
src/main/java/javax/json/JsonBuilderFactory.java | 81 ++
src/main/java/javax/json/JsonException.java | 27 +
src/main/java/javax/json/JsonMergePatch.java | 40 +
src/main/java/javax/json/JsonNumber.java | 61 ++
src/main/java/javax/json/JsonObject.java | 103 ++
src/main/java/javax/json/JsonObjectBuilder.java | 148 +++
src/main/java/javax/json/JsonPatch.java | 208 ++++
src/main/java/javax/json/JsonPatchBuilder.java | 193 ++++
src/main/java/javax/json/JsonPointer.java | 94 ++
src/main/java/javax/json/JsonReader.java | 32 +
src/main/java/javax/json/JsonReaderFactory.java | 32 +
src/main/java/javax/json/JsonString.java | 32 +
src/main/java/javax/json/JsonStructure.java | 28 +
src/main/java/javax/json/JsonValue.java | 149 +++
src/main/java/javax/json/JsonWriter.java | 39 +
src/main/java/javax/json/JsonWriterFactory.java | 32 +
src/main/java/javax/json/spi/JsonProvider.java | 105 +-
.../javax/json/stream/JsonGenerationException.java | 32 +
src/main/java/javax/json/stream/JsonGenerator.java | 88 ++
.../javax/json/stream/JsonGeneratorFactory.java | 32 +
src/main/java/javax/json/stream/JsonLocation.java | 26 +
src/main/java/javax/json/stream/JsonParser.java | 71 ++
.../java/javax/json/stream/JsonParserFactory.java | 39 +
.../javax/json/stream/JsonParsingException.java | 39 +
.../apache/johnzon/core/AbstractJsonFactory.java | 84 ++
.../org/apache/johnzon/core/BufferStrategy.java | 272 +++++
.../johnzon/core/CommentsJsonStreamParserImpl.java | 73 ++
.../java/org/apache/johnzon/core/DiffBase.java | 34 +
.../java/org/apache/johnzon/core/Experimental.java | 33 +
src/main/java/org/apache/johnzon/core/HStack.java | 66 ++
.../org/apache/johnzon/core/JohnzonJsonParser.java | 134 +++
.../apache/johnzon/core/JsonArrayBuilderImpl.java | 330 ++++++
.../org/apache/johnzon/core/JsonArrayImpl.java | 235 +++++
.../johnzon/core/JsonBuilderFactoryImpl.java | 92 ++
.../java/org/apache/johnzon/core/JsonChars.java | 82 ++
.../org/apache/johnzon/core/JsonDoubleImpl.java | 137 +++
.../johnzon/core/JsonGeneratorFactoryImpl.java | 79 ++
.../org/apache/johnzon/core/JsonGeneratorImpl.java | 827 +++++++++++++++
.../apache/johnzon/core/JsonInMemoryParser.java | 314 ++++++
.../org/apache/johnzon/core/JsonLocationImpl.java | 80 ++
.../java/org/apache/johnzon/core/JsonLongImpl.java | 121 +++
.../apache/johnzon/core/JsonMergePatchDiff.java | 81 ++
.../apache/johnzon/core/JsonMergePatchImpl.java | 80 ++
.../org/apache/johnzon/core/JsonNumberImpl.java | 132 +++
.../apache/johnzon/core/JsonObjectBuilderImpl.java | 178 ++++
.../org/apache/johnzon/core/JsonObjectImpl.java | 206 ++++
.../apache/johnzon/core/JsonParserFactoryImpl.java | 137 +++
.../apache/johnzon/core/JsonPatchBuilderImpl.java | 181 ++++
.../org/apache/johnzon/core/JsonPatchDiff.java | 102 ++
.../org/apache/johnzon/core/JsonPatchImpl.java | 213 ++++
.../org/apache/johnzon/core/JsonPointerImpl.java | 484 +++++++++
.../org/apache/johnzon/core/JsonPointerUtil.java | 49 +
.../org/apache/johnzon/core/JsonProviderImpl.java | 384 +++++++
.../apache/johnzon/core/JsonReaderFactoryImpl.java | 63 ++
.../org/apache/johnzon/core/JsonReaderImpl.java | 261 +++++
.../apache/johnzon/core/JsonStreamParserImpl.java | 1071 ++++++++++++++++++++
.../org/apache/johnzon/core/JsonStringImpl.java | 89 ++
.../apache/johnzon/core/JsonWriterFactoryImpl.java | 66 ++
.../org/apache/johnzon/core/JsonWriterImpl.java | 92 ++
.../core/RFC4627AwareInputStreamReader.java | 141 +++
.../org/apache/johnzon/core/SerializableValue.java | 56 +
.../java/org/apache/johnzon/core/SimpleStack.java | 61 ++
src/main/java/org/apache/johnzon/core/Strings.java | 127 +++
.../johnzon/core/ThreadLocalBufferCache.java | 53 +
70 files changed, 9699 insertions(+), 20 deletions(-)
diff --git a/pom.xml b/pom.xml
index 080b849..2761c6a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,7 @@
<parent>
<groupId>org.apache.sling</groupId>
<artifactId>sling</artifactId>
- <version>30</version>
+ <version>32</version>
<relativePath />
</parent>
@@ -38,17 +38,44 @@
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
<configuration>
+ <skip>true</skip>
<instructions>
<Export-Package>
- javax.json.*;-split-package:=first
+ javax.json;-split-package:=first,
+ javax.json.stream;-split-package:=merge-first,
+ javax.json.spi;-split-package:=first
</Export-Package>
<Private-Package>org.apache.johnzon.core</Private-Package>
<Import-Package />
+ <_noee>true</_noee>
</instructions>
</configuration>
</plugin>
<plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <forceCreation>true</forceCreation>
+ <archive>
+ <manifestFile>
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+ </manifestFile>
+ <manifestEntries>
+ <Require-Capability>osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"</Require-Capability>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
@@ -65,11 +92,5 @@
<artifactId>geronimo-json_1.1_spec</artifactId>
<version>1.0</version>
</dependency>
- <dependency>
- <groupId>org.apache.johnzon</groupId>
- <artifactId>johnzon-core</artifactId>
- <version>1.1.1</version>
- <scope>provided</scope>
- </dependency>
</dependencies>
</project>
diff --git a/src/main/java/java/util/function/Function.java b/src/main/java/java/util/function/Function.java
new file mode 100644
index 0000000..79877d6
--- /dev/null
+++ b/src/main/java/java/util/function/Function.java
@@ -0,0 +1,23 @@
+/*
+ * 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 java.util.function;
+
+public interface Function<V,R>{
+ R apply(V v);
+}
diff --git a/src/main/java/java/util/stream/Stream.java b/src/main/java/java/util/stream/Stream.java
new file mode 100644
index 0000000..7653f24
--- /dev/null
+++ b/src/main/java/java/util/stream/Stream.java
@@ -0,0 +1,22 @@
+/*
+ * 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 java.util.stream;
+
+public interface Stream<T> {
+}
diff --git a/src/main/java/javax/json/Json.java b/src/main/java/javax/json/Json.java
new file mode 100644
index 0000000..6675879
--- /dev/null
+++ b/src/main/java/javax/json/Json.java
@@ -0,0 +1,359 @@
+/*
+ * 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 javax.json;
+
+import javax.json.spi.JsonProvider;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParserFactory;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Map;
+
+public final class Json
+{
+ private Json()
+ {
+ // no-op
+ }
+
+ public static JsonParser createParser(Reader reader)
+ {
+ return JsonProvider.provider().createParser(reader);
+ }
+
+ public static JsonParser createParser(InputStream in)
+ {
+ return JsonProvider.provider().createParser(in);
+ }
+
+ public static JsonGenerator createGenerator(Writer writer)
+ {
+ return JsonProvider.provider().createGenerator(writer);
+ }
+
+ public static JsonGenerator createGenerator(OutputStream out)
+ {
+ return JsonProvider.provider().createGenerator(out);
+ }
+
+ public static JsonParserFactory createParserFactory(Map<String, ?> config)
+ {
+ return JsonProvider.provider().createParserFactory(config);
+ }
+
+ public static JsonGeneratorFactory createGeneratorFactory(Map<String, ?> config)
+ {
+ return JsonProvider.provider().createGeneratorFactory(config);
+ }
+
+ public static JsonWriter createWriter(Writer writer)
+ {
+ return JsonProvider.provider().createWriter(writer);
+ }
+
+ public static JsonWriter createWriter(OutputStream out)
+ {
+ return JsonProvider.provider().createWriter(out);
+ }
+
+ public static JsonReader createReader(Reader reader)
+ {
+ return JsonProvider.provider().createReader(reader);
+ }
+
+ public static JsonReader createReader(InputStream in)
+ {
+ return JsonProvider.provider().createReader(in);
+ }
+
+ public static JsonReaderFactory createReaderFactory(Map<String, ?> config)
+ {
+ return JsonProvider.provider().createReaderFactory(config);
+ }
+
+ public static JsonWriterFactory createWriterFactory(Map<String, ?> config)
+ {
+ return JsonProvider.provider().createWriterFactory(config);
+ }
+
+ public static JsonArrayBuilder createArrayBuilder()
+ {
+ return JsonProvider.provider().createArrayBuilder();
+ }
+
+ /**
+ * Create an empty JsonObjectBuilder
+ *
+ * @since 1.0
+ */
+ public static JsonObjectBuilder createObjectBuilder()
+ {
+ return JsonProvider.provider().createObjectBuilder();
+ }
+
+ /**
+ * Creates a JSON object builder, initialized with the specified JsonObject.
+ *
+ * @since 1.1
+ */
+ public static JsonObjectBuilder createObjectBuilder(JsonObject object)
+ {
+ return JsonProvider.provider().createObjectBuilder(object);
+ }
+
+ /**
+ * Creates a JSON object builder, initialized with the specified Map.
+ *
+ * @since 1.1
+ */
+ public static JsonObjectBuilder createObjectBuilder(Map<String, Object> map)
+ {
+ return JsonProvider.provider().createObjectBuilder(map);
+ }
+
+ public static JsonBuilderFactory createBuilderFactory(Map<String, ?> config)
+ {
+ return JsonProvider.provider().createBuilderFactory(config);
+ }
+
+ /**
+ * Creates a JSON array builder, initialized with an initial content
+ * taken from a JsonArray
+ *
+ * @param initialData the initial array in the builder
+ * @return a JSON array builder
+ * @since 1.1
+ */
+ public static JsonArrayBuilder createArrayBuilder(JsonArray initialData)
+ {
+ return JsonProvider.provider().createArrayBuilder(initialData);
+ }
+
+ /**
+ * Creates a JSON array builder, initialized with an initial content
+ *
+ * @param initialData the initial array in the builder
+ * @return a JSON array builder
+ * @since 1.1
+ */
+ public static JsonArrayBuilder createArrayBuilder(Collection<?> initialData)
+ {
+ return JsonProvider.provider().createArrayBuilder(initialData);
+ }
+
+ public static JsonString createValue(String value)
+ {
+ return JsonProvider.provider().createValue(value);
+ }
+
+ public static JsonNumber createValue(int value)
+ {
+ return JsonProvider.provider().createValue(value);
+ }
+
+ public static JsonNumber createValue(long value)
+ {
+ return JsonProvider.provider().createValue(value);
+ }
+
+ public static JsonNumber createValue(double value)
+ {
+ return JsonProvider.provider().createValue(value);
+ }
+
+ public static JsonNumber createValue(BigDecimal value)
+ {
+ return JsonProvider.provider().createValue(value);
+ }
+
+ public static JsonNumber createValue(BigInteger value)
+ {
+ return JsonProvider.provider().createValue(value);
+ }
+
+ /**
+ * Create a {@link JsonPatch} as defined in
+ * <a href="https://tools.ietf.org/html/rfc6902">RFC-6902</a>.
+ *
+ * @param array with the patch operations
+ * @return the JsonPatch based on the given operations
+ * @since 1.1
+ */
+ public static JsonPatch createPatch(JsonArray array)
+ {
+ return JsonProvider.provider().createPatch(array);
+ }
+
+ /**
+ * Create a {@link JsonPatch} by comparing the source to the target as defined in
+ * <a href="https://tools.ietf.org/html/rfc6902">RFC-6902</a>.
+ * <p>
+ * Applying this {@link JsonPatch} to the source you will give you the target.
+ *
+ * @see #createPatch(JsonArray)
+ * @since 1.1
+ */
+ public static JsonPatch createDiff(JsonStructure source, JsonStructure target)
+ {
+ return JsonProvider.provider().createDiff(source, target);
+ }
+
+ /**
+ * Create a new JsonPatchBuilder
+ *
+ * @since 1.1
+ */
+ public static JsonPatchBuilder createPatchBuilder()
+ {
+ return JsonProvider.provider().createPatchBuilder();
+ }
+
+ /**
+ * Create a new JsonPatchBuilder
+ *
+ * @param initialData the initial patch operations
+ * @since 1.1
+ */
+ public static JsonPatchBuilder createPatchBuilder(JsonArray initialData)
+ {
+ return JsonProvider.provider().createPatchBuilder(initialData);
+ }
+
+ /**
+ * Creates JSON Merge Patch (<a href="http://tools.ietf.org/html/rfc7396">RFC 7396</a>)
+ * from a specified {@link JsonValue}.
+ * Create a merged patch by comparing the source to the target.
+ * <p>
+ * Applying this JsonPatch to the source will give you the target.
+ * <p>
+ * If you have a JSON like
+ * <pre>
+ * {
+ * "a": "b",
+ * "c": {
+ * "d": "e",
+ * "f": "g"
+ * }
+ * }
+ * </pre>
+ * <p>
+ * Then you can change the value of "a" and removing "f" by sending:
+ * <pre>
+ * {
+ * "a":"z",
+ * "c": {
+ * "f": null
+ * }
+ * }
+ * </pre>
+ *
+ * @param patch the patch
+ * @return a JSON Merge Patch
+ * @since 1.1
+ */
+ public static JsonMergePatch createMergePatch(JsonValue patch)
+ {
+ return JsonProvider.provider().createMergePatch(patch);
+ }
+
+ /**
+ * Create a JSON Merge Patch (<a href="http://tools.ietf.org/html/rfc7396">RFC 7396</a>)
+ * from the source and target {@link JsonValue JsonValues}.
+ * Create a merged patch by comparing the source to the target.
+ * <p>
+ * Applying this JsonPatch to the source will give you the target.
+ * <p>
+ * If you have a JSON like
+ * <pre>
+ * {
+ * "a": "b",
+ * "c": {
+ * "d": "e",
+ * "f": "g"
+ * }
+ * }
+ * </pre>
+ * <p>
+ * Then you can change the value of "a" and removing "f" by sending:
+ * <pre>
+ * {
+ * "a":"z",
+ * "c": {
+ * "f": null
+ * }
+ * }
+ * </pre>
+ *
+ * @param source the source
+ * @param target the target
+ * @return a JSON Merge Patch
+ * @since 1.1
+ */
+ public static JsonMergePatch createMergeDiff(JsonValue source, JsonValue target)
+ {
+ return JsonProvider.provider().createMergeDiff(source, target);
+ }
+
+ /**
+ * Create a {@link JsonPointer} for the given path
+ *
+ * @since 1.1
+ */
+ public static JsonPointer createPointer(String path)
+ {
+ return JsonProvider.provider().createPointer(path);
+ }
+
+ /**
+ * @param pointer to encode
+ * @return the properly encoded JsonPointer string
+ * @since 1.1
+ */
+ public static String encodePointer(String pointer)
+ {
+ if (pointer == null || pointer.length() == 0)
+ {
+ return pointer;
+ }
+
+ return pointer.replace("~", "~0").replace("/", "~1");
+ }
+
+ /**
+ * @param escapedPointer
+ * @return the de-escaped JsonPointer
+ *
+ * @since 1.1
+ */
+ public static String decodePointer(String escapedPointer)
+ {
+ if (escapedPointer == null || escapedPointer.length() == 0)
+ {
+ return escapedPointer;
+ }
+
+ return escapedPointer.replace("~1", "/").replace("~0", "~");
+ }
+}
diff --git a/src/main/java/javax/json/JsonArray.java b/src/main/java/javax/json/JsonArray.java
new file mode 100644
index 0000000..4955214
--- /dev/null
+++ b/src/main/java/javax/json/JsonArray.java
@@ -0,0 +1,153 @@
+/*
+ * 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 javax.json;
+
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * A JsonArray e.g.
+ * <pre>
+ * [1,5,8]
+ * </pre>
+ * or
+ * <pre>
+ * [
+ * {"name":"karl", "age": 38},
+ * {"name":"sue", "age": 42},
+ * ]
+ * </pre>
+ *
+ *
+ */
+public interface JsonArray extends JsonStructure, List<JsonValue> {
+
+ /**
+ * @return the JsonObject at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to the JsonObject
+ */
+ JsonObject getJsonObject(int index);
+
+ /**
+ * @return the JsonArray at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to the JsonArray
+ */
+ JsonArray getJsonArray(int index);
+
+ /**
+ * @return the JsonNumber at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to the JsonNumber
+ */
+ JsonNumber getJsonNumber(int index);
+
+ /**
+ * @return the JsonString at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to the JsonString
+ */
+ JsonString getJsonString(int index);
+
+ /**
+ * @return the respective JsonValue at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to the given slazz
+ */
+ <T extends JsonValue> List<T> getValuesAs(Class<T> clazz);
+
+ /**
+ * Returns a list for the array. The value and the type of the elements
+ * in the list is specified by the {@code func} argument.
+ * <p>This method can be used to obtain a list of the unwrapped types, such as
+ * <pre>{@code
+ * List<String> strings = ary1.getValuesAs(JsonString::getString);
+ * List<Integer> ints = ary2.getValuesAs(JsonNumber::intValue);
+ * } </pre>
+ * It can also be used to obtain a list of simple projections, such as
+ * <pre> {@code
+ * Lsit<Integer> stringsizes = arr.getValueAs((JsonString v) -> v.getString().length();
+ * } </pre>
+ * @param <K> The element type (must be a subtype of JsonValue) of this JsonArray.
+ * @param <T> The element type of the returned List
+ * @param func The function that maps the elements of this JsonArray to the target elements.
+ * @return A List of the specified values and type.
+ * @throws ClassCastException if the {@code JsonArray} contains a value of wrong type
+ */
+ <T, K extends JsonValue> List<T> getValuesAs(Function<K, T> func);
+
+ /**
+ * @return the native String at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to a String
+ */
+ String getString(int index);
+
+ /**
+ * @return the native String at the given position or the defaultValue if null
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to a String
+ */
+ String getString(int index, String defaultValue);
+
+ /**
+ * @return the native int value at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to an int
+ */
+ int getInt(int index);
+
+ /**
+ * @return the native int value at the given position or the defaultValue if null
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to an int
+ */
+ int getInt(int index, int defaultValue);
+
+ /**
+ * @return the native boolean value at the given position
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to a boolean
+ */
+ boolean getBoolean(int index);
+
+ /**
+ * @return the native boolean value at the given position or the defaultValue if null
+ * @throws IndexOutOfBoundsException if the index is out of range
+ * @throws ClassCastException if the value at the specified position is not
+ * assignable to a boolean
+ */
+ boolean getBoolean(int index, boolean defaultValue);
+
+ /**
+ * @return whether the value at the given position is {@link JsonValue#NULL}.
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
+ boolean isNull(int index);
+}
+
diff --git a/src/main/java/javax/json/JsonArrayBuilder.java b/src/main/java/javax/json/JsonArrayBuilder.java
new file mode 100644
index 0000000..3de276f
--- /dev/null
+++ b/src/main/java/javax/json/JsonArrayBuilder.java
@@ -0,0 +1,94 @@
+/*
+ * 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 javax.json;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public interface JsonArrayBuilder {
+ JsonArrayBuilder add(JsonValue value);
+
+ JsonArrayBuilder add(String value);
+
+ JsonArrayBuilder add(BigDecimal value);
+
+ JsonArrayBuilder add(BigInteger value);
+
+ JsonArrayBuilder add(int value);
+
+ JsonArrayBuilder add(long value);
+
+ JsonArrayBuilder add(double value);
+
+ JsonArrayBuilder add(boolean value);
+
+ JsonArrayBuilder addNull();
+
+ JsonArrayBuilder add(JsonObjectBuilder builder);
+
+ JsonArrayBuilder add(JsonArrayBuilder builder);
+
+ JsonArray build();
+
+ JsonArrayBuilder addAll(JsonArrayBuilder builder);
+
+ JsonArrayBuilder add(int index, JsonValue value);
+
+ JsonArrayBuilder add(int index, String value);
+
+ JsonArrayBuilder add(int index, BigDecimal value);
+
+ JsonArrayBuilder add(int index, BigInteger value);
+
+ JsonArrayBuilder add(int index, int value);
+
+ JsonArrayBuilder add(int index, long value);
+
+ JsonArrayBuilder add(int index, double value);
+
+ JsonArrayBuilder add(int index, boolean value);
+
+ JsonArrayBuilder addNull(int index);
+
+ JsonArrayBuilder add(int index, JsonObjectBuilder builder);
+
+ JsonArrayBuilder add(int index, JsonArrayBuilder builder);
+
+ JsonArrayBuilder set(int index, JsonValue value);
+
+ JsonArrayBuilder set(int index, String value);
+
+ JsonArrayBuilder set(int index, BigDecimal value);
+
+ JsonArrayBuilder set(int index, BigInteger value);
+
+ JsonArrayBuilder set(int index, int value);
+
+ JsonArrayBuilder set(int index, long value);
+
+ JsonArrayBuilder set(int index, double value);
+
+ JsonArrayBuilder set(int index, boolean value);
+
+ JsonArrayBuilder setNull(int index);
+
+ JsonArrayBuilder set(int index, JsonObjectBuilder builder);
+
+ JsonArrayBuilder set(int index, JsonArrayBuilder builder);
+
+ JsonArrayBuilder remove(int index);
+}
diff --git a/src/main/java/javax/json/JsonBuilderFactory.java b/src/main/java/javax/json/JsonBuilderFactory.java
new file mode 100644
index 0000000..0958d26
--- /dev/null
+++ b/src/main/java/javax/json/JsonBuilderFactory.java
@@ -0,0 +1,81 @@
+/*
+ * 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 javax.json;
+
+import java.util.Map;
+
+public interface JsonBuilderFactory {
+ /**
+ * @return a new empty JsonObjectBuilder
+ */
+ JsonObjectBuilder createObjectBuilder();
+
+ /**
+ * @return a new empty JsonArrayBuilder
+ */
+ JsonArrayBuilder createArrayBuilder();
+
+ /**
+ * @return the config which got used when creating this builder factory.
+ */
+ Map<String, ?> getConfigInUse();
+
+ /**
+ * Create a JsonObjectBuilder filled with the given initial data.
+ *
+ * @throws NullPointerException if initialData is {@code null}
+ *
+ * @return a new pre initialised JsonObjectBuilder
+ *
+ * @since 1.1
+ */
+ JsonObjectBuilder createObjectBuilder(JsonObject initialData);
+
+ /**
+ * Create a JsonObjectBuilder filled with the given initial data.
+ *
+ * @throws NullPointerException if initialData is {@code null}
+ *
+ * @return a new pre initialised JsonObjectBuilder
+ *
+ * @since 1.1
+ */
+ JsonObjectBuilder createObjectBuilder(Map<String, Object> initialData);
+
+ /**
+ * Create a JsonArrayBuilder filled with the given initial data.
+ *
+ * @throws NullPointerException if initialData is {@code null}
+ *
+ * @return a new pre initialised JsonArrayBuilder
+ *
+ * @since 1.1
+ */
+ JsonArrayBuilder createArrayBuilder(JsonArray initialData);
+
+ /**
+ * Create a {@link JsonArrayBuilder} which is filled with the given initial content.
+ * @param initialData the content to immediately add to the JsonArrayBuilder
+ *
+ * @throws NullPointerException if initialData is {@code null}
+ *
+ * @return a new pre initialised JsonArrayBuilder
+ * @since 1.1
+ */
+ JsonArrayBuilder createArrayBuilder(java.util.Collection<?> initialData);
+}
+
diff --git a/src/main/java/javax/json/JsonException.java b/src/main/java/javax/json/JsonException.java
new file mode 100644
index 0000000..47c4d61
--- /dev/null
+++ b/src/main/java/javax/json/JsonException.java
@@ -0,0 +1,27 @@
+/*
+ * 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 javax.json;
+
+public class JsonException extends RuntimeException {
+ public JsonException(final String message) {
+ super(message);
+ }
+
+ public JsonException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/javax/json/JsonMergePatch.java b/src/main/java/javax/json/JsonMergePatch.java
new file mode 100644
index 0000000..0ff3b7d
--- /dev/null
+++ b/src/main/java/javax/json/JsonMergePatch.java
@@ -0,0 +1,40 @@
+/*
+ * 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 javax.json;
+
+/**
+ * A JSON MergePatch as defined in http://tools.ietf.org/html/rfc7396
+ *
+ * @see javax.json.spi.JsonProvider#createMergePatch(JsonValue)
+ * @see javax.json.spi.JsonProvider#createMergeDiff(JsonValue, JsonValue)
+ */
+public interface JsonMergePatch {
+
+ /**
+ * Applies the current JsonMergePatch to the given value
+ * @param valueToApplyPatchOn the base value. Will not get changed itself.
+ * @return the patched new JsonValue
+ */
+ JsonValue apply(JsonValue valueToApplyPatchOn);
+
+ /**
+ * @return the Patch as JsonValue
+ */
+ JsonValue toJsonValue();
+}
+
diff --git a/src/main/java/javax/json/JsonNumber.java b/src/main/java/javax/json/JsonNumber.java
new file mode 100644
index 0000000..ab0ace2
--- /dev/null
+++ b/src/main/java/javax/json/JsonNumber.java
@@ -0,0 +1,61 @@
+/*
+ * 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 javax.json;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * JsonValue which represents a number.
+ *
+ * The decimal point is defined as dot '.'.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4627">RFC-4627 JSON Specification</a>
+ */
+public interface JsonNumber extends JsonValue {
+ boolean isIntegral();
+
+ int intValue();
+
+ int intValueExact();
+
+ long longValue();
+
+ long longValueExact();
+
+ BigInteger bigIntegerValue();
+
+ BigInteger bigIntegerValueExact();
+
+ double doubleValue();
+
+ BigDecimal bigDecimalValue();
+
+ /**
+ * @since 1.1
+ */
+ Number numberValue();
+
+ @Override
+ String toString();
+
+ @Override
+ boolean equals(Object obj);
+
+ @Override
+ int hashCode();
+}
diff --git a/src/main/java/javax/json/JsonObject.java b/src/main/java/javax/json/JsonObject.java
new file mode 100644
index 0000000..c1e6f82
--- /dev/null
+++ b/src/main/java/javax/json/JsonObject.java
@@ -0,0 +1,103 @@
+/*
+ * 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 javax.json;
+
+import java.util.Map;
+
+/**
+ * A JsonObject, e.g.
+ * <pre>
+ * {
+ * "name":"karl",
+ * "age":38,
+ * "address": {
+ * "street":"dummystreet"
+ * "housenumber":12
+ * }
+ * }
+ * </pre>
+ *
+ * A JsonObject is always also a Map which uses the attribute names as key mapping
+ * to their JsonValues.
+ */
+public interface JsonObject extends JsonStructure, Map<String, JsonValue> {
+
+ /**
+ * @return the JsonArray with the given name or {@code null} if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ JsonArray getJsonArray(String name);
+
+ /**
+ * @return the JsonObject with the given name or {@code null} if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ JsonObject getJsonObject(String name);
+
+ /**
+ * @return the JsonNumber with the given name or {@code null} if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ JsonNumber getJsonNumber(String name);
+
+ /**
+ * @return the JsonString with the given name or {@code null} if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ JsonString getJsonString(String name);
+
+ /**
+ * @return the native string with the given name or {@code null} if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ String getString(String name);
+
+ /**
+ * @return the native string with the given name or the default value if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ String getString(String name, String defaultValue);
+
+ /**
+ * @return the int with the given name or {@code null} if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ int getInt(String name);
+
+ /**
+ * @return the int with the given name or the default value if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ int getInt(String name, int defaultValue);
+
+ /**
+ * @return the boolean with the given name or {@code null} if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ boolean getBoolean(String name);
+
+ /**
+ * @return the boolean with the given name or the default value if there is no attribute with that name
+ * @throws ClassCastException if the JsonValue cannot be correctly cast
+ */
+ boolean getBoolean(String name, boolean defaultValue);
+
+ /**
+ * @return whether the attribute with the given name is {@link JsonValue#NULL}
+ */
+ boolean isNull(String name);
+}
diff --git a/src/main/java/javax/json/JsonObjectBuilder.java b/src/main/java/javax/json/JsonObjectBuilder.java
new file mode 100644
index 0000000..e472a64
--- /dev/null
+++ b/src/main/java/javax/json/JsonObjectBuilder.java
@@ -0,0 +1,148 @@
+/*
+ * 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 javax.json;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * A JsonObjectBuilder can be used to build {@link JsonObject JsonObjects}.
+ * Instances are not thread safe.
+ *
+ * Calling any of those methods with either the {@code name} or {@code value} param as {@code null}
+ * will result in a {@code NullPointerException}
+ */
+public interface JsonObjectBuilder {
+ /**
+ * Add the given JsonValue value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value the JsonValue to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, JsonValue value);
+
+ /**
+ * Add the given String value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value the String value to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, String value);
+
+ /**
+ * Add the given BigInteger value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value the BigInteger value to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, BigInteger value);
+
+ /**
+ * Add the given BigDecimal value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value the BigDecimal value to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, BigDecimal value);
+
+ /**
+ * Add the given int value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, int value);
+
+ /**
+ * Add the given long value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, long value);
+
+ /**
+ * Add the given double value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, double value);
+
+ /**
+ * Add the given boolean value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param value to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, boolean value);
+
+ /**
+ * Add a {@link JsonValue#NULL} value to the JsonObject to be created.
+ * If a value with that name already exists it will be replaced by the null value.
+ * @param name the JSON attribute name
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder addNull(String name);
+
+ /**
+ * Use the given {@link JsonObjectBuilder} to create a {@link JsonObject} which will be
+ * added to the JsonObject to be created by this builder.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param builder for creating the JsonObject to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, JsonObjectBuilder builder);
+
+ /**
+ * Use the given {@link JsonArrayBuilder} to create a {@link JsonArray} which will be
+ * added to the JsonObject to be created by this builder.
+ * If a value with that name already exists it will be replaced by the new value.
+ * @param name the JSON attribute name
+ * @param builder for creating the JsonArray to add
+ * @return the current JsonObjectBuilder
+ */
+ JsonObjectBuilder add(String name, JsonArrayBuilder builder);
+
+ /**
+ * @return a {@link JsonObject} based on the added values.
+ */
+ JsonObject build();
+
+ /**
+ * Add all of the attributes of the given {@link JsonObjectBuilder} to the current one
+ *
+ * @since 1.1
+ */
+ JsonObjectBuilder addAll(JsonObjectBuilder builder);
+
+ /**
+ * Remove the attribute with the given name from the builder.
+ *
+ * @since 1.1
+ */
+ JsonObjectBuilder remove(String name);
+}
diff --git a/src/main/java/javax/json/JsonPatch.java b/src/main/java/javax/json/JsonPatch.java
new file mode 100644
index 0000000..7a36fcf
--- /dev/null
+++ b/src/main/java/javax/json/JsonPatch.java
@@ -0,0 +1,208 @@
+/*
+ * 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 javax.json;
+
+/**
+ * <p>
+ * JsonPatch is a format for expressing a sequence of operations to apply on
+ * a target JSON document.
+ * </p>
+ * <p>
+ * A JsonPatch is an array of 'operations' in the form e.g.
+ * <p>
+ * <pre>
+ * [
+ * { "op": "add", "path": "/foo/-", "value": ["abc", "def"] }
+ * { "path": "/a/b/c", "op": "add", "value": "foo" }
+ * ]
+ * </pre>
+ * <p>
+ * The 'operations' are performed in the order they are in the JsonPatch and applied
+ * to the 'result' JSON document from the previous operation.
+ * <p>
+ *
+ * @see Operation
+ *
+ * @since 1.1
+ */
+public interface JsonPatch {
+
+ /**
+ * <p>
+ * An enumeration of available operations for {@link JsonPatch}.<br>
+ * <p>
+ * NOTICE: the behavoir of some operations depends on which {@link JsonValue} they are performed.
+ * for details please check the documentation of the very operation.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc6902">RFC-6902</a>
+ *
+ * @since 1.1
+ */
+ enum Operation {
+
+ /**
+ * <p>
+ * required members are:
+ * <ul>
+ * <li>"op": "add"</li>
+ * <li>"path": "path/to/add"</li>
+ * <li>"value": "{@link JsonValue}ToAdd"</li>
+ * </ul>
+ * <p>
+ * if the "path/to" does not exist, this operation will result in an error.<br>
+ * if the "path/to/add" already exists, the value will be <strong>replaced</strong>
+ * <p>
+ * for {@link JsonArray}s the new value will be inserted at the specified index
+ * and the element(s) at/after are shifted to the right. the '-' character is used to append the value
+ * at the and of the {@link JsonArray}.
+ */
+ ADD("add"),
+
+ /**
+ * <p>
+ * required members are:
+ * <ul>
+ * <li>"op": "remove"</li>
+ * <li>"path": "path/to/remove"</li>
+ * </ul>
+ * <p>
+ * if the "path/to/remove" does not exist, the operation will fail.
+ * <p>
+ * for {@link JsonArray}s the values after the removed value are shifted to the left
+ */
+ REMOVE("remove"),
+
+ /**
+ * <p>
+ * required members are:
+ * <ul>
+ * <li>"op": "replace"</li>
+ * <li>"path": "path/to/replace"</li>
+ * <li>"value": "the new {@link JsonValue}"</li>
+ * </ul>
+ * <p>
+ * this operation is identical to {@link #REMOVE} followed by {@link #ADD}
+ */
+ REPLACE("replace"),
+
+ /**
+ * <p>
+ * required members are:
+ * <ul>
+ * <li>"op": "move"</li>
+ * <li>"from": "path/to/move/from"</li>
+ * <li>"path": "path/to/move/to"</li>
+ * </ul>
+ * <p>
+ * the operation will fail it the "path/to/move/from" does not exist
+ * <p>
+ * NOTICE: a location can not be moved into one of it's children. (from /a/b/c to /a/b/c/d)
+ * <p>
+ * this operation is identical to {@link #REMOVE} from "from" and {@link #ADD} to the "path"
+ */
+ MOVE("move"),
+
+ /**
+ * <p>
+ * required members are:
+ * <ul>
+ * <li>"op": "copy"</li>
+ * <li>"from": "path/to/copy/from"</li>
+ * <li>"path": "path/to/add"</li>
+ * </ul>
+ * <p>
+ * the operation will result in an error if the "from" location does not exist
+ * <p>
+ * this operation is identical to {@link #ADD} with the "from" value
+ */
+ COPY("copy"),
+
+ /**
+ * <p>
+ * required members are:
+ * <ul>
+ * <li>"op": "test"</li>
+ * <li>"path": "/path/to/test"</li>
+ * <li>"value": "{@link JsonValue} to test"</li>
+ * </ul>
+ * <p>
+ * this operation fails, if the value is NOT equal with the /path/to/test
+ * <p>
+ * ordering of the elements in a {@link JsonObject} is NOT significant however
+ * the position of an element in a {@link JsonArray} is significant for equality.
+ */
+ TEST("test");
+
+
+ private final String operationName;
+
+
+ Operation(String operationName) {
+ this.operationName = operationName;
+ }
+
+
+ /**
+ * @return the JSON operation name
+ */
+ public String operationName() {
+ return operationName;
+ }
+
+ /**
+ * Returns the {@link Operation} for the given {@code operationName}. If no {@link Operation}
+ * is present, a {@link JsonException} will be thrown.
+ *
+ * @param operationName {@code operationName} to convert to the enum constant.
+ *
+ * @return the {@link Operation Operation-constant} for given {@code operationName}
+ *
+ * @throws JsonException if no {@link Operation} has been found for the given {@code operationName}
+ */
+ public static Operation fromOperationName(String operationName) {
+
+ for (Operation op : values()) {
+ if (op.operationName().equalsIgnoreCase(operationName)) {
+ return op;
+ }
+ }
+
+ throw new JsonException("unknown value for the operationName of the JSON patch operation: " + operationName);
+ }
+ }
+
+
+ /**
+ * Applies the {@link JsonPatch} to the given {@code target}. If the
+ * given {@code target} is {@code null} a {@link NullPointerException}
+ * will be thrown.
+ *
+ * @param target - the target to apply the {@link JsonPatch}
+ *
+ * @return 'patched' {@link JsonStructure}
+ *
+ * @throws NullPointerException if {@code target} is {@code null}
+ */
+ <T extends JsonStructure> T apply(T target);
+
+ /**
+ * @return the JsonPatch as {@link JsonArray}
+ */
+ JsonArray toJsonArray();
+}
+
diff --git a/src/main/java/javax/json/JsonPatchBuilder.java b/src/main/java/javax/json/JsonPatchBuilder.java
new file mode 100644
index 0000000..775e048
--- /dev/null
+++ b/src/main/java/javax/json/JsonPatchBuilder.java
@@ -0,0 +1,193 @@
+/*
+ * 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 javax.json;
+
+/**
+ * TODO this is a final class in the spec, but that makes no sense.
+ * This class can be used to easily create {@link JsonPatch}es.
+ * To get an instance use {@link Json#createPatchBuilder()} or {@link Json#createPatchBuilder(JsonArray)}
+ * The order of the operations corresponds to the order they are builded.
+ * <p>
+ * NOTICE: A JsonPatchBuilder contains state and therefore is NOT threadsafe and should not be used concurrently.
+ * <p>
+ * The following {@link JsonPatch}
+ * <pre>
+ * "[
+ * {
+ * "op": "add",
+ * "path": "/add/object",
+ * "value": {
+ * "foo": "bar"
+ * }
+ * },
+ * {
+ * "op": "remove",
+ * "path": "/remove/it"
+ * },
+ * {
+ * "op": "move",
+ * "path": "move/to",
+ * "from": "move/from"
+ * }
+ * ]"
+ * </pre>
+ * can be build with the JsonPatchBuilder
+ * <pre>
+ *
+ * JsonPatch patch = Json.createJsonPatchBuilder()
+ * .add("/add/object", Json.createObjectBuilder()
+ * .add("foo", "bar")
+ * .build())
+ * .remove("/remove/it")
+ * .move("/move/to", "move/from")
+ * .build();
+ *
+ * </pre>
+ * <p>
+ * An instance of a JsonPatchBuilder can be reused for another {@link JsonPatch} after
+ * the {@link #build()}-Method was called.
+ */
+public interface JsonPatchBuilder {
+
+ /**
+ * Adds an 'add'-operation to the {@link JsonPatch}
+ *
+ * @param path as {@link JsonPointer} where the value should be added
+ * @param value the value to add
+ *
+ * @return the builder instance for chained method calls
+ *
+ * @throws NullPointerException if the given {@code path} is {@code null}
+ */
+ JsonPatchBuilder add(String path, JsonValue value);
+
+ /**
+ * @see #add(String, JsonValue)
+ */
+ JsonPatchBuilder add(String path, String value);
+
+ /**
+ * @see #add(String, JsonValue)
+ */
+ JsonPatchBuilder add(String path, int value);
+
+ /**
+ * @see #add(String, JsonValue)
+ */
+ JsonPatchBuilder add(String path, boolean value);
+
+
+ /**
+ * Adds a 'remove'-operation to the {@link JsonPatch}
+ *
+ * @param path as {@link JsonPointer} of the value which should get removed
+ *
+ * @return the builder instance for chained method calls
+ *
+ * @throws NullPointerException if the given {@code path} is {@code null}
+ */
+ JsonPatchBuilder remove(String path);
+
+
+ /**
+ * Adds a 'replace'-operation to the {@link JsonPatch}
+ *
+ * @param path as {@link JsonPointer} to the value which should get replaced
+ * @param value the new value
+ *
+ * @return the builder instance for chained method calls
+ *
+ * @throws NullPointerException if the given {@code path} is {@code null}
+ */
+ JsonPatchBuilder replace(String path, JsonValue value);
+
+ /**
+ * @see #replace(String, JsonValue)
+ */
+ JsonPatchBuilder replace(String path, String value);
+
+ /**
+ * @see #replace(String, JsonValue)
+ */
+ JsonPatchBuilder replace(String path, int value);
+
+ /**
+ * @see #replace(String, JsonValue)
+ */
+ JsonPatchBuilder replace(String path, boolean value);
+
+
+ /**
+ * Adds a 'move'-operation to the {@link JsonPatch}
+ *
+ * @param path where the value should get inserted as {@link JsonPointer}
+ * @param from where the value should be taken from as {@link JsonPointer}
+ *
+ * @return the builder instance for chained method calls
+ *
+ * @throws NullPointerException if the given {@code path} is {@code null}
+ * if the given {@code from} is {@code null}
+ */
+ JsonPatchBuilder move(String path, String from);
+
+
+ /**
+ * Adds a 'copy'-operation to the {@link JsonPatch}
+ *
+ * @param path where the copied value should get inserted as {@link JsonPointer}
+ * @param from value to copy as {@link JsonPointer}
+ *
+ * @return the builder instance for chained method calls
+ *
+ * @throws NullPointerException if the given {@code path} is {@code null}
+ * if the given {@code from} is {@code null}
+ */
+ JsonPatchBuilder copy(String path, String from);
+
+
+ /**
+ * Adds a 'test'-operation to the {@link JsonPointer}
+ *
+ * @param path as {@link JsonPointer} to the value to test
+ * @param value value to test
+
+ * @return the builder instance for chained method calls
+
+ * @throws NullPointerException if the given {@code path} is {@code null}
+ */
+ JsonPatchBuilder test(String path, JsonValue value);
+
+ /**
+ * @see #test(String, JsonValue)
+ */
+ JsonPatchBuilder test(String path, String value);
+
+ /**
+ * @see #test(String, JsonValue)
+ */
+ JsonPatchBuilder test(String path, int value);
+
+ /**
+ * @see #test(String, JsonValue)
+ */
+ JsonPatchBuilder test(String path, boolean value);
+
+
+ JsonPatch build();
+}
+
diff --git a/src/main/java/javax/json/JsonPointer.java b/src/main/java/javax/json/JsonPointer.java
new file mode 100644
index 0000000..a4018d2
--- /dev/null
+++ b/src/main/java/javax/json/JsonPointer.java
@@ -0,0 +1,94 @@
+/*
+ * 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 javax.json;
+
+
+/**
+ * <p>This class is an immutable representation of a JSON Pointer as specified in
+ * <a href="http://tools.ietf.org/html/rfc6901">RFC 6901</a>.
+ *
+ *
+ * <p>JSON Pointer is a string syntax for identifying a specific value
+ * within a JavaScript Object Notation (JSON) document [RFC4627].
+ * JSON Pointer is intended to be easily expressed in JSON string values
+ * as well as Uniform Resource Identifier (URI) [RFC3986] fragment identifiers.
+ *
+ * <p> The method {@link #getValue getValue()} returns the referenced value.
+ * The methods {@link #add add()}, {@link #replace replace()},
+ * and {@link #remove remove()} executes the operations specified in
+ * <a href="http://tools.ietf.org/html/rfc6902">RFC 6902</a>.
+ *
+ * @since 1.1
+ */
+public interface JsonPointer {
+
+ /**
+ * Get the JsonValue at the position referenced by this JsonPointer.
+ * @param target the JSON to apply this JsonPointer on
+ * @return the
+ * @throws JsonException if no value exists at the referenced location
+ * @throws NullPointerException if the target is {@code null}
+ */
+ JsonValue getValue(JsonStructure target);
+
+
+ /**
+ * Add or replace the value at the position referenced by this JsonPointer with
+ * the new value
+ * @param target structure in which the newValue should be added or replaced
+ * @param newValue the new value to set
+ * @param <T>
+ * @return the new structure after modifying the original JsonStrucure
+ * @throws JsonException if no value exists at the referenced location
+ * @throws NullPointerException if the target is {@code null}
+ */
+ <T extends JsonStructure> T add(T target, JsonValue newValue);
+
+
+ /**
+ * Remove the value from the referenced position inside the target JSON.
+ * @param target to remove the value from
+ * @param <T> the type of the passed JsonStructure
+ * @return a new JsonStructure with the value removed from the target
+ * @throws JsonException if no value exists at the referenced location
+ * @throws NullPointerException if the target is {@code null}
+ */
+ <T extends JsonStructure> T remove(T target);
+
+ /**
+ * Replace the value at the position referenced by this JsonPointer with
+ * the newValue.
+ *
+ * @param target structure in which the newValue should be replaced
+ * @param newValue the new value to set
+ * @param <T>
+ * @return the new structure after modifying the original JsonStrucure
+ * @throws JsonException if no value exists at the referenced location
+ * @throws NullPointerException if the target is {@code null}
+ */
+ <T extends JsonStructure> T replace(T target, JsonValue newValue);
+
+ /**
+ * Verify if the target JSON structure contains a value at the referenced location.
+ * @param target to check
+ * @return {@code true} if there is a value at the referenced location, {@code false} otherwise
+ * @throws NullPointerException if the target is {@code null}
+ */
+ boolean containsValue(JsonStructure target);
+
+}
diff --git a/src/main/java/javax/json/JsonReader.java b/src/main/java/javax/json/JsonReader.java
new file mode 100644
index 0000000..4284410
--- /dev/null
+++ b/src/main/java/javax/json/JsonReader.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.json;
+
+import java.io.Closeable;
+
+public interface JsonReader extends Closeable {
+ JsonStructure read();
+
+ JsonObject readObject();
+
+ JsonArray readArray();
+
+ @Override
+ void close();
+
+ JsonValue readValue();
+}
diff --git a/src/main/java/javax/json/JsonReaderFactory.java b/src/main/java/javax/json/JsonReaderFactory.java
new file mode 100644
index 0000000..8212fe1
--- /dev/null
+++ b/src/main/java/javax/json/JsonReaderFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.json;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+public interface JsonReaderFactory {
+ JsonReader createReader(Reader reader);
+
+ JsonReader createReader(InputStream in);
+
+ JsonReader createReader(InputStream in, Charset charset);
+
+ Map<String, ?> getConfigInUse();
+}
diff --git a/src/main/java/javax/json/JsonString.java b/src/main/java/javax/json/JsonString.java
new file mode 100644
index 0000000..868cfad
--- /dev/null
+++ b/src/main/java/javax/json/JsonString.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.json;
+
+/**
+ * JsonValue which represents a string.
+ */
+public interface JsonString extends JsonValue {
+ String getString();
+
+ CharSequence getChars();
+
+ @Override
+ boolean equals(Object obj);
+
+ @Override
+ int hashCode();
+}
diff --git a/src/main/java/javax/json/JsonStructure.java b/src/main/java/javax/json/JsonStructure.java
new file mode 100644
index 0000000..c4d353c
--- /dev/null
+++ b/src/main/java/javax/json/JsonStructure.java
@@ -0,0 +1,28 @@
+/*
+ * 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 javax.json;
+
+import javax.json.spi.JsonProvider;
+
+/**
+ * A complex type as JsonValue.
+ * This could either be a {@link JsonArray} or a {@link JsonObject}.
+ */
+public interface JsonStructure extends JsonValue {
+
+ JsonValue getValue(String jsonPointer);
+}
\ No newline at end of file
diff --git a/src/main/java/javax/json/JsonValue.java b/src/main/java/javax/json/JsonValue.java
new file mode 100644
index 0000000..8598a1c
--- /dev/null
+++ b/src/main/java/javax/json/JsonValue.java
@@ -0,0 +1,149 @@
+/*
+ * 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 javax.json;
+
+/**
+ * A single value in a JSON expression.
+ */
+public interface JsonValue {
+
+ /**
+ * The empty JSON object.
+ */
+ JsonObject EMPTY_JSON_OBJECT = Json.createObjectBuilder().build();
+
+ /**
+ * The empty JSON array.
+ */
+ JsonArray EMPTY_JSON_ARRAY = Json.createArrayBuilder().build();
+
+
+ /**
+ * A constant JsonValue for null values
+ */
+ JsonValue NULL = new JsonValue() {
+ @Override
+ public ValueType getValueType() {
+ return ValueType.NULL;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return obj instanceof JsonValue && getValueType().equals(JsonValue.class.cast(obj).getValueType());
+ }
+
+ @Override
+ public int hashCode() {
+ return ValueType.NULL.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "null";
+ }
+
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+ };
+
+ /**
+ * A constant JsonValue for TRUE
+ */
+ JsonValue TRUE = new JsonValue() {
+ @Override
+ public ValueType getValueType() {
+ return ValueType.TRUE;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return obj instanceof JsonValue && getValueType().equals(JsonValue.class.cast(obj).getValueType());
+ }
+
+ @Override
+ public int hashCode() {
+ return ValueType.TRUE.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return Boolean.TRUE.toString();
+ }
+
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+ };
+
+ /**
+ * A constant JsonValue for FALSE
+ */
+ JsonValue FALSE = new JsonValue() {
+ @Override
+ public ValueType getValueType() {
+ return ValueType.FALSE;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return obj instanceof JsonValue && getValueType().equals(JsonValue.class.cast(obj).getValueType());
+ }
+
+ @Override
+ public int hashCode() {
+ return ValueType.FALSE.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return Boolean.FALSE.toString();
+ }
+
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+ };
+
+ ValueType getValueType();
+
+ @Override
+ String toString();
+
+ enum ValueType {
+ ARRAY,
+ OBJECT, STRING, NUMBER,
+ TRUE, FALSE,
+ NULL
+ }
+
+ JsonObject asJsonObject();
+
+ JsonArray asJsonArray();
+}
diff --git a/src/main/java/javax/json/JsonWriter.java b/src/main/java/javax/json/JsonWriter.java
new file mode 100644
index 0000000..da2756a
--- /dev/null
+++ b/src/main/java/javax/json/JsonWriter.java
@@ -0,0 +1,39 @@
+/*
+ * 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 javax.json;
+
+import java.io.Closeable;
+
+/**
+ * Write a JsonObject, JsonArray JsonStructure or more generic
+ * a JsonValue to the output.
+ *
+ * @see JsonWriterFactory
+ */
+public interface JsonWriter extends Closeable {
+
+ void writeArray(JsonArray array);
+
+ void writeObject(JsonObject object);
+
+ void write(JsonStructure value);
+
+ void write(JsonValue value);
+
+ @Override
+ void close();
+}
diff --git a/src/main/java/javax/json/JsonWriterFactory.java b/src/main/java/javax/json/JsonWriterFactory.java
new file mode 100644
index 0000000..e292947
--- /dev/null
+++ b/src/main/java/javax/json/JsonWriterFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.json;
+
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+public interface JsonWriterFactory {
+ JsonWriter createWriter(Writer writer);
+
+ JsonWriter createWriter(OutputStream out);
+
+ JsonWriter createWriter(OutputStream out, Charset charset);
+
+ Map<String, ?> getConfigInUse();
+}
diff --git a/src/main/java/javax/json/spi/JsonProvider.java b/src/main/java/javax/json/spi/JsonProvider.java
index 5464ce6..63b2809 100644
--- a/src/main/java/javax/json/spi/JsonProvider.java
+++ b/src/main/java/javax/json/spi/JsonProvider.java
@@ -22,13 +22,26 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
import java.util.Map;
+import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonBuilderFactory;
+import javax.json.JsonMergePatch;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
+import javax.json.JsonPatch;
+import javax.json.JsonPatchBuilder;
+import javax.json.JsonPointer;
import javax.json.JsonReader;
import javax.json.JsonReaderFactory;
+import javax.json.JsonString;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
import javax.json.JsonWriter;
import javax.json.JsonWriterFactory;
import javax.json.stream.JsonGenerator;
@@ -51,32 +64,100 @@ public abstract class JsonProvider {
public abstract JsonParser createParser(Reader reader);
- public abstract JsonParser createParser(InputStream in);
+ public abstract JsonParser createParser(InputStream var1);
- public abstract JsonParserFactory createParserFactory(Map<String, ?> config);
+ public abstract JsonParserFactory createParserFactory(Map<String, ?> var1);
- public abstract JsonGenerator createGenerator(Writer writer);
+ public abstract JsonGenerator createGenerator(Writer var1);
- public abstract JsonGenerator createGenerator(OutputStream out);
+ public abstract JsonGenerator createGenerator(OutputStream var1);
- public abstract JsonGeneratorFactory createGeneratorFactory(Map<String, ?> config);
+ public abstract JsonGeneratorFactory createGeneratorFactory(Map<String, ?> var1);
- public abstract JsonReader createReader(Reader reader);
+ public abstract JsonReader createReader(Reader var1);
- public abstract JsonReader createReader(InputStream in);
+ public abstract JsonReader createReader(InputStream var1);
- public abstract JsonWriter createWriter(Writer writer);
+ public abstract JsonWriter createWriter(Writer var1);
- public abstract JsonWriter createWriter(OutputStream out);
+ public abstract JsonWriter createWriter(OutputStream var1);
- public abstract JsonWriterFactory createWriterFactory(Map<String, ?> config);
+ public abstract JsonWriterFactory createWriterFactory(Map<String, ?> var1);
- public abstract JsonReaderFactory createReaderFactory(Map<String, ?> config);
+ public abstract JsonReaderFactory createReaderFactory(Map<String, ?> var1);
public abstract JsonObjectBuilder createObjectBuilder();
+ public JsonObjectBuilder createObjectBuilder(JsonObject jsonObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonObjectBuilder createObjectBuilder(Map<String, Object> map) {
+ throw new UnsupportedOperationException();
+ }
+
public abstract JsonArrayBuilder createArrayBuilder();
- public abstract JsonBuilderFactory createBuilderFactory(Map<String, ?> config);
+ public JsonArrayBuilder createArrayBuilder(JsonArray initialData) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonArrayBuilder createArrayBuilder(Collection<?> initialData) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonPointer createPointer(String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ public abstract JsonBuilderFactory createBuilderFactory(Map<String, ?> var1);
+
+ public JsonString createValue(String value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonNumber createValue(int value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonNumber createValue(long value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonNumber createValue(double value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonNumber createValue(BigDecimal value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonNumber createValue(BigInteger value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonPatch createPatch(JsonArray array) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonPatch createDiff(JsonStructure source, JsonStructure target) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonPatchBuilder createPatchBuilder() {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonPatchBuilder createPatchBuilder(JsonArray initialData) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonMergePatch createMergePatch(JsonValue patch) {
+ throw new UnsupportedOperationException();
+ }
+
+ public JsonMergePatch createMergeDiff(JsonValue source, JsonValue target) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/src/main/java/javax/json/stream/JsonGenerationException.java b/src/main/java/javax/json/stream/JsonGenerationException.java
new file mode 100644
index 0000000..46e12d8
--- /dev/null
+++ b/src/main/java/javax/json/stream/JsonGenerationException.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.json.stream;
+
+import javax.json.JsonException;
+
+public class JsonGenerationException extends JsonException {
+ public JsonGenerationException(final String message) {
+ super(message);
+ }
+
+ public JsonGenerationException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+}
+
+
diff --git a/src/main/java/javax/json/stream/JsonGenerator.java b/src/main/java/javax/json/stream/JsonGenerator.java
new file mode 100644
index 0000000..de9587a
--- /dev/null
+++ b/src/main/java/javax/json/stream/JsonGenerator.java
@@ -0,0 +1,88 @@
+/*
+ * 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 javax.json.stream;
+
+import javax.json.JsonValue;
+import java.io.Closeable;
+import java.io.Flushable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public interface JsonGenerator extends Flushable, Closeable {
+ String PRETTY_PRINTING = "javax.json.stream.JsonGenerator.prettyPrinting"; // TODO: ensure it exists before releasing
+
+ JsonGenerator writeStartObject();
+
+ JsonGenerator writeStartObject(String name);
+
+ JsonGenerator writeStartArray();
+
+ JsonGenerator writeStartArray(String name);
+
+ /**
+ * Write the key with a colon;
+ *
+ * @throws JsonGenerationException if this method is not called within an object context
+ * @since 1.1
+ */
+ JsonGenerator writeKey(String name);
+
+ JsonGenerator write(String name, JsonValue value);
+
+ JsonGenerator write(String name, String value);
+
+ JsonGenerator write(String name, BigInteger value);
+
+ JsonGenerator write(String name, BigDecimal value);
+
+ JsonGenerator write(String name, int value);
+
+ JsonGenerator write(String name, long value);
+
+ JsonGenerator write(String name, double value);
+
+ JsonGenerator write(String name, boolean value);
+
+ JsonGenerator writeNull(String name);
+
+ JsonGenerator writeEnd();
+
+ JsonGenerator write(JsonValue value);
+
+ JsonGenerator write(String value);
+
+ JsonGenerator write(BigDecimal value);
+
+ JsonGenerator write(BigInteger value);
+
+ JsonGenerator write(int value);
+
+ JsonGenerator write(long value);
+
+ JsonGenerator write(double value);
+
+ JsonGenerator write(boolean value);
+
+ JsonGenerator writeNull();
+
+ @Override
+ void close();
+
+ @Override
+ void flush();
+
+}
diff --git a/src/main/java/javax/json/stream/JsonGeneratorFactory.java b/src/main/java/javax/json/stream/JsonGeneratorFactory.java
new file mode 100644
index 0000000..fbfc6c2
--- /dev/null
+++ b/src/main/java/javax/json/stream/JsonGeneratorFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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 javax.json.stream;
+
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+public interface JsonGeneratorFactory {
+ JsonGenerator createGenerator(Writer writer);
+
+ JsonGenerator createGenerator(OutputStream out);
+
+ JsonGenerator createGenerator(OutputStream out, Charset charset);
+
+ Map<String, ?> getConfigInUse();
+}
diff --git a/src/main/java/javax/json/stream/JsonLocation.java b/src/main/java/javax/json/stream/JsonLocation.java
new file mode 100644
index 0000000..0fc1501
--- /dev/null
+++ b/src/main/java/javax/json/stream/JsonLocation.java
@@ -0,0 +1,26 @@
+/*
+ * 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 javax.json.stream;
+
+public interface JsonLocation {
+ long getLineNumber();
+
+ long getColumnNumber();
+
+ long getStreamOffset();
+}
+
diff --git a/src/main/java/javax/json/stream/JsonParser.java b/src/main/java/javax/json/stream/JsonParser.java
new file mode 100644
index 0000000..cbfc720
--- /dev/null
+++ b/src/main/java/javax/json/stream/JsonParser.java
@@ -0,0 +1,71 @@
+/*
+ * 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 javax.json.stream;
+
+import java.io.Closeable;
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+
+public interface JsonParser extends Closeable {
+ boolean hasNext();
+
+ Event next();
+
+ String getString();
+
+ boolean isIntegralNumber();
+
+ int getInt();
+
+ long getLong();
+
+ BigDecimal getBigDecimal();
+
+ JsonLocation getLocation();
+
+ @Override
+ void close();
+
+ enum Event {
+ START_ARRAY, START_OBJECT,
+ KEY_NAME,
+ VALUE_STRING, VALUE_NUMBER, VALUE_TRUE, VALUE_FALSE, VALUE_NULL,
+ END_OBJECT, END_ARRAY
+ }
+
+ JsonObject getObject();
+
+ JsonValue getValue();
+
+ JsonArray getArray();
+
+ Stream<JsonValue> getArrayStream();
+
+ Stream<Map.Entry<String,JsonValue>> getObjectStream();
+
+ Stream<JsonValue> getValueStream();
+
+ void skipArray();
+
+ void skipObject();
+}
+
diff --git a/src/main/java/javax/json/stream/JsonParserFactory.java b/src/main/java/javax/json/stream/JsonParserFactory.java
new file mode 100644
index 0000000..aa28fb7
--- /dev/null
+++ b/src/main/java/javax/json/stream/JsonParserFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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 javax.json.stream;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+public interface JsonParserFactory {
+ JsonParser createParser(Reader reader);
+
+ JsonParser createParser(InputStream in);
+
+ JsonParser createParser(InputStream in, Charset charset);
+
+ JsonParser createParser(JsonObject obj);
+
+ JsonParser createParser(JsonArray array);
+
+ Map<String, ?> getConfigInUse();
+}
+
diff --git a/src/main/java/javax/json/stream/JsonParsingException.java b/src/main/java/javax/json/stream/JsonParsingException.java
new file mode 100644
index 0000000..757a8bc
--- /dev/null
+++ b/src/main/java/javax/json/stream/JsonParsingException.java
@@ -0,0 +1,39 @@
+/*
+ * 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 javax.json.stream;
+
+import javax.json.JsonException;
+
+public class JsonParsingException extends JsonException {
+ private final JsonLocation location;
+
+ public JsonParsingException(String message, JsonLocation location) {
+ super(message);
+ this.location = location;
+ }
+
+ public JsonParsingException(String message, Throwable cause, JsonLocation location) {
+ super(message, cause);
+ this.location = location;
+ }
+
+ public JsonLocation getLocation() {
+ return location;
+ }
+}
+
+
diff --git a/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java b/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java
new file mode 100644
index 0000000..4ab42d5
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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.johnzon.core;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.logging.Logger;
+
+public abstract class AbstractJsonFactory implements Serializable{
+
+ protected final Logger logger = Logger.getLogger(this.getClass().getName());
+
+ public static final String BUFFER_STRATEGY = "org.apache.johnzon.buffer-strategy";
+ public static final BufferStrategy DEFAULT_BUFFER_STRATEGY = BufferStrategy.QUEUE;
+
+ protected final Map<String, Object> internalConfig = new HashMap<String, Object>();
+
+ protected AbstractJsonFactory(final Map<String, ?> config, Collection<String> supportedConfigKeys, Collection<String> defaultSupportedConfigKeys) {
+ if(config != null && config.size() > 0) {
+
+ if(defaultSupportedConfigKeys != null) {
+ supportedConfigKeys = new ArrayList<String>(supportedConfigKeys);
+ supportedConfigKeys.addAll(defaultSupportedConfigKeys);
+ }
+
+ for (String configKey : config.keySet()) {
+ if(supportedConfigKeys.contains(configKey)) {
+ internalConfig.put(configKey, config.get(configKey));
+ } else {
+ logger.warning(configKey + " is not supported by " + getClass().getName());
+ }
+ }
+ }
+ }
+
+ protected BufferStrategy getBufferProvider() {
+ final Object name = internalConfig.get(BUFFER_STRATEGY);
+ if (name != null) {
+ return BufferStrategy.valueOf(name.toString().toUpperCase(Locale.ENGLISH));
+ }
+ return DEFAULT_BUFFER_STRATEGY;
+ }
+
+ protected int getInt(final String key, final int defaultValue) {
+ final Object intValue = internalConfig.get(key);
+ if (intValue == null) {
+ return defaultValue;
+ } else if (Number.class.isInstance(intValue)) {
+ return Number.class.cast(intValue).intValue();
+ }
+ return Integer.parseInt(intValue.toString());
+ }
+
+ protected boolean getBool(final String key, final boolean defaultValue) {
+ final Object boolValue = internalConfig.get(key);
+ if (boolValue == null) {
+ return defaultValue;
+ } else if (Boolean.class.isInstance(boolValue)) {
+ return Boolean.class.cast(boolValue);
+ }
+ return Boolean.parseBoolean(boolValue.toString());
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/BufferStrategy.java b/src/main/java/org/apache/johnzon/core/BufferStrategy.java
new file mode 100644
index 0000000..c37ecfb
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/BufferStrategy.java
@@ -0,0 +1,272 @@
+/*
+ * 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.johnzon.core;
+
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public enum BufferStrategy {
+ BY_INSTANCE {
+ @Override
+ public BufferProvider<char[]> newCharProvider(final int size) {
+ return new CharBufferByInstanceProvider(size);
+ }
+
+ @Override
+ public BufferProvider<StringBuilder> newStringBuilderProvider(final int size) {
+ return new StringBuilderByInstanceProvider(size);
+ }
+ },
+ THREAD_LOCAL {
+ @Override
+ public BufferProvider<char[]> newCharProvider(final int size) {
+ return new CharBufferThreadLocalProvider(size);
+ }
+
+ @Override
+ public BufferProvider<StringBuilder> newStringBuilderProvider(final int size) {
+ return new StringBuilderThreadLocalProvider(size);
+ }
+ },
+ QUEUE {
+ @Override
+ public BufferProvider<char[]> newCharProvider(final int size) {
+ return new CharBufferQueueProvider(size);
+ }
+
+ @Override
+ public BufferProvider<StringBuilder> newStringBuilderProvider(final int size) {
+ return new StringBuilderQueueProvider(size);
+ }
+ },
+ SINGLETON {
+ @Override
+ public BufferProvider<char[]> newCharProvider(final int size) {
+ return new CharBufferSingletonProvider(size);
+ }
+
+ @Override
+ public BufferProvider<StringBuilder> newStringBuilderProvider(final int size) {
+ return new StringBuilderSingletonProvider(size);
+ }
+ };
+
+ public abstract BufferProvider<char[]> newCharProvider(int size);
+ public abstract BufferProvider<StringBuilder> newStringBuilderProvider(int size);
+
+ public static interface BufferProvider<T> extends Serializable {
+ T newBuffer();
+
+ void release(T value);
+ }
+
+ private static class CharBufferSingletonProvider extends SingletonProvider<char[]> {
+ public CharBufferSingletonProvider(final int size) {
+ super(size);
+ }
+
+ @Override
+ protected char[] newInstance(int size) {
+ return new char[size];
+ }
+
+ @Override
+ public void release(final char[] value) {
+ // no-op
+ }
+ }
+
+ private static class StringBuilderSingletonProvider extends SingletonProvider<StringBuilder> {
+ public StringBuilderSingletonProvider(final int size) {
+ super(size);
+ }
+
+ @Override
+ protected StringBuilder newInstance(final int size) {
+ return new StringBuilder(size);
+ }
+
+ @Override
+ public void release(final StringBuilder value) {
+ value.setLength(0);
+ }
+ }
+
+ private static abstract class SingletonProvider<T> implements BufferProvider<T> {
+ protected final T buffer;
+
+ public SingletonProvider(final int size) {
+ buffer = newInstance(size);
+ }
+
+ protected abstract T newInstance(int size);
+
+ @Override
+ public T newBuffer() {
+ return buffer;
+ }
+
+ @Override
+ public void release(final T value) {
+ // no-op
+ }
+ }
+
+ private static abstract class ThreadLocalProvider<T> implements BufferProvider<T> {
+ private final ThreadLocalBufferCache<T> cache;
+
+ public ThreadLocalProvider(final int size) {
+ cache = new ThreadLocalBufferCache<T>(size) {
+ @Override
+ protected T newValue(int defaultSize) {
+ return newInstance(size);
+ }
+ };
+ }
+
+ protected abstract T newInstance(int size);
+
+ @Override
+ public T newBuffer() {
+ return cache.getCache();
+ }
+
+ @Override
+ public void release(final T value) {
+ cache.release(value);
+ }
+ }
+
+ private static class CharBufferThreadLocalProvider extends ThreadLocalProvider<char[]> {
+ public CharBufferThreadLocalProvider(int size) {
+ super(size);
+ }
+
+ @Override
+ protected char[] newInstance(final int size) {
+ return new char[size];
+ }
+ }
+
+ private static class StringBuilderThreadLocalProvider extends ThreadLocalProvider<StringBuilder> {
+ public StringBuilderThreadLocalProvider(int size) {
+ super(size);
+ }
+
+ @Override
+ protected StringBuilder newInstance(final int size) {
+ return new StringBuilder(size);
+ }
+
+ @Override
+ public void release(final StringBuilder value) {
+ value.setLength(0);
+ super.release(value);
+ }
+ }
+
+ private static class CharBufferByInstanceProvider implements BufferProvider<char[]> {
+ private final int size;
+
+ public CharBufferByInstanceProvider(final int size) {
+ this.size = size;
+ }
+
+ @Override
+ public char[] newBuffer() {
+ return new char[size];
+ }
+
+ @Override
+ public void release(final char[] value) {
+ // no-op
+ }
+ }
+
+ private static class StringBuilderByInstanceProvider implements BufferProvider<StringBuilder> {
+ private final int size;
+
+ public StringBuilderByInstanceProvider(final int size) {
+ this.size = size;
+ }
+
+ @Override
+ public StringBuilder newBuffer() {
+ return new StringBuilder(size);
+ }
+
+ @Override
+ public void release(final StringBuilder value) {
+ // no-op
+ }
+ }
+
+ private static abstract class QueueProvider<T> implements BufferProvider<T> {
+ private final int size;
+ private final ConcurrentLinkedQueue<T> queue = new ConcurrentLinkedQueue<T>();
+
+ public QueueProvider(final int size) {
+ this.size = size;
+ }
+
+ protected abstract T newInstance(int size);
+
+ @Override
+ public T newBuffer() {
+ final T buffer = queue.poll();
+ if (buffer == null) {
+ return newInstance(size);
+ }
+ return buffer;
+ }
+
+ @Override
+ public void release(final T value) {
+ queue.offer(value);
+ }
+ }
+
+ private static class CharBufferQueueProvider extends QueueProvider<char[]> {
+ public CharBufferQueueProvider(final int size) {
+ super(size);
+ }
+
+ @Override
+ protected char[] newInstance(int size) {
+ return new char[size];
+ }
+ }
+
+ private static class StringBuilderQueueProvider extends QueueProvider<StringBuilder> {
+ public StringBuilderQueueProvider(final int size) {
+ super(size);
+ }
+
+ @Override
+ protected StringBuilder newInstance(int size) {
+ return new StringBuilder(size);
+ }
+
+ @Override
+ public void release(final StringBuilder value) {
+ value.setLength(0);
+ super.release(value);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/CommentsJsonStreamParserImpl.java b/src/main/java/org/apache/johnzon/core/CommentsJsonStreamParserImpl.java
new file mode 100644
index 0000000..f9d298e
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/CommentsJsonStreamParserImpl.java
@@ -0,0 +1,73 @@
+/*
+ * 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.johnzon.core;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+public class CommentsJsonStreamParserImpl extends JsonStreamParserImpl {
+ public CommentsJsonStreamParserImpl(final InputStream inputStream,
+ final int maxStringLength,
+ final BufferStrategy.BufferProvider<char[]> bufferProvider,
+ final BufferStrategy.BufferProvider<char[]> valueBuffer,
+ final boolean autoAdjust) {
+ super(inputStream, maxStringLength, bufferProvider, valueBuffer, autoAdjust);
+ }
+
+ public CommentsJsonStreamParserImpl(final InputStream inputStream,
+ final Charset encoding,
+ final int maxStringLength,
+ final BufferStrategy.BufferProvider<char[]> bufferProvider,
+ final BufferStrategy.BufferProvider<char[]> valueBuffer,
+ final boolean autoAdjust) {
+ super(inputStream, encoding, maxStringLength, bufferProvider, valueBuffer, autoAdjust);
+ }
+
+ public CommentsJsonStreamParserImpl(final Reader reader,
+ final int maxStringLength,
+ final BufferStrategy.BufferProvider<char[]> bufferProvider,
+ final BufferStrategy.BufferProvider<char[]> valueBuffer,
+ final boolean autoAdjust) {
+ super(reader, maxStringLength, bufferProvider, valueBuffer, autoAdjust);
+ }
+
+ @Override
+ protected Event defaultHandling(final char c) {
+ if (c == '/') {
+ char next = readNextChar();
+ if (next == '/') { // fail
+ do {
+ next = readNextChar();
+ } while (next != EOL);
+ return next();
+ } else if (next == '*') {
+ next = 0;
+ char previous;
+ do {
+ previous = next;
+ next = readNextNonWhitespaceChar(readNextChar());
+ } while (next != '/' || previous != '*');
+ readNextNonWhitespaceChar(next);
+ return next();
+ }
+ }
+ return super.defaultHandling(c);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/DiffBase.java b/src/main/java/org/apache/johnzon/core/DiffBase.java
new file mode 100644
index 0000000..d6abfdf
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/DiffBase.java
@@ -0,0 +1,34 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+
+/**
+ * Commonly used methods for diffs
+ */
+class DiffBase {
+ protected boolean isJsonObject(JsonValue jsonValue) {
+ return jsonValue instanceof JsonObject;
+ }
+
+ protected boolean isJsonArray(JsonValue targetValue) {
+ return targetValue instanceof JsonArray;
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/Experimental.java b/src/main/java/org/apache/johnzon/core/Experimental.java
new file mode 100644
index 0000000..b645c99
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/Experimental.java
@@ -0,0 +1,33 @@
+/*
+ * 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.johnzon.core;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Marker for experimental API.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface Experimental {
+}
diff --git a/src/main/java/org/apache/johnzon/core/HStack.java b/src/main/java/org/apache/johnzon/core/HStack.java
new file mode 100644
index 0000000..b2d57bd
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/HStack.java
@@ -0,0 +1,66 @@
+/*
+ * 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.johnzon.core;
+
+import java.io.Serializable;
+
+public class HStack<T> implements Serializable {
+
+ private Node<T> topElement = null;
+ private int size;
+
+
+ private static final class Node<T> implements Serializable {
+ private final Node<T> previous;
+ private final T object;
+
+ private Node(final Node<T> previous, final T object) {
+ super();
+ this.previous = previous;
+ this.object = object;
+ }
+ }
+
+
+ void push(T object) {
+ topElement = new Node<T>(topElement, object);
+ size++;
+ }
+
+ T pop() {
+
+ if (topElement == null) {
+ return null;
+ }
+
+ T tmp = topElement.object;
+ topElement = topElement.previous;
+ size--;
+ return tmp;
+ }
+
+ T peek() {
+ return topElement == null ? null : topElement.object;
+ }
+
+ int size() {
+ return size;
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JohnzonJsonParser.java b/src/main/java/org/apache/johnzon/core/JohnzonJsonParser.java
new file mode 100644
index 0000000..b13852b
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JohnzonJsonParser.java
@@ -0,0 +1,134 @@
+/*
+ * 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.johnzon.core;
+
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import javax.json.stream.JsonLocation;
+import javax.json.stream.JsonParser;
+
+/**
+ * JsonParser with extended functionality
+ */
+public interface JohnzonJsonParser extends JsonParser {
+
+ boolean isNotTooLong();
+
+
+ public static class JohnzonJsonParserWrapper implements JohnzonJsonParser {
+ private final JsonParser jsonParser;
+
+ public JohnzonJsonParserWrapper(JsonParser jsonParser) {
+ this.jsonParser = jsonParser;
+ }
+
+ @Override
+ public boolean isNotTooLong() {
+ return true;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return jsonParser.hasNext();
+ }
+
+ @Override
+ public Event next() {
+ return jsonParser.next();
+ }
+
+ @Override
+ public String getString() {
+ return jsonParser.getString();
+ }
+
+ @Override
+ public boolean isIntegralNumber() {
+ return jsonParser.isIntegralNumber();
+ }
+
+ @Override
+ public int getInt() {
+ return jsonParser.getInt();
+ }
+
+ @Override
+ public long getLong() {
+ return jsonParser.getLong();
+ }
+
+ @Override
+ public BigDecimal getBigDecimal() {
+ return jsonParser.getBigDecimal();
+ }
+
+ @Override
+ public JsonLocation getLocation() {
+ return jsonParser.getLocation();
+ }
+
+ @Override
+ public void close() {
+ jsonParser.close();
+ }
+
+ @Override
+ public JsonObject getObject() {
+ return jsonParser.getObject();
+ }
+
+ @Override
+ public JsonValue getValue() {
+ return jsonParser.getValue();
+ }
+
+ @Override
+ public JsonArray getArray() {
+ return jsonParser.getArray();
+ }
+
+ @Override
+ public Stream<JsonValue> getArrayStream() {
+ return jsonParser.getArrayStream();
+ }
+
+ @Override
+ public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
+ return jsonParser.getObjectStream();
+ }
+
+ @Override
+ public Stream<JsonValue> getValueStream() {
+ return jsonParser.getValueStream();
+ }
+
+ @Override
+ public void skipArray() {
+ jsonParser.skipArray();
+ }
+
+ @Override
+ public void skipObject() {
+ jsonParser.skipObject();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java b/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
new file mode 100644
index 0000000..7790288
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
@@ -0,0 +1,330 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+class JsonArrayBuilderImpl implements JsonArrayBuilder, Serializable {
+ private List<JsonValue> tmpList;
+
+ public JsonArrayBuilderImpl() {
+ }
+
+ public JsonArrayBuilderImpl(JsonArray initialData) {
+ tmpList = new ArrayList<>(initialData);
+ }
+
+ public JsonArrayBuilderImpl(Collection<?> initialData) {
+ tmpList = new ArrayList<>();
+ for (Object initialValue : initialData) {
+ add(initialValue);
+ }
+ }
+
+ @Override
+ public JsonArrayBuilder addAll(final JsonArrayBuilder builder) {
+ for (JsonValue value : builder.build())
+ {
+ add(value);
+ }
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final JsonValue value) {
+ addValue(index, value);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final String value) {
+ addValue(index, new JsonStringImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final BigDecimal value) {
+ addValue(index, new JsonNumberImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final BigInteger value) {
+ addValue(index, new JsonNumberImpl(new BigDecimal(value)));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final int value) {
+ addValue(index, new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final long value) {
+ addValue(index, new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final double value) {
+ addValue(index, new JsonDoubleImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final boolean value) {
+ addValue(index, value ? JsonValue.TRUE : JsonValue.FALSE);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder addNull(final int index) {
+ addValue(index, JsonValue.NULL);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final JsonObjectBuilder builder) {
+ addValue(index, builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int index, final JsonArrayBuilder builder) {
+ addValue(index, builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final JsonValue value) {
+ setValue(index, value);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final String value) {
+ setValue(index, new JsonStringImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final BigDecimal value) {
+ setValue(index, new JsonNumberImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final BigInteger value) {
+ setValue(index, new JsonNumberImpl(new BigDecimal(value)));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final int value) {
+ setValue(index, new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final long value) {
+ setValue(index, new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final double value) {
+ setValue(index, new JsonDoubleImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final boolean value) {
+ setValue(index, value ? JsonValue.TRUE : JsonValue.FALSE);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder setNull(final int index) {
+ setValue(index, JsonValue.NULL);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final JsonObjectBuilder builder) {
+ setValue(index, builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder set(final int index, final JsonArrayBuilder builder) {
+ setValue(index, builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder remove(final int index) {
+ tmpList.remove(index);
+ return this;
+ }
+
+ public JsonArrayBuilder add(final Object value) {
+ if (value instanceof JsonValue) {
+ add((JsonValue) value);
+ } else if (value instanceof BigDecimal) {
+ add((BigDecimal) value);
+ } else if (value instanceof BigInteger) {
+ add((BigInteger) value);
+ } else if (value instanceof Boolean) {
+ add((boolean) value);
+ } else if (value instanceof Double) {
+ add((double) value);
+ } else if (value instanceof Integer) {
+ add((int) value);
+ } else if (value instanceof Long) {
+ add((long) value);
+ } else if (value instanceof String) {
+ add((String) value);
+ } else {
+ throw new JsonException("Illegal JSON type! type=" + value.getClass());
+ }
+
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final JsonValue value) {
+ addValue(value);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final String value) {
+ addValue(new JsonStringImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final BigDecimal value) {
+ addValue(new JsonNumberImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final BigInteger value) {
+ addValue(new JsonNumberImpl(new BigDecimal(value)));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final int value) {
+ addValue(new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final long value) {
+ addValue(new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final double value) {
+ addValue(new JsonDoubleImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final boolean value) {
+ addValue(value ? JsonValue.TRUE : JsonValue.FALSE);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder addNull() {
+ addValue(JsonValue.NULL);
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final JsonObjectBuilder builder) {
+ addValue(builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonArrayBuilder add(final JsonArrayBuilder builder) {
+ addValue(builder.build());
+ return this;
+ }
+
+ private void setValue(int idx, JsonValue value) {
+ if (value == null || tmpList == null) {
+ throw npe();
+ }
+ tmpList.set(idx, value);
+ }
+
+ private void addValue(JsonValue value) {
+ if (value == null) {
+ throw npe();
+ }
+
+ if(tmpList==null){
+ tmpList=new ArrayList<>();
+ }
+
+ tmpList.add(value);
+ }
+
+ private void addValue(int idx, JsonValue value) {
+ if (value == null) {
+ throw npe();
+ }
+
+ if(tmpList==null){
+ tmpList=new ArrayList<>();
+ }
+
+ tmpList.add(idx, value);
+ }
+
+ @Override
+ public JsonArray build() {
+ if(tmpList == null) {
+ return new JsonArrayImpl(Collections.<JsonValue>emptyList());
+ }
+ return new JsonArrayImpl(Collections.unmodifiableList(tmpList));
+ }
+
+ private static NullPointerException npe() {
+ throw new NullPointerException("value/builder must not be null");
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonArrayImpl.java b/src/main/java/org/apache/johnzon/core/JsonArrayImpl.java
new file mode 100644
index 0000000..a8d85e4
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonArrayImpl.java
@@ -0,0 +1,235 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+class JsonArrayImpl extends AbstractList<JsonValue> implements JsonArray, Serializable {
+ private Integer hashCode = null;
+ private final List<JsonValue> unmodifieableBackingList;
+ private int size = -1;
+
+ JsonArrayImpl(final List<JsonValue> backingList) {
+ super();
+ this.unmodifieableBackingList = backingList;
+ }
+
+ private <T> T value(final int idx, final Class<T> type) {
+ if (idx > unmodifieableBackingList.size()) {
+ throw new IndexOutOfBoundsException(idx + "/" + unmodifieableBackingList.size());
+ }
+ return type.cast(unmodifieableBackingList.get(idx));
+ }
+
+ @Override
+ public JsonObject getJsonObject(final int index) {
+ return value(index, JsonObject.class);
+ }
+
+ @Override
+ public JsonArray getJsonArray(final int index) {
+ return value(index, JsonArray.class);
+ }
+
+ @Override
+ public JsonNumber getJsonNumber(final int index) {
+ return value(index, JsonNumber.class);
+ }
+
+ @Override
+ public JsonString getJsonString(final int index) {
+ return value(index, JsonString.class);
+ }
+
+ @Override
+ public <T extends JsonValue> List<T> getValuesAs(Class<T> clazz) {
+ return (List<T>) unmodifieableBackingList;
+ }
+
+ @Override
+ public <T, K extends JsonValue> List<T> getValuesAs(Function<K, T> func) {
+ List<T> result = new ArrayList<T>();
+ for (K value : (List<K>) this) {
+ result.add(func.apply(value));
+ }
+ return result;
+ }
+
+ @Override
+ public String getString(final int index) {
+ return value(index, JsonString.class).getString();
+ }
+
+ @Override
+ public String getString(final int index, final String defaultValue) {
+ JsonValue val = null;
+ int s = size;
+
+ if (s == -1) {
+ s = unmodifieableBackingList.size();
+ size = s;
+ }
+
+ if (index > s - 1 || !((val = get(index)) instanceof JsonString)) {
+ return defaultValue;
+ } else {
+ return JsonString.class.cast(val).getString();
+ }
+ }
+
+ @Override
+ public int getInt(final int index) {
+ return value(index, JsonNumber.class).intValue();
+ }
+
+ @Override
+ public int getInt(final int index, final int defaultValue) {
+ JsonValue val = null;
+ int s = size;
+
+ if (s == -1) {
+ s = unmodifieableBackingList.size();
+ size = s;
+ }
+
+ if (index > s - 1 || !((val = get(index)) instanceof JsonNumber)) {
+ return defaultValue;
+ } else {
+ return JsonNumber.class.cast(val).intValue();
+ }
+ }
+
+ @Override
+ public boolean getBoolean(final int index) {
+ final JsonValue val = value(index, JsonValue.class);
+
+ if (JsonValue.TRUE.equals(val)) {
+ return true;
+ } else if (JsonValue.FALSE.equals(val)) {
+ return false;
+ } else {
+ throw new ClassCastException();
+ }
+
+ }
+
+ @Override
+ public boolean getBoolean(final int index, final boolean defaultValue) {
+
+ int s = size;
+
+ if (s == -1) {
+ s = unmodifieableBackingList.size();
+ size = s;
+ }
+
+ if (index > s - 1) {
+ return defaultValue;
+ }
+
+ final JsonValue val = get(index);
+ return JsonValue.TRUE.equals(val) || !JsonValue.FALSE.equals(val) && defaultValue;
+ }
+
+ @Override
+ public boolean isNull(final int index) {
+ return JsonValue.NULL.equals(value(index, JsonValue.class));
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return ValueType.ARRAY;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("[");
+ final Iterator<JsonValue> it = unmodifieableBackingList.iterator();
+ boolean hasNext = it.hasNext();
+ while (hasNext) {
+ final JsonValue jsonValue = it.next();
+ if (JsonString.class.isInstance(jsonValue)) {
+ builder.append(jsonValue.toString());
+ } else {
+ builder.append(jsonValue != JsonValue.NULL ? jsonValue.toString() : JsonChars.NULL);
+ }
+ hasNext = it.hasNext();
+ if (hasNext) {
+ builder.append(",");
+ }
+ }
+ return builder.append(']').toString();
+ }
+
+ @Override
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ @Override
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return JsonArray.class.isInstance(obj) && super.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ Integer h = hashCode;
+ if (h == null) {
+ h = unmodifieableBackingList.hashCode();
+ hashCode = h;
+ }
+ return h;
+ }
+
+ @Override
+ public JsonValue get(final int index) {
+ return unmodifieableBackingList.get(index);
+ }
+
+ @Override
+ public int size() {
+ return unmodifieableBackingList.size();
+ }
+
+ private Object writeReplace() throws ObjectStreamException {
+ return new SerializableValue(toString());
+ }
+
+ @Override
+ public JsonValue getValue(String jsonPointer) {
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonBuilderFactoryImpl.java b/src/main/java/org/apache/johnzon/core/JsonBuilderFactoryImpl.java
new file mode 100644
index 0000000..81719ff
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonBuilderFactoryImpl.java
@@ -0,0 +1,92 @@
+/*
+ * 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.johnzon.core;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+
+class JsonBuilderFactoryImpl implements JsonBuilderFactory {
+ private final Map<String, Object> internalConfig = new HashMap<String, Object>();
+ private static final String[] SUPPORTED_CONFIG_KEYS = new String[] {
+ //nothing yet
+
+ };
+ protected final Logger logger = Logger.getLogger(this.getClass().getName());
+
+ JsonBuilderFactoryImpl(final Map<String, ?> config) {
+ if (config != null && config.size() > 0) {
+ final List<String> supportedConfigKeys = Arrays.asList(SUPPORTED_CONFIG_KEYS);
+ for (String configKey : config.keySet()) {
+ if(supportedConfigKeys.contains(configKey)) {
+ internalConfig.put(configKey, config.get(configKey));
+ } else {
+ logger.warning(configKey + " is not supported by " + getClass().getName());
+ }
+ }
+ }
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder() {
+ return new JsonObjectBuilderImpl();
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder(JsonObject initialData) {
+ return new JsonObjectBuilderImpl(initialData);
+ }
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder() {
+ return new JsonArrayBuilderImpl();
+ }
+
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder(JsonArray initialData) {
+ return new JsonArrayBuilderImpl(initialData);
+ }
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder(Collection<?> initialData) {
+ return new JsonArrayBuilderImpl(initialData);
+ }
+
+ @Override
+ public Map<String, ?> getConfigInUse() {
+ return Collections.unmodifiableMap(internalConfig);
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder(Map<String, Object> initialValues) {
+ return new JsonObjectBuilderImpl(initialValues);
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonChars.java b/src/main/java/org/apache/johnzon/core/JsonChars.java
new file mode 100644
index 0000000..1e36913
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonChars.java
@@ -0,0 +1,82 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.stream.JsonParser.Event;
+
+public interface JsonChars {
+ char EOF = Character.MIN_VALUE;
+
+ char START_OBJECT_CHAR = '{';
+ char END_OBJECT_CHAR = '}';
+ char START_ARRAY_CHAR = '[';
+ char END_ARRAY_CHAR = ']';
+ char QUOTE_CHAR = '"';
+ char COMMA_CHAR = ',';
+ char KEY_SEPARATOR = ':';
+
+ char EOL = '\n';
+ char SPACE = ' ';
+
+ char TRUE_T = 't';
+ char TRUE_R = 'r';
+ char TRUE_U = 'u';
+ char TRUE_E = 'e';
+ char FALSE_F = 'f';
+ char FALSE_A = 'a';
+ char FALSE_L = 'l';
+ char FALSE_S = 's';
+ char FALSE_E = 'e';
+ char NULL_N = 'n';
+ char NULL_U = 'u';
+ char NULL_L = 'l';
+
+ char ZERO = '0';
+ char NINE = '9';
+ char DOT = '.';
+ char MINUS = '-';
+ char PLUS = '+';
+ char EXP_LOWERCASE = 'e';
+ char EXP_UPPERCASE = 'E';
+ char ESCAPE_CHAR = '\\';
+
+ char TAB = '\t';
+ char BACKSPACE = '\b';
+ char FORMFEED = '\f';
+ char CR = '\r';
+
+ String NULL = "null".intern();
+
+ static final byte START_ARRAY = (byte) Event.START_ARRAY.ordinal();
+ static final byte START_OBJECT = (byte) Event.START_OBJECT.ordinal();
+ static final byte KEY_NAME=(byte) Event.KEY_NAME.ordinal();
+ static final byte VALUE_STRING=(byte) Event.VALUE_STRING.ordinal();
+ static final byte VALUE_NUMBER=(byte) Event.VALUE_NUMBER.ordinal();
+ static final byte VALUE_TRUE=(byte) Event.VALUE_TRUE.ordinal();
+ static final byte VALUE_FALSE=(byte) Event.VALUE_FALSE.ordinal();
+ static final byte VALUE_NULL=(byte) Event.VALUE_NULL.ordinal();
+ static final byte END_OBJECT=(byte) Event.END_OBJECT.ordinal();
+ static final byte END_ARRAY=(byte) Event.END_ARRAY.ordinal();
+
+ static final byte COMMA_EVENT=Byte.MAX_VALUE;
+ static final byte KEY_SEPARATOR_EVENT=Byte.MIN_VALUE;
+
+ static final Event[] EVT_MAP =Event.values();
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonDoubleImpl.java b/src/main/java/org/apache/johnzon/core/JsonDoubleImpl.java
new file mode 100644
index 0000000..2e99c8a
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonDoubleImpl.java
@@ -0,0 +1,137 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Objects;
+
+final class JsonDoubleImpl implements JsonNumber, Serializable {
+ private final double value;
+
+ private Integer hashCode = null;
+
+ JsonDoubleImpl(final double value) {
+
+ if(Double.isInfinite(value) || Double.isNaN(value)) {
+ throw new NumberFormatException("double value must not be NaN or Infinite");
+ }
+
+ this.value = value;
+ }
+
+ @Override
+ public Number numberValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return false;
+ }
+
+ @Override
+ public int intValue() {
+ return (int) value;
+ }
+
+ @Override
+ public int intValueExact() {
+ checkFractionalPart();
+ return intValue();
+ }
+
+ @Override
+ public long longValue() {
+ return (long) value;
+ }
+
+ @Override
+ public long longValueExact() {
+ checkFractionalPart();
+ return (long) value;
+ }
+
+ @Override
+ public BigInteger bigIntegerValue() {
+ return new BigDecimal(toString()).toBigInteger();
+ }
+
+ @Override
+ public BigInteger bigIntegerValueExact() {
+ return new BigDecimal(toString()).toBigIntegerExact();
+ }
+
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ @Override
+ public BigDecimal bigDecimalValue() {
+ return new BigDecimal(toString());
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return ValueType.NUMBER;
+ }
+
+ @Override
+ public String toString() {
+ return Double.toString(value);
+ }
+
+ @Override
+ public int hashCode() {
+ if (hashCode == null) {
+ hashCode = bigDecimalValue().hashCode();
+ }
+
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (JsonDoubleImpl.class.isInstance(obj)) {
+ return JsonDoubleImpl.class.cast(obj).value == value;
+ }
+ return JsonNumber.class.isInstance(obj) && Objects.equals(JsonNumber.class.cast(obj).bigDecimalValue(), bigDecimalValue());
+ }
+
+ private void checkFractionalPart() {
+ if ((value % 1) != 0) {
+ throw new ArithmeticException("Not an int/long, use other value readers");
+ }
+ }
+
+ @Override
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ @Override
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonGeneratorFactoryImpl.java b/src/main/java/org/apache/johnzon/core/JsonGeneratorFactoryImpl.java
new file mode 100644
index 0000000..92abbcd
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonGeneratorFactoryImpl.java
@@ -0,0 +1,79 @@
+/*
+ * 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.johnzon.core;
+
+import static java.util.Arrays.asList;
+
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+
+public class JsonGeneratorFactoryImpl extends AbstractJsonFactory implements JsonGeneratorFactory {
+ public static final String GENERATOR_BUFFER_LENGTH = "org.apache.johnzon.default-char-buffer-generator";
+ public static final int DEFAULT_GENERATOR_BUFFER_LENGTH = Integer.getInteger(GENERATOR_BUFFER_LENGTH, 64 * 1024); //64k
+
+ static final Collection<String> SUPPORTED_CONFIG_KEYS = asList(
+ JsonGenerator.PRETTY_PRINTING, GENERATOR_BUFFER_LENGTH, BUFFER_STRATEGY
+ );
+ //key caching currently disabled
+ private final ConcurrentMap<String, String> cache = null;//new ConcurrentHashMap<String, String>();
+ private final boolean pretty;
+ private final BufferStrategy.BufferProvider<char[]> bufferProvider;
+
+ public JsonGeneratorFactoryImpl(final Map<String, ?> config) {
+
+ super(config, SUPPORTED_CONFIG_KEYS, null);
+
+ this.pretty = getBool(JsonGenerator.PRETTY_PRINTING, false);
+
+ final int bufferSize = getInt(GENERATOR_BUFFER_LENGTH, DEFAULT_GENERATOR_BUFFER_LENGTH);
+ if (bufferSize <= 0) {
+ throw new IllegalArgumentException("buffer length must be greater than zero");
+ }
+
+ this.bufferProvider = getBufferProvider().newCharProvider(bufferSize);
+ }
+
+ @Override
+ public JsonGenerator createGenerator(final Writer writer) {
+ return new JsonGeneratorImpl(writer, bufferProvider, cache, pretty);
+ }
+
+ @Override
+ public JsonGenerator createGenerator(final OutputStream out) {
+ return new JsonGeneratorImpl(out, bufferProvider, cache, pretty);
+ }
+
+ @Override
+ public JsonGenerator createGenerator(final OutputStream out, final Charset charset) {
+ return new JsonGeneratorImpl(out,charset, bufferProvider, cache, pretty);
+ }
+
+ @Override
+ public Map<String, ?> getConfigInUse() {
+ return Collections.unmodifiableMap(internalConfig);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java b/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java
new file mode 100644
index 0000000..bc17340
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java
@@ -0,0 +1,827 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonException;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import javax.json.stream.JsonGenerationException;
+import javax.json.stream.JsonGenerator;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+class JsonGeneratorImpl implements JsonGenerator, JsonChars, Serializable {
+ private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+
+ private final transient Writer writer;
+ private final BufferStrategy.BufferProvider<char[]> bufferProvider;
+ private final char[] buffer;
+ private int bufferPos = 0;
+ private final boolean prettyPrint;
+ private static final String INDENT = " ";
+ //private final ConcurrentMap<String, String> cache;
+ private int depth = 0;
+
+ private final HStack<GeneratorState> state = new HStack<GeneratorState>();
+
+ private enum GeneratorState {
+ INITIAL(false, true), START_OBJECT(true, false), IN_OBJECT(true, false), AFTER_KEY(false, true), START_ARRAY(false, true), IN_ARRAY(
+ false, true), END(false, false);
+
+ private final boolean acceptsKey;
+ private final boolean acceptsValue;
+
+ GeneratorState(final boolean acceptsKey, final boolean acceptsValue) {
+ this.acceptsKey = acceptsKey;
+ this.acceptsValue = acceptsValue;
+ }
+ }
+
+ JsonGeneratorImpl(final Writer writer, final BufferStrategy.BufferProvider<char[]> bufferProvider,
+ final ConcurrentMap<String, String> cache, final boolean prettyPrint) {
+ this.writer = writer;
+ //this.cache = cache;
+ this.buffer = bufferProvider.newBuffer();
+ this.bufferProvider = bufferProvider;
+ this.prettyPrint = prettyPrint;
+ state.push(GeneratorState.INITIAL);
+ }
+
+ JsonGeneratorImpl(final OutputStream out, final BufferStrategy.BufferProvider<char[]> bufferProvider,
+ final ConcurrentMap<String, String> cache, final boolean prettyPrint) {
+ this(new OutputStreamWriter(out, UTF8_CHARSET), bufferProvider, cache, prettyPrint);
+ }
+
+ JsonGeneratorImpl(final OutputStream out, final Charset encoding, final BufferStrategy.BufferProvider<char[]> bufferProvider,
+ final ConcurrentMap<String, String> cache, final boolean prettyPrint) {
+ this(new OutputStreamWriter(out, encoding), bufferProvider, cache, prettyPrint);
+ }
+
+ private void writeEol() {
+ if (prettyPrint) {
+ justWrite(EOL);
+ }
+ }
+
+ private void writeIndent() {
+ if (prettyPrint && depth > 0) {
+ for (int i = 0; i < depth; i++) {
+ justWrite(INDENT);
+ }
+ }
+ }
+
+ //caching currently disabled
+ //two problems:
+ // 1) not easy to get the escaped value efficiently when its streamed and the buffer is full and needs to be flushed
+ // 2) we have to use a kind of bounded threadsafe map to let the cache not grow indefinitely
+ private void writeCachedKey(final String name) {
+ /* String k = cache.get(name);
+
+ if (k == null) {
+
+ justWrite(QUOTE_CHAR);
+ int start = bufferPos;
+ writeEscaped0(name);
+ int end = bufferPos;
+ String escaped= get from buffer
+ ---
+ //FIXME if buffer is flushed this will not work here
+ cache.putIfAbsent(name, escaped);
+ justWrite(QUOTE_CHAR);
+ justWrite(KEY_SEPARATOR);
+ }else*/
+ {
+ justWrite(QUOTE_CHAR);
+ writeEscaped0(name);
+ justWrite(QUOTE_CHAR);
+ justWrite(KEY_SEPARATOR);
+ }
+
+ }
+
+ @Override
+ public JsonGenerator writeStartObject() {
+ prepareValue();
+ state.push(GeneratorState.START_OBJECT);
+
+ writeIndent();
+ depth++;
+ justWrite(START_OBJECT_CHAR);
+
+ writeEol();
+ return this;
+ }
+
+ @Override
+ public JsonGenerator writeStartObject(final String name) {
+ checkObject(false);
+ writeKey(name);
+ justWrite(START_OBJECT_CHAR);
+ writeEol();
+ state.push(GeneratorState.START_OBJECT);
+ depth++;
+ return this;
+ }
+
+ @Override
+ public JsonGenerator writeStartArray() {
+ prepareValue();
+ writeIndent();
+ state.push(GeneratorState.START_ARRAY);
+ justWrite(START_ARRAY_CHAR);
+ depth++;
+ writeEol();
+ return this;
+ }
+
+ @Override
+ public JsonGenerator writeStartArray(final String name) {
+ checkObject(false);
+ writeKey(name);
+ justWrite(START_ARRAY_CHAR);
+ writeEol();
+ state.push(GeneratorState.START_ARRAY);
+ depth++;
+ return this;
+ }
+
+ private void writeJsonValue(final String name, final JsonValue value) {
+ checkObject(false);
+ //TODO check null handling
+ switch (value.getValueType()) {
+ case ARRAY:
+ writeStartArray(name);
+ final JsonArray array = JsonArray.class.cast(value);
+ final Iterator<JsonValue> ait = array.iterator();
+ while (ait.hasNext()) {
+ write(ait.next());
+ }
+ writeEnd();
+
+ break;
+ case OBJECT:
+ writeStartObject(name);
+ final JsonObject object = JsonObject.class.cast(value);
+ final Iterator<Map.Entry<String, JsonValue>> oit = object.entrySet().iterator();
+ while (oit.hasNext()) {
+ final Map.Entry<String, JsonValue> keyval = oit.next();
+ write(keyval.getKey(), keyval.getValue());
+ }
+ writeEnd();
+
+ break;
+ case STRING:
+ write(name, JsonString.class.cast(value).getString());
+ break;
+ case NUMBER:
+ //TODO optimize
+ final JsonNumber number = JsonNumber.class.cast(value);
+ if (number.isIntegral()) {
+ write(name, number.longValueExact());
+ } else {
+ write(name, number.bigDecimalValue());
+ }
+ break;
+ case TRUE:
+ write(name, true);
+ break;
+ case FALSE:
+ write(name, false);
+ break;
+ case NULL:
+ writeNull(name);
+ break;
+ default:
+ throw new JsonGenerationException("Unknown JsonValue type");
+ }
+ }
+
+ private void writeJsonValue(final JsonValue value) {
+ checkArray(true);
+ //TODO check null handling
+ switch (value.getValueType()) {
+ case ARRAY:
+ writeStartArray();
+ final JsonArray array = JsonArray.class.cast(value);
+ final Iterator<JsonValue> ait = array.iterator();
+ while (ait.hasNext()) {
+ write(ait.next());
+ }
+ writeEnd();
+
+ break;
+ case OBJECT:
+ writeStartObject();
+ final JsonObject object = JsonObject.class.cast(value);
+ final Iterator<Map.Entry<String, JsonValue>> oit = object.entrySet().iterator();
+ while (oit.hasNext()) {
+ final Map.Entry<String, JsonValue> keyval = oit.next();
+ write(keyval.getKey(), keyval.getValue());
+ }
+ writeEnd();
+
+ break;
+ case STRING:
+ write(JsonString.class.cast(value).getString());
+ break;
+ case NUMBER:
+ //TODO optimize
+ final JsonNumber number = JsonNumber.class.cast(value);
+ if (number.isIntegral()) {
+ write(number.longValueExact());
+ } else {
+ write(number.bigDecimalValue());
+ }
+ break;
+ case TRUE:
+ write(true);
+ break;
+ case FALSE:
+ write(false);
+ break;
+ case NULL:
+ writeNull();
+ break;
+ default:
+ throw new JsonGenerationException("Unknown JsonValue type");
+ }
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final JsonValue value) {
+ checkObject(false);
+ writeJsonValue(name, value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final String value) {
+ checkObject(false);
+ writeKey(name);
+ writeValueAsJsonString(value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final BigInteger value) {
+ checkObject(false);
+ writeKey(name);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final BigDecimal value) {
+ checkObject(false);
+ writeKey(name);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final int value) {
+ checkObject(false);
+ writeKey(name);
+ writeValue(value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final long value) {
+ checkObject(false);
+ writeKey(name);
+ writeValue(value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final double value) {
+ checkObject(false);
+ checkDoubleRange(value);
+ writeKey(name);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String name, final boolean value) {
+ checkObject(false);
+ writeKey(name);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator writeNull(final String name) {
+ checkObject(false);
+ writeKey(name);
+ writeValue(NULL);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator writeEnd() {
+ checkArrayOrObject(false);
+ final GeneratorState last = state.pop();
+ depth--;
+ if (last != GeneratorState.START_ARRAY) {
+ writeEol();
+ }
+ writeIndent();
+ if (last == GeneratorState.IN_ARRAY || last == GeneratorState.START_ARRAY) {
+ justWrite(END_ARRAY_CHAR);
+ } else {
+ justWrite(END_OBJECT_CHAR);
+ }
+ alignState();
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final JsonValue value) {
+ checkArray(true);
+ writeJsonValue(value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final String value) {
+ checkArray(false);
+ writeValueAsJsonString(value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final BigDecimal value) {
+ checkArray(false);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final BigInteger value) {
+ checkArray(false);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final int value) {
+ checkArray(false);
+ writeValue(value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final long value) {
+ checkArray(false);
+ writeValue(value);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final double value) {
+ checkArray(false);
+ checkDoubleRange(value);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator write(final boolean value) {
+ checkArray(false);
+ writeValue(String.valueOf(value));
+ return this;
+ }
+
+ @Override
+ public JsonGenerator writeNull() {
+ checkArray(false);
+ writeValue(NULL);
+ return this;
+ }
+
+ @Override
+ public JsonGenerator writeKey(final String key) {
+ final GeneratorState currentState = currentState();
+ if (!currentState.acceptsKey) {
+ throw new JsonGenerationException("state " + currentState + " does not accept a key");
+ }
+ if (currentState == GeneratorState.IN_OBJECT) {
+ justWrite(COMMA_CHAR);
+ writeEol();
+ }
+
+ writeIndent();
+
+ writeCachedKey(key);
+ state.push(GeneratorState.AFTER_KEY);
+ return this;
+ }
+
+
+
+ @Override
+ public void close() {
+ try {
+ if (currentState() != GeneratorState.END) {
+ throw new JsonGenerationException("Invalid json");
+ }
+ } finally {
+ flushBuffer();
+ try {
+ writer.close();
+ } catch (final IOException e) {
+ throw new JsonException(e.getMessage(), e);
+ } finally {
+ bufferProvider.release(buffer);
+ }
+ }
+ }
+
+ @Override
+ public void flush() {
+ flushBuffer();
+ try {
+ writer.flush();
+ } catch (final IOException e) {
+ throw new JsonException(e.getMessage(), e);
+ }
+ }
+
+ private void flushBuffer() {
+ if (bufferPos > 0) {
+ try {
+ writer.write(buffer, 0, bufferPos);
+ bufferPos = 0;
+ } catch (final IOException e) {
+ throw new JsonException(e.getMessage(), e);
+ }
+ }
+ }
+
+ private void writeEscaped0(final String value) {
+ int len = 0;
+ if (value == null || (len = value.length()) == 0) {
+ return;
+ }
+
+ for (int i = 0; i < len; i++) {
+ char c = value.charAt(i);
+
+ while (c != ESCAPE_CHAR && c != QUOTE_CHAR && c >= SPACE) {
+
+ //read fast
+ justWrite(c);
+
+ if (i >= len - 1) {
+ return;
+ }
+
+ i++;
+ c = value.charAt(i);
+ }
+
+ switch (c) {
+ case QUOTE_CHAR:
+ case ESCAPE_CHAR:
+ justWrite(ESCAPE_CHAR);
+ justWrite(c);
+ break;
+ default:
+ if (c < SPACE) {
+ switch (c) {
+ case EOL:
+ justWrite("\\n");
+ break;
+ case '\r':
+ justWrite("\\r");
+ break;
+ case '\t':
+ justWrite("\\t");
+ break;
+ case '\b':
+ justWrite("\\b");
+ break;
+ case '\f':
+ justWrite("\\f");
+ break;
+ default:
+ justWrite(toUnicode(c));
+ }
+ } else if ((c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+ justWrite(toUnicode(c));
+ } else {
+ justWrite(c);
+ }
+ }
+ }
+ }
+
+ private static final String UNICODE_PREFIX = "\\u";
+ private static final String UNICODE_PREFIX_HELPER = "000";
+
+ private static String toUnicode(final char c) {
+ final String hex = UNICODE_PREFIX_HELPER + Integer.toHexString(c);
+ final String s = UNICODE_PREFIX + hex.substring(hex.length() - 4);
+ return s;
+ }
+
+ private void justWrite(final String value) {
+ final int valueLength = value.length();
+
+ if (bufferPos + valueLength >= buffer.length) {
+
+ int start = 0;
+ int len = buffer.length - bufferPos;
+
+ while (true) {
+ int end = start + len;
+ if (end > valueLength) {
+ end = valueLength;
+ }
+
+ value.getChars(start, end, buffer, bufferPos);
+
+ bufferPos += (end - start);
+ start += (len);
+
+ if (start >= valueLength) {
+ return;
+ }
+
+ if (bufferPos >= buffer.length) {
+ flushBuffer();
+ len = buffer.length;
+ }
+ }
+ } else {
+ //fits completely into the buffer
+ value.getChars(0, valueLength, buffer, bufferPos);
+ bufferPos += valueLength;
+ }
+ }
+
+ private void justWrite(final char value) {
+ if (bufferPos >= buffer.length) {
+ flushBuffer();
+ }
+ buffer[bufferPos++] = value;
+ }
+
+ private void checkObject(final boolean allowInitial) {
+ final GeneratorState currentState = currentState();
+ if (currentState != GeneratorState.IN_OBJECT && currentState != GeneratorState.START_OBJECT) {
+ if (!allowInitial || currentState != GeneratorState.INITIAL) {
+ throw new JsonGenerationException("write(name, param) is only valid in objects");
+ }
+ }
+ }
+
+ private void checkArray(final boolean allowInitial) {
+ final GeneratorState currentState = currentState();
+ if (currentState != GeneratorState.IN_ARRAY && currentState != GeneratorState.START_ARRAY) {
+ if (!allowInitial || currentState != GeneratorState.INITIAL) {
+ throw new JsonGenerationException("write(param) is only valid in arrays");
+ }
+ }
+ }
+
+ private void checkArrayOrObject(final boolean allowInitial) {
+ final GeneratorState currentState = currentState();
+ if (currentState != GeneratorState.IN_ARRAY && currentState != GeneratorState.START_ARRAY
+ && currentState != GeneratorState.IN_OBJECT && currentState != GeneratorState.START_OBJECT) {
+ if (!allowInitial || currentState != GeneratorState.INITIAL) {
+ throw new JsonGenerationException("only valid within array or object");
+ }
+ }
+ }
+
+ private static void checkDoubleRange(final double value) {
+ if (Double.isInfinite(value) || Double.isNaN(value)) {
+ throw new NumberFormatException("double can't be infinite or NaN");
+ }
+ }
+
+ private void prepareValue() {
+ final GeneratorState currentState = currentState();
+ if (!currentState.acceptsValue) {
+ throw new JsonGenerationException("state " + currentState + " does not accept a value");
+ }
+ if (currentState == GeneratorState.IN_ARRAY) {
+ justWrite(',');
+ writeEol();
+ }
+ }
+
+ private void alignState() {
+
+ if (currentState() == GeneratorState.AFTER_KEY) {
+ state.pop();
+ }
+ switch (currentState()) {
+ case START_ARRAY:
+ swapState(GeneratorState.IN_ARRAY);
+ break;
+ case START_OBJECT:
+ swapState(GeneratorState.IN_OBJECT);
+ break;
+ case INITIAL:
+ swapState(GeneratorState.END);
+ break;
+ default:
+ }
+ }
+
+ private void swapState(final GeneratorState newState) {
+ state.pop();
+ state.push(newState);
+ }
+
+ private GeneratorState currentState() {
+ return state.peek();
+ }
+
+ private void writeValueAsJsonString(final String value) {
+ prepareValue();
+ final GeneratorState peek = state.peek();
+ if (peek == GeneratorState.START_ARRAY || peek == GeneratorState.IN_ARRAY) {
+ writeIndent();
+ }
+ justWrite(QUOTE_CHAR);
+ writeEscaped0(value);
+ justWrite(QUOTE_CHAR);
+ alignState();
+ }
+
+ private void writeValue(final String value) {
+ prepareValue();
+ final GeneratorState peek = state.peek();
+ if (peek == GeneratorState.START_ARRAY || peek == GeneratorState.IN_ARRAY) {
+ writeIndent();
+ }
+ justWrite(String.valueOf(value));
+ alignState();
+ }
+
+ private void writeValue(final int value) {
+ prepareValue();
+ final GeneratorState peek = state.peek();
+ if (peek == GeneratorState.START_ARRAY || peek == GeneratorState.IN_ARRAY) {
+ writeIndent();
+ }
+ writeInt0(value);
+ alignState();
+ }
+
+ private void writeValue(final long value) {
+ prepareValue();
+ final GeneratorState peek = state.peek();
+ if (peek == GeneratorState.START_ARRAY || peek == GeneratorState.IN_ARRAY) {
+ writeIndent();
+ }
+ writeLong0(value);
+ alignState();
+ }
+
+ //unoptimized, see below
+ private void writeLong0(final long i) {
+ justWrite(String.valueOf(i));
+ }
+
+ //unoptimized, see below
+ private void writeInt0(final int i) {
+ justWrite(String.valueOf(i));
+ }
+
+ //optimized number optimizations
+ /*
+ private void writeLong0(final long i) {
+ if (i == Long.MIN_VALUE) {
+ justWrite("-9223372036854775808");
+ return;
+ }
+ final int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
+ final char[] buf = new char[size];
+ getChars(i, size, buf);
+ justWrite(buf);
+ }
+
+ private void writeInt0(final int i) {
+ if (i == Integer.MIN_VALUE) {
+ justWrite("-2147483648");
+ return;
+ }
+ final int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
+ final char[] buf = new char[size];
+ getChars(i, size, buf);
+ justWrite(buf);
+ }
+
+ private final static char[] DIGIT_TENS = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1',
+ '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4',
+ '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6',
+ '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9', '9', '9',
+ '9', '9', '9', '9', '9', '9', '9', };
+
+ private final static char[] DIGIT_ONES = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',
+ '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',
+ '3', '4', '5', '6', '7', '8', '9', };
+
+ private final static char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+ 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
+
+ // Requires positive x
+ private static int stringSize(final long x) {
+ long p = 10;
+ for (int i = 1; i < 19; i++) {
+ if (x < p) {
+ return i;
+ }
+ p = 10 * p;
+ }
+ return 19;
+ }
+
+ private static void getChars(long i, final int index, final char[] buf) {
+ long q;
+ int r;
+ int charPos = index;
+ char sign = 0;
+
+ if (i < 0) {
+ sign = '-';
+ i = -i;
+ }
+
+ // Get 2 digits/iteration using longs until quotient fits into an int
+ while (i > Integer.MAX_VALUE) {
+ q = i / 100;
+ // really: r = i - (q * 100);
+ r = (int) (i - ((q << 6) + (q << 5) + (q << 2)));
+ i = q;
+ buf[--charPos] = DIGIT_ONES[r];
+ buf[--charPos] = DIGIT_TENS[r];
+ }
+
+ // Get 2 digits/iteration using ints
+ int q2;
+ int i2 = (int) i;
+ while (i2 >= 65536) {
+ q2 = i2 / 100;
+ // really: r = i2 - (q * 100);
+ r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2));
+ i2 = q2;
+ buf[--charPos] = DIGIT_ONES[r];
+ buf[--charPos] = DIGIT_TENS[r];
+ }
+
+ // Fall thru to fast mode for smaller numbers
+ // assert(i2 <= 65536, i2);
+ for (;;) {
+ q2 = (i2 * 52429) >>> (16 + 3);
+ r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ...
+ buf[--charPos] = DIGITS[r];
+ i2 = q2;
+ if (i2 == 0) {
+ break;
+ }
+ }
+ if (sign != 0) {
+ buf[--charPos] = sign;
+ }
+ }
+ */
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonInMemoryParser.java b/src/main/java/org/apache/johnzon/core/JsonInMemoryParser.java
new file mode 100644
index 0000000..1f2e33e
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonInMemoryParser.java
@@ -0,0 +1,314 @@
+/*
+ * 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.johnzon.core;
+
+import java.math.BigDecimal;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.stream.Stream;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import javax.json.JsonValue.ValueType;
+import javax.json.stream.JsonLocation;
+
+class JsonInMemoryParser implements JohnzonJsonParser {
+
+ private final SimpleStack<Iterator<Event>> stack = new SimpleStack<Iterator<Event>>();
+
+ private Event currentEvent;
+ private JsonValue currentValue;
+
+ private class ArrayIterator implements Iterator<Event> {
+
+ private final Iterator<JsonValue> aentries;
+ private Boolean end = null;
+
+ public ArrayIterator(final JsonArray ja) {
+ aentries = ja.iterator();
+
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !Boolean.TRUE.equals(end);
+ }
+
+ @Override
+ public Event next() {
+
+ if (end == null) {
+ end = Boolean.FALSE;
+ return Event.START_ARRAY;
+ } else if (!aentries.hasNext()) {
+
+ if (!stack.isEmpty()) {
+ stack.pop();
+ }
+
+ end = Boolean.TRUE;
+
+ return Event.END_ARRAY;
+ } else {
+
+ final JsonValue val = aentries.next();
+
+ final ValueType vt = val.getValueType();
+
+ if (vt == ValueType.OBJECT) {
+ stack.push(new ObjectIterator((JsonObject) val));
+ return stack.peek().next();
+
+ } else if (vt == ValueType.ARRAY) {
+ stack.push(new ArrayIterator((JsonArray) val));
+ return stack.peek().next();
+
+ } else {
+ currentValue = val;
+ return getEvent(vt);
+ }
+ }
+
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ }
+
+ private class ObjectIterator implements Iterator<Event> {
+
+ private final Iterator<Map.Entry<String, JsonValue>> oentries;
+ private JsonValue jsonValue;
+ private Boolean end = null;
+
+ public ObjectIterator(final JsonObject jo) {
+ oentries = jo.entrySet().iterator();
+
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !Boolean.TRUE.equals(end);
+ }
+
+ @Override
+ public Event next() {
+
+ if (end == null) {
+ end = Boolean.FALSE;
+ return Event.START_OBJECT;
+ } else if (jsonValue == null && !oentries.hasNext()) {
+
+ if (!stack.isEmpty()) {
+ stack.pop();
+ }
+
+ end = Boolean.TRUE;
+
+ return Event.END_OBJECT;
+ } else if (jsonValue == null) {
+
+ final Map.Entry<String, JsonValue> tmp = oentries.next();
+ jsonValue = tmp.getValue();
+ currentValue = new JsonStringImpl(tmp.getKey());
+ return Event.KEY_NAME;
+
+ } else {
+
+ final ValueType vt = jsonValue.getValueType();
+
+ if (vt == ValueType.OBJECT) {
+ stack.push(new ObjectIterator((JsonObject) jsonValue));
+ jsonValue = null;
+ return stack.peek().next();
+
+ } else if (vt == ValueType.ARRAY) {
+ stack.push(new ArrayIterator((JsonArray) jsonValue));
+ jsonValue = null;
+ return stack.peek().next();
+
+ } else {
+
+ final Event ret = getEvent(vt);
+ currentValue = jsonValue;
+ jsonValue = null;
+ return ret;
+ }
+
+ }
+
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ }
+
+ private static Event getEvent(final ValueType value) {
+
+ switch (value) {
+ case NUMBER:
+ return Event.VALUE_NUMBER;
+ case STRING:
+ return Event.VALUE_STRING;
+ case FALSE:
+ return Event.VALUE_FALSE;
+ case NULL:
+ return Event.VALUE_NULL;
+ case TRUE:
+ return Event.VALUE_TRUE;
+ default:
+ throw new IllegalArgumentException(value + " not supported");
+
+ }
+
+ }
+
+ JsonInMemoryParser(final JsonObject object) {
+ stack.push(new ObjectIterator(object));
+ }
+
+ JsonInMemoryParser(final JsonArray array) {
+ stack.push(new ArrayIterator(array));
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !stack.isEmpty();
+ }
+
+ @Override
+ public Event next() {
+
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ currentEvent = stack.peek().next();
+
+ return currentEvent;
+ }
+
+ @Override
+ public String getString() {
+ if (currentEvent != Event.KEY_NAME && currentEvent != Event.VALUE_STRING) {
+ throw new IllegalStateException("String is for numbers and strings");
+ }
+ return JsonString.class.cast(currentValue).getString();
+ }
+
+ @Override
+ public boolean isIntegralNumber() {
+ if (currentEvent != Event.VALUE_NUMBER) {
+ throw new IllegalStateException("isIntegralNumber is for numbers");
+ }
+ return JsonNumber.class.cast(currentValue).isIntegral();
+ }
+
+ @Override
+ public boolean isNotTooLong() {
+ return true;
+ }
+
+ @Override
+ public int getInt() {
+ if (currentEvent != Event.VALUE_NUMBER) {
+ throw new IllegalStateException("getInt is for numbers");
+ }
+ return JsonNumber.class.cast(currentValue).intValue();
+ }
+
+ @Override
+ public long getLong() {
+ if (currentEvent != Event.VALUE_NUMBER) {
+ throw new IllegalStateException("getLong is for numbers");
+ }
+ return JsonNumber.class.cast(currentValue).longValue();
+ }
+
+ @Override
+ public BigDecimal getBigDecimal() {
+ if (currentEvent != Event.VALUE_NUMBER) {
+ throw new IllegalStateException("getBigDecimal is for numbers");
+ }
+ return JsonNumber.class.cast(currentValue).bigDecimalValue();
+ }
+
+ @Override
+ public JsonLocation getLocation() { // no location for in memory parsers
+ return JsonLocationImpl.UNKNOWN_LOCATION;
+ }
+
+ @Override
+ public void close() {
+ // no-op
+ }
+
+ @Override
+ public JsonObject getObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public JsonValue getValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public JsonArray getArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Stream<JsonValue> getArrayStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Stream<JsonValue> getValueStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void skipArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void skipObject() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonLocationImpl.java b/src/main/java/org/apache/johnzon/core/JsonLocationImpl.java
new file mode 100644
index 0000000..11fd074
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonLocationImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.johnzon.core;
+
+import java.io.Serializable;
+
+import javax.json.stream.JsonLocation;
+
+final class JsonLocationImpl implements JsonLocation, Serializable {
+
+ public static final JsonLocation UNKNOWN_LOCATION = new JsonLocationImpl(-1, -1, -1);
+
+ private final long lineNumber;
+ private final long columnNumber;
+ private final long streamOffset;
+
+ JsonLocationImpl(final long lineNumber, final long columnNumber, final long streamOffset) {
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+ this.streamOffset = streamOffset;
+ }
+
+ @Override
+ public long getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public long getColumnNumber() {
+ return columnNumber;
+ }
+
+ @Override
+ public long getStreamOffset() {
+ return streamOffset;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final JsonLocationImpl that = JsonLocationImpl.class.cast(o);
+ return columnNumber == that.columnNumber && lineNumber == that.lineNumber && streamOffset == that.streamOffset;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (lineNumber ^ (lineNumber >>> 32));
+ result = 31 * result + (int) (columnNumber ^ (columnNumber >>> 32));
+ result = 31 * result + (int) (streamOffset ^ (streamOffset >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "[lineNumber=" + lineNumber + ", columnNumber=" + columnNumber + ", streamOffset=" + streamOffset + "]";
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonLongImpl.java b/src/main/java/org/apache/johnzon/core/JsonLongImpl.java
new file mode 100644
index 0000000..c9ae38f
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonLongImpl.java
@@ -0,0 +1,121 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+public final class JsonLongImpl implements JsonNumber, Serializable {
+ private final long value;
+ private Integer hashCode = null;
+
+ JsonLongImpl(final long value) {
+ this.value = value;
+ }
+
+ @Override
+ public Number numberValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return true;
+ }
+
+ @Override
+ public int intValue() {
+ return (int) value;
+ }
+
+ @Override
+ public int intValueExact() {
+ return intValue();
+ }
+
+ @Override
+ public long longValue() {
+ return value;
+ }
+
+ @Override
+ public long longValueExact() {
+ return value;
+ }
+
+ @Override
+ public BigInteger bigIntegerValue() {
+ return new BigInteger(toString());
+ }
+
+ @Override
+ public BigInteger bigIntegerValueExact() {
+ return bigIntegerValue();
+ }
+
+ @Override
+ public double doubleValue() {
+ return value;
+ }
+
+ @Override
+ public BigDecimal bigDecimalValue() {
+ return new BigDecimal(toString());
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return ValueType.NUMBER;
+ }
+
+ @Override
+ public String toString() {
+ return Long.toString(value);
+ }
+
+ @Override
+ public int hashCode() {
+ if (hashCode == null) {
+ hashCode = bigDecimalValue().hashCode();
+ }
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (JsonLongImpl.class.isInstance(obj)) {
+ return JsonLongImpl.class.cast(obj).value == value;
+ }
+ return JsonNumber.class.isInstance(obj) && JsonNumber.class.cast(obj).bigDecimalValue().equals(bigDecimalValue());
+ }
+
+ @Override
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ @Override
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonMergePatchDiff.java b/src/main/java/org/apache/johnzon/core/JsonMergePatchDiff.java
new file mode 100644
index 0000000..5175e8d
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonMergePatchDiff.java
@@ -0,0 +1,81 @@
+/*
+ * 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.johnzon.core;
+
+import java.util.Map;
+
+import javax.json.JsonMergePatch;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+/**
+ * Creates a JsonMergePatch as diff between two JsonValues
+ */
+class JsonMergePatchDiff extends DiffBase {
+ private final JsonValue source;
+ private final JsonValue target;
+
+ public JsonMergePatchDiff(JsonValue source, JsonValue target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ public JsonMergePatch calculateDiff() {
+ return new JsonMergePatchImpl(diff(source, target));
+ }
+
+ private JsonValue diff(JsonValue source, JsonValue target) {
+ JsonObjectBuilder builder = new JsonObjectBuilderImpl();
+
+ if (isJsonObject(source) && isJsonObject(target)) {
+ JsonObject srcObj = source.asJsonObject();
+ JsonObject targetObj = target.asJsonObject();
+ for (Map.Entry<String, JsonValue> sourceEntry : srcObj.entrySet()) {
+ String attributeName = sourceEntry.getKey();
+ if (targetObj.containsKey(attributeName)) {
+ // compare the attribute values
+ JsonValue attribDiff = diff(sourceEntry.getValue(), targetObj.get(attributeName));
+ if (!JsonValue.EMPTY_JSON_OBJECT.equals(attribDiff)) {
+ builder.add(attributeName, attribDiff);
+ }
+ } else {
+ // attribute got removed
+ builder.add(attributeName, JsonValue.NULL);
+ }
+ }
+
+ for (Map.Entry<String, JsonValue> targetEntry : targetObj.entrySet()) {
+ String attributeName = targetEntry.getKey();
+ if (!srcObj.containsKey(attributeName)) {
+ // add operation
+ builder.add(attributeName, targetEntry.getValue());
+ }
+ }
+
+ return builder.build();
+ } else if (source.equals(target)) {
+ // if the two objects are identical, then return an empty patch
+ return JsonValue.EMPTY_JSON_OBJECT;
+ } else {
+ // as defined in the RFC anything else than comparing JsonObjects will result
+ // in completely replacing the source with the target
+ // That means our target is the patch.
+ return target;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonMergePatchImpl.java b/src/main/java/org/apache/johnzon/core/JsonMergePatchImpl.java
new file mode 100644
index 0000000..b3f55c6
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonMergePatchImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.johnzon.core;
+
+
+import java.util.Map;
+
+import javax.json.JsonMergePatch;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+/**
+ * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
+ */
+public class JsonMergePatchImpl implements JsonMergePatch {
+ private final JsonValue patch;
+
+ public JsonMergePatchImpl(JsonValue patch) {
+ this.patch = patch;
+ }
+
+ @Override
+ public JsonValue apply(JsonValue valueToApplyPatchOn) {
+ return applyPatch(valueToApplyPatchOn, patch);
+ }
+
+ private JsonValue applyPatch(JsonValue valueToApplyPatchOn, JsonValue patch) {
+ if (patch == null) {
+ return JsonValue.NULL;
+ } else if (patch instanceof JsonObject && valueToApplyPatchOn instanceof JsonObject) {
+ // we only apply an actual patch IF both sides are a JsonObject
+ JsonObject patchObject = patch.asJsonObject();
+
+ return applyJsonObjectPatch(valueToApplyPatchOn.asJsonObject(), patchObject);
+ } else {
+ // this must be a native JsonValue or JsonObject, so we just replace the
+ // the whole original valueToApplyPatchOn with the new jsonValue
+ return patch;
+ }
+ }
+
+ private JsonValue applyJsonObjectPatch(JsonObject jsonObject, JsonObject patch) {
+ JsonObjectBuilder builder = new JsonObjectBuilderImpl(jsonObject);
+
+ for (Map.Entry<String, JsonValue> patchAttrib : patch.entrySet()) {
+ String attribName = patchAttrib.getKey();
+ if (patchAttrib.getValue().equals(JsonValue.NULL)) {
+ builder.remove(attribName);
+ } else {
+ JsonValue originalAttrib = jsonObject.get(attribName);
+ if (originalAttrib == null) {
+ builder.add(attribName, patchAttrib.getValue());
+ } else {
+ builder.add(attribName, applyPatch(originalAttrib, patchAttrib.getValue()));
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
+ public JsonValue toJsonValue() {
+ return patch;
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonNumberImpl.java b/src/main/java/org/apache/johnzon/core/JsonNumberImpl.java
new file mode 100644
index 0000000..7ffe348
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonNumberImpl.java
@@ -0,0 +1,132 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+final class JsonNumberImpl implements JsonNumber, Serializable {
+ private final BigDecimal value;
+ private transient Integer hashCode = null;
+
+ JsonNumberImpl(final BigDecimal decimal) {
+ if (decimal == null) {
+ throw new NullPointerException("decimal must not be null");
+ }
+
+ this.value = decimal;
+ }
+
+ @Override
+ public Number numberValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return value.scale() == 0;
+ }
+
+ @Override
+ public int intValue() {
+ return value.intValue();
+ }
+
+ @Override
+ public int intValueExact() {
+ checkFractionalPart();
+ return value.intValueExact();
+ }
+
+ @Override
+ public long longValue() {
+ return value.longValue();
+ }
+
+ @Override
+ public long longValueExact() {
+ checkFractionalPart();
+ return value.longValueExact();
+ }
+
+ @Override
+ public BigInteger bigIntegerValue() {
+ return value.toBigInteger();
+ }
+
+ @Override
+ public BigInteger bigIntegerValueExact() {
+ return value.toBigIntegerExact();
+ }
+
+ @Override
+ public double doubleValue() {
+ return value.doubleValue();
+ }
+
+ @Override
+ public BigDecimal bigDecimalValue() {
+ return value;
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return ValueType.NUMBER;
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ Integer h = hashCode;
+ if (h == null) {
+ h = value.hashCode();
+ hashCode = h;
+ }
+ return h;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return JsonNumber.class.isInstance(obj) && JsonNumber.class.cast(obj).bigDecimalValue().equals(value);
+ }
+
+ private void checkFractionalPart() {
+ if (value.remainder(BigDecimal.ONE).doubleValue() != 0) {
+ throw new ArithmeticException("Not an int/long, use other value readers");
+ }
+ }
+
+ @Override
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ @Override
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java b/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
new file mode 100644
index 0000000..69a2adb
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
@@ -0,0 +1,178 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+class JsonObjectBuilderImpl implements JsonObjectBuilder, Serializable {
+ private Map<String, JsonValue> attributeMap = new LinkedHashMap<>();
+
+ public JsonObjectBuilderImpl() {
+ }
+
+ public JsonObjectBuilderImpl(JsonObject initialData) {
+ attributeMap = new LinkedHashMap<>(initialData);
+ }
+
+ public JsonObjectBuilderImpl(Map<String, Object> initialValues) {
+ this();
+ for (Map.Entry<String, Object> entry : initialValues.entrySet()) {
+ add(entry.getKey(), entry.getValue());
+ }
+ }
+
+
+ /**
+ * Internal method to add a value where we do not yet know the type at compile time.
+ */
+ public void add(final String name, final Object value) {
+ if (value instanceof JsonValue) {
+ add(name, (JsonValue) value);
+ } else if (value instanceof BigDecimal) {
+ add(name, (BigDecimal) value);
+ } else if (value instanceof BigInteger) {
+ add(name, (BigInteger) value);
+ } else if (value instanceof Boolean) {
+ add(name, (boolean) value);
+ } else if (value instanceof Double) {
+ add(name, (double) value);
+ } else if (value instanceof Integer) {
+ add(name, (int) value);
+ } else if (value instanceof Long) {
+ add(name, (long) value);
+ } else if (value instanceof String) {
+ add(name, (String) value);
+ } else if (value == null) {
+ addNull(name);
+ } else {
+ throw new JsonException("Illegal JSON type! name=" + name + " type=" + value.getClass());
+ }
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final JsonValue value) {
+ putValue(name, value);
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final String value) {
+ putValue(name, new JsonStringImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final BigInteger value) {
+ putValue(name, new JsonNumberImpl(new BigDecimal(value)));
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final BigDecimal value) {
+ putValue(name, new JsonNumberImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final int value) {
+ putValue(name, new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final long value) {
+ putValue(name, new JsonLongImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final double value) {
+ putValue(name, new JsonDoubleImpl(value));
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final boolean value) {
+ putValue(name, value ? JsonValue.TRUE : JsonValue.FALSE);
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder addNull(final String name) {
+ putValue(name, JsonValue.NULL);
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final JsonObjectBuilder builder) {
+ putValue(name, builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder add(final String name, final JsonArrayBuilder builder) {
+ putValue(name, builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder addAll(JsonObjectBuilder builder) {
+ attributeMap.putAll(builder.build());
+ return this;
+ }
+
+ @Override
+ public JsonObjectBuilder remove(String name) {
+ attributeMap.remove(name);
+ return this;
+ }
+
+ private void putValue(String name, JsonValue value){
+ if(name == null || value == null) {
+ throw new NullPointerException("name or value/builder must not be null");
+ }
+
+ attributeMap.put(name, value);
+ }
+
+
+ @Override
+ public JsonObject build() {
+
+ if(attributeMap == null || attributeMap.isEmpty()) {
+ return new JsonObjectImpl(Collections.EMPTY_MAP);
+ } else {
+ Map<String, JsonValue> dump = (Collections.unmodifiableMap(attributeMap));
+ attributeMap =null;
+ return new JsonObjectImpl(dump);
+ }
+
+
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonObjectImpl.java b/src/main/java/org/apache/johnzon/core/JsonObjectImpl.java
new file mode 100644
index 0000000..8a81545
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonObjectImpl.java
@@ -0,0 +1,206 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import javax.json.spi.JsonProvider;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.AbstractMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+
+final class JsonObjectImpl extends AbstractMap<String, JsonValue> implements JsonObject, Serializable {
+ private transient Integer hashCode = null;
+ private final Map<String, JsonValue> unmodifieableBackingMap;
+
+ private <T> T value(final String name, final Class<T> clazz) {
+ final JsonValue v = unmodifieableBackingMap.get(name);
+ if (v != null) {
+ return clazz.cast(v);
+ }
+ return null;
+ }
+
+ private <T> T valueOrExcpetion(final String name, final Class<T> clazz) {
+ T value = value(name, clazz);
+ if (value == null) {
+ throw new NullPointerException("no mapping for " + name);
+ }
+
+ return value;
+ }
+
+ JsonObjectImpl(final Map<String, JsonValue> backingMap) {
+ super();
+ this.unmodifieableBackingMap = backingMap;
+ }
+
+ @Override
+ public JsonArray getJsonArray(final String name) {
+ return value(name, JsonArray.class);
+ }
+
+ @Override
+ public JsonObject getJsonObject(final String name) {
+ return value(name, JsonObject.class);
+ }
+
+ @Override
+ public JsonNumber getJsonNumber(final String name) {
+ return value(name, JsonNumber.class);
+ }
+
+ @Override
+ public JsonString getJsonString(final String name) {
+ return value(name, JsonString.class);
+ }
+
+ @Override
+ public String getString(final String name) {
+ return valueOrExcpetion(name, JsonString.class).getString();
+ }
+
+ @Override
+ public String getString(final String name, final String defaultValue) {
+ final Object v = value(name, JsonValue.class);
+ if (v != null && v instanceof JsonString) {
+ return JsonString.class.cast(v).getString();
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public int getInt(final String name) {
+ return valueOrExcpetion(name, JsonNumber.class).intValue();
+ }
+
+ @Override
+ public int getInt(final String name, final int defaultValue) {
+ final Object v = value(name, JsonValue.class);
+ if (v != null && v instanceof JsonNumber) {
+ return JsonNumber.class.cast(v).intValue();
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public boolean getBoolean(final String name) {
+ final JsonValue obj = valueOrExcpetion(name, JsonValue.class);
+ if (JsonValue.TRUE == obj) {
+ return true;
+ }
+ if (JsonValue.FALSE == obj) {
+ return false;
+ }
+ throw new ClassCastException("Wrong value for a boolean: " + obj);
+ }
+
+ @Override
+ public boolean getBoolean(final String name, final boolean defaultValue) {
+ final Object v = value(name, JsonValue.class);
+ if (v != null) {
+ return JsonValue.TRUE.equals(v) || !JsonValue.FALSE.equals(v) && defaultValue;
+ }
+ return defaultValue;
+ }
+
+ @Override
+ public boolean isNull(final String name) {
+ return JsonValue.NULL.equals(valueOrExcpetion(name, JsonValue.class));
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return ValueType.OBJECT;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("{");
+ final Iterator<Map.Entry<String, JsonValue>> it = unmodifieableBackingMap.entrySet().iterator();
+ boolean hasNext = it.hasNext();
+ while (hasNext) {
+ final Map.Entry<String, JsonValue> entry = it.next();
+
+ builder.append('"').append(entry.getKey()).append("\":");
+
+ final JsonValue value = entry.getValue();
+ if (JsonString.class.isInstance(value)) {
+ builder.append(value.toString());
+ } else {
+ builder.append(value != JsonValue.NULL ? value.toString() : JsonChars.NULL);
+ }
+
+ hasNext = it.hasNext();
+ if (hasNext) {
+ builder.append(",");
+ }
+ }
+ return builder.append('}').toString();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return JsonObjectImpl.class.isInstance(obj)
+ && unmodifieableBackingMap.equals(JsonObjectImpl.class.cast(obj).unmodifieableBackingMap);
+ }
+
+ @Override
+ public int hashCode() {
+ Integer h = hashCode;
+ if (h == null) {
+ h = unmodifieableBackingMap.hashCode();
+ hashCode = h;
+ }
+ return h;
+ }
+
+ @Override
+ public Set<java.util.Map.Entry<String, JsonValue>> entrySet() {
+ return unmodifieableBackingMap.entrySet();
+ }
+
+ private Object writeReplace() throws ObjectStreamException {
+ return new SerializableValue(toString());
+ }
+
+ @Override
+ public JsonValue getValue(String jsonPointer) {
+ return JsonProvider.provider().createPointer(jsonPointer).getValue(this);
+ }
+
+ @Override
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ @Override
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java b/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
new file mode 100644
index 0000000..440b4cb
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
@@ -0,0 +1,137 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParserFactory;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+
+public class JsonParserFactoryImpl extends AbstractJsonFactory implements JsonParserFactory {
+ public static final String MAX_STRING_LENGTH = "org.apache.johnzon.max-string-length";
+ public static final int DEFAULT_MAX_STRING_LENGTH = Integer.getInteger(MAX_STRING_LENGTH, 256 * 1024); //256kB
+
+ public static final String AUTO_ADJUST_STRING_BUFFER = "org.apache.johnzon.auto-adjust-buffer";
+ public static final String BUFFER_LENGTH = "org.apache.johnzon.default-char-buffer";
+ public static final int DEFAULT_BUFFER_LENGTH = Integer.getInteger(BUFFER_LENGTH, 64 * 1024); //64k
+
+ public static final String SUPPORTS_COMMENTS = "org.apache.johnzon.supports-comments";
+ public static final boolean DEFAULT_SUPPORTS_COMMENT = Boolean.getBoolean(SUPPORTS_COMMENTS); //default is false;
+
+ static final Collection<String> SUPPORTED_CONFIG_KEYS = asList(
+ BUFFER_STRATEGY, MAX_STRING_LENGTH, BUFFER_LENGTH, SUPPORTS_COMMENTS, AUTO_ADJUST_STRING_BUFFER
+ );
+
+ private final int maxSize;
+ private final BufferStrategy.BufferProvider<char[]> bufferProvider;
+ private final BufferStrategy.BufferProvider<char[]> valueBufferProvider;
+ private final boolean supportsComments;
+ private final boolean autoAdjustBuffers;
+
+ JsonParserFactoryImpl(final Map<String, ?> config) {
+ super(config, SUPPORTED_CONFIG_KEYS, null);
+
+ final int bufferSize = getInt(BUFFER_LENGTH, DEFAULT_BUFFER_LENGTH);
+ if (bufferSize <= 0) {
+ throw new IllegalArgumentException("buffer length must be greater than zero");
+ }
+
+ this.maxSize = getInt(MAX_STRING_LENGTH, DEFAULT_MAX_STRING_LENGTH);
+ this.bufferProvider = getBufferProvider().newCharProvider(bufferSize);
+ this.valueBufferProvider = getBufferProvider().newCharProvider(maxSize);
+ this.supportsComments = getBool(SUPPORTS_COMMENTS, DEFAULT_SUPPORTS_COMMENT);
+ this.autoAdjustBuffers = getBool(AUTO_ADJUST_STRING_BUFFER, true);
+ }
+
+ private JsonStreamParserImpl getDefaultJsonParserImpl(final InputStream in) {
+ if (supportsComments) {
+ return new CommentsJsonStreamParserImpl(in, maxSize, bufferProvider, valueBufferProvider, autoAdjustBuffers);
+ }
+ //UTF Auto detection RFC 4627
+ return new JsonStreamParserImpl(in, maxSize, bufferProvider, valueBufferProvider, autoAdjustBuffers);
+ }
+
+ private JsonStreamParserImpl getDefaultJsonParserImpl(final InputStream in, final Charset charset) {
+ if (supportsComments) {
+ return new CommentsJsonStreamParserImpl(in, charset, maxSize, bufferProvider, valueBufferProvider, autoAdjustBuffers);
+ }
+ //use provided charset
+ return new JsonStreamParserImpl(in, charset, maxSize, bufferProvider, valueBufferProvider, autoAdjustBuffers);
+ }
+
+ private JsonStreamParserImpl getDefaultJsonParserImpl(final Reader in) {
+ if (supportsComments) {
+ return new CommentsJsonStreamParserImpl(in, maxSize, bufferProvider, valueBufferProvider, autoAdjustBuffers);
+ }
+ //no charset necessary
+ return new JsonStreamParserImpl(in, maxSize, bufferProvider, valueBufferProvider, autoAdjustBuffers);
+ }
+
+ @Override
+ public JsonParser createParser(final Reader reader) {
+ return getDefaultJsonParserImpl(reader);
+ }
+
+ @Override
+ public JsonParser createParser(final InputStream in) {
+ return getDefaultJsonParserImpl(in);
+ }
+
+ @Override
+ public JsonParser createParser(final InputStream in, final Charset charset) {
+ return getDefaultJsonParserImpl(in, charset);
+ }
+
+ @Override
+ public JsonParser createParser(final JsonObject obj) {
+ // no need of a comment version since JsonObject has no comment event
+ return new JsonInMemoryParser(obj);
+ }
+
+ @Override
+ public JsonParser createParser(final JsonArray array) {
+ // no need of a comment version since JsonObject has no comment event
+ return new JsonInMemoryParser(array);
+ }
+
+ @Override
+ public Map<String, ?> getConfigInUse() {
+ return Collections.unmodifiableMap(internalConfig);
+ }
+
+ public JsonStreamParserImpl createInternalParser(final InputStream in) {
+ return getDefaultJsonParserImpl(in);
+ }
+
+ public JsonStreamParserImpl createInternalParser(final InputStream in, final Charset charset) {
+ return getDefaultJsonParserImpl(in, charset);
+ }
+
+ public JsonStreamParserImpl createInternalParser(final Reader reader) {
+ return getDefaultJsonParserImpl(reader);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonPatchBuilderImpl.java b/src/main/java/org/apache/johnzon/core/JsonPatchBuilderImpl.java
new file mode 100644
index 0000000..ada2db7
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonPatchBuilderImpl.java
@@ -0,0 +1,181 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonPatch;
+import javax.json.JsonPatchBuilder;
+import javax.json.JsonValue;
+import java.util.ArrayList;
+import java.util.List;
+
+class JsonPatchBuilderImpl implements JsonPatchBuilder {
+
+ private final List<JsonPatchImpl.PatchValue> operations;
+
+
+ JsonPatchBuilderImpl() {
+ operations = new ArrayList<>();
+ }
+
+ JsonPatchBuilderImpl(JsonArray initialData) {
+ operations = new ArrayList<>(initialData.size());
+
+ for (JsonValue value : initialData) {
+
+ JsonObject operation = (JsonObject) value;
+
+ JsonPatch.Operation op = JsonPatch.Operation.fromOperationName(operation.getString("op"));
+ String path = operation.getString("path");
+ String from = operation.getString("from", null);
+ JsonValue jsonValue = operation.get("value");
+
+ operations.add(new JsonPatchImpl.PatchValue(op,
+ path,
+ from,
+ jsonValue));
+ }
+ }
+
+
+ @Override
+ public JsonPatchBuilder add(String path, JsonValue value) {
+ return addOperation(new JsonPatchImpl.PatchValue(JsonPatch.Operation.ADD,
+ path,
+ null,
+ value));
+ }
+
+ @Override
+ public JsonPatchBuilder add(String path, String value) {
+ return add(path, toJsonString(value));
+ }
+
+ @Override
+ public JsonPatchBuilder add(String path, int value) {
+ return add(path, toJsonNumber(value));
+ }
+
+ @Override
+ public JsonPatchBuilder add(String path, boolean value) {
+ return add(path, toJsonBoolean(value));
+ }
+
+
+ @Override
+ public JsonPatchBuilder remove(String path) {
+ return addOperation(new JsonPatchImpl.PatchValue(JsonPatch.Operation.REMOVE,
+ path,
+ null,
+ null));
+ }
+
+
+ @Override
+ public JsonPatchBuilder replace(String path, JsonValue value) {
+ return addOperation(new JsonPatchImpl.PatchValue(JsonPatch.Operation.REPLACE,
+ path,
+ null,
+ value));
+ }
+
+ @Override
+ public JsonPatchBuilder replace(String path, String value) {
+ return replace(path, toJsonString(value));
+ }
+
+ @Override
+ public JsonPatchBuilder replace(String path, int value) {
+ return replace(path, toJsonNumber(value));
+ }
+
+ @Override
+ public JsonPatchBuilder replace(String path, boolean value) {
+ return replace(path, toJsonBoolean(value));
+ }
+
+
+ @Override
+ public JsonPatchBuilder move(String path, String from) {
+ return addOperation(new JsonPatchImpl.PatchValue(JsonPatch.Operation.MOVE,
+ path,
+ from,
+ null));
+ }
+
+
+ @Override
+ public JsonPatchBuilder copy(String path, String from) {
+ return addOperation(new JsonPatchImpl.PatchValue(JsonPatch.Operation.COPY,
+ path,
+ from,
+ null));
+ }
+
+
+ @Override
+ public JsonPatchBuilder test(String path, JsonValue value) {
+ return addOperation(new JsonPatchImpl.PatchValue(JsonPatch.Operation.TEST,
+ path,
+ null,
+ value));
+ }
+
+ @Override
+ public JsonPatchBuilder test(String path, String value) {
+ return test(path, toJsonString(value));
+ }
+
+ @Override
+ public JsonPatchBuilder test(String path, int value) {
+ return test(path, toJsonNumber(value));
+ }
+
+ @Override
+ public JsonPatchBuilder test(String path, boolean value) {
+ return test(path, toJsonBoolean(value));
+ }
+
+
+ @Override
+ public JsonPatch build() {
+ JsonPatchImpl patch = new JsonPatchImpl(new ArrayList<>(operations));
+
+ return patch;
+
+ }
+
+
+ private JsonPatchBuilder addOperation(JsonPatchImpl.PatchValue operation) {
+ operations.add(operation);
+ return this;
+ }
+
+ private static JsonValue toJsonBoolean(boolean value) {
+ return value ? JsonValue.TRUE : JsonValue.FALSE;
+ }
+
+ private static JsonValue toJsonString(String value) {
+ return value == null ? JsonValue.NULL : new JsonStringImpl(value);
+ }
+
+ private static JsonValue toJsonNumber(int value) {
+ return new JsonLongImpl(value);
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonPatchDiff.java b/src/main/java/org/apache/johnzon/core/JsonPatchDiff.java
new file mode 100644
index 0000000..301a775
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonPatchDiff.java
@@ -0,0 +1,102 @@
+/*
+ * 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.johnzon.core;
+
+import java.util.Map;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonPatch;
+import javax.json.JsonPatchBuilder;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+
+/**
+ * Create a diff from a source and target JsonStructure
+ */
+class JsonPatchDiff extends DiffBase {
+
+ private final JsonStructure source;
+ private final JsonStructure target;
+
+ JsonPatchDiff(JsonStructure source, JsonStructure target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ JsonPatch calculateDiff() {
+ JsonPatchBuilder patchBuilder = new JsonPatchBuilderImpl();
+
+ diff(patchBuilder, "", source, target);
+
+ return patchBuilder.build();
+ }
+
+ private void diff(JsonPatchBuilder patchBuilder, String basePath, JsonValue source, JsonValue target) {
+ if (isJsonObject(source) && isJsonObject(target)) {
+ diffJsonObjects(patchBuilder, basePath + "/", (JsonObject) source, (JsonObject) target);
+ } else if (isJsonArray(source) && isJsonArray(target)) {
+ diffJsonArray(patchBuilder, basePath + "/", (JsonArray) source, (JsonArray) target);
+ } else if (!source.equals(target)){
+ patchBuilder.replace(basePath, target);
+ }
+ }
+
+ private void diffJsonArray(JsonPatchBuilder patchBuilder, String basePath, JsonArray source, JsonArray target) {
+ for (int i = 0; i < source.size(); i++) {
+ JsonValue sourceValue = source.get(i);
+
+ if (target.size() <= i) {
+ patchBuilder.remove(basePath + i);
+ continue;
+ }
+
+ diff(patchBuilder, basePath + i, sourceValue, target.get(i));
+ }
+
+ if (target.size() > source.size()) {
+
+ for (int i = target.size() - source.size(); i < target.size(); i++) {
+ patchBuilder.add(basePath + i, target.get(i));
+ }
+ }
+
+ }
+
+ private void diffJsonObjects(JsonPatchBuilder patchBuilder, String basePath, JsonObject source, JsonObject target) {
+
+ for (Map.Entry<String, JsonValue> sourceEntry : source.entrySet()) {
+ String attributeName = sourceEntry.getKey();
+
+ if (target.containsKey(attributeName)) {
+ diff(patchBuilder, basePath + JsonPointerUtil.encode(attributeName), sourceEntry.getValue(), target.get(attributeName));
+ } else {
+ // the value got removed
+ patchBuilder.remove(basePath + JsonPointerUtil.encode(attributeName));
+ }
+ }
+
+ for (Map.Entry<String, JsonValue> targetEntry : target.entrySet()) {
+ if (!source.containsKey(targetEntry.getKey())) {
+ patchBuilder.add(basePath + JsonPointerUtil.encode(targetEntry.getKey()), targetEntry.getValue());
+ }
+ }
+
+ }
+
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java b/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
new file mode 100644
index 0000000..257ab40
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
@@ -0,0 +1,213 @@
+/*
+ * 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.johnzon.core;
+
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonPatch;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+
+class JsonPatchImpl implements JsonPatch {
+
+ private final List<PatchValue> patches;
+
+
+ JsonPatchImpl(PatchValue... patches) {
+ this.patches = Arrays.asList(patches);
+ }
+
+ JsonPatchImpl(List<PatchValue> patches) {
+ if (patches == null) {
+ this.patches = Collections.emptyList();
+ } else {
+ this.patches = Collections.unmodifiableList(patches);
+ }
+ }
+
+
+ @Override
+ public <T extends JsonStructure> T apply(T target) {
+
+ //X TODO JsonPointer should use generics like JsonPatch
+ JsonStructure patched = target;
+
+ for (PatchValue patch : patches) {
+
+ switch (patch.operation) {
+ case ADD:
+ patched = patch.path.add(patched, patch.value);
+ break;
+ case REMOVE:
+ patched = patch.path.remove(patched);
+ break;
+ case REPLACE:
+ // first remove the existing element and then add the new value
+ patched = patch.path.add(patch.path.remove(patched), patch.value);
+ break;
+ case MOVE:
+ JsonValue valueToMove = patch.from.getValue(patched);
+ patched = patch.path.add(patch.from.remove(patched), valueToMove);
+ break;
+ case COPY:
+ JsonValue toCopy = patch.from.getValue(patched);
+ patched = patch.path.add(patched, toCopy);
+ break;
+ case TEST:
+ JsonValue toTest = patch.path.getValue(patched);
+ if (!toTest.equals(patch.value)) {
+ throw new JsonException("JsonPatch.Operation.TEST fails! Values are not equal");
+ }
+ break;
+ default:
+ throw new IllegalStateException("unsupported operation: " + patch.operation);
+ }
+ }
+
+ //X TODO dirty cast can be removed after JsonPointer uses generics like JsonPatch
+ return (T) patched;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ JsonPatchImpl jsonPatch = (JsonPatchImpl) o;
+
+ return patches.equals(jsonPatch.patches);
+ }
+
+ @Override
+ public int hashCode() {
+ return patches.hashCode();
+ }
+
+
+ @Override
+ public JsonArray toJsonArray() {
+
+ JsonArrayBuilder builder = Json.createArrayBuilder();
+ for (PatchValue patch : patches) {
+ builder.add(patch.toJson());
+ }
+
+ return builder.build();
+ }
+
+
+
+ static class PatchValue {
+ private final JsonPatch.Operation operation;
+ private final JsonPointerImpl path;
+ private final JsonPointerImpl from;
+ private final JsonValue value;
+
+ PatchValue(JsonPatch.Operation operation,
+ String path,
+ String from,
+ JsonValue value) {
+ this.operation = operation;
+ this.path = new JsonPointerImpl(path);
+
+ // ignore from if we do not need it
+ if (operation == JsonPatch.Operation.MOVE || operation == JsonPatch.Operation.COPY) {
+ this.from = new JsonPointerImpl(from);
+ } else {
+ this.from = null;
+ }
+
+ this.value = value;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ PatchValue that = (PatchValue) o;
+
+ if (operation != that.operation) {
+ return false;
+ }
+ if (!path.equals(that.path)) {
+ return false;
+ }
+ if (from != null ? !from.equals(that.from) : that.from != null) {
+ return false;
+ }
+ return value != null ? value.equals(that.value) : that.value == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = operation.hashCode();
+ result = 31 * result + path.hashCode();
+ result = 31 * result + (from != null ? from.hashCode() : 0);
+ result = 31 * result + (value != null ? value.hashCode() : 0);
+ return result;
+ }
+
+
+ @Override
+ public String toString() {
+ return "{" +
+ "op: " + operation +
+ ", path: " + path +
+ ", from: " + from +
+ ", value: " + value +
+ '}';
+ }
+
+ JsonObject toJson() {
+ JsonObjectBuilder builder = Json.createObjectBuilder()
+ .add("op", operation.name().toLowerCase())
+ .add("path", path.getJsonPointer());
+
+ if (from != null) {
+ builder.add("from", from.getJsonPointer());
+ }
+
+ if (value != null) {
+ builder.add("value", value);
+ }
+
+ return builder.build();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonPointerImpl.java b/src/main/java/org/apache/johnzon/core/JsonPointerImpl.java
new file mode 100644
index 0000000..b674ad9
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonPointerImpl.java
@@ -0,0 +1,484 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonPointer;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+
+public class JsonPointerImpl implements JsonPointer {
+
+ private final String jsonPointer;
+ private final List<String> referenceTokens = new ArrayList<>();
+ private final String lastReferenceToken;
+
+ /**
+ * Constructs and initializes a JsonPointer.
+ *
+ * @param jsonPointer the JSON Pointer string
+ * @throws NullPointerException if {@code jsonPointer} is {@code null}
+ * @throws JsonException if {@code jsonPointer} is not a valid JSON Pointer
+ */
+ public JsonPointerImpl(String jsonPointer) {
+ if (jsonPointer == null) {
+ throw new NullPointerException("jsonPointer must not be null");
+ }
+ if (!jsonPointer.equals("") && !jsonPointer.startsWith("/")) {
+ throw new JsonException("A non-empty JsonPointer string must begin with a '/'");
+ }
+
+ this.jsonPointer = jsonPointer;
+ String[] encodedReferenceTokens = jsonPointer.split("/", -1);
+
+ for (String encodedReferenceToken : encodedReferenceTokens) {
+ referenceTokens.add(JsonPointerUtil.decode(encodedReferenceToken));
+ }
+ lastReferenceToken = referenceTokens.get(referenceTokens.size() - 1);
+ }
+
+ /**
+ * Compares this {@code JsonPointer} with another object.
+ *
+ * @param obj the object to compare this {@code JsonPointer} against
+ * @return true if the given object is a {@code JsonPointer} with the same
+ * reference tokens as this one, false otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ JsonPointerImpl that = (JsonPointerImpl) obj;
+ return jsonPointer.equals(that.jsonPointer);
+ }
+
+ /**
+ * Returns the hash code value for this {@code JsonPointer} object.
+ * The hash code of this object is defined by the hash codes of it's reference tokens.
+ *
+ * @return the hash code value for this {@code JsonPointer} object
+ */
+ @Override
+ public int hashCode() {
+ return jsonPointer.hashCode();
+ }
+
+ /**
+ * Returns the value at the referenced location in the specified {@code target}
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @return the referenced value in the target.
+ * @throws NullPointerException if {@code target} is null
+ * @throws JsonException if the referenced value does not exist
+ */
+ public JsonValue getValue(JsonStructure target) {
+ if (target == null) {
+ throw new NullPointerException("target must not be null");
+ }
+ if (isEmptyJsonPointer()) {
+ return target;
+ }
+
+ JsonValue jsonValue = target;
+ for (int i = 1; i < referenceTokens.size(); i++) {
+ jsonValue = getValue(jsonValue, referenceTokens.get(i), i, referenceTokens.size() - 1);
+ }
+ return jsonValue;
+ }
+
+ @Override
+ public boolean containsValue(JsonStructure target) {
+ try {
+ getValue(target);
+ return true;
+ } catch (JsonException je) {
+ return false;
+ }
+ }
+
+ /**
+ * Adds or replaces a value at the referenced location in the specified
+ * {@code target} with the specified {@code value}.
+ * <ol>
+ * <li>If the reference is the target (empty JSON Pointer string),
+ * the specified {@code value}, which must be the same type as
+ * specified {@code target}, is returned.</li>
+ * <li>If the reference is an array element, the specified {@code value} is inserted
+ * into the array, at the referenced index. The value currently at that location, and
+ * any subsequent values, are shifted to the right (adds one to the indices).
+ * Index starts with 0. If the reference is specified with a "-", or if the
+ * index is equal to the size of the array, the value is appended to the array.</li>
+ * <li>If the reference is a name/value pair of a {@code JsonObject}, and the
+ * referenced value exists, the value is replaced by the specified {@code value}.
+ * If the value does not exist, a new name/value pair is added to the object.</li>
+ * </ol>
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @param value the value to be added
+ * @return the transformed {@code target} after the value is added.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the reference is an array element and
+ * the index is out of range ({@code index < 0 || index > array size}),
+ * or if the pointer contains references to non-existing objects or arrays.
+ */
+ public JsonStructure add(JsonStructure target, JsonValue value) {
+ validateAdd(target);
+ if (isEmptyJsonPointer()) {
+ if (value.getClass() != target.getClass()) {
+ throw new JsonException("The value must have the same type as the target");
+ }
+ return (JsonStructure) value;
+ }
+
+ return addInternal(target, value);
+ }
+
+ /**
+ * Adds or replaces a value at the referenced location in the specified
+ * {@code target} with the specified {@code value}.
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @param value the value to be added
+ * @return the transformed {@code target} after the value is added.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the reference is an array element and
+ * the index is out of range ({@code index < 0 || index > array size}),
+ * or if the pointer contains references to non-existing objects or arrays.
+ * @see #add(JsonStructure, JsonValue)
+ */
+ public JsonObject add(JsonObject target, JsonValue value) {
+ validateAdd(target);
+
+ return addInternal(target, value);
+ }
+
+ /**
+ * Adds or replaces a value at the referenced location in the specified
+ * {@code target} with the specified {@code value}.
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @param value the value to be added
+ * @return the transformed {@code target} after the value is added.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the reference is an array element and
+ * the index is out of range ({@code index < 0 || index > array size}),
+ * or if the pointer contains references to non-existing objects or arrays.
+ * @see #add(JsonStructure, JsonValue)
+ */
+ public JsonArray add(JsonArray target, JsonValue value) {
+ validateAdd(target);
+
+ return addInternal(target, value);
+ }
+
+ /**
+ * Replaces the value at the referenced location in the specified
+ * {@code target} with the specified {@code value}.
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @param value the value to be stored at the referenced location
+ * @return the transformed {@code target} after the value is replaced.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the referenced value does not exist,
+ * or if the reference is the target.
+ */
+ public JsonStructure replace(JsonStructure target, JsonValue value) {
+ if (target instanceof JsonObject) {
+ return replace((JsonObject) target, value);
+ } else {
+ return replace((JsonArray) target, value);
+ }
+ }
+
+ /**
+ * Replaces the value at the referenced location in the specified
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @param value the value to be stored at the referenced location
+ * @return the transformed {@code target} after the value is replaced.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the referenced value does not exist,
+ * or if the reference is the target.
+ * @see #replace(JsonStructure, JsonValue)
+ */
+ public JsonObject replace(JsonObject target, JsonValue value) {
+ return add(remove(target), value);
+ }
+
+ /**
+ * Replaces the value at the referenced location in the specified
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @param value the value to be stored at the referenced location
+ * @return the transformed {@code target} after the value is replaced.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the referenced value does not exist,
+ * or if the reference is the target.
+ * @see #replace(JsonStructure, JsonValue)
+ */
+ public JsonArray replace(JsonArray target, JsonValue value) {
+ return add(remove(target), value);
+ }
+
+ /**
+ * Removes the value at the reference location in the specified {@code target}
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @return the transformed {@code target} after the value is removed.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the referenced value does not exist,
+ * or if the reference is the target.
+ */
+ public JsonStructure remove(JsonStructure target) {
+ if (target instanceof JsonObject) {
+ return remove((JsonObject) target);
+ } else {
+ return remove((JsonArray) target);
+ }
+ }
+
+ /**
+ * Removes the value at the reference location in the specified {@code target}
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @return the transformed {@code target} after the value is removed.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the referenced value does not exist,
+ * or if the reference is the target.
+ * @see #remove(JsonStructure)
+ */
+ public JsonObject remove(JsonObject target) {
+ validateRemove(target);
+
+ return (JsonObject) remove(target, 1, referenceTokens.size() - 1);
+ }
+
+ /**
+ * Removes the value at the reference location in the specified {@code target}
+ *
+ * @param target the target referenced by this {@code JsonPointer}
+ * @return the transformed {@code target} after the value is removed.
+ * @throws NullPointerException if {@code target} is {@code null}
+ * @throws JsonException if the referenced value does not exist,
+ * or if the reference is the target.
+ * @see #remove(JsonStructure)
+ */
+ public JsonArray remove(JsonArray target) {
+ validateRemove(target);
+
+ return (JsonArray) remove(target, 1, referenceTokens.size() - 1);
+ }
+
+ String getJsonPointer() {
+ return jsonPointer;
+ }
+
+ private void validateAdd(JsonValue target) {
+ validateJsonPointer(target, referenceTokens.size() - 1);
+ }
+
+ private void validateRemove(JsonValue target) {
+ validateJsonPointer(target, referenceTokens.size());
+ if (isEmptyJsonPointer()) {
+ throw new JsonException("The reference must not be the target");
+ }
+ }
+
+ private boolean isEmptyJsonPointer() {
+ return jsonPointer.equals("");
+ }
+
+ private JsonValue getValue(JsonValue jsonValue, String referenceToken, int currentPosition, int referencePosition) {
+ if (jsonValue instanceof JsonObject) {
+ JsonObject jsonObject = (JsonObject) jsonValue;
+ jsonValue = jsonObject.get(referenceToken);
+
+ if (jsonValue != null) {
+ return jsonValue;
+ }
+ throw new JsonException("'" + jsonObject + "' contains no value for name '" + referenceToken + "'");
+ } else if (jsonValue instanceof JsonArray) {
+ validateArrayIndex(referenceToken);
+
+ try {
+ JsonArray jsonArray = (JsonArray) jsonValue;
+ int arrayIndex = Integer.parseInt(referenceToken);
+ validateArraySize(jsonArray, arrayIndex, jsonArray.size());
+ return jsonArray.get(arrayIndex);
+ } catch (NumberFormatException e) {
+ throw new JsonException("'" + referenceToken + "' is no valid array index", e);
+ }
+ } else {
+ if (currentPosition != referencePosition) {
+ return jsonValue;
+ }
+ throw new JsonException("'" + jsonValue + "' contains no element for '" + referenceToken + "'");
+ }
+ }
+
+ private <T extends JsonStructure> T addInternal(T jsonValue, JsonValue newValue) {
+ List<String> currentPath = new ArrayList<>();
+ currentPath.add("");
+
+ return (T) addInternal(jsonValue, newValue, currentPath);
+ }
+
+ private JsonValue addInternal(JsonValue jsonValue, JsonValue newValue, List<String> currentPath) {
+ if (jsonValue instanceof JsonObject) {
+ JsonObject jsonObject = (JsonObject) jsonValue;
+ JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+
+ if (jsonObject.isEmpty()) {
+ objectBuilder.add(lastReferenceToken, newValue);
+ } else {
+ for (Map.Entry<String, JsonValue> entry : jsonObject.entrySet()) {
+
+ currentPath.add(entry.getKey());
+ objectBuilder.add(entry.getKey(), addInternal(entry.getValue(), newValue, currentPath));
+ currentPath.remove(entry.getKey());
+
+ if (currentPath.size() == referenceTokens.size() - 1 &&
+ currentPath.get(currentPath.size() - 1).equals(referenceTokens.get(referenceTokens.size() - 2))) {
+ objectBuilder.add(lastReferenceToken, newValue);
+ }
+ }
+ }
+ return objectBuilder.build();
+ } else if (jsonValue instanceof JsonArray) {
+ JsonArray jsonArray = (JsonArray) jsonValue;
+ JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+
+ int arrayIndex = -1;
+ if (currentPath.size() == referenceTokens.size() - 1 &&
+ currentPath.get(currentPath.size() - 1).equals(referenceTokens.get(referenceTokens.size() - 2))) {
+
+ arrayIndex = getArrayIndex(lastReferenceToken, jsonArray, true);
+ }
+
+ int jsonArraySize = jsonArray.size();
+ for (int i = 0; i <= jsonArraySize; i++) {
+ if (i == arrayIndex) {
+ arrayBuilder.add(newValue);
+ }
+ if (i == jsonArraySize) {
+ break;
+ }
+
+ String path = String.valueOf(i);
+ currentPath.add(path);
+ arrayBuilder.add(addInternal(jsonArray.get(i), newValue, currentPath));
+ currentPath.remove(path);
+ }
+ return arrayBuilder.build();
+ }
+ return jsonValue;
+ }
+
+ private JsonValue remove(JsonValue jsonValue, int currentPosition, int referencePosition) {
+ if (jsonValue instanceof JsonObject) {
+ JsonObject jsonObject = (JsonObject) jsonValue;
+ JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+
+ for (Map.Entry<String, JsonValue> entry : jsonObject.entrySet()) {
+ if (currentPosition == referencePosition
+ && lastReferenceToken.equals(entry.getKey())) {
+ continue;
+ }
+ objectBuilder.add(entry.getKey(), remove(entry.getValue(), currentPosition + 1, referencePosition));
+ }
+ return objectBuilder.build();
+ } else if (jsonValue instanceof JsonArray) {
+ JsonArray jsonArray = (JsonArray) jsonValue;
+ JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+
+ int arrayIndex = -1;
+ if (currentPosition == referencePosition) {
+ arrayIndex = getArrayIndex(lastReferenceToken, jsonArray, false);
+ }
+
+ int jsonArraySize = jsonArray.size();
+ for (int i = 0; i < jsonArraySize; i++) {
+ if (i == arrayIndex) {
+ continue;
+ }
+ arrayBuilder.add(remove(jsonArray.get(i), currentPosition + 1, referencePosition));
+ }
+ return arrayBuilder.build();
+ }
+ return jsonValue;
+ }
+
+ private int getArrayIndex(String referenceToken, JsonArray jsonArray, boolean addOperation) {
+ if (addOperation && referenceToken.equals("-")) {
+ return jsonArray.size();
+ }
+
+ validateArrayIndex(referenceToken);
+
+ try {
+ int arrayIndex = Integer.parseInt(referenceToken);
+ int arraySize = addOperation ? jsonArray.size() + 1 : jsonArray.size();
+ validateArraySize(jsonArray, arrayIndex, arraySize);
+ return arrayIndex;
+ } catch (NumberFormatException e) {
+ throw new JsonException("'" + referenceToken + "' is no valid array index", e);
+ }
+ }
+
+ private void validateJsonPointer(JsonValue target, int size) throws NullPointerException, JsonException {
+ if (target == null) {
+ throw new NullPointerException("target must not be null");
+ }
+
+ JsonValue jsonValue = target;
+ for (int i = 1; i < size; i++) {
+ jsonValue = getValue(jsonValue, referenceTokens.get(i), i, referenceTokens.size() - 1);
+ }
+ }
+
+ private void validateArrayIndex(String referenceToken) throws JsonException {
+ if (referenceToken.startsWith("+") || referenceToken.startsWith("-")) {
+ throw new JsonException("An array index must not start with '" + referenceToken.charAt(0) + "'");
+ }
+ if (referenceToken.startsWith("0") && referenceToken.length() > 1) {
+ throw new JsonException("An array index must not start with a leading '0'");
+ }
+ }
+
+ private void validateArraySize(JsonArray jsonArray, int arrayIndex, int arraySize) throws JsonException {
+ if (arrayIndex >= arraySize) {
+ throw new JsonException("'" + jsonArray + "' contains no element for index " + arrayIndex);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonPointerUtil.java b/src/main/java/org/apache/johnzon/core/JsonPointerUtil.java
new file mode 100644
index 0000000..91bc9c2
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonPointerUtil.java
@@ -0,0 +1,49 @@
+/*
+ * 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.johnzon.core;
+
+public class JsonPointerUtil {
+
+ private JsonPointerUtil() {
+
+ }
+
+ /**
+ * Transforms "~" to "~0" and then "/" to "~1"
+ */
+ public static String encode(String s) {
+ if (s == null || s.length() == 0) {
+ return s;
+ }
+
+ return s.replace("~", "~0").replace("/", "~1");
+ }
+
+ /**
+ * Transforms "~1" to "/" and then "~0" to "~",
+ */
+ public static String decode(String s) {
+ if (s == null || s.length() == 0) {
+ return s;
+ }
+
+ return s.replace("~1", "/").replace("~0", "~");
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java b/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java
new file mode 100644
index 0000000..afc1da2
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java
@@ -0,0 +1,384 @@
+/*
+ * 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.johnzon.core;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonMergePatch;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonPatch;
+import javax.json.JsonPatchBuilder;
+import javax.json.JsonPointer;
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+import javax.json.JsonString;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.spi.JsonProvider;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParserFactory;
+
+@ProviderType
+public class JsonProviderImpl extends JsonProvider implements Serializable {
+ private static final JsonProvider DELEGATE = new JsonProviderDelegate();
+
+ @Override
+ public JsonParser createParser(final Reader reader) {
+ return DELEGATE.createParser(reader);
+ }
+
+ @Override
+ public JsonParser createParser(final InputStream inputStream) {
+ return DELEGATE.createParser(inputStream);
+ }
+
+ @Override
+ public JsonParserFactory createParserFactory(final Map<String, ?> stringMap) {
+ return DELEGATE.createParserFactory(stringMap);
+ }
+
+ @Override
+ public JsonGenerator createGenerator(final Writer writer) {
+ return DELEGATE.createGenerator(writer);
+ }
+
+ @Override
+ public JsonGenerator createGenerator(final OutputStream outputStream) {
+ return DELEGATE.createGenerator(outputStream);
+ }
+
+ @Override
+ public JsonGeneratorFactory createGeneratorFactory(final Map<String, ?> stringMap) {
+ return DELEGATE.createGeneratorFactory(stringMap);
+ }
+
+ @Override
+ public JsonReader createReader(final Reader reader) {
+ return DELEGATE.createReader(reader);
+ }
+
+ @Override
+ public JsonReader createReader(final InputStream inputStream) {
+ return DELEGATE.createReader(inputStream);
+ }
+
+ @Override
+ public JsonWriter createWriter(final Writer writer) {
+ return DELEGATE.createWriter(writer);
+ }
+
+ @Override
+ public JsonWriter createWriter(final OutputStream outputStream) {
+ return DELEGATE.createWriter(outputStream);
+ }
+
+ @Override
+ public JsonWriterFactory createWriterFactory(final Map<String, ?> stringMap) {
+ return DELEGATE.createWriterFactory(stringMap);
+ }
+
+ @Override
+ public JsonReaderFactory createReaderFactory(final Map<String, ?> stringMap) {
+ return DELEGATE.createReaderFactory(stringMap);
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder() {
+ return DELEGATE.createObjectBuilder();
+ }
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder() {
+ return DELEGATE.createArrayBuilder();
+ }
+
+ @Override
+ public JsonBuilderFactory createBuilderFactory(final Map<String, ?> stringMap) {
+ return DELEGATE.createBuilderFactory(stringMap);
+ }
+
+ @Override
+ public JsonPatchBuilder createPatchBuilder() {
+ return DELEGATE.createPatchBuilder();
+ }
+
+ @Override
+ public JsonPatchBuilder createPatchBuilder(JsonArray initialData) {
+ return DELEGATE.createPatchBuilder(initialData);
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder(JsonObject jsonObject) {
+ return DELEGATE.createObjectBuilder(jsonObject);
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder(Map<String, Object> map) {
+ return DELEGATE.createObjectBuilder(map);
+ }
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder(JsonArray initialData) {
+ return DELEGATE.createArrayBuilder(initialData);
+ }
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder(Collection<?> initialData) {
+ return DELEGATE.createArrayBuilder(initialData);
+ }
+
+ @Override
+ public JsonPointer createPointer(String path) {
+ return DELEGATE.createPointer(path);
+ }
+
+ @Override
+ public JsonString createValue(String value) {
+ return DELEGATE.createValue(value);
+ }
+
+ @Override
+ public JsonNumber createValue(int value) {
+ return DELEGATE.createValue(value);
+ }
+
+ @Override
+ public JsonNumber createValue(long value) {
+ return DELEGATE.createValue(value);
+ }
+
+ @Override
+ public JsonNumber createValue(double value) {
+ return DELEGATE.createValue(value);
+ }
+
+ @Override
+ public JsonNumber createValue(BigDecimal value) {
+ return DELEGATE.createValue(value);
+ }
+
+ @Override
+ public JsonNumber createValue(BigInteger value) {
+ return DELEGATE.createValue(value);
+ }
+
+ @Override
+ public JsonPatch createPatch(JsonArray array) {
+ return DELEGATE.createPatch(array);
+ }
+
+ @Override
+ public JsonPatch createDiff(JsonStructure source, JsonStructure target) {
+ return DELEGATE.createDiff(source, target);
+ }
+
+ @Override
+ public JsonMergePatch createMergePatch(JsonValue patch) {
+ return DELEGATE.createMergePatch(patch);
+ }
+
+ @Override
+ public JsonMergePatch createMergeDiff(JsonValue source, JsonValue target) {
+ return DELEGATE.createMergeDiff(source, target);
+ }
+
+ static class JsonProviderDelegate extends JsonProvider {
+ private final JsonReaderFactory readerFactory = new JsonReaderFactoryImpl(null);
+ private final JsonParserFactory parserFactory = new JsonParserFactoryImpl(null);
+ private final JsonGeneratorFactory generatorFactory = new JsonGeneratorFactoryImpl(null);
+ private final JsonWriterFactory writerFactory = new JsonWriterFactoryImpl(null);
+ private final JsonBuilderFactoryImpl builderFactory = new JsonBuilderFactoryImpl(null);
+
+ @Override
+ public JsonParser createParser(final InputStream in) {
+ return parserFactory.createParser(in);
+ }
+
+ @Override
+ public JsonParser createParser(final Reader reader) {
+ return parserFactory.createParser(reader);
+ }
+
+ @Override
+ public JsonReader createReader(final InputStream in) {
+ return readerFactory.createReader(in);
+ }
+
+ @Override
+ public JsonReader createReader(final Reader reader) {
+ return readerFactory.createReader(reader);
+ }
+
+ @Override
+ public JsonParserFactory createParserFactory(final Map<String, ?> config) {
+ return (config == null || config.isEmpty()) ? parserFactory : new JsonParserFactoryImpl(config);
+ }
+
+ @Override
+ public JsonReaderFactory createReaderFactory(final Map<String, ?> config) {
+ return (config == null || config.isEmpty()) ? readerFactory : new JsonReaderFactoryImpl(config);
+ }
+
+ @Override
+ public JsonGenerator createGenerator(final Writer writer) {
+ return generatorFactory.createGenerator(writer);
+ }
+
+ @Override
+ public JsonGenerator createGenerator(final OutputStream out) {
+ return generatorFactory.createGenerator(out);
+ }
+
+ @Override
+ public JsonGeneratorFactory createGeneratorFactory(final Map<String, ?> config) {
+ return (config == null || config.isEmpty()) ? generatorFactory : new JsonGeneratorFactoryImpl(config);
+ }
+
+ @Override
+ public JsonWriter createWriter(final Writer writer) {
+ return writerFactory.createWriter(writer);
+ }
+
+ @Override
+ public JsonWriter createWriter(final OutputStream out) {
+ return writerFactory.createWriter(out);
+ }
+
+ @Override
+ public JsonWriterFactory createWriterFactory(final Map<String, ?> config) {
+ return (config == null || config.isEmpty()) ? writerFactory : new JsonWriterFactoryImpl(config);
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder() {
+ return builderFactory.createObjectBuilder();
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder(JsonObject jsonObject) {
+ return builderFactory.createObjectBuilder(jsonObject);
+ }
+
+ @Override
+ public JsonObjectBuilder createObjectBuilder(Map<String, Object> initialValues) {
+ return builderFactory.createObjectBuilder(initialValues);
+ }
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder() {
+ return builderFactory.createArrayBuilder();
+ }
+
+ @Override
+ public JsonArrayBuilder createArrayBuilder(JsonArray initialData) {
+ return builderFactory.createArrayBuilder(initialData);
+ }
+
+ public JsonArrayBuilder createArrayBuilder(Collection<?> initialData) {
+ return builderFactory.createArrayBuilder(initialData);
+ }
+
+ @Override
+ public JsonString createValue(String value) {
+ return new JsonStringImpl(value);
+ }
+
+ @Override
+ public JsonNumber createValue(int value) {
+ return new JsonLongImpl(value);
+ }
+
+ @Override
+ public JsonNumber createValue(long value) {
+ return new JsonLongImpl(value);
+ }
+
+ @Override
+ public JsonNumber createValue(double value) {
+ return new JsonDoubleImpl(value);
+ }
+
+ @Override
+ public JsonNumber createValue(BigDecimal value) {
+ return new JsonNumberImpl(value);
+ }
+
+ @Override
+ public JsonNumber createValue(BigInteger value) {
+ return new JsonLongImpl(value.longValue());
+ }
+
+ @Override
+ public JsonBuilderFactory createBuilderFactory(final Map<String, ?> config) {
+ return (config == null || config.isEmpty()) ? builderFactory : new JsonBuilderFactoryImpl(config);
+ }
+
+ @Override
+ public JsonPatchBuilder createPatchBuilder() {
+ return new JsonPatchBuilderImpl();
+ }
+
+ @Override
+ public JsonPatchBuilder createPatchBuilder(JsonArray initialData) {
+ return new JsonPatchBuilderImpl(initialData);
+ }
+
+ @Override
+ public JsonPointer createPointer(String path) {
+ return new JsonPointerImpl(path);
+ }
+
+ public JsonPatch createPatch(JsonArray array) {
+ return createPatchBuilder(array).build();
+ }
+
+ @Override
+ public JsonPatch createDiff(JsonStructure source, JsonStructure target) {
+ return new JsonPatchDiff(source, target).calculateDiff();
+ }
+
+ public JsonMergePatch createMergePatch(JsonValue patch) {
+ return new JsonMergePatchImpl(patch);
+ }
+
+ @Override
+ public JsonMergePatch createMergeDiff(JsonValue source, JsonValue target) {
+ return new JsonMergePatchDiff(source, target).calculateDiff();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonReaderFactoryImpl.java b/src/main/java/org/apache/johnzon/core/JsonReaderFactoryImpl.java
new file mode 100644
index 0000000..98484b1
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonReaderFactoryImpl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.johnzon.core;
+
+import static java.util.Arrays.asList;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+
+class JsonReaderFactoryImpl extends AbstractJsonFactory implements JsonReaderFactory {
+ static final Collection<String> SUPPORTED_CONFIG_KEYS = asList(
+
+ );
+ private final JsonParserFactoryImpl parserFactory;
+
+ JsonReaderFactoryImpl(final Map<String, ?> config) {
+ super(config, SUPPORTED_CONFIG_KEYS, JsonParserFactoryImpl.SUPPORTED_CONFIG_KEYS);
+ this.parserFactory = new JsonParserFactoryImpl(internalConfig);
+ }
+
+ @Override
+ public JsonReader createReader(final Reader reader) {
+ return new JsonReaderImpl(parserFactory.createInternalParser(reader));
+ }
+
+ @Override
+ public JsonReader createReader(final InputStream in) {
+ return new JsonReaderImpl(parserFactory.createInternalParser(in));
+ }
+
+ @Override
+ public JsonReader createReader(final InputStream in, final Charset charset) {
+ return new JsonReaderImpl(parserFactory.createInternalParser(in, charset));
+ }
+
+ @Override
+ public Map<String, ?> getConfigInUse() {
+ return Collections.unmodifiableMap(internalConfig);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java b/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
new file mode 100644
index 0000000..e3af4af
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
@@ -0,0 +1,261 @@
+/*
+ * 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.johnzon.core;
+
+
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonReader;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParsingException;
+
+public class JsonReaderImpl implements JsonReader {
+ private final JohnzonJsonParser parser;
+ private boolean closed = false;
+
+ public JsonReaderImpl(final JsonParser parser) {
+ if (parser instanceof JohnzonJsonParser) {
+ this.parser = (JohnzonJsonParser) parser;
+ } else {
+ this.parser = new JohnzonJsonParser.JohnzonJsonParserWrapper(parser);
+ }
+ }
+
+ @Override
+ public JsonStructure read() {
+ return JsonStructure.class.cast(readValue());
+ }
+
+ @Override
+ public JsonValue readValue() {
+ checkClosed();
+
+ if (!parser.hasNext()) {
+ throw new IllegalStateException("Nothing to read");
+ }
+ final JsonParser.Event next = parser.next();
+ switch (next) {
+ case START_OBJECT:
+ final JsonObjectBuilder objectBuilder = new JsonObjectBuilderImpl();
+ parseObject(objectBuilder);
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ close();
+ return objectBuilder.build();
+ case START_ARRAY:
+ final JsonArrayBuilder arrayBuilder = new JsonArrayBuilderImpl();
+ parseArray(arrayBuilder);
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ close();
+ return arrayBuilder.build();
+ case VALUE_STRING:
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ final JsonStringImpl string = new JsonStringImpl(parser.getString());
+ close();
+ return string;
+ case VALUE_FALSE:
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ close();
+ return JsonValue.FALSE;
+ case VALUE_TRUE:
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ close();
+ return JsonValue.TRUE;
+ case VALUE_NULL:
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ close();
+ return JsonValue.NULL;
+ case VALUE_NUMBER:
+ if (parser.hasNext()) {
+ throw new JsonParsingException("Expected end of file", parser.getLocation());
+ }
+ final JsonNumber number = new JsonNumberImpl(parser.getBigDecimal());
+ close();
+ return number;
+ default:
+ close();
+ throw new JsonParsingException("Unknown structure: " + next, parser.getLocation());
+ }
+ }
+
+ @Override
+ public JsonObject readObject() {
+ final JsonStructure read = read();
+ checkType(JsonObject.class, read);
+ return JsonObject.class.cast(read);
+ }
+
+ @Override
+ public JsonArray readArray() {
+ final JsonStructure read = read();
+ checkType(JsonArray.class, read);
+ return JsonArray.class.cast(read);
+ }
+
+ private void checkType(final Class<?> expected, final JsonStructure read) {
+ if (!expected.isInstance(read)) {
+ throw new JsonParsingException("Expecting " + expected + " but got " + read, parser.getLocation());
+ }
+ }
+
+ @Override
+ public void close() {
+
+ if (!closed) {
+ closed = true;
+ parser.close();
+ }
+
+ }
+
+ private void parseObject(final JsonObjectBuilder builder) {
+ String key = null;
+ while (parser.hasNext()) {
+ final JsonParser.Event next = parser.next();
+ switch (next) {
+ case KEY_NAME:
+ key = parser.getString();
+ break;
+
+ case VALUE_STRING:
+ builder.add(key, new JsonStringImpl(parser.getString()));
+ break;
+
+ case START_OBJECT:
+ JsonObjectBuilder subObject = null;
+ parseObject(subObject = new JsonObjectBuilderImpl());
+ builder.add(key, subObject);
+ break;
+
+ case START_ARRAY:
+ JsonArrayBuilder subArray = null;
+ parseArray(subArray = new JsonArrayBuilderImpl());
+ builder.add(key, subArray);
+ break;
+
+ case VALUE_NUMBER:
+ if (parser.isIntegralNumber() && parser.isNotTooLong()) {
+ builder.add(key, new JsonLongImpl(parser.getLong()));
+ } else {
+ builder.add(key, new JsonNumberImpl(parser.getBigDecimal()));
+ }
+ break;
+
+ case VALUE_NULL:
+ builder.addNull(key);
+ break;
+
+ case VALUE_TRUE:
+ builder.add(key, true);
+ break;
+
+ case VALUE_FALSE:
+ builder.add(key, false);
+ break;
+
+ case END_OBJECT:
+ return;
+
+ case END_ARRAY:
+ throw new JsonParsingException("']', shouldn't occur", parser.getLocation());
+
+ default:
+ throw new JsonParsingException(next.name() + ", shouldn't occur", parser.getLocation());
+ }
+ }
+ }
+
+ private void parseArray(final JsonArrayBuilder builder) {
+ while (parser.hasNext()) {
+ final JsonParser.Event next = parser.next();
+ switch (next) {
+ case VALUE_STRING:
+ builder.add(new JsonStringImpl(parser.getString()));
+ break;
+
+ case VALUE_NUMBER:
+ if (parser.isIntegralNumber()) {
+ builder.add(new JsonLongImpl(parser.getLong()));
+ } else {
+ builder.add(new JsonNumberImpl(parser.getBigDecimal()));
+ }
+ break;
+
+ case START_OBJECT:
+ JsonObjectBuilder subObject = null;
+ parseObject(subObject = new JsonObjectBuilderImpl());
+ builder.add(subObject);
+ break;
+
+ case START_ARRAY:
+ JsonArrayBuilder subArray = null;
+ parseArray(subArray = new JsonArrayBuilderImpl());
+ builder.add(subArray);
+ break;
+
+ case END_ARRAY:
+ return;
+
+ case VALUE_NULL:
+ builder.addNull();
+ break;
+
+ case VALUE_TRUE:
+ builder.add(true);
+ break;
+
+ case VALUE_FALSE:
+ builder.add(false);
+ break;
+
+ case KEY_NAME:
+ throw new JsonParsingException("array doesn't have keys", parser.getLocation());
+
+ case END_OBJECT:
+ throw new JsonParsingException("'}', shouldn't occur", parser.getLocation());
+
+ default:
+ throw new JsonParsingException(next.name() + ", shouldn't occur", parser.getLocation());
+ }
+ }
+ }
+
+ private void checkClosed() {
+ if (closed) {
+ throw new IllegalStateException("read(), readObject(), readArray() or close() method was already called");
+ }
+
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java b/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
new file mode 100644
index 0000000..b8a0005
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
@@ -0,0 +1,1071 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import javax.json.stream.JsonLocation;
+import javax.json.stream.JsonParsingException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.nio.charset.Charset;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.stream.Stream;
+
+//This class represents either the Json tokenizer and the Json parser.
+public class JsonStreamParserImpl implements JsonChars, JohnzonJsonParser {
+ private final boolean autoAdjust;
+
+ //the main buffer where the stream will be buffered
+ private final char[] buffer;
+
+ //current parser position within the buffer
+ //Initial MIN_VALUE will trigger buffer refill, normally bufferPos is >= -1
+ //-1 would cause a re-read of the first character in the buffer (which is at zero index)
+ private int bufferPos = Integer.MIN_VALUE;
+
+ //available character in the buffer. It might be <= "buffer.length".
+ private int availableCharsInBuffer;
+
+ //start and end position of values in the buffer
+ //may cross boundaries, then value is in fallBackCopyBuffer
+ private int startOfValueInBuffer = -1;
+ private int endOfValueInBuffer = -1;
+
+ private final Reader in;
+
+ //do we read from a character stream or a byte stream
+ //not used at the moment but maybe relevant in future to calculate the JsonLocation offset
+ @SuppressWarnings("unused")
+ private final BufferStrategy.BufferProvider<char[]> bufferProvider;
+ private final BufferStrategy.BufferProvider<char[]> valueProvider;
+
+ //max length for strings and numbers (max count of characters)
+ private final int maxValueLength;
+
+ //we use a byte here, because comparing bytes
+ //is more efficient than comparing enums
+ //Additionally we handle internally two more event: COMMA_EVENT and KEY_SEPARATOR_EVENT
+ private byte previousEvent;
+
+ //this buffer is used to store current String or Number value in case that
+ //within the value a buffer boundary is crossed or the string contains escaped characters
+ private char[] fallBackCopyBuffer;
+ private boolean releaseFallBackCopyBufferLength = true;
+ private int fallBackCopyBufferLength;
+
+ // location (line, column, offset)
+ // We try to calculate this efficiently so we do not just increment the values per char read
+ // Instead we calculate the column and offset relative to the pastBufferReadCount and/or lastLineBreakPosition.
+ private long currentLine = 1;
+ private long lastLineBreakPosition;
+ private long pastBufferReadCount;
+
+ //cache (if current value is a number) integral state and the number itself if its only one digit
+ private boolean isCurrentNumberIntegral = true;
+ private int currentIntegralNumber = Integer.MIN_VALUE; //for number from 0 - 9
+
+ //maybe we want also cache BigDecimals
+ //private BigDecimal currentBigDecimalNumber = null;
+
+ //We need a stack if we want detect bad formatted Json do determine if we are within an array or not
+ //example
+ // Streamparser sees: ],1 <-- we look from here
+ //the 1 is only allowed if we are within an array
+ //This can only be determined by build up a stack which tracks the trail of Json objects and arrays
+ //This stack here is only needed for validating the above mentioned case, if we want to be lenient we can skip suing the stack.
+ //Stack can cause out of memory issues when the nesting depth of a Json stream is too deep.
+ private StructureElement currentStructureElement = null;
+
+ //minimal stack implementation
+ private static final class StructureElement {
+ private final StructureElement previous;
+ private final boolean isArray;
+
+ StructureElement(final StructureElement previous, final boolean isArray) {
+ super();
+ this.previous = previous;
+ this.isArray = isArray;
+ }
+ }
+
+ //detect charset according to RFC 4627
+ public JsonStreamParserImpl(final InputStream inputStream, final int maxStringLength,
+ final BufferStrategy.BufferProvider<char[]> bufferProvider, final BufferStrategy.BufferProvider<char[]> valueBuffer,
+ final boolean autoAdjust) {
+
+ this(inputStream, null, null, maxStringLength, bufferProvider, valueBuffer, autoAdjust);
+ }
+
+ //use charset provided
+ public JsonStreamParserImpl(final InputStream inputStream, final Charset encoding, final int maxStringLength,
+ final BufferStrategy.BufferProvider<char[]> bufferProvider, final BufferStrategy.BufferProvider<char[]> valueBuffer,
+ final boolean autoAdjust) {
+
+ this(inputStream, null, encoding, maxStringLength, bufferProvider, valueBuffer, autoAdjust);
+ }
+
+ public JsonStreamParserImpl(final Reader reader, final int maxStringLength, final BufferStrategy.BufferProvider<char[]> bufferProvider,
+ final BufferStrategy.BufferProvider<char[]> valueBuffer, final boolean autoAdjust) {
+
+ this(null, reader, null, maxStringLength, bufferProvider, valueBuffer, autoAdjust);
+ }
+
+ private JsonStreamParserImpl(final InputStream inputStream, final Reader reader, final Charset encoding, final int maxStringLength,
+ final BufferStrategy.BufferProvider<char[]> bufferProvider, final BufferStrategy.BufferProvider<char[]> valueBuffer,
+ final boolean autoAdjust) {
+
+ this.autoAdjust = autoAdjust;
+ this.maxValueLength = maxStringLength <= 0 ? 8192 : maxStringLength;
+ this.fallBackCopyBuffer = valueBuffer.newBuffer();
+ this.buffer = bufferProvider.newBuffer();
+ this.bufferProvider = bufferProvider;
+ this.valueProvider = valueBuffer;
+
+ if (fallBackCopyBuffer.length < maxStringLength) {
+ throw cust("Size of value buffer cannot be smaller than maximum string length");
+ }
+
+ if (reader != null) {
+ this.in = reader;
+ } else if (encoding == null) {
+ this.in = new RFC4627AwareInputStreamReader(inputStream);
+
+ } else {
+ this.in = new InputStreamReader(inputStream, encoding.newDecoder());
+ }
+ }
+
+ //append a single char to the value buffer
+ private void appendToCopyBuffer(final char c) {
+ fallBackCopyBuffer[fallBackCopyBufferLength++] = c;
+ }
+
+ //copy content between "start" and "end" from buffer to value buffer
+ private void copyCurrentValue() {
+ final int length = endOfValueInBuffer - startOfValueInBuffer;
+ if (length > 0) {
+
+ if (length > maxValueLength) {
+ throw tmc();
+ }
+
+ if (fallBackCopyBufferLength >= fallBackCopyBuffer.length - length) { // not good at runtime but handled
+ if (!autoAdjust) {
+ throw new ArrayIndexOutOfBoundsException("Buffer too small for such a long string");
+ }
+
+ final char[] newArray = new char[fallBackCopyBuffer.length + getBufferExtends(fallBackCopyBuffer.length)];
+ // TODO: log to adjust size once?
+ System.arraycopy(fallBackCopyBuffer, 0, newArray, 0, fallBackCopyBufferLength);
+ System.arraycopy(buffer, startOfValueInBuffer, newArray, fallBackCopyBufferLength, length);
+ if (releaseFallBackCopyBufferLength) {
+ bufferProvider.release(fallBackCopyBuffer);
+ releaseFallBackCopyBufferLength = false;
+ }
+ fallBackCopyBuffer = newArray;
+ } else {
+ System.arraycopy(buffer, startOfValueInBuffer, fallBackCopyBuffer, fallBackCopyBufferLength, length);
+ }
+ fallBackCopyBufferLength += length;
+ }
+
+ startOfValueInBuffer = endOfValueInBuffer = -1;
+ }
+
+ /**
+ * @return the amount of bytes the current buffer should get extended with
+ */
+ protected int getBufferExtends(int currentLength) {
+ return currentLength/4;
+ }
+
+
+ @Override
+ public final boolean hasNext() {
+
+ if (currentStructureElement != null ||
+ (previousEvent != END_ARRAY && previousEvent != END_OBJECT &&
+ previousEvent != VALUE_STRING && previousEvent != VALUE_FALSE && previousEvent != VALUE_TRUE &&
+ previousEvent != VALUE_NULL && previousEvent != VALUE_NUMBER) ||
+ previousEvent == 0) {
+
+ return true;
+ }
+
+ //detect garbage at the end of the file after last object or array is closed
+ if (bufferPos < availableCharsInBuffer) {
+
+ final char c = readNextNonWhitespaceChar(readNextChar());
+
+ if (c == EOF) {
+ return false;
+ }
+
+ if (bufferPos < availableCharsInBuffer) {
+ throw uexc("EOF expected");
+ }
+
+ }
+
+ return false;
+
+ }
+
+ private static boolean isAsciiDigit(final char value) {
+ return value <= NINE && value >= ZERO;
+ }
+
+ //check if value is a valid hex digit and return the numeric value
+ private int parseHexDigit(final char value) {
+
+ if (isAsciiDigit(value)) {
+ return value - 48;
+ } else if (value <= 'f' && value >= 'a') {
+ return (value) - 87;
+ } else if ((value <= 'F' && value >= 'A')) {
+ return (value) - 55;
+ } else {
+ throw uexc("Invalid hex character");
+ }
+ }
+
+ private JsonLocation createLocation() {
+
+ //we start with column = 1, so column is always >= 1
+ //APi is not clear in this, but starting column with 1 is convenient
+ long column = 1;
+ long charOffset = 0;
+
+ if (bufferPos >= -1) {
+
+ charOffset = pastBufferReadCount + bufferPos + 1;
+ column = lastLineBreakPosition == 0 ? charOffset + 1 : charOffset - lastLineBreakPosition;
+ }
+
+ //For now its unclear how to calculate offset for (byte) inputsream.
+ //API says count bytes but thats dependent on encoding and not efficient
+ //skip this for now, count always bytes and defer this until the JSR TCK arrives.
+
+ return new JsonLocationImpl(currentLine, column, charOffset);
+ }
+
+ //read the next char from the stream and set/increment the bufferPos
+ //will also refill buffer if necessary
+ //if we are currently processing a value (string or number) and buffer
+ //refill is necessary copy the already read value part into the value buffer
+ protected final char readNextChar() {
+
+ if ((availableCharsInBuffer - bufferPos) <= 1) {
+ //fillbuffer
+
+ //copy content from old buffer to valuebuffer
+ //correct start end mark
+ if (startOfValueInBuffer > -1 && endOfValueInBuffer == -1) {
+ endOfValueInBuffer = availableCharsInBuffer;
+ copyCurrentValue();
+
+ startOfValueInBuffer = 0;
+ }
+
+ if (bufferPos >= -1) {
+ pastBufferReadCount += availableCharsInBuffer;
+ }
+
+ try {
+ availableCharsInBuffer = in.read(buffer, 0, buffer.length);
+ if (availableCharsInBuffer <= 0) {
+ return EOF;
+ }
+
+ } catch (final IOException e) {
+ close();
+ throw uexio(e);
+ }
+
+ bufferPos = 0;
+ //end fillbuffer
+ } else {
+
+ //since JOHNZON-18 not longer necessary
+ //prevent "bufferoverflow
+ //if(bufferPos + 1 >= availableCharsInBuffer) {
+ // return EOF;
+ //}
+
+ bufferPos++;
+ }
+
+ return buffer[bufferPos];
+ }
+
+ //skip whitespaces
+ //tracks location informations (line, column)
+ //returns the first non whitespace character
+ protected final char readNextNonWhitespaceChar(char c) {
+
+ int dosCount = 0;
+
+ while (c == SPACE || c == TAB || c == CR || c == EOL) {
+
+ if (c == EOL) {
+ currentLine++;
+ lastLineBreakPosition = pastBufferReadCount + bufferPos;
+ }
+
+ //prevent DOS (denial of service) attack
+ if (dosCount >= maxValueLength) {
+ throw tmc();
+ }
+ dosCount++;
+
+ //read next character
+ c = readNextChar();
+
+ }
+
+ return c;
+ }
+
+ @Override
+ public final Event next() {
+ //main entry, make decision how to handle the current character in the stream
+
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ if (previousEvent != 0 && currentStructureElement == null) {
+ throw uexc("Unexpected end of structure");
+ }
+
+ final char c = readNextNonWhitespaceChar(readNextChar());
+
+ if (c == COMMA_CHAR) {
+ //last event must one of the following-> " ] } LITERAL
+ if (previousEvent == KEY_SEPARATOR_EVENT || previousEvent == START_ARRAY
+ || previousEvent == START_OBJECT || previousEvent == COMMA_EVENT
+ || previousEvent == KEY_NAME) {
+ throw uexc("Expected \" ] } LITERAL");
+ }
+
+ previousEvent = COMMA_EVENT;
+ return next();
+
+ }
+
+ if (c == KEY_SEPARATOR) {
+
+ if (previousEvent != KEY_NAME) {
+ throw uexc("A : can only follow a key name");
+ }
+
+ previousEvent = KEY_SEPARATOR_EVENT;
+ return next();
+
+ }
+
+ if (!isCurrentNumberIntegral) {
+ isCurrentNumberIntegral = true;
+ }
+ // if (currentBigDecimalNumber != null) {
+ // currentBigDecimalNumber = null;
+ // }
+ if (currentIntegralNumber != Integer.MIN_VALUE) {
+ currentIntegralNumber = Integer.MIN_VALUE;
+ }
+
+ if (fallBackCopyBufferLength != 0) {
+ fallBackCopyBufferLength = 0;
+ }
+
+ startOfValueInBuffer = endOfValueInBuffer = -1;
+
+ switch (c) {
+
+ case START_OBJECT_CHAR:
+
+ return handleStartObject();
+
+ case END_OBJECT_CHAR:
+
+ return handleEndObject();
+
+ case START_ARRAY_CHAR:
+
+ return handleStartArray();
+
+ case END_ARRAY_CHAR:
+
+ return handleEndArray();
+
+ case QUOTE_CHAR:
+
+ return handleQuote();
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case MINUS:
+ case FALSE_F: // false
+ case TRUE_T: // true
+ case NULL_N: // null
+
+ return handleLiteral();
+
+ default:
+
+ return defaultHandling(c);
+ }
+ }
+
+ protected Event defaultHandling(char c) {
+ if (c == EOF) {
+ throw uexc("End of file hit too early");
+ }
+ throw uexc("Expected structural character or digit or 't' or 'n' or 'f' or '-'");
+ }
+
+ private Event handleStartObject() {
+
+ //last event must one of the following-> : , [
+ if (previousEvent != 0 && previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) {
+ throw uexc("Expected : , [");
+ }
+
+ //push upon the stack
+ if (currentStructureElement == null) {
+ currentStructureElement = new StructureElement(null, false);
+ } else {
+ if (!currentStructureElement.isArray && previousEvent != KEY_SEPARATOR_EVENT) {
+ throw uexc("Expected :");
+ }
+ final StructureElement localStructureElement = new StructureElement(currentStructureElement, false);
+ currentStructureElement = localStructureElement;
+ }
+
+ return EVT_MAP[previousEvent = START_OBJECT];
+
+ }
+
+ private Event handleEndObject() {
+
+ //last event must one of the following-> " ] { } LITERAL
+ if (previousEvent == START_ARRAY || previousEvent == COMMA_EVENT || previousEvent == KEY_NAME
+ || previousEvent == KEY_SEPARATOR_EVENT || currentStructureElement == null) {
+ throw uexc("Expected \" ] { } LITERAL");
+ }
+
+ if (currentStructureElement.isArray) {
+ throw uexc("Expected : ]");
+ }
+
+ //pop from stack
+ currentStructureElement = currentStructureElement.previous;
+
+ return EVT_MAP[previousEvent = END_OBJECT];
+ }
+
+ private Event handleStartArray() {
+
+ //last event must one of the following-> : , [
+ if (previousEvent != 0 && previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) {
+ throw uexc("Expected : , [");
+ }
+
+ //push upon the stack
+ if (currentStructureElement == null) {
+ currentStructureElement = new StructureElement(null, true);
+ } else {
+ if (!currentStructureElement.isArray && previousEvent != KEY_SEPARATOR_EVENT) {
+ throw uexc("Expected \"");
+ }
+ final StructureElement localStructureElement = new StructureElement(currentStructureElement, true);
+ currentStructureElement = localStructureElement;
+ }
+
+ return EVT_MAP[previousEvent = START_ARRAY];
+ }
+
+ private Event handleEndArray() {
+
+ //last event must one of the following-> [ ] } " LITERAL
+ if (previousEvent == START_OBJECT || previousEvent == COMMA_EVENT || previousEvent == KEY_SEPARATOR_EVENT
+ || currentStructureElement == null) {
+ throw uexc("Expected [ ] } \" LITERAL");
+ }
+
+ if (!currentStructureElement.isArray) {
+ throw uexc("Expected : }");
+ }
+
+ //pop from stack
+ currentStructureElement = currentStructureElement.previous;
+
+ return EVT_MAP[previousEvent = END_ARRAY];
+ }
+
+ //read a string, gets called recursively
+ //Handles escape/d characters
+ //if string contains escape chars and/or cross buffer boundary then copy in the value buffer
+ //if not then denote string start and end in startOfValueInBuffer and endOfValueInBuffer and read directly from buffer
+ private void readString() {
+
+ do {
+ char n = readNextChar();
+ //when first called n its first char after the starting quote
+ //after that its the next character after the while loop below
+
+ if (n == QUOTE_CHAR) {
+ endOfValueInBuffer = startOfValueInBuffer = bufferPos; //->"" case
+ return;
+ } else if (n == EOL) {
+ throw uexc("Unexpected linebreak");
+
+ } else if (/* n >= '\u0000' && */ n <= '\u001F') {
+ throw uexc("Unescaped control character");
+
+ } else if (n == ESCAPE_CHAR) {
+
+ n = readNextChar();
+
+ // \ u XXXX -> unicode char
+ if (n == 'u') {
+ n = parseUnicodeHexChars();
+ appendToCopyBuffer(n);
+
+ // \\ -> \
+ } else if (n == ESCAPE_CHAR) {
+ appendToCopyBuffer(n);
+
+ //another escape chars, for example \t
+ } else {
+ appendToCopyBuffer(Strings.asEscapedChar(n));
+
+ }
+
+ } else {
+
+ startOfValueInBuffer = bufferPos;
+ endOfValueInBuffer = -1;
+
+ while ((n = readNextChar()) > '\u001F' && n != ESCAPE_CHAR && n != EOL && n != QUOTE_CHAR) {
+ //read fast
+ }
+
+ endOfValueInBuffer = bufferPos;
+
+ if (n == QUOTE_CHAR) {
+
+ if (fallBackCopyBufferLength > 0) {
+ copyCurrentValue();
+ } else {
+ if ((endOfValueInBuffer - startOfValueInBuffer) > maxValueLength) {
+ throw tmc();
+ }
+
+ }
+
+ return;
+ } else if (n == EOL) {
+ throw uexc("Unexpected linebreak");
+
+ } else if (n >= '\u0000' && n <= '\u001F') {
+ throw uexc("Unescaped control character");
+ }
+
+ copyCurrentValue();
+
+ //current n is one of < '\u001F' -OR- ESCAPE_CHAR -OR- EOL -OR- QUOTE
+
+ bufferPos--; //unread one char
+
+ }
+ } while (true);
+
+ // before this do while(true) it was:
+ //
+ //recurse until string is terminated by a non escaped quote
+ //readString();
+ //
+ //
+ // but recursive = can't read big strings
+
+ }
+
+ //maybe we want to check invalid utf encoding
+ //not clear yet if the InputStreamReader is doing that
+
+ /*
+ private char checkSurrogates(char n, char highSurrogate) {
+ //check for invalid surrogates
+ //high followed by low
+ if (Character.isHighSurrogate(n)) {
+
+ if (highSurrogate != 0) {
+ throw uexc("Unexpected high surrogate");
+ }
+ return n;
+ } else if (Character.isLowSurrogate(n)) {
+
+ if (highSurrogate == 0) {
+ throw uexc("Unexpected low surrogate");
+ } else if (!Character.isSurrogatePair(highSurrogate, n)) {
+ throw uexc("Invalid surrogate pair");
+ }
+ return 0;
+ } else if (highSurrogate != 0 && !Character.isLowSurrogate(n)) {
+ throw uexc("Expected low surrogate");
+ }
+
+ return highSurrogate;
+ }*/
+
+ //read the next four chars, check them and treat them as an single unicode char
+ private char parseUnicodeHexChars() {
+ // \u08Ac etc
+ return (char) (((parseHexDigit(readNextChar())) * 4096) + ((parseHexDigit(readNextChar())) * 256)
+ + ((parseHexDigit(readNextChar())) * 16) + ((parseHexDigit(readNextChar()))));
+
+ }
+
+ private Event handleQuote() {
+
+ //always the beginning quote of a key or value
+
+ //last event must one of the following-> : { [ ,
+ if (previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_OBJECT && previousEvent != START_ARRAY
+ && previousEvent != COMMA_EVENT) {
+ throw uexc("Expected : { [ ,");
+ }
+ //starting quote already consumed
+ readString();
+ //end quote already consumed
+
+ //make the decision if its an key or value
+ if (previousEvent == KEY_SEPARATOR_EVENT) {
+ //must be value
+
+ if (currentStructureElement != null && currentStructureElement.isArray) {
+ //not in array, only allowed within array
+ throw uexc("Key value pair not allowed in an array");
+ }
+
+ return EVT_MAP[previousEvent = VALUE_STRING];
+
+ } else { //Event is START_OBJECT OR START_ARRAY OR COMMA_EVENT
+ //must be a key if we are in an object, if not its a value
+
+ if ((currentStructureElement != null && currentStructureElement.isArray) || currentStructureElement == null) {
+ return EVT_MAP[previousEvent = VALUE_STRING];
+ }
+
+ return EVT_MAP[previousEvent = KEY_NAME];
+ }
+
+ }
+
+ //read a number
+ //if a number cross buffer boundary then copy in the value buffer
+ //if not then denote string start and end in startOfValueInBuffer and endOfValueInBuffer and read directly from buffer
+ private void readNumber() {
+
+ char c = buffer[bufferPos];
+
+ //start can change on any read() if we cross buffer boundary
+ startOfValueInBuffer = bufferPos;
+ endOfValueInBuffer = -1;
+
+ char y = EOF;
+
+ //sum up the digit values
+ int cumulatedDigitValue = 0;
+ while (isAsciiDigit(y = readNextChar())) {
+
+ if (c == ZERO) {
+ throw uexc("Leading zeros not allowed");
+ }
+
+ if (c == MINUS && cumulatedDigitValue == 48) {
+ throw uexc("Leading zeros after minus not allowed");
+ }
+
+ cumulatedDigitValue += y;
+
+ }
+
+ if (c == MINUS && cumulatedDigitValue == 0) {
+
+ throw uexc("Unexpected premature end of number");
+ }
+
+ if (y == DOT) {
+ isCurrentNumberIntegral = false;
+ cumulatedDigitValue = 0;
+ while (isAsciiDigit(y = readNextChar())) {
+ cumulatedDigitValue++;
+ }
+
+ if (cumulatedDigitValue == 0) {
+
+ throw uexc("Unexpected premature end of number");
+ }
+
+ }
+
+ if (y == EXP_LOWERCASE || y == EXP_UPPERCASE) {
+ isCurrentNumberIntegral = false;
+
+ y = readNextChar(); //+ or - or digit
+
+ if (!isAsciiDigit(y) && y != MINUS && y != PLUS) {
+ throw uexc("Expected DIGIT or + or -");
+ }
+
+ if (y == MINUS || y == PLUS) {
+ y = readNextChar();
+ if (!isAsciiDigit(y)) {
+ throw uexc("Unexpected premature end of number");
+ }
+
+ }
+
+ while (isAsciiDigit(y = readNextChar())) {
+ //no-op
+ }
+
+ }
+
+ endOfValueInBuffer = y == EOF && endOfValueInBuffer < 0 ? -1 : bufferPos;
+
+ if (y == COMMA_CHAR || y == END_ARRAY_CHAR || y == END_OBJECT_CHAR || y == EOL || y == SPACE || y == TAB || y == CR || y == EOF) {
+
+ bufferPos--;//unread one char
+
+ //['-', DIGIT]
+ if (isCurrentNumberIntegral && c == MINUS && cumulatedDigitValue >= 48 && cumulatedDigitValue <= 57) {
+
+ currentIntegralNumber = -(cumulatedDigitValue - 48); //optimize -0 till -9
+ return;
+ }
+
+ //[DIGIT]
+ if (isCurrentNumberIntegral && c != MINUS && cumulatedDigitValue == 0) {
+
+ currentIntegralNumber = (c - 48); //optimize 0 till 9
+ return;
+ }
+
+ if (fallBackCopyBufferLength > 0) {
+
+ //we crossed a buffer boundary, use value buffer
+ copyCurrentValue();
+
+ } else {
+ if ((endOfValueInBuffer - startOfValueInBuffer) >= maxValueLength) {
+ throw tmc();
+ }
+ }
+
+ return;
+
+ }
+
+ throw uexc("Unexpected premature end of number");
+
+ }
+
+ //handles false, true, null and numbers
+ private Event handleLiteral() {
+
+ //last event must one of the following-> : , [
+ if (previousEvent != KEY_SEPARATOR_EVENT && previousEvent != START_ARRAY && previousEvent != COMMA_EVENT) {
+ throw uexc("Expected : , [");
+ }
+
+ if (previousEvent == COMMA_EVENT && !currentStructureElement.isArray) {
+ //only allowed within array
+ throw uexc("Not in an array context");
+ }
+
+ char c = buffer[bufferPos];
+
+ // probe literals
+ switch (c) {
+ case TRUE_T:
+
+ if (readNextChar() != TRUE_R || readNextChar() != TRUE_U || readNextChar() != TRUE_E) {
+ throw uexc("Expected LITERAL: true");
+ }
+ return EVT_MAP[previousEvent = VALUE_TRUE];
+ case FALSE_F:
+
+ if (readNextChar() != FALSE_A || readNextChar() != FALSE_L || readNextChar() != FALSE_S || readNextChar() != FALSE_E) {
+ throw uexc("Expected LITERAL: false");
+ }
+
+ return EVT_MAP[previousEvent = VALUE_FALSE];
+
+ case NULL_N:
+
+ if (readNextChar() != NULL_U || readNextChar() != NULL_L || readNextChar() != NULL_L) {
+ throw uexc("Expected LITERAL: null");
+ }
+ return EVT_MAP[previousEvent = VALUE_NULL];
+
+ default:
+ readNumber();
+ return EVT_MAP[previousEvent = VALUE_NUMBER];
+ }
+
+ }
+
+ @Override
+ public String getString() {
+ if (previousEvent == KEY_NAME || previousEvent == VALUE_STRING || previousEvent == VALUE_NUMBER) {
+
+ //if there a content in the value buffer read from them, if not use main buffer
+ return fallBackCopyBufferLength > 0 ? new String(fallBackCopyBuffer, 0, fallBackCopyBufferLength) : new String(buffer,
+ startOfValueInBuffer, endOfValueInBuffer - startOfValueInBuffer);
+ } else {
+ throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getString()");
+ }
+ }
+
+ @Override
+ public boolean isIntegralNumber() {
+
+ if (previousEvent != VALUE_NUMBER) {
+ throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support isIntegralNumber()");
+ } else {
+ return isCurrentNumberIntegral;
+ }
+ }
+
+ @Override
+ public boolean isNotTooLong() {
+ return (endOfValueInBuffer - startOfValueInBuffer) < 19;
+ }
+
+ @Override
+ public int getInt() {
+ if (previousEvent != VALUE_NUMBER) {
+ throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getInt()");
+ } else if (isCurrentNumberIntegral && currentIntegralNumber != Integer.MIN_VALUE) {
+ return currentIntegralNumber;
+ } else if (isCurrentNumberIntegral) {
+ //if there a content in the value buffer read from them, if not use main buffer
+ final Integer retVal = fallBackCopyBufferLength > 0 ? parseIntegerFromChars(fallBackCopyBuffer, 0, fallBackCopyBufferLength)
+ : parseIntegerFromChars(buffer, startOfValueInBuffer, endOfValueInBuffer);
+ if (retVal == null) {
+ return getBigDecimal().intValue();
+ } else {
+ return retVal.intValue();
+ }
+ } else {
+ return getBigDecimal().intValue();
+ }
+ }
+
+ @Override
+ public long getLong() {
+ if (previousEvent != VALUE_NUMBER) {
+ throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getLong()");
+ } else if (isCurrentNumberIntegral && currentIntegralNumber != Integer.MIN_VALUE) {
+ return currentIntegralNumber;
+ } else if (isCurrentNumberIntegral) {
+ //if there a content in the value buffer read from them, if not use main buffer
+ final Long retVal = fallBackCopyBufferLength > 0 ? parseLongFromChars(fallBackCopyBuffer, 0, fallBackCopyBufferLength)
+ : parseLongFromChars(buffer, startOfValueInBuffer, endOfValueInBuffer);
+ if (retVal == null) {
+ return getBigDecimal().longValue();
+ } else {
+ return retVal.longValue();
+ }
+ } else {
+ return getBigDecimal().longValue();
+ }
+
+ }
+
+ @Override
+ public BigDecimal getBigDecimal() {
+ if (previousEvent != VALUE_NUMBER) {
+ throw new IllegalStateException(EVT_MAP[previousEvent] + " doesn't support getBigDecimal()");
+ // } else if (currentBigDecimalNumber != null) {
+ // return currentBigDecimalNumber;
+ } else if (isCurrentNumberIntegral && currentIntegralNumber != Integer.MIN_VALUE) {
+ return new BigDecimal(currentIntegralNumber);
+ } else if (isCurrentNumberIntegral) {
+ //if there a content in the value buffer read from them, if not use main buffer
+ final Long retVal = fallBackCopyBufferLength > 0 ? parseLongFromChars(fallBackCopyBuffer, 0, fallBackCopyBufferLength)
+ : parseLongFromChars(buffer, startOfValueInBuffer, endOfValueInBuffer);
+ if (retVal == null) {
+ return (/*currentBigDecimalNumber = */fallBackCopyBufferLength > 0 ? new BigDecimal(fallBackCopyBuffer, 0,
+ fallBackCopyBufferLength) : new BigDecimal(buffer, startOfValueInBuffer,
+ (endOfValueInBuffer - startOfValueInBuffer)));
+ } else {
+ return (/*currentBigDecimalNumber = */new BigDecimal(retVal.longValue()));
+ }
+ } else {
+ //if there a content in the value buffer read from them, if not use main buffer
+ return (/*currentBigDecimalNumber = */fallBackCopyBufferLength > 0 ? new BigDecimal(fallBackCopyBuffer, 0,
+ fallBackCopyBufferLength) : new BigDecimal(buffer, startOfValueInBuffer, (endOfValueInBuffer - startOfValueInBuffer)));
+ }
+
+ }
+
+ @Override
+ public JsonLocation getLocation() {
+ return createLocation();
+ }
+
+ @Override
+ public void close() {
+ bufferProvider.release(buffer);
+ if (releaseFallBackCopyBufferLength) {
+ valueProvider.release(fallBackCopyBuffer);
+ }
+
+ try {
+ in.close();
+ } catch (final IOException e) {
+ throw new JsonException("Unexpected IO exception " + e.getMessage(), e);
+ }
+ }
+
+ //parse a char[] to long while checking overflow
+ //if overflowed return null
+ //no additional checks since we are sure here that there are no non digits in the array
+ private static Long parseLongFromChars(final char[] chars, final int start, final int end) {
+
+ long retVal = 0;
+ final boolean negative = chars[start] == MINUS;
+ for (int i = negative ? start + 1 : start; i < end; i++) {
+ final long tmp = retVal * 10 + (chars[i] - ZERO);
+ if (tmp < retVal) { //check overflow
+ return null;
+ } else {
+ retVal = tmp;
+ }
+ }
+
+ return negative ? -retVal : retVal;
+ }
+
+ //parse a char[] to int while checking overflow
+ //if overflowed return null
+ //no additional checks since we are sure here that there are no non digits in the array
+ private static Integer parseIntegerFromChars(final char[] chars, final int start, final int end) {
+
+ int retVal = 0;
+ final boolean negative = chars[start] == MINUS;
+ for (int i = negative ? start + 1 : start; i < end; i++) {
+ final int tmp = retVal * 10 + (chars[i] - ZERO);
+ if (tmp < retVal) { //check overflow
+ return null;
+ } else {
+ retVal = tmp;
+ }
+ }
+
+ return negative ? -retVal : retVal;
+ }
+
+ @Override
+ public JsonObject getObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public JsonValue getValue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public JsonArray getArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Stream<JsonValue> getArrayStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Stream<JsonValue> getValueStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void skipArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void skipObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ private JsonParsingException uexc(final char c, final String message) {
+ final JsonLocation location = createLocation();
+ return new JsonParsingException("Unexpected character '" + c + "' (Codepoint: " + String.valueOf(c).codePointAt(0) + ") on "
+ + location + ". Reason is [[" + message + "]]", location);
+ }
+
+ private JsonParsingException uexc(final String message) {
+ final char c = bufferPos < 0 ? 0 : buffer[bufferPos];
+ return uexc(c, message);
+ }
+
+ private JsonParsingException tmc() {
+ final JsonLocation location = createLocation();
+ return new JsonParsingException("Too many characters. Maximum string/number length of " + maxValueLength + " exceeded on "
+ + location + ". Maybe increase org.apache.johnzon.max-string-length in jsonp factory properties or system properties.", location);
+ }
+
+ private JsonParsingException uexio(final IOException e) {
+ final JsonLocation location = createLocation();
+ return new JsonParsingException("Unexpected IO exception on " + location, e, location);
+ }
+
+ private JsonParsingException cust(final String message) {
+ final JsonLocation location = createLocation();
+ return new JsonParsingException("General exception on " + location + ". Reason is [[" + message + "]]", location);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/johnzon/core/JsonStringImpl.java b/src/main/java/org/apache/johnzon/core/JsonStringImpl.java
new file mode 100644
index 0000000..43b62fa
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonStringImpl.java
@@ -0,0 +1,89 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import java.io.Serializable;
+
+final class JsonStringImpl implements JsonString, Serializable {
+ private final String value;
+ private String escape;
+ private transient Integer hashCode = null;
+
+
+ JsonStringImpl(final String value) {
+ if(value == null) {
+ throw new NullPointerException("value must not be null");
+ }
+
+ this.value = value;
+ }
+
+ @Override
+ public String getString() {
+ return value;
+ }
+
+ @Override
+ public CharSequence getChars() {
+ return value;
+ }
+
+ @Override
+ public ValueType getValueType() {
+ return ValueType.STRING;
+ }
+
+ @Override
+ public String toString() {
+ String s = escape;
+ if (s == null) {
+ s = JsonChars.QUOTE_CHAR+Strings.escape(value)+JsonChars.QUOTE_CHAR;
+ escape=s;
+ }
+ return s;
+ }
+
+ @Override
+ public int hashCode() {
+ Integer h = hashCode;
+ if (h == null) {
+ h = value.hashCode();
+ hashCode=h;
+ }
+ return h;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return JsonString.class.isInstance(obj) && JsonString.class.cast(obj).getString().equals(value);
+ }
+
+ @Override
+ public JsonObject asJsonObject() {
+ return JsonObject.class.cast(this);
+ }
+
+ @Override
+ public JsonArray asJsonArray() {
+ return JsonArray.class.cast(this);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonWriterFactoryImpl.java b/src/main/java/org/apache/johnzon/core/JsonWriterFactoryImpl.java
new file mode 100644
index 0000000..84f3df2
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonWriterFactoryImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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.johnzon.core;
+
+import static java.util.Arrays.asList;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGeneratorFactory;
+
+class JsonWriterFactoryImpl extends AbstractJsonFactory implements JsonWriterFactory{
+ private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+ static final Collection<String> SUPPORTED_CONFIG_KEYS = asList(
+
+ );
+ private final JsonGeneratorFactory factory;
+
+ JsonWriterFactoryImpl(final Map<String, ?> config) {
+ super(config, SUPPORTED_CONFIG_KEYS, JsonGeneratorFactoryImpl.SUPPORTED_CONFIG_KEYS);
+ this.factory = new JsonGeneratorFactoryImpl(internalConfig);
+ }
+
+ @Override
+ public JsonWriter createWriter(final Writer writer) {
+ return new JsonWriterImpl(factory.createGenerator(writer));
+ }
+
+ @Override
+ public JsonWriter createWriter(final OutputStream out) {
+ return createWriter(new OutputStreamWriter(out, UTF8_CHARSET));
+ }
+
+ @Override
+ public JsonWriter createWriter(final OutputStream out, final Charset charset) {
+ return createWriter(new OutputStreamWriter(out, charset));
+ }
+
+ @Override
+ public Map<String, ?> getConfigInUse() {
+ return Collections.unmodifiableMap(internalConfig);
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/JsonWriterImpl.java b/src/main/java/org/apache/johnzon/core/JsonWriterImpl.java
new file mode 100644
index 0000000..2577f1f
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/JsonWriterImpl.java
@@ -0,0 +1,92 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.json.JsonValue;
+import javax.json.JsonWriter;
+import javax.json.stream.JsonGenerator;
+import java.io.Serializable;
+
+class JsonWriterImpl implements JsonWriter, Serializable {
+ private final JsonGenerator generator;
+ private boolean closed = false;
+
+ JsonWriterImpl(final JsonGenerator generator) {
+ this.generator = generator;
+ }
+
+ @Override
+ public void writeArray(final JsonArray array) {
+ checkClosed();
+ try {
+ generator.write(array);
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void writeObject(final JsonObject object) {
+ checkClosed();
+ try {
+ generator.write(object);
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void write(final JsonValue value) {
+ checkClosed();
+ try {
+ generator.write(value);
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void write(final JsonStructure value) {
+ checkClosed();
+ try {
+ generator.write(value);
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void close() {
+
+ if (!closed) {
+ closed = true;
+ generator.close();
+ }
+ }
+
+ private void checkClosed() {
+ if (closed) {
+ throw new IllegalStateException("writeArray(), writeObject(), write() or close() method was already called");
+ }
+
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java b/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
new file mode 100644
index 0000000..645cf0e
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
@@ -0,0 +1,141 @@
+/*
+ * 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.johnzon.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PushbackInputStream;
+import java.nio.charset.Charset;
+
+import javax.json.JsonException;
+
+final class RFC4627AwareInputStreamReader extends InputStreamReader {
+
+ RFC4627AwareInputStreamReader(final InputStream in) {
+ this(new PushbackInputStream(in,4));
+ }
+
+ private RFC4627AwareInputStreamReader(final PushbackInputStream in) {
+ super(in, getCharset(in).newDecoder());
+
+ }
+
+ /**
+ * According to the Java API "An attempt is made to read as many as len bytes, but a smaller number may be read".
+ * [http://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html#read(byte[],%20int,%20int)]
+ * For this reason we need to ensure that we've read all the bytes that we need out of this stream.
+ */
+ private static byte[] readAllBytes(final PushbackInputStream inputStream) throws IOException {
+ final int first = inputStream.read();
+ final int second = inputStream.read();
+ if(first == -1|| second == -1) {
+ throw new JsonException("Invalid Json. Valid Json has at least 2 bytes");
+ }
+ final int third = inputStream.read();
+ final int fourth = inputStream.read();
+ if(third == -1) {
+ return new byte[] { (byte) first, (byte) second };
+ } else if(fourth == -1) {
+ return new byte[] { (byte) first, (byte) second, (byte) third };
+ } else {
+ return new byte[] { (byte) first, (byte) second, (byte) third, (byte) fourth };
+ }
+ }
+
+ /*
+ * RFC 4627
+
+ JSON text SHALL be encoded in Unicode. The default encoding is
+ UTF-8.
+
+ Since the first two characters of a JSON text will always be ASCII
+ characters [RFC0020], it is possible to determine whether an octet
+ stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
+ at the pattern of nulls in the first four octets.
+
+ 00 00 00 xx UTF-32BE
+ 00 xx 00 xx UTF-16BE
+ xx 00 00 00 UTF-32LE
+ xx 00 xx 00 UTF-16LE
+ xx xx xx xx UTF-8
+
+ */
+
+ private static Charset getCharset(final PushbackInputStream inputStream) {
+ Charset charset = Charset.forName("UTF-8");
+ int bomLength=0;
+ try {
+ final byte[] utfBytes = readAllBytes(inputStream);
+ int first = (utfBytes[0] & 0xFF);
+ int second = (utfBytes[1] & 0xFF);
+ if (first == 0x00) {
+ charset = (second == 0x00) ? Charset.forName("UTF-32BE") : Charset.forName("UTF-16BE");
+ } else if (utfBytes.length > 2 && second == 0x00) {
+ int third = (utfBytes[2] & 0xFF);
+ charset = (third == 0x00) ? Charset.forName("UTF-32LE") : Charset.forName("UTF-16LE");
+ } else {
+
+ /*check BOM
+
+ Encoding hex byte order mark
+ UTF-8 EF BB BF
+ UTF-16 (BE) FE FF
+ UTF-16 (LE) FF FE
+ UTF-32 (BE) 00 00 FE FF
+ UTF-32 (LE) FF FE 00 00
+ */
+
+ //We do not check for UTF-32BE because that is already covered above and we
+ //do not to unread anything.
+
+ if(first == 0xFE && second == 0xFF) {
+ charset = Charset.forName("UTF-16BE");
+ bomLength=2;
+ } else if(first == 0xFF && second == 0xFE) {
+ if(utfBytes.length > 3 && (utfBytes[2]&0xff) == 0x00 && (utfBytes[3]&0xff) == 0x00) {
+ charset = Charset.forName("UTF-32LE");
+ bomLength=4;
+ }else {
+ charset = Charset.forName("UTF-16LE");
+ bomLength=2;
+ }
+ } else if (utfBytes.length > 2 && first == 0xEF && second == 0xBB && (utfBytes[2]&0xff) == 0xBF) {
+ //UTF-8 with BOM
+ bomLength=3;
+ }
+ }
+ //assume UTF8
+ if(bomLength > 0 && bomLength < 4) {
+ //do not unread BOM, only bytes after BOM
+ inputStream.unread(utfBytes,bomLength,utfBytes.length - bomLength);
+ } else {
+ //no BOM, unread all read bytes
+ inputStream.unread(utfBytes);
+ }
+
+
+ } catch (final IOException e) {
+ throw new JsonException("Unable to detect charset due to "+e.getMessage(), e);
+ }
+
+ return charset;
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/SerializableValue.java b/src/main/java/org/apache/johnzon/core/SerializableValue.java
new file mode 100644
index 0000000..76b3ec6
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/SerializableValue.java
@@ -0,0 +1,56 @@
+/*
+ * 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.johnzon.core;
+
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+import javax.json.spi.JsonProvider;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class SerializableValue implements Serializable {
+ private static final AtomicReference<JsonReaderFactory> FACTORY_ATOMIC_REFERENCE = new AtomicReference<JsonReaderFactory>();
+
+ private final String value;
+
+ SerializableValue(final String value) {
+ this.value = value;
+ }
+
+ private Object readResolve() throws ObjectStreamException {
+ final JsonReader parser = factory().createReader(new StringReader(value));
+ try {
+ return parser.read();
+ } finally {
+ parser.close();
+ }
+ }
+
+ private static JsonReaderFactory factory() { // avoid to create too much instances of provider or factories, not needed
+ JsonReaderFactory factory = FACTORY_ATOMIC_REFERENCE.get();
+ if (factory == null) {
+ FACTORY_ATOMIC_REFERENCE.compareAndSet(null, JsonProvider.provider().createReaderFactory(Collections.<String, Object>emptyMap()));
+ factory = FACTORY_ATOMIC_REFERENCE.get();
+ }
+ return factory;
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/SimpleStack.java b/src/main/java/org/apache/johnzon/core/SimpleStack.java
new file mode 100644
index 0000000..74bd14f
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/SimpleStack.java
@@ -0,0 +1,61 @@
+/*
+ * 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.johnzon.core;
+
+class SimpleStack<T> {
+
+ private Element<T> head;
+
+ void push(final T element) {
+
+ final Element<T> tmp = new Element<T>();
+ tmp.payload = element;
+ tmp.previous = head;
+ head = tmp;
+
+ }
+
+ T pop() {
+
+ final T tmp = head.payload;
+ head = head.previous;
+ return tmp;
+
+ }
+
+ T peek() {
+ return head.payload;
+ }
+
+ boolean isEmpty() {
+ return head == null;
+ }
+
+ private static class Element<T> {
+
+ public Element() {
+
+ }
+
+ private Element<T> previous;
+ private T payload;
+
+ }
+
+}
diff --git a/src/main/java/org/apache/johnzon/core/Strings.java b/src/main/java/org/apache/johnzon/core/Strings.java
new file mode 100644
index 0000000..412f6ef
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/Strings.java
@@ -0,0 +1,127 @@
+/*
+ * 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.johnzon.core;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.json.stream.JsonParsingException;
+
+class Strings implements JsonChars {
+ private static final BufferStrategy.BufferProvider<StringBuilder> BUILDER_CACHE =
+ BufferStrategy.valueOf(System.getProperty("johnzon.string-builder.strategy", "QUEUE"))
+ .newStringBuilderProvider(Integer.getInteger("org.apache.johnzon.default-string-builder", 1024));
+
+ private static final String UNICODE_PREFIX = "\\u";
+ private static final String UNICODE_PREFIX_HELPER = "000";
+ private static final ConcurrentMap<Character, String> UNICODE_CACHE = new ConcurrentHashMap<Character, String>();
+
+ static char asEscapedChar(final char current) {
+ switch (current) {
+ case 'r':
+ return '\r';
+ case 't':
+ return '\t';
+ case 'b':
+ return '\b';
+ case 'f':
+ return '\f';
+ case 'n':
+ return '\n';
+ case '"':
+ return '\"';
+ case '\\':
+ return '\\';
+ case '/':
+ return '/';
+ default:
+ if(Character.isHighSurrogate(current) || Character.isLowSurrogate(current)) {
+ return current;
+ }
+ throw new JsonParsingException("Invalid escape sequence '"+current +"' (Codepoint: "+String.valueOf(current).
+ codePointAt(0),JsonLocationImpl.UNKNOWN_LOCATION);
+ }
+
+ }
+
+ static String escape(final String value) {
+
+ if(value == null || value.length()==0) {
+ return value;
+ }
+
+ final StringBuilder builder = BUILDER_CACHE.newBuffer();
+ try {
+ for (int i = 0; i < value.length(); i++) {
+ final char c = value.charAt(i);
+ switch (c) {
+ case QUOTE_CHAR:
+ case ESCAPE_CHAR:
+ builder.append(ESCAPE_CHAR).append(c);
+ break;
+ default:
+ if (c < SPACE) { // we could do a single switch but actually we should rarely enter this if so no need to pay it
+ switch (c) {
+ case EOL:
+ builder.append("\\n");
+ break;
+ case '\r':
+ builder.append("\\r");
+ break;
+ case '\t':
+ builder.append("\\t");
+ break;
+ case '\b':
+ builder.append("\\b");
+ break;
+ case '\f':
+ builder.append("\\f");
+ break;
+ default:
+ builder.append(toUnicode(c));
+ }
+ } else if ((c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+ builder.append(toUnicode(c));
+ } else {
+ builder.append(c);
+ }
+ }
+ }
+ return builder.toString();
+ } finally {
+ BUILDER_CACHE.release(builder);
+ }
+ }
+
+ private static String toUnicode(final char c) {
+ final String found = UNICODE_CACHE.get(c);
+ if (found != null) {
+ return found;
+ }
+
+ final String hex = UNICODE_PREFIX_HELPER + Integer.toHexString(c);
+ final String s = UNICODE_PREFIX + hex.substring(hex.length() - 4);
+ UNICODE_CACHE.putIfAbsent(c, s);
+ return s;
+ }
+
+ private Strings() {
+ // no-op
+ }
+}
diff --git a/src/main/java/org/apache/johnzon/core/ThreadLocalBufferCache.java b/src/main/java/org/apache/johnzon/core/ThreadLocalBufferCache.java
new file mode 100644
index 0000000..f973fcd
--- /dev/null
+++ b/src/main/java/org/apache/johnzon/core/ThreadLocalBufferCache.java
@@ -0,0 +1,53 @@
+/*
+ * 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.johnzon.core;
+
+import java.io.Serializable;
+
+public abstract class ThreadLocalBufferCache<T> implements Serializable {
+ // alloc are expensive so using a very trivial buffer, we remove from TL to say we use it and reset it when we are done (close)
+ private final ThreadLocal<T> buffers;
+ private final int defaultSize;
+
+ public ThreadLocalBufferCache(final int defaultSize) {
+ this.buffers = new ThreadLocal<T>() {
+ @Override
+ protected T initialValue() {
+ return ThreadLocalBufferCache.this.newValue(defaultSize);
+ }
+ };
+ this.defaultSize = defaultSize;
+ }
+
+ protected abstract T newValue(final int defaultSize);
+
+ public T getCache() {
+ final T cachedLoadedChars = buffers.get();
+ if (cachedLoadedChars == null) {
+ return newValue(defaultSize);
+ }
+ buffers.set(null); // remove just kills the buffers
+ return cachedLoadedChars;
+ }
+
+
+ public void release(final T buffer) {
+ buffers.set(buffer);
+ }
+}
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.