Implemented hashCode() with caching for JsonArrayImpl, JsonObjectImpl and JsonNumber. (#125)

Signed-off-by: Jean-Philippe Gariépy <Jean-Philippe.Gariepy@nuecho.com>
diff --git a/impl/src/main/java/org/glassfish/json/JsonArrayBuilderImpl.java b/impl/src/main/java/org/glassfish/json/JsonArrayBuilderImpl.java
index 97a7022..7040d70 100644
--- a/impl/src/main/java/org/glassfish/json/JsonArrayBuilderImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonArrayBuilderImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -360,6 +360,7 @@
     private static final class JsonArrayImpl extends AbstractList<JsonValue> implements JsonArray {
         private final List<JsonValue> valueList;    // Unmodifiable
         private final BufferPool bufferPool;
+        private int hashCode;
 
         JsonArrayImpl(List<JsonValue> valueList, BufferPool bufferPool) {
             this.valueList = valueList;
@@ -462,6 +463,14 @@
         }
 
         @Override
+        public int hashCode() {
+            if (hashCode == 0) {
+                hashCode = super.hashCode();
+            }
+            return hashCode;
+        }
+
+        @Override
         public String toString() {
             StringWriter sw = new StringWriter();
             try (JsonWriter jw = new JsonWriterImpl(sw, bufferPool)) {
diff --git a/impl/src/main/java/org/glassfish/json/JsonNumberImpl.java b/impl/src/main/java/org/glassfish/json/JsonNumberImpl.java
index 5be3828..0a9578a 100644
--- a/impl/src/main/java/org/glassfish/json/JsonNumberImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonNumberImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2017 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -28,6 +28,8 @@
  */
 abstract class JsonNumberImpl implements JsonNumber {
 
+    private int hashCode;
+
     static JsonNumber getJsonNumber(int num) {
         return new JsonIntNumber(num);
     }
@@ -240,7 +242,10 @@
 
     @Override
     public int hashCode() {
-        return bigDecimalValue().hashCode();
+        if (hashCode == 0) {
+            hashCode = bigDecimalValue().hashCode();
+        }
+        return hashCode;
     }
 
     @Override
diff --git a/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java b/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java
index 9e7e5a1..594851e 100644
--- a/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java
+++ b/impl/src/main/java/org/glassfish/json/JsonObjectBuilderImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -202,6 +202,7 @@
     private static final class JsonObjectImpl extends AbstractMap<String, JsonValue> implements JsonObject {
         private final Map<String, JsonValue> valueMap;      // unmodifiable
         private final BufferPool bufferPool;
+        private int hashCode;
 
         JsonObjectImpl(Map<String, JsonValue> valueMap, BufferPool bufferPool) {
             this.valueMap = valueMap;
@@ -295,6 +296,14 @@
         }
 
         @Override
+        public int hashCode() {
+            if (hashCode == 0) {
+                hashCode = super.hashCode();
+            }
+            return hashCode;
+        }
+
+        @Override
         public String toString() {
             StringWriter sw = new StringWriter();
             try (JsonWriter jw = new JsonWriterImpl(sw, bufferPool)) {
diff --git a/tests/src/test/java/org/glassfish/json/tests/JsonArrayTest.java b/tests/src/test/java/org/glassfish/json/tests/JsonArrayTest.java
index 72ed89b..17b516b 100644
--- a/tests/src/test/java/org/glassfish/json/tests/JsonArrayTest.java
+++ b/tests/src/test/java/org/glassfish/json/tests/JsonArrayTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -111,4 +111,18 @@
         }
     }
 
+    public void testHashCode() {
+        JsonArray array1 = Json.createArrayBuilder().add(1).add(2).add(3).build();
+        assertTrue(array1.hashCode() == array1.hashCode()); //1st call compute hashCode, 2nd call returns cached value
+
+        JsonArray array2 = Json.createArrayBuilder().add(1).add(2).add(3).build();
+        assertTrue(array1.hashCode() == array2.hashCode());
+
+        JsonArray array3 = Json.createArrayBuilder().build(); //org.glassfish.json.JsonArrayBuilderImpl.JsonArrayImpl
+        JsonArray array4 = JsonValue.EMPTY_JSON_ARRAY; //javax.json.EmptyArray
+
+        assertTrue(array3.equals(array4));
+        assertTrue(array3.hashCode() == array4.hashCode()); //equal instances have same hashCode
+    }
+
 }
diff --git a/tests/src/test/java/org/glassfish/json/tests/JsonNumberTest.java b/tests/src/test/java/org/glassfish/json/tests/JsonNumberTest.java
index e5cc5c4..e55a357 100644
--- a/tests/src/test/java/org/glassfish/json/tests/JsonNumberTest.java
+++ b/tests/src/test/java/org/glassfish/json/tests/JsonNumberTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -206,5 +206,14 @@
         }
     }
 
+    public void testHashCode() {
+        JsonNumber jsonNumber1 = Json.createValue(1);
+        assertTrue(jsonNumber1.hashCode() == jsonNumber1.bigDecimalValue().hashCode());
+
+        JsonNumber jsonNumber2 = Json.createValue(1);
+
+        assertTrue(jsonNumber1.equals(jsonNumber2));
+        assertTrue(jsonNumber1.hashCode() == jsonNumber2.hashCode());
+    }
 
 }
diff --git a/tests/src/test/java/org/glassfish/json/tests/JsonObjectTest.java b/tests/src/test/java/org/glassfish/json/tests/JsonObjectTest.java
index 2c15518..59fdddb 100644
--- a/tests/src/test/java/org/glassfish/json/tests/JsonObjectTest.java
+++ b/tests/src/test/java/org/glassfish/json/tests/JsonObjectTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2017 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -122,4 +122,18 @@
         }
     }
 
+    public void testHashCode() {
+        JsonObject object1 = Json.createObjectBuilder().add("a", 1).add("b", 2).add("c", 3).build();
+        assertTrue(object1.hashCode() == object1.hashCode()); //1st call compute hashCode, 2nd call returns cached value
+
+        JsonObject object2 = Json.createObjectBuilder().add("a", 1).add("b", 2).add("c", 3).build();
+        assertTrue(object1.hashCode() == object2.hashCode());
+
+        JsonObject object3 = Json.createObjectBuilder().build(); //org.glassfish.json.JsonArrayBuilderImpl.JsonArrayImpl
+        JsonObject object4 = JsonValue.EMPTY_JSON_OBJECT; //javax.json.EmptyObject
+
+        assertTrue(object3.equals(object4));
+        assertTrue(object3.hashCode() == object4.hashCode()); //equal instances have same hashCode
+    }
+
 }
diff --git a/tests/src/test/java/org/glassfish/json/tests/JsonStringTest.java b/tests/src/test/java/org/glassfish/json/tests/JsonStringTest.java
index c982e0e..6d1b164 100644
--- a/tests/src/test/java/org/glassfish/json/tests/JsonStringTest.java
+++ b/tests/src/test/java/org/glassfish/json/tests/JsonStringTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2017 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -45,6 +45,18 @@
         escapedString("abc\"\\/abc");
     }
 
+    public void testHashCode() {
+        String string1 = new String("a");
+        JsonString jsonString1 = Json.createValue(string1);
+        assertTrue(jsonString1.hashCode() == jsonString1.getString().hashCode());
+
+        String string2 = new String("a");
+        JsonString jsonString2 = Json.createValue(string2);
+
+        assertTrue(jsonString1.equals(jsonString2));
+        assertTrue(jsonString1.hashCode() == jsonString2.hashCode());
+    }
+
     void escapedString(String str) throws Exception {
         JsonArray exp = Json.createArrayBuilder().add(str).build();
         String parseStr = "["+exp.get(0).toString()+"]";