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

commit 2866e75668c04dc6a7a3f577714afd0183338b15
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Oct 3 18:00:36 2024 +0200

    Better detection of when EPSG data are not available and fallback should be 
used.
    This is needed for fixing test failures when SIS is built without local 
EPSG dataset.
---
 .../apache/sis/referencing/AuthorityFactories.java | 202 ++++++++++++---------
 .../main/org/apache/sis/referencing/CRS.java       |   5 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |  10 +-
 .../apache/sis/referencing/IdentifiedObjects.java  |   2 +
 .../factory/IdentifiedObjectFinder.java            | 105 ++++++++++-
 .../factory/MultiAuthoritiesFactory.java           |  16 +-
 .../sis/referencing/EPSGFactoryFallbackTest.java   |   4 +-
 .../sis/storage/sql/feature/InfoStatements.java    |   4 +-
 .../storage/sql/feature/InfoStatementsTest.java    |   8 +-
 9 files changed, 253 insertions(+), 103 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
index 064e7f6daa..8b5d66f2dd 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/AuthorityFactories.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.referencing;
 
+import java.util.Set;
 import java.util.Iterator;
 import java.util.ServiceLoader;
 import java.util.logging.Level;
@@ -24,6 +25,7 @@ import java.util.logging.Logger;
 import java.sql.SQLTransientException;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.AuthorityFactory;
+import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.cs.CSAuthorityFactory;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.datum.DatumAuthorityFactory;
@@ -37,6 +39,7 @@ import org.apache.sis.system.SystemListener;
 import org.apache.sis.referencing.internal.EPSGFactoryProxy;
 import org.apache.sis.referencing.factory.MultiAuthoritiesFactory;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
+import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
 import org.apache.sis.referencing.factory.sql.EPSGFactory;
 import org.apache.sis.util.logging.Logging;
@@ -57,11 +60,10 @@ final class AuthorityFactories<T extends AuthorityFactory> 
extends LazySet<T> {
     static final Logger LOGGER = Logger.getLogger(Loggers.CRS_FACTORY);
 
     /**
-     * An array containing only the EPSG factory. Content of this array is 
initially null.
+     * The EPSG factory, or {@code null} if not yet initialized.
      * The EPSG factory will be created when first needed by {@link 
#initialValues()}.
-     * This array is returned directly (not cloned) by {@link 
#initialValues()}.
      */
-    private static final GeodeticAuthorityFactory[] EPSG = new 
GeodeticAuthorityFactory[1];
+    private static GeodeticAuthorityFactory EPSG;
 
     /**
      * The unique system-wide authority factory instance that contains all 
factories found on the module path,
@@ -80,7 +82,7 @@ final class AuthorityFactories<T extends AuthorityFactory> 
extends LazySet<T> {
 
         @Override
         public void reload() {
-            EPSG(null);
+            setEPSG(null);
             super.reload();
         }
     };
@@ -115,38 +117,59 @@ final class AuthorityFactories<T extends 
AuthorityFactory> extends LazySet<T> {
         return ServiceLoader.load(service, 
Reflect.getContextClassLoader()).iterator();
     }
 
+    /**
+     * Invoked by {@link LazySet} for adding the EPSG factory before any other 
factory fetched by {@code ServiceLoader}.
+     * We put the EPSG factory first because it is often used anyway even for 
{@code CRS} and {@code AUTO} namespaces.
+     * This method tries to instantiate an {@link EPSGFactory} if possible, or 
an {@link EPSGFactoryFallback} otherwise.
+     *
+     * @return the EPSG factory in an array.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    protected T[] initialValues() {
+        return (T[]) new GeodeticAuthorityFactory[] {getEPSG()};
+    }
+
+    /**
+     * Invoked by {@link LazySet} for fetching the next element from the given 
iterator.
+     * Skips the {@link EPSGFactoryProxy} if possible, or returns {@code null} 
otherwise.
+     * Note that {@link MultiAuthoritiesFactory} is safe to null values.
+     */
+    @Override
+    protected T next(final Iterator<? extends T> it) {
+        T e = it.next();
+        if (e instanceof EPSGFactoryProxy) {
+            e = it.hasNext() ? it.next() : null;
+        }
+        return e;
+    }
+
     /**
      * Sets the EPSG factory to the given value.
+     *
+     * @param  factory  the factory to use, or {@code null} for reloading.
      */
     @Configuration
-    static void EPSG(final GeodeticAuthorityFactory factory) {
-        synchronized (EPSG) {
-            EPSG[0] = factory;
-        }
+    static synchronized void setEPSG(final GeodeticAuthorityFactory factory) {
+        EPSG = factory;
     }
 
     /**
      * Returns the factory connected to the EPSG geodetic dataset if possible, 
or the EPSG fallback otherwise.
-     * If an EPSG data source has been found, then this method returns an 
instance of {@link EPSGFactory} but
-     * there is no guarantee that attempts to use that factory will succeed; 
for example maybe the EPSG schema
+     * If an EPSG data source has been found, then this method returns an 
instance of {@link EPSGFactory}, but
+     * there is no guarantee that attempts to use that factory will succeed. 
For example, maybe the EPSG schema
      * does not exist. Callers should be prepared to either receive an {@link 
EPSGFactoryFallback} directly if
      * the EPSG data source does not exist, or replace the {@code EPSGFactory} 
by a {@code EPSGFactoryFallback}
      * later if attempt to use the returned factory fails.
      */
-    static GeodeticAuthorityFactory EPSG() {
-        synchronized (EPSG) {
-            GeodeticAuthorityFactory factory = EPSG[0];
-            if (factory == null) {
-                try {
-                    factory = new EPSGFactory(null);
-                } catch (FactoryException e) {
-                    log(e, false);
-                    factory = EPSGFactoryFallback.INSTANCE;
-                }
-                EPSG[0] = factory;
-            }
-            return factory;
+    static synchronized GeodeticAuthorityFactory getEPSG() {
+        if (EPSG == null) try {
+            EPSG = new EPSGFactory(null);
+        } catch (FactoryException e) {
+            log(e, false);
+            EPSG = EPSGFactoryFallback.INSTANCE;
         }
+        return EPSG;
     }
 
     /**
@@ -155,47 +178,42 @@ final class AuthorityFactories<T extends 
AuthorityFactory> extends LazySet<T> {
      * the same exception to be thrown and logged on every calls to {@link 
CRS#forCode(String)}.
      */
     static GeodeticAuthorityFactory fallback(final UnavailableFactoryException 
e) throws UnavailableFactoryException {
-        final boolean isTransient = (e.getCause() instanceof 
SQLTransientException);
         final AuthorityFactory unavailable = e.getUnavailableFactory();
-        GeodeticAuthorityFactory factory;
-        final boolean alreadyDone;
-        synchronized (EPSG) {
-            factory = EPSG[0];
-            alreadyDone = (factory == EPSGFactoryFallback.INSTANCE);
-            if (!alreadyDone) {                             // May have been 
set in another thread (race condition).
-                if (unavailable != factory) {
-                    throw e;                                // Exception did 
not come from a factory that we control.
-                }
-                factory = EPSGFactoryFallback.INSTANCE;
-                if (!isTransient) {
-                    ALL.reload();
-                    EPSG[0] = factory;
+        if (unavailable instanceof EPSGFactoryFallback) {
+            throw e;
+        }
+        boolean isWarning = true;
+        if (!(e.getCause() instanceof SQLTransientException)) {
+            synchronized (AuthorityFactories.class) {
+                if (unavailable == EPSG) {
+                    ALL.reload();               // Must be before setting the 
`EPSG` field.
+                    EPSG = EPSGFactoryFallback.INSTANCE;
+                    isWarning = false;          // Use config level.
                 }
             }
         }
-        if (!alreadyDone) {
-            log(e, true);
-        }
-        return factory;
+        log(e, isWarning);
+        return EPSGFactoryFallback.INSTANCE;    // Do not return `EPSG` 
because it may still have the previous value.
     }
 
     /**
-     * Notifies that a factory is unavailable, but without giving a fallback 
and without logging.
-     * The caller is responsible for throwing an exception, or for logging a 
warning and provide its own fallback.
+     * Notifies that a factory is unavailable, but without logging.
+     * The callers are responsible for either throwing an exception,
+     * or for logging a warning and do their own fallback.
      *
-     * @return {@code false} if the caller can try again, or {@code true} if 
the failure can be considered final.
+     * @return {@code false} if the caller may want to try again, or
+     *         {@code true} if the failure is considered definitive.
      */
-    static boolean failure(final UnavailableFactoryException e) {
-        if (!(e.getCause() instanceof SQLTransientException)) {
-            final AuthorityFactory unavailable = e.getUnavailableFactory();
-            synchronized (EPSG) {
-                final GeodeticAuthorityFactory factory = EPSG[0];
-                if (factory == EPSGFactoryFallback.INSTANCE) {      // May 
have been set in another thread.
-                    return false;
-                }
-                if (unavailable == factory) {
-                    ALL.reload();
-                    EPSG[0] = EPSGFactoryFallback.INSTANCE;
+    static boolean isUnavailable(final UnavailableFactoryException e) {
+        final AuthorityFactory unavailable = e.getUnavailableFactory();
+        if (!(unavailable instanceof EPSGFactoryFallback)) {
+            if (e.getCause() instanceof SQLTransientException) {
+                return false;
+            }
+            synchronized (AuthorityFactories.class) {
+                if (unavailable == EPSG) {
+                    ALL.reload();   // Must be before setting the `EPSG` field.
+                    EPSG = EPSGFactoryFallback.INSTANCE;
                     return false;
                 }
             }
@@ -205,14 +223,14 @@ final class AuthorityFactories<T extends 
AuthorityFactory> extends LazySet<T> {
 
     /**
      * Logs the given exception at the given level. This method pretends that 
the logging come from
-     * {@link CRS#getAuthorityFactory(String)}, which is the public facade for 
{@link #EPSG()}.
+     * {@link CRS#getAuthorityFactory(String)}, which is the public facade for 
{@link #getEPSG()}.
      */
     private static void log(final Exception e, final boolean isWarning) {
         String message = e.getMessage();        // Prefer the locale of system 
administrator.
         if (message == null) {
             message = e.toString();
         }
-        final LogRecord record = new LogRecord(isWarning ? Level.WARNING : 
Level.CONFIG, message);
+        final var record = new LogRecord(isWarning ? Level.WARNING : 
Level.CONFIG, message);
         if (isWarning && !(e instanceof UnavailableFactoryException)) {
             record.setThrown(e);
         }
@@ -220,32 +238,54 @@ final class AuthorityFactories<T extends 
AuthorityFactory> extends LazySet<T> {
     }
 
     /**
-     * Invoked by {@link LazySet} for adding the EPSG factory before any other 
factory fetched by {@code ServiceLoader}.
-     * We put the EPSG factory first because it is often used anyway even for 
{@code CRS} and {@code AUTO} namespaces.
+     * Creates a finder which can be used for looking up unidentified objects 
using the EPSG database.
+     * The returned finder uses the fallback if the main <abbr>EPSG</abbr> 
factory appears to be unavailable.
      *
-     * <p>This method tries to instantiate an {@link EPSGFactory} if possible,
-     * or an {@link EPSGFactoryFallback} otherwise.</p>
-     *
-     * @return the EPSG factory in an array. Callers shall not modify the 
returned array.
+     * @return a finder to use for looking up unidentified objects.
+     * @throws FactoryException if the finder cannot be created.
      */
-    @Override
-    @SuppressWarnings("unchecked")
-    protected T[] initialValues() {
-        EPSG();                         // Force EPSGFactory instantiation if 
not already done.
-        return (T[]) EPSG;
-    }
-
-    /**
-     * Invoked by {@link LazySet} for fetching the next element from the given 
iterator.
-     * Skips the {@link EPSGFactoryProxy} if possible, or returns {@code null} 
otherwise.
-     * Note that {@link MultiAuthoritiesFactory} is safe to null values.
-     */
-    @Override
-    protected T next(final Iterator<? extends T> it) {
-        T e = it.next();
-        if (e instanceof EPSGFactoryProxy) {
-            e = it.hasNext() ? it.next() : null;
+    static IdentifiedObjectFinder finderForEPSG() throws FactoryException {
+        final GeodeticAuthorityFactory factory = getEPSG();
+        if (factory instanceof EPSGFactoryFallback) {
+            return ((EPSGFactoryFallback) factory).newIdentifiedObjectFinder();
         }
-        return e;
+        return new 
IdentifiedObjectFinder.Wrapper(factory.newIdentifiedObjectFinder()) {
+            /** Whether the fallback has already been set. */
+            private boolean isUsingFallback;
+
+            /** Report that the main factory is not available and switch to 
the fallback. */
+            private void report(UnavailableFactoryException e) throws 
FactoryException {
+                if (isUsingFallback) throw e;
+                isUsingFallback = true;
+                delegate(fallback(e).newIdentifiedObjectFinder());
+            }
+
+            /** Lookups objects which are approximately equal, using the 
fallback if necessary. */
+            @Override public Set<IdentifiedObject> find(final IdentifiedObject 
object) throws FactoryException {
+                for (;;) try {      // Executed at most twice.
+                    return super.find(object);
+                } catch (UnavailableFactoryException e) {
+                    report(e);
+                }
+            }
+
+            /** Lookups an object which is approximately equal, using the 
fallback if necessary. */
+            @Override public IdentifiedObject findSingleton(final 
IdentifiedObject object) throws FactoryException {
+                for (;;) try {      // Executed at most twice.
+                    return super.findSingleton(object);
+                } catch (UnavailableFactoryException e) {
+                    report(e);
+                }
+            }
+
+            /** Returns a set of authority codes, using the fallback if 
necessary. */
+            @Override protected Set<String> getCodeCandidates(final 
IdentifiedObject object) throws FactoryException {
+                for (;;) try {      // Executed at most twice.
+                    return super.getCodeCandidates(object);
+                } catch (UnavailableFactoryException e) {
+                    report(e);
+                }
+            }
+        };
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index 98c9574f51..13609d693e 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -713,7 +713,7 @@ public final class CRS extends Static {
         try {
             return factory.createOperation(sourceCRS, targetCRS, context);
         } catch (UnavailableFactoryException e) {
-            if (AuthorityFactories.failure(e)) {
+            if (AuthorityFactories.isUnavailable(e)) {
                 throw e;
             } else try {
                 // Above method call replaced the EPSG factory by a fallback. 
Try again.
@@ -755,9 +755,10 @@ public final class CRS extends Static {
         try {
             return factory.createOperations(sourceCRS, targetCRS, context);
         } catch (UnavailableFactoryException e) {
-            if (AuthorityFactories.failure(e)) {
+            if (AuthorityFactories.isUnavailable(e)) {
                 throw e;
             } else try {
+                // Above method call replaced the EPSG factory by a fallback. 
Try again.
                 return List.of(factory.createOperation(sourceCRS, targetCRS, 
context));
             } catch (FactoryException ex) {
                 ex.addSuppressed(e);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index cb5394aeac..68d218eee4 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -2121,7 +2121,7 @@ public enum CommonCRS {
      * Returns the same properties as the given object, except for the 
identifier which is set to the given code.
      */
     private static Map<String,?> properties(final IdentifiedObject template, 
final short code) {
-        final Map<String,Object> properties = new 
HashMap<>(IdentifiedObjects.getProperties(template, exclude()));
+        final var properties = new 
HashMap<String,Object>(IdentifiedObjects.getProperties(template, exclude()));
         properties.put(GeographicCRS.IDENTIFIERS_KEY, new 
NamedIdentifier(Citations.EPSG, String.valueOf(code)));
         return properties;
     }
@@ -2139,7 +2139,7 @@ public enum CommonCRS {
      */
     private static GeodeticAuthorityFactory factory() {
         if (!EPSGFactoryFallback.FORCE_HARDCODED) {
-            final GeodeticAuthorityFactory factory = AuthorityFactories.EPSG();
+            final GeodeticAuthorityFactory factory = 
AuthorityFactories.getEPSG();
             if (!(factory instanceof EPSGFactoryFallback)) {
                 return factory;
             }
@@ -2154,9 +2154,9 @@ public enum CommonCRS {
     private static void failure(final Object caller, final String method, 
final FactoryException e, final int code) {
         String message = 
Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1, 
(Constants.EPSG + ':') + code);
         message = Exceptions.formatChainedMessages(null, message, e);
-        final LogRecord record = new LogRecord(Level.WARNING, message);
-        if (!(e instanceof UnavailableFactoryException) || 
AuthorityFactories.failure((UnavailableFactoryException) e)) {
-            // Append the stack trace only if the exception is the one we 
expect when the factory is not available.
+        final var record = new LogRecord(Level.WARNING, message);
+        if (!(e instanceof UnavailableFactoryException && 
AuthorityFactories.isUnavailable((UnavailableFactoryException) e))) {
+            // Append the stack trace only if the exception is for a reason 
different than unavailable factory.
             record.setThrown(e);
         }
         Logging.completeAndLog(AuthorityFactories.LOGGER, caller.getClass(), 
method, record);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
index c8b2c0c321..18cfc6617d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/IdentifiedObjects.java
@@ -713,6 +713,8 @@ public final class IdentifiedObjects extends Static {
         final GeodeticAuthorityFactory factory;
         if (authority == null) {
             factory = AuthorityFactories.ALL;
+        } else if (authority.equalsIgnoreCase(Constants.EPSG)) {
+            return AuthorityFactories.finderForEPSG();
         } else {
             factory = 
AuthorityFactories.ALL.getAuthorityFactory(GeodeticAuthorityFactory.class, 
authority, null);
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
index 387556425c..4f179926bf 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
@@ -554,7 +554,7 @@ public class IdentifiedObjectFinder {
     }
 
     /**
-     * Returns a set of authority codes that <strong>may</strong> identify the 
same object as the specified one.
+     * Returns a set of authority codes that <em>may</em> identify the same 
object as the specified one.
      * The returned set must contains <em>at least</em> the code of every 
objects that are
      * {@link ComparisonMode#APPROXIMATE approximately equal} to the specified 
one.
      * However, the set may conservatively contains the code for more objects 
if an exact search is too expensive.
@@ -590,4 +590,107 @@ public class IdentifiedObjectFinder {
         Logging.completeAndLog(GeodeticAuthorityFactory.LOGGER, 
IdentifiedObjectFinder.class,
                                "find", new LogRecord(Level.FINER, 
exception.getMessage()));
     }
+
+    /**
+     * An object finder which delegates some or all work to another object 
finder.
+     * The default implementation of all {@code Wrapper} methods delegates the 
work to the object finder
+     * specified at construction time or in the last call to {@link 
#delegate(IdentifiedObjectFinder)}.
+     * Subclasses can override methods for modifying some find operations.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 1.5
+     * @since   1.5
+     */
+    public static abstract class Wrapper extends IdentifiedObjectFinder {
+        /**
+         * The finder doing the actual work, or {@code this} for using the
+         * default method implementation provided by the parent class.
+         */
+        private IdentifiedObjectFinder delegate;
+
+        /**
+         * Creates a new object finder which will delegate the actual work to 
the given finder.
+         *
+         * @param  finder  the object finder to which to delegate the work.
+         */
+        protected Wrapper(final IdentifiedObjectFinder finder) {
+            super(finder.factory);
+            delegate = finder;
+        }
+
+        /**
+         * Sets a new finder to which to delegate the actual search operations.
+         * If the specified finder is {@code this}, then this class will 
delegate to the default
+         * method implementations provided by the {@code 
IdentifiedObjectFinder} parent class.
+         * Otherwise, the specified finder should be a new instance not in use 
by other code.
+         *
+         * @param  finder  the object finder to which to delegate the work. 
Can be {@code this}.
+         * @throws FactoryException if the delegate cannot be set.
+         */
+        protected void delegate(final IdentifiedObjectFinder finder) throws 
FactoryException {
+            if (delegate != null) {
+                delegate.wrapper = null;
+            }
+            delegate = Objects.requireNonNull(finder);
+        }
+
+        /**
+         * Returns the object finder to which to delegate the actual search 
operations.
+         * If this method returns {@code this}, then the search operations 
will be delegated
+         * to the default methods provided by the {@code 
IdentifiedObjectFinder} parent class.
+         *
+         * @return the object finder to which to delegate the work. May be 
{@code this}.
+         * @throws FactoryException if the delegate cannot be created.
+         */
+        protected IdentifiedObjectFinder delegate() throws FactoryException {
+            if (delegate != this) {
+                delegate.setWrapper(this);  // Done on each call because it 
also copies the configuration.
+            }
+            return delegate;
+        }
+
+        /**
+         * Lookups objects which are approximately equal to the specified 
object.
+         * The default method implementation delegates the work to the finder 
specified by {@link #delegate()}.
+         */
+        @Override
+        public Set<IdentifiedObject> find(final IdentifiedObject object) 
throws FactoryException {
+            @SuppressWarnings("LocalVariableHidesMemberVariable")
+            final IdentifiedObjectFinder delegate = delegate();
+            return (delegate != this) ? delegate.find(object) : 
super.find(object);
+        }
+
+        /**
+         * Lookups only one object which is approximately equal to the 
specified object.
+         * The default method implementation delegates the work to the finder 
specified by {@link #delegate()}.
+         */
+        @Override
+        public IdentifiedObject findSingleton(final IdentifiedObject object) 
throws FactoryException {
+            @SuppressWarnings("LocalVariableHidesMemberVariable")
+            final IdentifiedObjectFinder delegate = delegate();
+            return (delegate != this) ? delegate.findSingleton(object) : 
super.findSingleton(object);
+        }
+
+        /**
+         * Creates an object equals (optionally ignoring metadata), to the 
specified object.
+         * The default method implementation delegates the work to the finder 
specified by {@link #delegate()}.
+         */
+        @Override
+        Set<IdentifiedObject> createFromCodes(final IdentifiedObject object) 
throws FactoryException {
+            @SuppressWarnings("LocalVariableHidesMemberVariable")
+            final IdentifiedObjectFinder delegate = delegate();
+            return (delegate != this) ? delegate.createFromCodes(object) : 
super.createFromCodes(object);
+        }
+
+        /**
+         * Returns a set of authority codes that <em>may</em> identify the 
same object as the specified one.
+         * The default method implementation delegates the work to the finder 
specified by {@link #delegate()}.
+         */
+        @Override
+        protected Set<String> getCodeCandidates(final IdentifiedObject object) 
throws FactoryException {
+            @SuppressWarnings("LocalVariableHidesMemberVariable")
+            final IdentifiedObjectFinder delegate = delegate();
+            return (delegate != this) ? delegate.getCodeCandidates(object) : 
super.getCodeCandidates(object);
+        }
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
index 02561e639f..682ea459d2 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
@@ -581,8 +581,8 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
      */
     /*
      * This method is declared final for avoiding the false impression than 
overriding this method would change
-     * the behavior of MultiAuthoritiesFactory. It would not because the 
'create(…)' method invokes the private
-     * 'getAuthorityFactory(…)' instead of the public one.
+     * the behavior of MultiAuthoritiesFactory. It would not because the 
`create(…)` method invokes the private
+     * `getAuthorityFactory(…)` instead of the public one.
      */
     public final <T extends AuthorityFactory> T getAuthorityFactory(final 
Class<T> type,
             final String authority, final String version) throws 
NoSuchAuthorityFactoryException
@@ -636,7 +636,7 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
             final Iterable<? extends AuthorityFactory> provider = 
providers.get(request.type);
             if (provider != null) {
                 final Iterator<? extends AuthorityFactory> it;
-                synchronized (provider) {               // Should never be 
null because of the 'doneMask' check.
+                synchronized (provider) {               // Should never be 
null because of the `doneMask` check.
                     it = provider.iterator();
                     while (it.hasNext()) {
                         factory = it.next();
@@ -664,7 +664,7 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
                                 /*
                                  * Before to cache the factory with a key 
containing the factory version, make sure
                                  * that we took in account the version of the 
default factory. This will prevent the
-                                 * call to 'cache(versioned, factory)' to 
overwrite the default factory.
+                                 * call to `cache(versioned, factory)` to 
overwrite the default factory.
                                  */
                                 if (factory != cached) {
                                     
cache(unversioned.versionOf(cached.getAuthority()), cached);
@@ -786,7 +786,7 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
              * is present. The remainder steps are the same as if the user 
gave a simple code (e.g. "EPSG:4326").
              */
             if (uri.authority == null) {
-                // We want this check before the 'code' value is modified 
below.
+                // We want this check before the `code` value is modified 
below.
                 throw new NoSuchAuthorityCodeException(
                         Resources.format(Resources.Keys.MissingAuthority_1, 
code), null, uri.code, code);
             }
@@ -1540,7 +1540,7 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
         /*
          * Identify the type requested by the user and create all components 
with the assumption that they will
          * be of that type. This is the most common case. If during iteration 
we find an object of another kind,
-         * then the array type will be downgraded to IdentifiedObject[]. The 
'componentType' variable will keep
+         * then the array type will be downgraded to IdentifiedObject[]. The 
`componentType` variable will keep
          * its non-null value only if the array stay of the expected sub-type.
          */
         final AuthorityFactoryIdentifier.Type requestedType;
@@ -1823,8 +1823,8 @@ public class MultiAuthoritiesFactory extends 
GeodeticAuthorityFactory implements
                     ((ServiceLoader<?>) provider).reload();
                 }
                 /*
-                 * Clear the 'iterationCompleted' bit before to clear the 
cache so that if another thread
-                 * invokes 'getAuthorityFactory(…)', it will block on the 
synchronized(provider) statement
+                 * Clear the `iterationCompleted` bit before to clear the 
cache so that if another thread
+                 * invokes `getAuthorityFactory(…)`, it will block on the 
synchronized(provider) statement
                  * until we finished the cleanup.
                  */
                 final int type = entry.getKey().ordinal();
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/EPSGFactoryFallbackTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/EPSGFactoryFallbackTest.java
index dc82cff0f2..30ef5d3382 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/EPSGFactoryFallbackTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/EPSGFactoryFallbackTest.java
@@ -241,7 +241,7 @@ public final class EPSGFactoryFallbackTest extends 
TestCaseWithLogs {
      */
     @Configuration
     private static void setEPSGFactory(final GeodeticAuthorityFactory factory) 
{
-        AuthorityFactories.EPSG(factory);
+        AuthorityFactories.setEPSG(factory);
         for (final CommonCRS          crs : CommonCRS         .values()) 
crs.clear();
         for (final CommonCRS.Vertical crs : CommonCRS.Vertical.values()) 
crs.clear();
         for (final CommonCRS.Temporal crs : CommonCRS.Temporal.values()) 
crs.clear();
@@ -255,7 +255,7 @@ public final class EPSGFactoryFallbackTest extends 
TestCaseWithLogs {
      */
     @Test
     public void compareAllCodes() throws FactoryException {
-        final GeodeticAuthorityFactory EPSG = AuthorityFactories.EPSG();
+        final GeodeticAuthorityFactory EPSG = AuthorityFactories.getEPSG();
         try {
             setEPSGFactory(EPSGFactoryFallback.INSTANCE);
             final var codes = new 
ArrayList<String>(EPSGFactoryFallback.INSTANCE.getAuthorityCodes(CoordinateReferenceSystem.class));
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index ce1545dd4f..111d0ae99f 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@ -641,11 +641,11 @@ public class InfoStatements implements Localized, 
AutoCloseable {
                         if (error == null) error = e;
                         else error.addSuppressed(e);
                     }
-                    continue;                           // Ignore codes that 
are not integers.
+                    continue;       // Ignore codes that are not integers.
                 }
                 final SRID search = new SRID(crs, authority, code);
                 if (done.putIfAbsent(search, code > 0) != null) {
-                    continue;                           // Skip 
"authority:code" that we already tried.
+                    continue;       // Skip "authority:code" that we already 
tried.
                 }
                 /*
                  * Found an "authority:code" pair that we did not tested 
before.
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java
 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java
index 7a3f333d12..c84a671fa1 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java
@@ -139,8 +139,12 @@ public final class InfoStatementsTest extends TestCase {
                            CoordinateReferenceSystem.IDENTIFIERS_KEY, new 
ImmutableIdentifier(null, "FOO", "4326")),
                     HardCodedDatum.SPHERE, null, HardCodedCS.GEODETIC_2D);
 
-            c.setReadOnly(false); assertEquals(1, info.findSRID(clash));
-            c.setReadOnly(true);  assertEquals(1, info.findSRID(clash));
+            c.setReadOnly(false);
+            final int code = info.findSRID(clash);
+            assertNotEquals(4326, code);
+            assertTrue(code > 0);
+            c.setReadOnly(true);
+            assertEquals(code, info.findSRID(clash));
         }
     }
 


Reply via email to