This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new b561bf2d1c Remove the `DefaultFactories` class because it does not 
work in the context of JPMS. Instead, callers need to invoke 
`java.util.ServiceLoader.load(…)` themselves, because JPMS uses the caller for 
checking authorizations.
b561bf2d1c is described below

commit b561bf2d1c370e09b73b2e28737dbeb13ad184e1
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Jun 10 15:18:40 2023 +0200

    Remove the `DefaultFactories` class because it does not work in the context 
of JPMS.
    Instead, callers need to invoke `java.util.ServiceLoader.load(…)` 
themselves,
    because JPMS uses the caller for checking authorizations.
---
 .../apache/sis/internal/jaxb/TypeRegistration.java |   4 +-
 .../sis/internal/metadata/ReferencingServices.java |  14 +-
 .../apache/sis/internal/metadata/package-info.java |   2 +-
 .../sis/internal/metadata/sql/Initializer.java     |  18 +-
 .../sis/internal/metadata/sql/LocalDataSource.java |   6 +-
 .../java/org/apache/sis/xml/MarshallerPool.java    |   4 +-
 .../sis/internal/jaxb/gml/TimePeriodTest.java      |   2 +-
 .../apache/sis/internal/referencing/LazySet.java   |   4 +-
 .../sis/referencing/EPSGFactoryFallback.java       |   7 +-
 .../sis/referencing/factory/sql/EPSGInstaller.java |   5 +-
 .../sis/internal/converter/SystemRegistry.java     |   7 +-
 .../sis/internal/system/DefaultFactories.java      | 213 ---------------------
 .../org/apache/sis/internal/system/Reflect.java    | 106 ++++++++++
 .../internal/temporal/DefaultTemporalFactory.java  |   9 +-
 .../apache/sis/internal/temporal/package-info.java |   2 +-
 .../sis/internal/util/TemporalUtilities.java       |  45 +++--
 .../apache/sis/setup/InstallationResources.java    |  15 +-
 .../org/apache/sis/storage/DataStoreRegistry.java  |   6 +-
 18 files changed, 200 insertions(+), 269 deletions(-)

diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java
index df04748525..fac23dd33c 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java
@@ -29,8 +29,8 @@ import java.lang.ref.WeakReference;
 import jakarta.xml.bind.JAXBContext;
 import jakarta.xml.bind.JAXBException;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.system.Reflect;
 import org.apache.sis.internal.system.SystemListener;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.DelayedExecutor;
 import org.apache.sis.internal.system.DelayedRunnable;
 
@@ -163,7 +163,7 @@ public abstract class TypeRegistration {
     private static ServiceLoader<TypeRegistration> services() {
         ServiceLoader<TypeRegistration> s = services;
         if (s == null) {
-            services = s = 
DefaultFactories.createServiceLoader(TypeRegistration.class);
+            services = s = ServiceLoader.load(TypeRegistration.class, 
Reflect.getContextClassLoader());
             DelayedExecutor.schedule(new DelayedRunnable(1, TimeUnit.MINUTES) {
                 @Override public void run() {services = null;}
             });
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
index 84c7e38341..8e297de80f 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
@@ -31,7 +31,6 @@ import 
org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.OptionalDependency;
 import org.apache.sis.internal.system.Modules;
 
@@ -45,7 +44,7 @@ import org.apache.sis.internal.system.Modules;
  * <cite>"referencing by coordinates"</cite> but needed by metadata.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   0.3
  */
 public class ReferencingServices extends OptionalDependency {
@@ -278,20 +277,11 @@ public class ReferencingServices extends 
OptionalDependency {
 
     /**
      * Returns the default coordinate operation factory.
-     * The default implementation is used when the referencing module is not 
on the classpath,
-     * in which case this method uses whatever implementation is found by the 
service loader.
-     * Otherwise (if the referencing module is present), this method will be 
overridden with
-     * the SIS specific implementation.
      *
      * @return the coordinate operation factory to use.
      */
     public CoordinateOperationFactory getCoordinateOperationFactory() {
-        final CoordinateOperationFactory factory = 
DefaultFactories.forClass(CoordinateOperationFactory.class);
-        if (factory != null) {
-            return factory;
-        } else {
-            throw moduleNotFound();
-        }
+        throw moduleNotFound();
     }
 
     /**
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java
index a825ad016e..3280628ec4 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/package-info.java
@@ -24,7 +24,7 @@
  * may change in incompatible ways in any future version without notice.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   0.3
  */
 package org.apache.sis.internal.metadata;
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java
index 45d7946804..b2b2218133 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Initializer.java
@@ -19,13 +19,14 @@ package org.apache.sis.internal.metadata.sql;
 import java.util.Locale;
 import java.util.function.Supplier;
 import java.util.concurrent.Callable;
-import java.io.IOException;
+import java.util.ServiceLoader;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import javax.sql.DataSource;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
+import java.io.IOException;
 import javax.naming.Context;
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
@@ -38,10 +39,10 @@ import javax.naming.event.NamingExceptionEvent;
 import javax.naming.event.ObjectChangeListener;
 import org.apache.sis.setup.InstallationResources;
 import org.apache.sis.internal.system.Configuration;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.DataDirectory;
 import org.apache.sis.internal.system.SystemListener;
 import org.apache.sis.internal.system.Shutdown;
+import org.apache.sis.internal.system.Reflect;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.logging.Logging;
 
@@ -111,6 +112,15 @@ public abstract class Initializer {
     protected Initializer() {
     }
 
+    /**
+     * Returns initializers found on the class path.
+     *
+     * @return initializers found on the class path.
+     */
+    public static ServiceLoader<Initializer> load() {
+        return ServiceLoader.load(Initializer.class, 
Reflect.getContextClassLoader());
+    }
+
     /**
      * Invoked for populating an initially empty database.
      *
@@ -191,7 +201,7 @@ public abstract class Initializer {
                  */
                 Logging.recoverableException(SystemListener.LOGGER, 
Listener.class, "objectChanged", e);
             }
-            for (Initializer init : 
DefaultFactories.createServiceLoader(Initializer.class)) {
+            for (Initializer init : load()) {
                 init.dataSourceChanged();
             }
         }
@@ -374,7 +384,7 @@ public abstract class Initializer {
      * @since 0.8
      */
     private static DataSource embedded() {
-        for (InstallationResources res : 
DefaultFactories.createServiceLoader(InstallationResources.class)) {
+        for (InstallationResources res : InstallationResources.load()) {
             if (res.getAuthorities().contains(EMBEDDED)) try {
                 final String[] names = res.getResourceNames(EMBEDDED);
                 for (int i=0; i<names.length; i++) {
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java
index b5923c713a..f9a20817ed 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/LocalDataSource.java
@@ -33,7 +33,7 @@ import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.system.DataDirectory;
-import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.system.Reflect;
 import org.apache.sis.internal.util.Strings;
 
 
@@ -203,7 +203,7 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
             case HSQL:  classname = "org.hsqldb.jdbc.JDBCDataSource"; break;
             default:    throw new IllegalArgumentException(dialect.toString());
         }
-        final ClassLoader loader = DefaultFactories.getContextClassLoader();
+        final ClassLoader loader = Reflect.getContextClassLoader();
         final Class<?> c = Class.forName(classname, true, loader);
         source = (DataSource) c.getConstructor().newInstance();
         final Class<?>[] args = {String.class};
@@ -258,7 +258,7 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
                 default: enabler = null; break;
             }
             try (Connection c = source.getConnection()) {
-                for (Initializer init : 
DefaultFactories.createServiceLoader(Initializer.class)) {
+                for (Initializer init : Initializer.load()) {
                     init.createSchema(c);
                 }
             } finally {
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java 
b/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java
index 80b42769a8..a619e14691 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/MarshallerPool.java
@@ -26,10 +26,10 @@ import jakarta.xml.bind.JAXBException;
 import jakarta.xml.bind.Marshaller;
 import jakarta.xml.bind.Unmarshaller;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.internal.system.Reflect;
 import org.apache.sis.internal.system.Configuration;
 import org.apache.sis.internal.system.DelayedExecutor;
 import org.apache.sis.internal.system.DelayedRunnable;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.jaxb.AdapterReplacement;
 import org.apache.sis.internal.jaxb.TypeRegistration;
 import org.apache.sis.internal.jaxb.Context;
@@ -180,7 +180,7 @@ public class MarshallerPool {
     public MarshallerPool(final JAXBContext context, final Map<String,?> 
properties) throws JAXBException {
         ArgumentChecks.ensureNonNull("context", context);
         this.context = context;
-        replacements = 
DefaultFactories.createServiceLoader(AdapterReplacement.class);
+        replacements = ServiceLoader.load(AdapterReplacement.class, 
Reflect.getContextClassLoader());
         implementation = Implementation.detect(context);
         /*
          * Prepares a copy of the property map (if any), then removes the
diff --git 
a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java
 
b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java
index 9fea8fa905..bd35cbeeb1 100644
--- 
a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java
+++ 
b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/TimePeriodTest.java
@@ -68,7 +68,7 @@ public final class TimePeriodTest extends TestCase {
      * Creates a GeoAPI instant object for the given date.
      */
     private static Instant instant(final String date) {
-        return DefaultTemporalFactory.INSTANCE.createInstant(date(date));
+        return DefaultTemporalFactory.provider().createInstant(date(date));
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java
index aa77fb140d..e70e9da650 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/LazySet.java
@@ -22,7 +22,7 @@ import java.util.Objects;
 import java.util.Iterator;
 import java.util.ServiceLoader;
 import java.util.NoSuchElementException;
-import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.system.Reflect;
 import org.apache.sis.internal.util.SetOfUnknownSize;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 
@@ -151,7 +151,7 @@ public class LazySet<E> extends SetOfUnknownSize<E> {
      */
     private boolean canPullMore() {
         if (sourceIterator == null && cachedElements == null) {
-            sourceIterator = 
DefaultFactories.createServiceLoader(service).iterator();
+            sourceIterator = ServiceLoader.load(service, 
Reflect.getContextClassLoader()).iterator();
             if (createCache()) {
                 return true;
             }
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
index 7fbbe71697..e50ac160b2 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
@@ -18,6 +18,7 @@ package org.apache.sis.referencing;
 
 import java.util.Set;
 import java.util.LinkedHashSet;
+import java.util.ServiceLoader;
 import javax.measure.Unit;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
@@ -43,7 +44,6 @@ import org.opengis.metadata.citation.Citation;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
 import org.apache.sis.internal.referencing.Resources;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Fallback;
 import org.apache.sis.internal.util.MetadataServices;
 import org.apache.sis.internal.util.Constants;
@@ -63,7 +63,7 @@ import org.apache.sis.measure.Units;
  * The EPSG identifiers are provided as references where to find the complete 
definitions.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   0.7
  */
 @Fallback
@@ -364,8 +364,7 @@ final class EPSGFactoryFallback extends 
GeodeticAuthorityFactory
     private synchronized String getInstallationURL() {
         if (installationURL == null) {
             installationURL = URLs.EPSG_INSTALL;            // To be used as 
fallback.
-            final Iterable<InstallationResources> services =
-                    
DefaultFactories.createServiceLoader(InstallationResources.class);
+            final ServiceLoader<InstallationResources> services = 
InstallationResources.load();
             /*
              * Following loop will be executed one or two times. First, we 
check for resources that are
              * specifically for EPSG geodetic dataset. If none are found, 
fallback on embedded database.
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
index b65c097978..4c93879553 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
@@ -29,7 +29,6 @@ import java.io.BufferedReader;
 import org.apache.sis.util.StringBuilders;
 import org.apache.sis.internal.metadata.sql.ScriptRunner;
 import org.apache.sis.internal.metadata.sql.SQLUtilities;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.system.Fallback;
 import org.apache.sis.util.Exceptions;
@@ -47,7 +46,7 @@ import static org.apache.sis.internal.util.Constants.EPSG;
  * in the test directory for more information about how the scripts are 
formatted.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.7
  */
 final class EPSGInstaller extends ScriptRunner {
@@ -274,7 +273,7 @@ final class EPSGInstaller extends ScriptRunner {
      */
     private static InstallationResources lookupProvider(final Locale locale) 
throws IOException {
         InstallationResources fallback = null;
-        for (final InstallationResources provider : 
DefaultFactories.createServiceLoader(InstallationResources.class)) {
+        for (final InstallationResources provider : 
InstallationResources.load()) {
             if (provider.getAuthorities().contains(EPSG)) {
                 if (!provider.getClass().isAnnotationPresent(Fallback.class)) {
                     return provider;
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java
 
b/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java
index 679da2ab24..de13b3569c 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemRegistry.java
@@ -17,12 +17,13 @@
 package org.apache.sis.internal.converter;
 
 import java.util.Date;
+import java.util.ServiceLoader;
 import org.opengis.util.CodeList;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.util.UnconvertibleObjectException;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.SystemListener;
+import org.apache.sis.internal.system.Reflect;
 import org.apache.sis.internal.system.Modules;
 
 
@@ -49,7 +50,7 @@ import org.apache.sis.internal.system.Modules;
  * The same {@link #INSTANCE} can be safely used by many threads without 
synchronization on the part of the caller.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.4
  * @since   0.3
  */
 public final class SystemRegistry extends ConverterRegistry {
@@ -105,7 +106,7 @@ public final class SystemRegistry extends ConverterRegistry 
{
      */
     @Override
     protected void initialize() {
-        for (ObjectConverter<?,?> converter : 
DefaultFactories.createServiceLoader(ObjectConverter.class)) {
+        for (ObjectConverter<?,?> converter : 
ServiceLoader.load(ObjectConverter.class, Reflect.getContextClassLoader())) {
             if (converter instanceof SystemConverter<?,?>) {
                 converter = ((SystemConverter<?,?>) converter).unique();
             }
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/system/DefaultFactories.java
 
b/core/sis-utility/src/main/java/org/apache/sis/internal/system/DefaultFactories.java
deleted file mode 100644
index c5e90963cd..0000000000
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/system/DefaultFactories.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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.sis.internal.system;
-
-import java.util.Set;
-import java.util.Map;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.ServiceLoader;
-import java.util.ServiceConfigurationError;
-import java.util.function.Consumer;
-import org.apache.sis.util.logging.Logging;
-
-
-/**
- * Default factories defined in the {@code sis-utility} module.
- * This is a temporary placeholder until we leverage the "dependency 
injection" pattern.
- * A candidate replacement is JSR-330.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @author  Guilhem Legal (Geomatys)
- * @version 1.4
- *
- * @see <a href="https://jcp.org/en/jsr/detail?id=330";>JSR-330</a>
- * @see <a href="https://issues.apache.org/jira/browse/SIS-102";>SIS-102</a>
- *
- * @since 0.3
- */
-public final class DefaultFactories extends SystemListener {
-    /**
-     * Cache of factories found by {@link ServiceLoader} from {@code 
META-INF/services} files content.
-     */
-    private static final Map<Class<?>, Object> FACTORIES = new 
IdentityHashMap<>(4);
-    static {
-        SystemListener.add(new DefaultFactories());
-    }
-
-    /**
-     * For the singleton system listener only.
-     */
-    private DefaultFactories() {
-        super(Modules.UTILITIES);
-    }
-
-    /**
-     * Discards cached factories when the classpath has changed.
-     */
-    @Override
-    protected void classpathChanged() {
-        synchronized (DefaultFactories.class) {
-            FACTORIES.clear();
-        }
-    }
-
-    /**
-     * Returns the default factory implementing the given interface.
-     * This method gives preference to Apache SIS implementation of factories 
if present.
-     * This is a temporary mechanism while we are waiting for a real 
dependency injection mechanism.
-     *
-     * @param  <T>   the interface type.
-     * @param  type  the interface type.
-     * @return a factory implementing the given interface, or {@code null} if 
none.
-     */
-    public static synchronized <T> T forClass(final Class<T> type) {
-        T factory = type.cast(FACTORIES.get(type));
-        if (factory == null && !FACTORIES.containsKey(type)) {
-            T fallback = null;
-            for (final T candidate : createServiceLoader(type)) {
-                if 
(candidate.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX)) {
-                    if (factory != null) {
-                        throw new ServiceConfigurationError("Found two 
implementations of " + type);
-                    }
-                    factory = candidate;
-                } else if (fallback == null) {
-                    fallback = candidate;
-                }
-            }
-            if (factory == null) {
-                factory = fallback;
-            }
-            /*
-             * Verifies if the factory that we just selected is the same 
implementation than an existing instance.
-             * The main case for this test is 
org.apache.sis.referencing.factory.GeodeticObjectFactory, where the
-             * same class implements 3 factory interfaces.
-             */
-            if (factory != null) {
-                for (final Object existing : FACTORIES.values()) {
-                    if (existing != null && 
factory.getClass().equals(existing.getClass())) {
-                        factory = type.cast(existing);
-                        break;
-                    }
-                }
-            }
-            FACTORIES.put(type, factory);
-        }
-        return factory;
-    }
-
-    /**
-     * Returns a service loader for the given type using the default class 
loader.
-     * The default is the current thread {@linkplain 
Thread#getContextClassLoader() context class loader},
-     * provided that it can access at least the Apache SIS stores.
-     *
-     * @param  <T>      the compile-time value of {@code service} argument.
-     * @param  service  the interface or abstract class representing the 
service.
-     * @return a new service loader for the given service type.
-     *
-     * @since 0.8
-     */
-    public static <T> ServiceLoader<T> createServiceLoader(final Class<T> 
service) {
-        try {
-            return ServiceLoader.load(service, getContextClassLoader());
-        } catch (SecurityException e) {
-            /*
-             * We were not allowed to invoke 
Thread.currentThread().getContextClassLoader().
-             * But ServiceLoader.load(Class) may be allowed to, since it is 
part of JDK.
-             */
-            Logging.recoverableException(SystemListener.LOGGER, 
DefaultFactories.class, "createServiceLoader", e);
-            return ServiceLoader.load(service);
-        }
-    }
-
-    /**
-     * Returns the context class loader, but makes sure that it has Apache SIS 
on its classpath.
-     * First, this method invokes {@link Thread#getContextClassLoader()} for 
the current thread.
-     * Then this method scans over all Apache SIS classes on the stack trace. 
For each SIS class,
-     * its loader is compared to the above-cited context class loader. If the 
context class loader
-     * is equal or is a child of the SIS loader, then it is left unchanged. 
Otherwise the context
-     * class loader is replaced by the SIS one.
-     *
-     * <p>The intent of this method is to ensure that {@link 
ServiceLoader#load(Class)} will find the
-     * Apache SIS services even in an environment that defined an unsuitable 
context class loader.</p>
-     *
-     * @return the context class loader if suitable, or another class loader 
otherwise.
-     * @throws SecurityException if this method is not allowed to get the 
current thread
-     *         context class loader or one of its parent.
-     *
-     * @since 0.8
-     */
-    public static ClassLoader getContextClassLoader() throws SecurityException 
{
-        final Walker walker = new Walker();
-        return 
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk((stream)
 -> {
-            stream.forEach(walker);
-            return walker.loader;
-        });
-    }
-
-    /**
-     * Action to be executed for each stack frame inspected by {@link 
#getContextClassLoader()}.
-     * The action is initialized to the context class loader of current thread.
-     * Then it checks if the class loader should be replaced by another one 
containing at least
-     * the Apache SIS class loader.
-     */
-    private static final class Walker implements 
Consumer<StackWalker.StackFrame> {
-        /**
-         * The context class loader to be returned by {@link 
#getContextClassLoader()}.
-         */
-        ClassLoader loader;
-
-        /**
-         * All parents of {@link #loader}.
-         */
-        private final Set<ClassLoader> parents;
-
-        /**
-         * Creates a new walker initialized to the context class loader of 
current thread.
-         */
-        Walker() {
-            parents = new HashSet<>();
-            setClassLoader(Thread.currentThread().getContextClassLoader());
-        }
-
-        /**
-         * Set the class loader to the given value, which may be null.
-         */
-        private void setClassLoader(ClassLoader c) {
-            loader = c;
-            while (c != null) {
-                parents.add(c);
-                c = c.getParent();
-            }
-        }
-
-        /**
-         * If the given stack frame is an Apache SIS method, ensures that 
{@link #loader}
-         * is the SIS class loader or has the SIS class loader as a parent.
-         */
-        @Override
-        public void accept(final StackWalker.StackFrame frame) {
-            if (frame.getClassName().startsWith(Modules.CLASSNAME_PREFIX)) {
-                ClassLoader c = frame.getDeclaringClass().getClassLoader();
-                if (!parents.contains(c)) {
-                    parents.clear();
-                    setClassLoader(c);
-                }
-            }
-        }
-    }
-}
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Reflect.java 
b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Reflect.java
new file mode 100644
index 0000000000..35808d7986
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Reflect.java
@@ -0,0 +1,106 @@
+/*
+ * 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.sis.internal.system;
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.ServiceLoader;
+import java.util.function.Consumer;
+
+
+/**
+ * Contextual information fetching by reflection.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @author  Guilhem Legal (Geomatys)
+ * @version 1.4
+ * @since   0.8
+ */
+public final class Reflect implements Consumer<StackWalker.StackFrame> {
+    /**
+     * Returns the context class loader, but makes sure that it has Apache SIS 
on its classpath.
+     * First, this method invokes {@link Thread#getContextClassLoader()} for 
the current thread.
+     * Then this method scans over all Apache SIS classes on the stack trace. 
For each SIS class,
+     * its loader is compared to the above-cited context class loader. If the 
context class loader
+     * is equal or is a child of the SIS loader, then it is left unchanged. 
Otherwise the context
+     * class loader is replaced by the SIS one.
+     *
+     * <p>The intent of this method is to ensure that {@link 
ServiceLoader#load(Class)} will find the
+     * Apache SIS services even in an environment that defined an unsuitable 
context class loader.
+     * Note that the call to {@code ServiceLoader.load(…)} must be done from 
the caller class.
+     * We cannot provide this convenience in this class because of JPMS 
encapsulation.</p>
+     *
+     * @return the context class loader if suitable, or another class loader 
otherwise.
+     * @throws SecurityException if this method is not allowed to get the 
current thread
+     *         context class loader or one of its parent.
+     */
+    public static ClassLoader getContextClassLoader() throws SecurityException 
{
+        final Reflect walker = new Reflect();
+        return 
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk((stream)
 -> {
+            stream.forEach(walker);
+            return walker.loader;
+        });
+    }
+
+    /**
+     * The context class loader to be returned by {@link 
#getContextClassLoader()}.
+     */
+    private ClassLoader loader;
+
+    /**
+     * All parents of {@link #loader}.
+     */
+    private final Set<ClassLoader> parents;
+
+    /**
+     * Creates a new walker initialized to the context class loader of current 
thread.
+     */
+    private Reflect() {
+        parents = new HashSet<>();
+        setClassLoader(Thread.currentThread().getContextClassLoader());
+    }
+
+    /**
+     * Set the class loader to the given value, which may be null.
+     */
+    private void setClassLoader(ClassLoader c) {
+        loader = c;
+        while (c != null) {
+            parents.add(c);
+            c = c.getParent();
+        }
+    }
+
+    /**
+     * Action to be executed for each stack frame inspected by {@link 
#getContextClassLoader()}.
+     * The action is initialized to the context class loader of current thread.
+     * Then it checks if the class loader should be replaced by another one
+     * containing at least the Apache SIS class loader.
+     *
+     * @param  frame  a stack frame being inspected.
+     */
+    @Override
+    public void accept(final StackWalker.StackFrame frame) {
+        if (frame.getClassName().startsWith(Modules.CLASSNAME_PREFIX)) {
+            ClassLoader c = frame.getDeclaringClass().getClassLoader();
+            if (!parents.contains(c)) {
+                parents.clear();
+                setClassLoader(c);
+            }
+        }
+    }
+}
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java
 
b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java
index 4f1573343a..c69ef42d31 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/DefaultTemporalFactory.java
@@ -32,12 +32,17 @@ import org.apache.sis.util.resources.Errors;
  * GeoAPI temporal interfaces are expected to change a lot in a future 
revision.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   1.2
  */
 public final class DefaultTemporalFactory implements TemporalFactory {
     /** The unique instance of this factory. */
-    public static final TemporalFactory INSTANCE = new 
DefaultTemporalFactory();
+    private static final TemporalFactory INSTANCE = new 
DefaultTemporalFactory();
+
+    /** {@return the unique instance of this factory}. */
+    public static TemporalFactory provider() {
+        return INSTANCE;
+    }
 
     /** Creates the singleton instance. */
     private DefaultTemporalFactory() {
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java
 
b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java
index da91b0c8d7..e616a33332 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/internal/temporal/package-info.java
@@ -20,7 +20,7 @@
  * the temporal GeoAPI interfaces are expected to change a lot in a future 
revision.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   1.2
  */
 package org.apache.sis.internal.temporal;
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
index 4955bc7c17..4d991af9b0 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
@@ -17,12 +17,14 @@
 package org.apache.sis.internal.util;
 
 import java.util.Date;
+import java.util.ServiceLoader;
 import org.opengis.temporal.Instant;
 import org.opengis.temporal.Period;
 import org.opengis.temporal.TemporalFactory;
 import org.opengis.temporal.TemporalPrimitive;
-import org.apache.sis.util.Static;
-import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.system.Reflect;
+import org.apache.sis.internal.system.SystemListener;
 import org.apache.sis.internal.temporal.DefaultTemporalFactory;
 
 
@@ -32,28 +34,47 @@ import 
org.apache.sis.internal.temporal.DefaultTemporalFactory;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Guilhem Legal (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.3
  */
-public final class TemporalUtilities extends Static {
+public final class TemporalUtilities extends SystemListener {
     /**
-     * Do not allow instantiation of this class.
+     * The default factory to use for implementations.
+     */
+    private static volatile TemporalFactory implementation;
+
+    static {
+        SystemListener.add(new TemporalUtilities());
+    }
+
+    /**
+     * For the singleton system listener only.
      */
     private TemporalUtilities() {
+        super(Modules.METADATA);
     }
 
     /**
-     * Returns a temporal factory if available.
+     * Discards the cached factory when the classpath has changed.
+     */
+    @Override
+    protected void classpathChanged() {
+        implementation = null;
+    }
+
+    /**
+     * Returns a temporal factory, or a default implementation if none.
      *
      * @return the temporal factory.
-     * @throws UnsupportedOperationException if the temporal factory is not 
available on the classpath.
      */
-    public static TemporalFactory getTemporalFactory() throws 
UnsupportedOperationException {
-        final TemporalFactory factory = 
DefaultFactories.forClass(TemporalFactory.class);
-        if (factory != null) {
-            return factory;
+    public static TemporalFactory getTemporalFactory() {
+        TemporalFactory factory = implementation;
+        if (factory == null) {
+            factory = ServiceLoader.load(TemporalFactory.class, 
Reflect.getContextClassLoader())
+                    .findFirst().orElseGet(DefaultTemporalFactory::provider);
+            implementation = factory;
         }
-        return DefaultTemporalFactory.INSTANCE;
+        return factory;
     }
 
     /**
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java
 
b/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java
index 63c86d07c6..8d12b842cd 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/setup/InstallationResources.java
@@ -18,11 +18,13 @@ package org.apache.sis.setup;
 
 import java.util.Set;
 import java.util.Locale;
+import java.util.ServiceLoader;
 import java.io.IOException;
 import java.io.BufferedReader;
 import org.apache.sis.internal.util.URLs;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.MetadataServices;
+import org.apache.sis.internal.system.Reflect;
 
 
 /**
@@ -66,7 +68,7 @@ import org.apache.sis.internal.util.MetadataServices;
  * for writing the database; see above-cited web page for more information).
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.7
  */
 public abstract class InstallationResources {
@@ -76,6 +78,17 @@ public abstract class InstallationResources {
     protected InstallationResources() {
     }
 
+    /**
+     * Returns installation resources found on the class path.
+     *
+     * @return installation resources found on the class path.
+     *
+     * @since 1.4
+     */
+    public static ServiceLoader<InstallationResources> load() {
+        return ServiceLoader.load(InstallationResources.class, 
Reflect.getContextClassLoader());
+    }
+
     /**
      * Returns identifiers of the resources provided by this instance.
      * The values recognized by SIS are listed below
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
index 9f850b5f31..2290c9ad63 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
@@ -21,9 +21,9 @@ import java.util.LinkedList;
 import java.util.Set;
 import java.util.Iterator;
 import java.util.ServiceLoader;
+import org.apache.sis.internal.system.Reflect;
 import org.apache.sis.internal.storage.Resources;
 import org.apache.sis.internal.storage.StoreMetadata;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.LazySet;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ArraysExt;
@@ -43,7 +43,7 @@ import org.apache.sis.util.ArraysExt;
  * on the part of the caller.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   0.4
  */
 final class DataStoreRegistry {
@@ -60,7 +60,7 @@ final class DataStoreRegistry {
      * provided that it can access at least the Apache SIS stores.
      */
     public DataStoreRegistry() {
-        loader = DefaultFactories.createServiceLoader(DataStoreProvider.class);
+        this(Reflect.getContextClassLoader());
     }
 
     /**


Reply via email to