JOHNZON-304 Json.createDiff does not handle properly arrays overflow (more elements in target than source) + minor toString/cache values enhancements (useful for debug purposes)
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayImpl.java
index 7f87069..d05cfbf 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayImpl.java
@@ -163,6 +163,9 @@
 
     @Override
     public String toString() {
+        if (unmodifieableBackingList.isEmpty()) {
+            return "[]";
+        }
         final StringWriter writer = new StringWriter();
         try (final JsonGenerator generator = new JsonGeneratorImpl(writer, provider, false)) {
             generator.writeStartArray();
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectImpl.java
index 03d65fe..4e45f08 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectImpl.java
@@ -143,6 +143,9 @@
 
     @Override
     public String toString() {
+        if (unmodifieableBackingMap.isEmpty()) {
+            return "{}";
+        }
         final StringWriter writer = new StringWriter();
         try (final JsonGenerator generator = new JsonGeneratorImpl(writer, provider, false)) {
             generator.writeStartObject();
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchDiff.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchDiff.java
index 8610184..29577f0 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchDiff.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchDiff.java
@@ -61,19 +61,16 @@
 
     private void diffJsonArray(JsonPatchBuilder patchBuilder, String basePath, JsonArray source, JsonArray target) {
         for (int i = 0; i < source.size(); i++) {
-            JsonValue sourceValue = source.get(i);
-
+            final 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++) {
+            for (int i = source.size(); i < target.size(); i++) {
                 patchBuilder.add(basePath + i, target.get(i));
             }
         }
@@ -81,8 +78,7 @@
     }
 
     private void diffJsonObjects(JsonPatchBuilder patchBuilder, String basePath, JsonObject source, JsonObject target) {
-
-        for (Map.Entry<String, JsonValue> sourceEntry : source.entrySet()) {
+        for (final Map.Entry<String, JsonValue> sourceEntry : source.entrySet()) {
             String attributeName = sourceEntry.getKey();
 
             if (target.containsKey(attributeName)) {
@@ -93,13 +89,10 @@
             }
         }
 
-        for (Map.Entry<String, JsonValue> targetEntry : target.entrySet()) {
+        for (final Map.Entry<String, JsonValue> targetEntry : target.entrySet()) {
             if (!source.containsKey(targetEntry.getKey())) {
                 patchBuilder.add(basePath + JsonPointerUtil.encode(targetEntry.getKey()), targetEntry.getValue());
             }
         }
-
     }
-
-
 }
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
index ec3109d..91bb436 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonPatchImpl.java
@@ -38,6 +38,7 @@
     private final JsonProvider provider;
     private final List<PatchValue> patches;
 
+    private volatile JsonArray json;
 
     JsonPatchImpl(final JsonProvider provider, final PatchValue... patches) {
         this(provider, Arrays.asList(patches));
@@ -118,16 +119,30 @@
 
     @Override
     public JsonArray toJsonArray() {
-
-        JsonArrayBuilder builder = provider.createArrayBuilder();
-        for (PatchValue patch : patches) {
-            builder.add(patch.toJson());
+        if (patches.isEmpty()) {
+            return JsonValue.EMPTY_JSON_ARRAY;
         }
-
-        return builder.build();
+        if (json == null) {
+            synchronized (this) {
+                if (json == null) {
+                    final JsonArrayBuilder builder = provider.createArrayBuilder();
+                    for (final PatchValue patch : patches) {
+                        builder.add(patch.toJson());
+                    }
+                    json = builder.build();
+                }
+            }
+        }
+        return json;
     }
 
-
+    @Override
+    public String toString() {
+        if (patches.isEmpty()) {
+            return "[]";
+        }
+        return toJsonArray().toString();
+    }
 
     static class PatchValue {
         private final JsonProvider provider;
@@ -136,6 +151,10 @@
         private final JsonPointerImpl from;
         private final JsonValue value;
 
+        private volatile String str;
+        private volatile JsonObject json;
+        private volatile Integer hash;
+
         PatchValue(final JsonProvider provider,
                    final JsonPatch.Operation operation,
                    final String path,
@@ -181,38 +200,54 @@
 
         @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;
+            if (hash == null) {
+                synchronized (this) {
+                    if (hash == null) {
+                        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);
+                        hash = result;
+                    }
+                }
+            }
+            return hash;
         }
 
 
         @Override
         public String toString() {
-            return "{" +
-                   "op: " + operation +
-                   ", path: " + path +
-                   ", from: " + from +
-                   ", value: " + value +
-                   '}';
+            if (str == null) {
+                synchronized (this) {
+                    if (str == null) {
+                        str = "{op: " + operation + ", path: " + path + ", from: " + from + ", value: " + value + '}';
+                    }
+                }
+            }
+            return str;
         }
 
         JsonObject toJson() {
-            JsonObjectBuilder builder = provider.createObjectBuilder()
-                                            .add("op", operation.name().toLowerCase())
-                                            .add("path", path.getJsonPointer());
+            if (json == null) {
+                synchronized (this) {
+                    if (json == null) {
+                        JsonObjectBuilder builder = provider.createObjectBuilder()
+                                .add("op", operation.name().toLowerCase())
+                                .add("path", path.getJsonPointer());
 
-            if (from != null) {
-                builder.add("from", from.getJsonPointer());
+                        if (from != null) {
+                            builder.add("from", from.getJsonPointer());
+                        }
+
+                        if (value != null) {
+                            builder.add("value", value);
+                        }
+
+                        json = builder.build();
+                    }
+                }
             }
-
-            if (value != null) {
-                builder.add("value", value);
-            }
-
-            return builder.build();
+            return json;
         }
     }
 }
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchDiffTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchDiffTest.java
index 51bada2..d591b98 100644
--- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchDiffTest.java
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonPatchDiffTest.java
@@ -32,6 +32,25 @@
 import org.junit.Test;
 
 public class JsonPatchDiffTest {
+    @Test
+    public void fromEmptyArray() {
+        final JsonObject from = Json.createObjectBuilder().add("testEmpty", JsonValue.EMPTY_JSON_ARRAY).build();
+        final JsonObject to = Json.createObjectBuilder()
+                .add("testEmpty", Json.createArrayBuilder().add("something"))
+                .build();
+        final  JsonPatch diff = Json.createDiff(from, to);
+        assertEquals("[{\"op\":\"add\",\"path\":\"/testEmpty/0\",\"value\":\"something\"}]", diff.toString());
+    }
+
+    @Test
+    public void toEmptyArray() {
+        final JsonObject from = Json.createObjectBuilder()
+                .add("testEmpty", Json.createArrayBuilder().add("something"))
+                .build();
+        final JsonObject to = Json.createObjectBuilder().add("testEmpty", JsonValue.EMPTY_JSON_ARRAY).build();
+        final  JsonPatch diff = Json.createDiff(from, to);
+        assertEquals("[{\"op\":\"remove\",\"path\":\"/testEmpty/0\"}]", diff.toString());
+    }
 
     @Test
     public void testAddDiff() {