Json factory methods are very inefficient #154

Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
diff --git a/api/src/main/java/jakarta/json/spi/JsonProvider.java b/api/src/main/java/jakarta/json/spi/JsonProvider.java
index 938f7bc..694bb8f 100644
--- a/api/src/main/java/jakarta/json/spi/JsonProvider.java
+++ b/api/src/main/java/jakarta/json/spi/JsonProvider.java
@@ -16,22 +16,40 @@
 
 package jakarta.json.spi;
 
-import jakarta.json.*;
-import jakarta.json.stream.JsonGenerator;
-import jakarta.json.stream.JsonGeneratorFactory;
-import jakarta.json.stream.JsonParser;
-import jakarta.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.Iterator;
 import java.util.Map;
-import java.util.ServiceLoader;
-import java.math.BigDecimal;
-import java.math.BigInteger;
 import java.util.Optional;
+import java.util.ServiceLoader;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonException;
+import jakarta.json.JsonMergePatch;
+import jakarta.json.JsonNumber;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+import jakarta.json.JsonPatch;
+import jakarta.json.JsonPatchBuilder;
+import jakarta.json.JsonPointer;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonReaderFactory;
+import jakarta.json.JsonString;
+import jakarta.json.JsonStructure;
+import jakarta.json.JsonValue;
+import jakarta.json.JsonWriter;
+import jakarta.json.JsonWriterFactory;
+import jakarta.json.stream.JsonGenerator;
+import jakarta.json.stream.JsonGeneratorFactory;
+import jakarta.json.stream.JsonParser;
+import jakarta.json.stream.JsonParserFactory;
 
 /**
  * Service provider for JSON processing objects.
@@ -44,6 +62,10 @@
 public abstract class JsonProvider {
 
     /**
+     * The name of the property that contains the name of the class capable of creating new JsonProvider objects.
+     */
+    public static final String JSONP_PROVIDER_FACTORY = "jakarta.json.spi.JsonProvider";
+    /**
      * A constant representing the name of the default
      * {@code JsonProvider} implementation class.
      */
@@ -57,15 +79,29 @@
     }
 
     /**
-     * Creates a JSON provider object. The provider is loaded using the
-     * {@link ServiceLoader#load(Class)} method. If there are no available
-     * service providers, this method returns the default service provider.
+     * Creates a JSON provider object.
+     *
+     * Implementation discovery consists of following steps:
+     * <ol>
+     * <li>If the system property {@value #JSONP_PROVIDER_FACTORY} exists,
+     *    then its value is assumed to be the provider factory class.
+     *    This phase of the look up enables per-JVM override of the JsonProvider implementation.</li>
+     * <li>The provider is loaded using the {@link ServiceLoader#load(Class)} method.</li>
+     * <li>If all the steps above fail, then the rest of the look up is unspecified. That said,
+     *    the recommended behavior is to simply look for some hard-coded platform default Jakarta
+     *    JSON Processing implementation. This phase of the look up is so that a platform can have
+     *    its own Jakarta JSON Processing implementation as the last resort.</li>
+     * </ol>
      * Users are recommended to cache the result of this method.
      *
      * @see ServiceLoader
      * @return a JSON provider
      */
     public static JsonProvider provider() {
+        String factory = System.getProperty(JsonProvider.class.getName());
+        if (factory != null) {
+            return newInstance(LazyFactoryLoader.JSON_PROVIDER);
+        }
         ServiceLoader<JsonProvider> loader = ServiceLoader.load(JsonProvider.class);
         Iterator<JsonProvider> it = loader.iterator();
         if (it.hasNext()) {
@@ -85,6 +121,35 @@
     }
 
     /**
+     * Creates a new instance from the specified class
+     * @param clazz class to instance
+     * @return the JsonProvider instance
+     * @throws IllegalArgumentException for reflection issues
+     */
+    private static JsonProvider newInstance(Class<JsonProvider> clazz) {
+        checkPackageAccess(clazz.getName());
+        try {
+            return clazz.getConstructor().newInstance();
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalArgumentException("Cannot instance " + clazz.getName(), e);
+        }
+    }
+
+    /**
+     * Make sure that the current thread has an access to the package of the given name.
+     * @param className The class name to check.
+     */
+    private static void checkPackageAccess(String className) {
+        SecurityManager s = System.getSecurityManager();
+        if (s != null) {
+            int i = className.lastIndexOf('.');
+            if (i != -1) {
+                s.checkPackageAccess(className.substring(0, i));
+            }
+        }
+    }
+
+    /**
      * Creates a JSON parser from a character stream.
      *
      * @param reader i/o reader from which JSON is to be read
@@ -508,4 +573,32 @@
             throw new UnsupportedOperationException(number + " type is not known");
         }
     }
+    
+    /**
+     * Lazy loads the class specified in System property with the key JSONP_PROVIDER_FACTORY.
+     * If no property is set, the value of {@link #JSON_PROVIDER} will be null.
+     * In case of errors an IllegalStateException is thrown.
+     *
+     */
+    @SuppressWarnings("unchecked")
+    private static class LazyFactoryLoader {
+
+        /**
+         * JSON provider class
+         */
+        private static final Class<JsonProvider> JSON_PROVIDER;
+
+        static {
+            String className = System.getProperty(JSONP_PROVIDER_FACTORY);
+            if (className != null) {
+                try {
+                    JSON_PROVIDER = (Class<JsonProvider>) Class.forName(className);
+                } catch (ReflectiveOperationException e) {
+                    throw new IllegalStateException("Cannot instance " + className, e);
+                }
+            } else {
+                JSON_PROVIDER = null;
+            }
+        }
+    }
 }
diff --git a/impl-tck/tck-tests/pom.xml b/impl-tck/tck-tests/pom.xml
index ba4f2de..cc77e86 100644
--- a/impl-tck/tck-tests/pom.xml
+++ b/impl-tck/tck-tests/pom.xml
@@ -57,6 +57,8 @@
                     <trimStackTrace>false</trimStackTrace>
                     <failIfNoTests>true</failIfNoTests>
                     <argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
+                    <forkCount>1</forkCount>
+                    <reuseForks>false</reuseForks>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/tck/tck-tests/src/main/java/jakarta/jsonp/tck/api/provider/JsonProviderTest.java b/tck/tck-tests/src/main/java/jakarta/jsonp/tck/api/provider/JsonProviderTest.java
new file mode 100644
index 0000000..fb584c6
--- /dev/null
+++ b/tck/tck-tests/src/main/java/jakarta/jsonp/tck/api/provider/JsonProviderTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2021 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
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package jakarta.jsonp.tck.api.provider;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Map;
+
+import org.junit.Test;
+
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObjectBuilder;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonReaderFactory;
+import jakarta.json.JsonWriter;
+import jakarta.json.JsonWriterFactory;
+import jakarta.json.spi.JsonProvider;
+import jakarta.json.stream.JsonGenerator;
+import jakarta.json.stream.JsonGeneratorFactory;
+import jakarta.json.stream.JsonParser;
+import jakarta.json.stream.JsonParserFactory;
+
+/**
+ * Tests related to JsonProvider.
+ *
+ */
+public class JsonProviderTest {
+    
+    /**
+     * Verifies it is possible to obtain the JsonProvider implementation from a System property.
+     */
+    @Test
+    public void systemProperty() {
+        System.setProperty(JsonProvider.JSONP_PROVIDER_FACTORY, DummyJsonProvider.class.getName());
+        JsonProvider provider = JsonProvider.provider();
+        assertEquals(DummyJsonProvider.class, provider.getClass());
+    }
+
+    public static class DummyJsonProvider extends JsonProvider {
+
+        @Override
+        public JsonParser createParser(Reader reader) {
+            return null;
+        }
+
+        @Override
+        public JsonParser createParser(InputStream in) {
+            return null;
+        }
+
+        @Override
+        public JsonParserFactory createParserFactory(Map<String, ?> config) {
+            return null;
+        }
+
+        @Override
+        public JsonGenerator createGenerator(Writer writer) {
+            return null;
+        }
+
+        @Override
+        public JsonGenerator createGenerator(OutputStream out) {
+            return null;
+        }
+
+        @Override
+        public JsonGeneratorFactory createGeneratorFactory(Map<String, ?> config) {
+            return null;
+        }
+
+        @Override
+        public JsonReader createReader(Reader reader) {
+            return null;
+        }
+
+        @Override
+        public JsonReader createReader(InputStream in) {
+            return null;
+        }
+
+        @Override
+        public JsonWriter createWriter(Writer writer) {
+            return null;
+        }
+
+        @Override
+        public JsonWriter createWriter(OutputStream out) {
+            return null;
+        }
+
+        @Override
+        public JsonWriterFactory createWriterFactory(Map<String, ?> config) {
+            return null;
+        }
+
+        @Override
+        public JsonReaderFactory createReaderFactory(Map<String, ?> config) {
+            return null;
+        }
+
+        @Override
+        public JsonObjectBuilder createObjectBuilder() {
+            return null;
+        }
+
+        @Override
+        public JsonArrayBuilder createArrayBuilder() {
+            return null;
+        }
+
+        @Override
+        public JsonBuilderFactory createBuilderFactory(Map<String, ?> config) {
+            return null;
+        }
+        
+    }
+}