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;
+ }
+
+ }
+}