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 90823cb52c Refactor `Capabilities` for deferred loading of 
`FunctionRegistry` and for case-insensitive search of function names.
90823cb52c is described below

commit 90823cb52c7bb3d1728cc6715b3f2f68529c6c83
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Oct 31 17:04:47 2025 +0100

    Refactor `Capabilities` for deferred loading of `FunctionRegistry` and for 
case-insensitive search of function names.
---
 .../main/org/apache/sis/filter/Capabilities.java   | 280 ++++++++++++++++++++-
 .../apache/sis/filter/DefaultFilterFactory.java    | 133 +---------
 .../org/apache/sis/filter/FunctionRegister.java    |  16 +-
 .../main/org/apache/sis/filter/math/Function.java  |  12 +-
 .../main/org/apache/sis/filter/math/Registry.java  |  22 +-
 .../main/org/apache/sis/filter/sqlmm/Registry.java |  20 +-
 .../apache/sis/filter/visitor/FunctionNames.java   |  14 ++
 .../storage/sql/feature/SelectionClauseWriter.java |   4 +-
 .../sis/util/internal/shared/AbstractIterator.java |   2 +-
 .../sis/util/internal/shared/CollectionsExt.java   |  22 ++
 10 files changed, 346 insertions(+), 179 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Capabilities.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Capabilities.java
index 240218641c..fa59d42a3e 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Capabilities.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Capabilities.java
@@ -19,8 +19,23 @@ package org.apache.sis.filter;
 import java.util.Map;
 import java.util.Set;
 import java.util.Collection;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.ServiceLoader;
 import java.util.Optional;
 import org.opengis.util.LocalName;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.collection.CodeListSet;
+import org.apache.sis.util.internal.shared.AbstractMap;
+import org.apache.sis.referencing.internal.shared.LazySet;
+import org.apache.sis.feature.internal.shared.AttributeConvention;
+import org.apache.sis.geometry.wrapper.Geometries;
+import org.apache.sis.filter.sqlmm.Registry;
+import org.apache.sis.system.Reflect;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.Expression;
 import org.opengis.filter.ComparisonOperatorName;
 import org.opengis.filter.capability.Conformance;
 import org.opengis.filter.capability.IdCapabilities;
@@ -29,33 +44,78 @@ import org.opengis.filter.capability.FilterCapabilities;
 import org.opengis.filter.capability.ScalarCapabilities;
 import org.opengis.filter.capability.SpatialCapabilities;
 import org.opengis.filter.capability.TemporalCapabilities;
-import org.apache.sis.util.collection.CodeListSet;
-import org.apache.sis.feature.internal.shared.AttributeConvention;
 
 
 /**
- * Metadata about the specific elements that Apache SIS implementation 
supports.
+ * Metadata about the specific elements that Apache <abbr>SIS</abbr> 
implementation supports.
+ * This is also an unmodifiable map of all functions supported by this factory,
+ * together with their providers. This class is thread-safe.
  *
  * @todo Missing {@link SpatialCapabilities} and {@link TemporalCapabilities}.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
-final class Capabilities implements FilterCapabilities, Conformance, 
IdCapabilities, ScalarCapabilities {
+@SuppressWarnings("ReturnOfCollectionOrArrayField")
+final class Capabilities extends AbstractMap<String, AvailableFunction>
+        implements FilterCapabilities, Conformance, IdCapabilities, 
ScalarCapabilities
+{
+    /**
+     * The providers of functions. Each {@code FunctionRegister} instance 
typically provides many functions.
+     * There is one provider for SQL/MM, one provider for mathematical 
functions, <i>etc</i>.
+     */
+    private final LazySet<FunctionRegister> providers;
+
+    /**
+     * Providers of functions identified by their names.
+     * Created when first needed and reset to {@code null} when not needed 
anymore.
+     *
+     * @see #nextFunctionRegister()
+     */
+    private Iterator<FunctionRegister> registerIterator;
+
+    /**
+     * Names of all functions sorted in increasing order ignoring case.
+     * This array is initially empty and is increased when new providers are 
tested.
+     * The array may contain duplicated values if two or more {@link 
FunctionRegister}
+     * declare a function of the same name.
+     *
+     * @see #indexOfFunction(String)
+     */
+    private String[] functionNames;
+
     /**
-     * The filter factory which is providing the functions.
+     * The provider for each function name. The length of this array shall be 
equal to the length
+     * of the {@link #functionNames} array. The provider at index <var>i</var> 
shall accept a function of
+     * the name at index <var>i</var>.
      *
-     * @see #getFunctions()
+     * <h4>Design note</h4>
+     * The combination of {@link #functionNames} and {@code providersForName} 
could be replaced by {@link java.util.TreeSet}.
+     * But we use array as an efficient way to get both the key (function 
name) and value for a given key in order to
+     * resolve the real names in case-insensitive searches. We also need to 
support duplicated names.
+     *
+     * @see #createFunction(String, Expression[])
      */
-    private final DefaultFilterFactory<?,?,?> factory;
+    private FunctionRegister[] registerForName;
 
     /**
-     * Creates a new capability document.
+     * Creates a new capability document and map for the specified geometry 
library.
      *
-     * @param  factory  the filter factory which is providing functions.
+     * @param  geometries  the library to use for creating geometry objects.
      */
-    Capabilities(final DefaultFilterFactory<?,?,?> factory) {
-        this.factory = factory;
+    Capabilities(final Geometries<?> geometries) {
+        functionNames = CharSequences.EMPTY_ARRAY;
+        providers = new LazySet<FunctionRegister>() {
+            /** Returns the SQLMM functions to check first. */
+            @Override protected FunctionRegister[] initialValues() {
+                return new FunctionRegister[] {new Registry(geometries)};
+            }
+
+            /** Returns the other functions (math, etc.) to check after SQLMM. 
*/
+            @Override protected Iterator<FunctionRegister> 
createSourceIterator() {
+                return ServiceLoader.load(FunctionRegister.class, 
Reflect.getContextClassLoader()).iterator();
+            }
+        };
     }
 
     /**
@@ -159,7 +219,201 @@ final class Capabilities implements FilterCapabilities, 
Conformance, IdCapabilit
      * @return the function that may be used in filter expressions.
      */
     @Override
-    public Map<String,AvailableFunction> getFunctions() {
-        return factory.new Functions();
+    public Map<String, AvailableFunction> getFunctions() {
+        return this;
+    }
+
+    /**
+     * Returns the next function register, or {@code null} if none.
+     * This method shall be invoked in a block synchronized on {@code this}.
+     * This method is invoked only for deferred loading of function registers.
+     * After all function registers have been loaded, this method always 
returns {@code null}.
+     */
+    private FunctionRegister nextFunctionRegister() {
+        assert Thread.holdsLock(this);
+        if (registerIterator == null) {
+            if (registerForName != null) {
+                return null;   // We already extracted all providers.
+            }
+            registerIterator = providers.iterator();
+        }
+        if (!registerIterator.hasNext()) {
+            return null;
+        }
+        final FunctionRegister register = registerIterator.next();
+        /*
+         * Combine the list of functions of the new register with the list of 
functions of all previous registers.
+         * If the same name is used twice, the previous registers have 
precedence in `registerIterator` order.
+         */
+        final String[] combined = ArraysExt.concatenate(functionNames, 
register.getNames().toArray(String[]::new));
+        Arrays.sort(combined, String.CASE_INSENSITIVE_ORDER);
+        final var registers = new FunctionRegister[combined.length];
+        Arrays.fill(registers, register);
+        if (registerForName != null) {
+            // ArrayIndexOutOfBoundsException on `i` should never happen.
+            for (int i=0, s=0; s < functionNames.length; i++) {
+                if (combined[i].equalsIgnoreCase(functionNames[s])) {
+                    registers[i] = registerForName[s++];
+                }
+            }
+        }
+        registerForName = registers;
+        functionNames = combined;
+        return register;
+    }
+
+    /**
+     * Returns the index of the provider for the function of the given name.
+     * This method shall be invoked in a block synchronized on {@code this}.
+     * If many providers define a function of the given name, then there is
+     * no guaranteed about which provider is returned.
+     *
+     * @param  name  case-insensitive name of the function to get.
+     * @return index of the provider for the given name, or -1 if not found.
+     */
+    private int indexOfFunction(final String name) {
+        int i;
+        while ((i = Arrays.binarySearch(functionNames, name, 
String.CASE_INSENSITIVE_ORDER)) < 0) {
+            if (nextFunctionRegister() == null) {
+                return -1;
+            }
+        }
+        return i;
+    }
+
+    /**
+     * Always returns {@code false} since we should always have at least the 
SQLMM function register.
+     */
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    /**
+     * Returns the number of functions.
+     */
+    @Override
+    @SuppressWarnings("empty-statement")
+    public synchronized int size() {
+        while (nextFunctionRegister() != null);     // Ensure that all 
providers have been loaded.
+        return functionNames.length;
+    }
+
+    /**
+     * Returns whether this map contains a function of the given name, 
ignoring case.
+     *
+     * @param  key  name of the function to search.
+     * @return whether this map contains a function of the given name, 
ignoring case.
+     */
+    @Override
+    public boolean containsKey(final Object key) {
+        if (key instanceof String) {
+            synchronized (this) {
+                return indexOfFunction((String) key) >= 0;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the description of the first function (in iteration order) of 
the given name.
+     * This method finds the register, then delegates to {@link 
FunctionRegister#describe(String)}.
+     *
+     * @param  key  name of the function to describe.
+     * @return description of the requested function, or {@code null} if none.
+     */
+    @Override
+    public AvailableFunction get(final Object key) {
+        if (key instanceof String) {
+            String name = (String) key;
+            FunctionRegister register;
+            synchronized (this) {
+                int i = indexOfFunction(name);
+                if (i < 0) return null;
+                while (i != 0 && name.equalsIgnoreCase(functionNames[i-1])) 
i--;
+                name = functionNames[i];
+                register = registerForName[i];
+            }
+            return register.describe(name);
+        }
+        return null;
+    }
+
+    /**
+     * Creates an implementation-specific function. If many registers have a 
function of the given name,
+     * then this method tries all them in iteration order until one of them 
succeed.
+     *
+     * @param  <R>         type of resources.
+     * @param  name        name of the function to call.
+     * @param  parameters  expressions providing values for the function 
arguments.
+     * @return an expression which will call the specified function, or {@code 
null} if the given name is unknown.
+     * @throws IllegalArgumentException if the arguments are illegal for the 
specified function.
+     */
+    @SuppressWarnings("empty-statement")
+    public <R> Expression<R,?> createFunction(String name, final 
Expression<R,?>[] parameters) {
+        final String[] names;
+        final FunctionRegister[] registers;
+        synchronized (this) {
+            int i = indexOfFunction(name);
+            if (i < 0) return null;
+            int upper = i;
+            while (++upper < functionNames.length && 
name.equalsIgnoreCase(functionNames[upper]));
+            while (i != 0 && name.equalsIgnoreCase(functionNames[i-1])) i--;
+            names     = Arrays.copyOfRange(functionNames,   i, upper);
+            registers = Arrays.copyOfRange(registerForName, i, upper);
+        }
+        IllegalArgumentException cause = null;
+        for (int i=0; i<names.length; i++) try {
+            return registers[i].create(names[i], parameters);
+        } catch (IllegalArgumentException e) {
+            if (cause == null) cause = e;
+            else cause.addSuppressed(e);
+        }
+        if (cause != null) {
+            throw cause;
+        }
+        return null;
+    }
+
+    /**
+     * Returns an iterator over the entries in this map.
+     * This is a lazy iterator which will load providers only if requested.
+     */
+    @Override
+    protected EntryIterator<String, AvailableFunction> entryIterator() {
+        final Iterator<FunctionRegister> it = providers.iterator();
+        return new EntryIterator<>() {
+            /** Iterator over the names of functions of the current provider. 
*/
+            private Iterator<String> names;
+
+            /** The next name to return. */
+            private String next;
+
+            /** Returns whether there is a next element. */
+            @Override protected boolean next() {
+                for (;;) {
+                    if (names != null && names.hasNext()) {
+                        next = names.next();
+                        return true;
+                    }
+                    names = null;
+                    if (!it.hasNext()) {
+                        next = null;
+                        return false;
+                    }
+                    names = it.next().getNames().iterator();
+                }
+            }
+
+            /** Returns the current function name. */
+            @Override protected String getKey() {
+                return next;
+            }
+
+            /** Returns the current function description. */
+            @Override protected AvailableFunction getValue() {
+                return get(next);
+            }
+        };
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index aa7d605961..9afd590393 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
@@ -16,10 +16,7 @@
  */
 package org.apache.sis.filter;
 
-import java.util.Map;
-import java.util.HashMap;
 import java.util.Collection;
-import java.util.ServiceLoader;
 import java.util.Optional;
 import javax.measure.Quantity;
 import javax.measure.quantity.Length;
@@ -30,20 +27,17 @@ import org.apache.sis.geometry.WraparoundMethod;
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.feature.internal.Resources;
 import org.apache.sis.filter.base.UnaryFunction;
-import org.apache.sis.filter.sqlmm.Registry;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.iso.AbstractFactory;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.internal.shared.LazyCandidate;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.Iterator;
 import java.time.Instant;
 import org.opengis.filter.*;
 import org.opengis.feature.Feature;
-import org.opengis.filter.capability.AvailableFunction;
 import org.opengis.filter.capability.FilterCapabilities;
 import org.apache.sis.util.internal.shared.Strings;
-import org.apache.sis.util.internal.shared.AbstractMap;
 
 
 /**
@@ -53,7 +47,7 @@ import org.apache.sis.util.internal.shared.AbstractMap;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
  *
  * @param  <R>  the type of resources (e.g. {@link 
org.opengis.feature.Feature}) to use as inputs.
  * @param  <G>  base class of geometry objects. The implementation-neutral 
type is GeoAPI {@link Geometry},
@@ -86,7 +80,8 @@ public abstract class DefaultFilterFactory<R,G,T> extends 
AbstractFactory implem
      *
      * @see #function(String, Expression...)
      */
-    private final Map<String,FunctionRegister> availableFunctions;
+    @LazyCandidate
+    private final Capabilities availableFunctions;
 
     /**
      * Creates a new factory for geometries and temporal objects of the given 
types.
@@ -126,7 +121,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends 
AbstractFactory implem
         }
         this.temporal = temporal;
         this.wraparound = wraparound;
-        availableFunctions = new HashMap<>();
+        availableFunctions = new Capabilities(library);
     }
 
     /**
@@ -172,8 +167,9 @@ public abstract class DefaultFilterFactory<R,G,T> extends 
AbstractFactory implem
      * @return description of the abilities of this factory.
      */
     @Override
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public FilterCapabilities getCapabilities() {
-        return new Capabilities(this);              // Cheap to construct, no 
need to cache.
+        return availableFunctions;
     }
 
     /**
@@ -1036,37 +1032,6 @@ public abstract class DefaultFilterFactory<R,G,T> 
extends AbstractFactory implem
         return new ArithmeticFunction.Divide<>(operand1, operand2);
     }
 
-    /**
-     * Returns the provider for the function of the given name.
-     * If the given name is {@code null}, then this method only
-     * ensures that {@link #availableFunctions} is initialized.
-     *
-     * @param  name  name of the function to get, or {@code null} if none.
-     * @return the register for the given function, or {@code null} if none.
-     */
-    private FunctionRegister register(final String name) {
-        final FunctionRegister register;
-        synchronized (availableFunctions) {
-            if (availableFunctions.isEmpty()) {
-                /*
-                 * Load functions when first needed or if the module path 
changed since last invocation.
-                 * The SQLMM factory is hard-coded because it depends on the 
geometry library.
-                 */
-                final Registry r = new Registry(library);
-                for (final String fn : r.getNames()) {
-                    availableFunctions.put(fn, r);
-                }
-                for (final FunctionRegister er : 
ServiceLoader.load(FunctionRegister.class)) {
-                    for (final String fn : er.getNames()) {
-                        availableFunctions.putIfAbsent(fn, er);
-                    }
-                }
-            }
-            register = availableFunctions.get(name);
-        }
-        return register;
-    }
-
     /**
      * Creates an implementation-specific function.
      * The names of available functions is given by {@link #getCapabilities()}.
@@ -1085,87 +1050,11 @@ public abstract class DefaultFilterFactory<R,G,T> 
extends AbstractFactory implem
         for (int i=0; i<parameters.length; i++) {
             ArgumentChecks.ensureNonNullElement("parameters", i, 
parameters[i]);
         }
-        final FunctionRegister register = register(name);
-        if (register == null) {
-            throw new 
IllegalArgumentException(Resources.format(Resources.Keys.UnknownFunction_1, 
name));
-        }
-        return register.create(name, parameters);
-    }
-
-    /**
-     * Map of all functions supported by this factory, together with their 
providers.
-     * This is a view over {@link #availableFunctions} which delegates 
descriptions
-     * to {@link FunctionRegister#describe(String)}. No values is stored in 
this map.
-     *
-     * @see Capabilities#getFunctions()
-     */
-    final class Functions extends AbstractMap<String, AvailableFunction> {
-        /**
-         * Creates a new map.
-         */
-        Functions() {
-        }
-
-        /**
-         * Returns the number of functions.
-         */
-        @Override
-        public int size() {
-            synchronized (availableFunctions) {
-                register(null);     // Ensure that `availableFunctions` is 
initialized.
-                return availableFunctions.size();
-            }
-        }
-
-        /**
-         * Returns the description of the function of the given name.
-         * This method delegates to {@link FunctionRegister#describe(String)}.
-         *
-         * @param  key  name of the function to describe.
-         * @return description of the requested function, or {@code null} if 
none.
-         */
-        @Override
-        public AvailableFunction get(final Object key) {
-            if (key instanceof String) {
-                final String name = (String) key;
-                final FunctionRegister register = register(name);
-                if (register != null) {
-                    return register.describe(name);
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Returns an iterator over the entries in this map.
-         */
-        @Override
-        protected EntryIterator<String, AvailableFunction> entryIterator() {
-            final Iterator<Entry<String, FunctionRegister>> it;
-            synchronized (availableFunctions) {
-                register(null);     // Ensure that `availableFunctions` is 
initialized.
-                it = availableFunctions.entrySet().iterator();
-            }
-            /*
-             * Following is theoretically not thread-safe, but it is okay in 
our case
-             * because the `availableFunctions` map is not changed after 
construction.
-             */
-            return new EntryIterator<>() {
-                private Entry<String, FunctionRegister> entry;
-
-                @Override protected boolean next() {
-                    return (entry = it.hasNext() ? it.next() : null) != null;
-                }
-
-                @Override protected String getKey() {
-                    return entry.getKey();
-                }
-
-                @Override protected AvailableFunction getValue() {
-                    return entry.getValue().describe(getKey());
-                }
-            };
+        final Expression<R,?> expression = 
availableFunctions.createFunction(name, parameters);
+        if (expression != null) {
+            return expression;
         }
+        throw new 
IllegalArgumentException(Resources.format(Resources.Keys.UnknownFunction_1, 
name));
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
index 1789b46fb0..1589a3e661 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/FunctionRegister.java
@@ -16,7 +16,7 @@
  */
 package org.apache.sis.filter;
 
-import java.util.Collection;
+import java.util.Set;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.filter.Expression;
@@ -34,7 +34,7 @@ import org.opengis.filter.capability.AvailableFunction;
  * Checks against name collisions may be added in a future version.</p>
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.5
+ * @version 1.6
  * @since   1.5
  *
  * @see org.opengis.filter.FilterFactory#function(String, Expression...)
@@ -49,27 +49,25 @@ public interface FunctionRegister {
 
     /**
      * Returns the names of all functions that this factory can create.
-     * It is currently implementer responsibility to ensure that there are no 
name collisions with
-     * functions provided by other factories (this problem may be improved in 
future SIS release).
      *
      * @return set of supported function names.
      */
-    Collection<String> getNames();
+    Set<String> getNames();
 
     /**
      * Describes the parameters of a function.
      *
-     * @param  name  name of the function to describe (not null).
+     * @param  name  case-sensitive name of the function to describe.
      * @return description of the function parameters.
-     * @throws IllegalArgumentException if function name is unknown..
+     * @throws IllegalArgumentException if the given function name is unknown.
      */
     AvailableFunction describe(String name) throws IllegalArgumentException;
 
     /**
-     * Creates a new function of the given name with given parameters.
+     * Creates a new function of the given name with the given parameters.
      *
      * @param  <R>         the type of resources (e.g. {@link 
org.opengis.feature.Feature}) used as inputs.
-     * @param  name        name of the function to create (not null).
+     * @param  name        case-sensitive name of the function to create as an 
expression.
      * @param  parameters  function parameters.
      * @return function for the given name and parameters.
      * @throws IllegalArgumentException if function name is unknown or some 
parameters are illegal.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Function.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Function.java
index 344f31d55d..95d2b36864 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Function.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Function.java
@@ -18,7 +18,6 @@ package org.apache.sis.filter.math;
 
 import java.util.Map;
 import java.util.List;
-import java.util.Locale;
 import java.util.function.DoubleUnaryOperator;
 import java.util.function.DoubleBinaryOperator;
 import org.opengis.util.TypeName;
@@ -222,24 +221,17 @@ enum Function implements AvailableFunction {
      */
     final synchronized ScopedName getFunctionName() {
         if (name == null) {
-            name = Node.createName(camelCaseName());
+            name = Node.createName(name());
         }
         return name;
     }
 
-    /**
-     * Returns the function name in the case to show to users.
-     */
-    final String camelCaseName() {
-        return name().toLowerCase(Locale.US).intern();
-    }
-
     /**
      * Returns the attribute type to declare in feature types that store 
result of this function.
      */
     final synchronized AttributeType<Double> getResultType() {
         if (resultType == null) {
-            resultType = Node.createType(Double.class, camelCaseName());
+            resultType = Node.createType(Double.class, name());
         }
         return resultType;
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Registry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Registry.java
index e38c18a892..d36b1ea8e8 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Registry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Registry.java
@@ -16,12 +16,10 @@
  */
 package org.apache.sis.filter.math;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
+import java.util.Set;
 import org.apache.sis.filter.FunctionRegister;
+import org.apache.sis.filter.visitor.FunctionNames;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.internal.shared.Constants;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -57,34 +55,34 @@ public final class Registry implements FunctionRegister {
      * Returns the names of all functions that this factory can create.
      */
     @Override
-    public Collection<String> getNames() {
-        return Containers.derivedList(Arrays.asList(Function.values()), 
Function::camelCaseName);
+    public Set<String> getNames() {
+        return FunctionNames.of(Function.class);
     }
 
     /**
      * Describes the parameters of a function.
      *
-     * @param  name  name of the function to describe (not null).
+     * @param  name  case-sensitive name of the function to describe.
      * @return description of the function parameters.
-     * @throws IllegalArgumentException if function name is unknown..
+     * @throws IllegalArgumentException if the given function name is unknown.
      */
     @Override
     public AvailableFunction describe(String name) {
-        return Function.valueOf(name.toUpperCase(Locale.US));
+        return Function.valueOf(name);
     }
 
     /**
      * Creates a new function of the given name with given parameters.
      *
      * @param  <R>         the type of resources (e.g. {@link 
org.opengis.feature.Feature}) used as inputs.
-     * @param  name        name of the function to create (not null).
+     * @param  name        case-sensitive name of the function to create as an 
expression.
      * @param  parameters  function parameters.
      * @return function for the given name and parameters.
      * @throws IllegalArgumentException if function name is unknown or some 
parameters are illegal.
      */
     @Override
-    public <R> Expression<R, ?> create(final String name, final 
Expression<R,?>[] parameters) {
-        final Function function = 
Function.valueOf(name.toUpperCase(Locale.US));
+    public <R> Expression<R,?> create(final String name, final 
Expression<R,?>[] parameters) {
+        final Function function = Function.valueOf(name);
         ArgumentChecks.ensureCountBetween("parameters", false,
                                           function.getMinParameterCount(),
                                           function.getMaxParameterCount(),
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
index d0a7a26723..545e902304 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/Registry.java
@@ -16,11 +16,10 @@
  */
 package org.apache.sis.filter.sqlmm;
 
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.Set;
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.filter.FunctionRegister;
-import org.apache.sis.util.collection.Containers;
+import org.apache.sis.filter.visitor.FunctionNames;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.filter.Expression;
@@ -61,16 +60,16 @@ public final class Registry implements FunctionRegister {
      * Returns the names of all functions known to this register.
      */
     @Override
-    public Collection<String> getNames() {
-        return Containers.derivedList(Arrays.asList(SQLMM.values()), 
SQLMM::name);
+    public Set<String> getNames() {
+        return FunctionNames.of(SQLMM.class);
     }
 
     /**
      * Describes the parameters of a function.
      *
-     * @param  name  name of the function to describe.
+     * @param  name  case-sensitive name of the function to describe.
      * @return description of the function parameters.
-     * @throws IllegalArgumentException if function name is unknown..
+     * @throws IllegalArgumentException if the given function name is unknown.
      */
     @Override
     public AvailableFunction describe(final String name) {
@@ -83,9 +82,10 @@ public final class Registry implements FunctionRegister {
      * has been cloned and does not contain null elements.
      * This method verifies only the number of parameters.
      *
-     * @param  name        name of the function to call.
-     * @param  parameters  expressions providing values for the function 
arguments.
-     * @return an expression which will call the specified function.
+     * @param  <R>         the type of resources (e.g. {@link 
org.opengis.feature.Feature}) used as inputs.
+     * @param  name        case-sensitive name of the function to create as an 
expression.
+     * @param  parameters  function parameters.
+     * @return function for the given name and parameters.
      * @throws IllegalArgumentException if function name is unknown or some 
parameters are illegal.
      */
     @Override
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/visitor/FunctionNames.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/visitor/FunctionNames.java
index e1d1b1ad0f..ad4a3bd834 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/visitor/FunctionNames.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/visitor/FunctionNames.java
@@ -16,9 +16,13 @@
  */
 package org.apache.sis.filter.visitor;
 
+import java.util.Arrays;
+import java.util.Set;
 import org.opengis.util.CodeList;
 import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.filter.sqlmm.SQLMM;
+import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.internal.shared.CollectionsExt;
 
 
 /**
@@ -96,4 +100,14 @@ public final class FunctionNames {
     public static CodeList<?> resourceId() {
         return 
DefaultFilterFactory.forFeatures().resourceId("resourceId").getOperatorType();
     }
+
+    /**
+     * Returns the names of all enumeration values, to be interpreted as 
function names.
+     *
+     * @param  type  type of the enumeration for which to get the names.
+     * @return the names viewed as a collection.
+     */
+    public static Set<String> of(final Class<? extends Enum<?>> type) {
+        return 
CollectionsExt.viewAsSet(Containers.derivedList(Arrays.asList(type.getEnumConstants()),
 Enum::name));
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
index f60aa46b36..b108cbe6c2 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
@@ -215,7 +215,7 @@ public class SelectionClauseWriter extends Visitor<Feature, 
SelectionClause> {
 
     /**
      * Invoked when an unsupported filter is found. The SQL string is marked 
as invalid and
-     * may be truncated (later) to the length that it has the last time that 
it was valid.
+     * may be truncated (later) to the length that it had the last time that 
it was valid.
      */
     @Override
     protected final void typeNotFound(CodeList<?> type, Filter<Feature> 
filter, SelectionClause sql) {
@@ -224,7 +224,7 @@ public class SelectionClauseWriter extends Visitor<Feature, 
SelectionClause> {
 
     /**
      * Invoked when an unsupported expression is found. The SQL string is 
marked as invalid
-     * and may be truncated (later) to the length that it has the last time 
that it was valid.
+     * and may be truncated (later) to the length that it had the last time 
that it was valid.
      */
     @Override
     protected final void typeNotFound(String type, Expression<Feature,?> 
expression, SelectionClause sql) {
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/AbstractIterator.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/AbstractIterator.java
index 47c7ff97d5..d0299d2bb1 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/AbstractIterator.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/AbstractIterator.java
@@ -33,7 +33,7 @@ import java.util.NoSuchElementException;
 public abstract class AbstractIterator<E> implements Iterator<E> {
     /**
      * The next value to be returned by {@link #next()}, or {@code null} if 
not yet determined.
-     * This field should be set by a non-null value by {@link #hasNext()}, 
unless there are no more elements.
+     * This field should be set to a non-null value by {@link #hasNext()}, 
unless there are no more elements.
      */
     protected E next;
 
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/CollectionsExt.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/CollectionsExt.java
index 0c259bbd23..fcff2d39c4 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/CollectionsExt.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/CollectionsExt.java
@@ -17,6 +17,7 @@
 package org.apache.sis.util.internal.shared;
 
 import java.util.*;
+import java.util.function.IntFunction;
 import java.lang.reflect.Array;
 import org.opengis.util.CodeList;
 import org.opengis.parameter.InvalidParameterCardinalityException;
@@ -337,6 +338,27 @@ public final class CollectionsExt {
         return JDK19.newLinkedHashSet(count);
     }
 
+    /**
+     * Returns the given collection viewed as an unmodifiable set.
+     * It is caller's responsibility to ensure that the collection contains no 
duplicated values.
+     *
+     * @param  <E>       type of elements in the set.
+     * @param  elements  the elements to view as a set.
+     * @return the given collection viewed as a set.
+     */
+    public static <E> Set<E> viewAsSet(final Collection<E> elements) {
+        return new AbstractSet<E>() {
+            @Override public int         size()                       {return 
elements.size();}
+            @Override public boolean     isEmpty()                    {return 
elements.isEmpty();}
+            @Override public boolean     contains(Object o)           {return 
elements.contains(o);}
+            @Override public boolean     containsAll(Collection<?> c) {return 
elements.containsAll(c);}
+            @Override public Iterator<E> iterator()                   {return 
elements.iterator();}
+            @Override public Object[]    toArray()                    {return 
elements.toArray();}
+            @Override public <T> T[]     toArray(T[] a)               {return 
elements.toArray(a);}
+            @Override public <T> T[]     toArray(IntFunction<T[]> g)  {return 
elements.toArray(g);}
+        };
+    }
+
     /**
      * Returns the specified array as an immutable set, or {@code null} if the 
array is null.
      * If the given array contains duplicated elements, i.e. elements that are 
equal in the


Reply via email to