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