Merge pull request #110 from jungm/jsonp-2.1

Implement JSON-P 2.1
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
index 3aaa280..b1ad933 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
@@ -46,7 +46,8 @@
 
     public JsonArrayBuilderImpl(final JsonArray initialData,
                                 final BufferStrategy.BufferProvider<char[]> provider,
-                                final RejectDuplicateKeysMode rejectDuplicateKeysMode, final JsonProviderImpl jsonProvider) {
+                                final RejectDuplicateKeysMode rejectDuplicateKeysMode,
+                                final JsonProviderImpl jsonProvider) {
         this.tmpList = new ArrayList<>(initialData);
         this.bufferProvider = provider;
         this.rejectDuplicateKeysMode = rejectDuplicateKeysMode;
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java
index 53d7a43..9feb34c 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonGeneratorImpl.java
@@ -449,8 +449,8 @@
         try {
             if (ex == null) {
                 flushBuffer();
+                writer.close();
             }
-            writer.close();
         } catch (final IOException e) {
             if (ex != null) {
                 throw ex;
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonInMemoryParser.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonInMemoryParser.java
index 7dad95c..0655f70 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonInMemoryParser.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonInMemoryParser.java
@@ -174,6 +174,11 @@
     }
 
     @Override
+    public Event currentEvent() {
+        return currentEvent;
+    }
+
+    @Override
     public Event current() {
         if (currentEvent == null && hasNext()) {
             internalNext();
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
index 56383f5..59b9cdd 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
@@ -199,7 +199,9 @@
         if(attributeMap == null || attributeMap.isEmpty()) {
             return JsonValue.EMPTY_JSON_OBJECT;
         } else {
-            Map<String, JsonValue> dump = (Collections.unmodifiableMap(attributeMap));
+            Map<String, JsonValue> dump = Collections.unmodifiableMap(new LinkedHashMap<>(attributeMap));
+
+            attributeMap.clear();
             return new JsonObjectImpl(dump, bufferProvider);
         }
     }
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java
index 31dd7e1..fce0fac 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonProviderImpl.java
@@ -180,8 +180,13 @@
     }
 
     @Override
+    public JsonNumber createValue(Number number) {
+        return createValue(new BigDecimal(number.toString()));
+    }
+
+    @Override
     public JsonNumber createValue(final BigInteger value) {
-        return new JsonNumberImpl(new BigDecimal(value.toString()), this::checkBigDecimalScale);
+        return createValue((Number) value);
     }
 
     @Override
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
index 8468f3e..d9b5338 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
@@ -370,13 +370,19 @@
     }
 
     @Override
+    public Event currentEvent() {
+        return previousEvent >= 0 && previousEvent < Event.values().length
+                ? Event.values()[previousEvent]
+                : null;
+    }
+
+    @Override
     public Event current() {
         if (previousEvent < 0 && hasNext()) {
             internalNext();
         }
-        return previousEvent >= 0 && previousEvent < Event.values().length
-                ? Event.values()[previousEvent]
-                : null;
+
+        return currentEvent();
     }
 
     private void unreadChar() {
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/RejectDuplicateKeysMode.java b/johnzon-core/src/main/java/org/apache/johnzon/core/RejectDuplicateKeysMode.java
index d1fb2c5..fe89a6d 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/RejectDuplicateKeysMode.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/RejectDuplicateKeysMode.java
@@ -18,8 +18,10 @@
  */
 package org.apache.johnzon.core;
 
+import jakarta.json.JsonConfig;
 import jakarta.json.JsonException;
 import jakarta.json.JsonValue;
+
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -33,9 +35,11 @@
         if (map.put(k, v) != null) {
             throw new JsonException("Rejected key: '" + k + "', already present");
         }
-    });
+    }),
+    FIRST(Map::putIfAbsent);
 
     static final List<String> CONFIG_KEYS = asList(
+            JsonConfig.KEY_STRATEGY, // jsonp 2.1 spec
             "johnzon.rejectDuplicateKeys", // our specific one
             "org.glassfish.json.rejectDuplicateKeys" // the spec includes it (yes :facepalm:)
     );
@@ -44,12 +48,14 @@
         if (config == null) {
             return DEFAULT;
         }
+
         return CONFIG_KEYS.stream()
                 .map(config::get)
                 .filter(Objects::nonNull)
                 .findFirst()
                 .map(String::valueOf)
-                .map(it -> "false".equalsIgnoreCase(it) ? "DEFAULT" : it) // alias to avoid to add an enum value for nothing
+                .map(it -> "false".equalsIgnoreCase(it) || "LAST".equalsIgnoreCase(it) ? "DEFAULT" : it) // aliases to avoid to add an enum value for nothing
+                .map(it -> "NONE".equalsIgnoreCase(it) ? "true" : it)
                 .map(it -> valueOf(it.toUpperCase(Locale.ROOT).trim()))
                 .orElse(DEFAULT);
     }
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonInMemoryParserTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonInMemoryParserTest.java
new file mode 100644
index 0000000..a219a86
--- /dev/null
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonInMemoryParserTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.junit.Test;
+
+import jakarta.json.stream.JsonParser;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+
+public class JsonInMemoryParserTest {
+    @Test
+    public void testSpecCurrentEvent() {
+        JsonParser parser = new JsonInMemoryParser(
+                new JsonObjectImpl(
+                        Collections.emptyMap(),
+                        BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10)),
+                BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10),
+                (JsonProviderImpl) JsonProviderImpl.provider());
+
+        assertEquals(null, parser.currentEvent());
+
+        parser.next();
+        assertEquals(JsonParser.Event.START_OBJECT, parser.currentEvent());
+    }
+
+
+    @Test
+    public void testJohnzonParserCurrent() {
+        JohnzonJsonParser parser = new JsonInMemoryParser(
+                new JsonObjectImpl(
+                        Collections.emptyMap(),
+                        BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10)),
+                BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10),
+                (JsonProviderImpl) JsonProviderImpl.provider());
+
+        assertEquals(JsonParser.Event.START_OBJECT, parser.current());
+    }
+}
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java
index c57f94f..328217c 100644
--- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonObjectBuilderImplTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertEquals;
 
 import jakarta.json.Json;
+import jakarta.json.JsonConfig;
 import jakarta.json.JsonException;
 import jakarta.json.JsonObject;
 import jakarta.json.JsonObjectBuilder;
@@ -45,6 +46,36 @@
                 .add("foo", 2);
     }
 
+    @Test(expected = JsonException.class)
+    public void keyStrategyNone() {
+        Json.createBuilderFactory(singletonMap(JsonConfig.KEY_STRATEGY, JsonConfig.KeyStrategy.NONE))
+                .createObjectBuilder()
+                .add("foo", 1)
+                .add("foo", 2);
+    }
+
+    @Test
+    public void keyStrategyFirst() {
+        JsonObject built = Json.createBuilderFactory(singletonMap(JsonConfig.KEY_STRATEGY, JsonConfig.KeyStrategy.FIRST))
+                .createObjectBuilder()
+                .add("foo", 1)
+                .add("foo", 2)
+                .build();
+
+        assertEquals("{\"foo\":1}", built.toString());
+    }
+
+    @Test
+    public void keyStrategyLast() {
+        JsonObject built = Json.createBuilderFactory(singletonMap(JsonConfig.KEY_STRATEGY, JsonConfig.KeyStrategy.LAST))
+                .createObjectBuilder()
+                .add("foo", 1)
+                .add("foo", 2)
+                .build();
+
+        assertEquals("{\"foo\":2}", built.toString());
+    }
+
     @Test
     public void createObjectBuilderMapSupport() {
         final Map<String, Object> initial = new HashMap<>();
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonProviderTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonProviderTest.java
index 412df42..ea78422 100644
--- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonProviderTest.java
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonProviderTest.java
@@ -83,4 +83,14 @@
 
         Assert.assertEquals(bi, val.bigIntegerValue());
     }
+
+    @Test
+    public void testJsonCreateValueNumber() {
+        Number someNumber = 42;
+        JsonNumber val = Json.createValue(someNumber);
+
+        Assert.assertNotNull(val);
+        Assert.assertEquals(JsonValue.ValueType.NUMBER, val.getValueType());
+        Assert.assertEquals(someNumber, val.intValue());
+    }
 }
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java
index 66fe7fc..dc5f243 100644
--- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java
@@ -37,6 +37,7 @@
 
 import jakarta.json.Json;
 import jakarta.json.JsonArray;
+import jakarta.json.JsonConfig;
 import jakarta.json.JsonException;
 import jakarta.json.JsonNumber;
 import jakarta.json.JsonObject;
@@ -74,6 +75,35 @@
                 "}")).readObject();
     }
 
+    @Test(expected = JsonException.class)
+    public void keyStrategyNone() {
+        Json.createReaderFactory(singletonMap(JsonConfig.KEY_STRATEGY, JsonConfig.KeyStrategy.NONE)).createReader(new StringReader("{" +
+                "\"a\":1," +
+                "\"a\":2" +
+                "}")).readObject();
+    }
+
+
+    @Test
+    public void keyStrategyFirstKey() {
+        JsonObject object = Json.createReaderFactory(singletonMap(JsonConfig.KEY_STRATEGY, JsonConfig.KeyStrategy.FIRST)).createReader(new StringReader("{" +
+                "\"a\":1," +
+                "\"a\":2" +
+                "}")).readObject();
+
+        assertEquals(1, object.getInt("a"));
+    }
+
+    @Test
+    public void keyStrategyLastKey() {
+        JsonObject object = Json.createReaderFactory(singletonMap(JsonConfig.KEY_STRATEGY, JsonConfig.KeyStrategy.LAST)).createReader(new StringReader("{" +
+                "\"a\":1," +
+                "\"a\":2" +
+                "}")).readObject();
+
+        assertEquals(2, object.getInt("a"));
+    }
+
     @Test(expected = JsonParsingException.class)
     public void badTypeObject() {
         Json.createReaderFactory(getFactoryConfig()).createReader(new StringReader("[]")).readObject();
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonStreamParserImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonStreamParserImplTest.java
index 2440650..bf767ab 100644
--- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonStreamParserImplTest.java
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonStreamParserImplTest.java
@@ -33,6 +33,37 @@
 
 public class JsonStreamParserImplTest {
     @Test
+    public void testSpecCurrentEvent() {
+        String json = "{}";
+
+        final JsonParser parser = new JsonStreamParserImpl(new ByteArrayInputStream(json
+                .getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8,
+                10,
+                BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10),
+                BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10),
+                true, (JsonProviderImpl) JsonProviderImpl.provider());
+
+        assertEquals(null, parser.currentEvent());
+
+        parser.next();
+        assertEquals(JsonParser.Event.START_OBJECT, parser.currentEvent());
+    }
+
+    @Test
+    public void testJohnzonParserCurrent() {
+        String json = "{}";
+
+        final JohnzonJsonParser parser = new JsonStreamParserImpl(new ByteArrayInputStream(json
+                .getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8,
+                10,
+                BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10),
+                BufferStrategyFactory.valueOf("QUEUE").newCharProvider(10),
+                true, (JsonProviderImpl) JsonProviderImpl.provider());
+
+        assertEquals(JsonParser.Event.START_OBJECT, parser.current());
+    }
+
+    @Test
     public void ensureNoArrayBoundErrorWhenOverflow() throws IOException {
         final String json = new JsonObjectBuilderImpl(
             emptyMap(),