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 d60db2c5d3 Make filter functions available in `FilterCapabilities`. d60db2c5d3 is described below commit d60db2c5d3c8aba6f6c151885286f3f4bf6a1436 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Aug 3 19:54:36 2023 +0200 Make filter functions available in `FilterCapabilities`. --- .../java/org/apache/sis/filter/Capabilities.java | 25 +++- .../apache/sis/filter/DefaultFilterFactory.java | 130 ++++++++++++++++++--- .../apache/sis/internal/feature/Geometries.java | 2 +- .../sis/internal/filter/FunctionRegister.java | 4 +- .../internal/filter/sqlmm/FunctionDescription.java | 96 ++++++++++++++- .../apache/sis/internal/filter/sqlmm/SQLMM.java | 2 +- .../org/apache/sis/filter/CapabilitiesTest.java | 22 +++- 7 files changed, 245 insertions(+), 36 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java index 81910b8c35..74b5ec99b4 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java +++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java @@ -16,6 +16,7 @@ */ package org.apache.sis.filter; +import java.util.Map; import java.util.Set; import java.util.Collection; import java.util.Optional; @@ -27,6 +28,7 @@ import org.apache.sis.internal.feature.AttributeConvention; import org.opengis.filter.ComparisonOperatorName; import org.opengis.filter.capability.Conformance; import org.opengis.filter.capability.IdCapabilities; +import org.opengis.filter.capability.AvailableFunction; import org.opengis.filter.capability.FilterCapabilities; import org.opengis.filter.capability.ScalarCapabilities; import org.opengis.filter.capability.SpatialCapabilities; @@ -40,19 +42,24 @@ import org.opengis.filter.capability.TemporalCapabilities; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 1.1 */ final class Capabilities implements FilterCapabilities, Conformance, IdCapabilities, ScalarCapabilities { /** - * The unique instance of the capabilities document. + * The filter factory which is providing the functions. + * + * @see #getFunctions() */ - static final Capabilities INSTANCE = new Capabilities(); + private final DefaultFilterFactory<?,?,?> factory; /** * Creates a new capability document. + * + * @param factory the filter factory which is providing functions. */ - private Capabilities() { + Capabilities(final DefaultFilterFactory<?,?,?> factory) { + this.factory = factory; } /** @@ -149,4 +156,14 @@ final class Capabilities implements FilterCapabilities, Conformance, IdCapabilit public boolean implementsSorting() { return true; } + + /** + * Enumerates the functions that may be used in filter expressions. + * + * @return the function that may be used in filter expressions. + */ + @Override + public Map<String,AvailableFunction> getFunctions() { + return factory.new Functions(); + } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java index 536c1fa8d2..cefc741868 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java +++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java @@ -18,6 +18,7 @@ package org.apache.sis.filter; import java.util.Map; import java.util.HashMap; +import java.util.Iterator; import java.util.Collection; import java.util.ServiceLoader; import java.time.Instant; @@ -34,10 +35,12 @@ import org.apache.sis.geometry.WraparoundMethod; import org.apache.sis.util.iso.AbstractFactory; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; +import org.apache.sis.internal.util.AbstractMap; // Branch-dependent imports import org.opengis.filter.*; import org.opengis.feature.Feature; +import org.opengis.filter.capability.AvailableFunction; import org.opengis.filter.capability.FilterCapabilities; @@ -127,7 +130,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem * * @return factory operating on {@link Feature} instances. * - * @todo The type of temporal object is not yet determined. + * @todo The type of temporal objects is not yet determined. */ public static FilterFactory<Feature, Object, Object> forFeatures() { return Features.DEFAULT; @@ -142,7 +145,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem */ @Override public FilterCapabilities getCapabilities() { - return Capabilities.INSTANCE; + return new Capabilities(this); // Cheap to construct, no need to cache. } /** @@ -160,7 +163,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem * @see #forFeatures() */ static final FilterFactory<Feature,Object,Object> DEFAULT = - new Features<>(Object.class, Object.class, WraparoundMethod.SPLIT);; + new Features<>(Object.class, Object.class, WraparoundMethod.SPLIT); /** * Creates a new factory operating on {@link Feature} instances. @@ -992,23 +995,14 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem } /** - * Creates an implementation-specific function. - * The names of available functions is given by {@link #getCapabilities()}. + * 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 call. - * @param parameters expressions providing values for the function arguments. - * @return an expression which will call the specified function. - * @throws IllegalArgumentException if the given name is not recognized, - * or if the arguments are illegal for the specified function. + * @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. */ - @Override - public Expression<R,?> function(final String name, Expression<R,?>[] parameters) { - ArgumentChecks.ensureNonNull("name", name); - ArgumentChecks.ensureNonNull("parameters", parameters); - parameters = parameters.clone(); - for (int i=0; i<parameters.length; i++) { - ArgumentChecks.ensureNonNullElement("parameters", i, parameters[i]); - } + private FunctionRegister register(final String name) { final FunctionRegister register; synchronized (availableFunctions) { if (availableFunctions.isEmpty()) { @@ -1030,6 +1024,28 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem } register = availableFunctions.get(name); } + return register; + } + + /** + * Creates an implementation-specific function. + * The names of available functions is given by {@link #getCapabilities()}. + * + * @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. + * @throws IllegalArgumentException if the given name is not recognized, + * or if the arguments are illegal for the specified function. + */ + @Override + public Expression<R,?> function(final String name, Expression<R,?>[] parameters) { + ArgumentChecks.ensureNonNull("name", name); + ArgumentChecks.ensureNonNull("parameters", parameters); + parameters = parameters.clone(); + 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)); } @@ -1037,7 +1053,83 @@ public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory implem } /** - * Indicates an property by which contents should be sorted, along with intended order. + * 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() { + } + + /** + * {@return 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; + } + + /** + * {@return 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()); + } + }; + } + } + + /** + * Indicates a property by which contents should be sorted, along with intended order. * The given expression should evaluate to {@link Comparable} objects, * but {@link Iterable} objects are accepted as well. * diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java index 564d4fa847..08e7953081 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java @@ -143,7 +143,7 @@ public abstract class Geometries<G> implements Serializable { * * @param library the desired library, or {@code null} for the default. * @return the specified or the default geometry implementation (never {@code null}). - * @throws IllegalArgumentException if a non-null library is specified by that library is not available. + * @throws IllegalArgumentException if a non-null library is specified but that library is not available. */ public static Geometries<?> factory(final GeometryLibrary library) { Geometries<?> g = GeometryFactories.DEFAULT; diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java index 0a147fcae7..15c6344218 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/FunctionRegister.java @@ -37,8 +37,6 @@ import org.opengis.filter.capability.AvailableFunction; * @since 1.0 * * @see org.opengis.filter.FilterFactory#function(String, Expression...) - * - * @todo Replace by {@link org.opengis.filter.capability.ExtendedCapabilities}. */ public interface FunctionRegister { /** @@ -60,7 +58,7 @@ public interface FunctionRegister { /** * Describes the parameters of a function. * - * @param name name of the function to describe (not null). + * @param name name of the function to describe (not null). * @return description of the function parameters. * @throws IllegalArgumentException if function name is unknown.. */ diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionDescription.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionDescription.java index d3b8407ef9..137c19ac84 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionDescription.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/FunctionDescription.java @@ -32,7 +32,7 @@ import org.opengis.filter.capability.AvailableFunction; * Description of a SQLMM function with its parameters. * * @todo Argument descriptions are incomplete. They have no good names, - * and the types are missing (null) except for geometry types. + * and the types are missing (they are {@code null}) except for geometry types. * * @author Martin Desruisseaux (Geomatys) * @version 1.4 @@ -120,9 +120,7 @@ final class FunctionDescription implements AvailableFunction { } /** - * Returns the type of return value. - * - * @return the type of return value. + * {@return the type of return value}. */ @Override public TypeName getReturnType() { @@ -172,5 +170,95 @@ final class FunctionDescription implements AvailableFunction { public TypeName getValueType() { return type; } + + /** + * Tests whether the given object is equal to this argument description. + * + * @param obj the object to test for equality. + * @return whether the given object describes the same argument than this. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof Arg) { + final var other = (Arg) obj; + return name.equals(other.name) + && type.equals(other.type); + } + return false; + } + + /** + * {@return a hash-code value for this argument description}. + */ + @Override + public int hashCode() { + return name.hashCode() + type.hashCode(); + } + + /** + * {@return a string representation of this argument}. + * Current version includes the name and the type. + * Should be used only for debugging purposes. + */ + @Override + public String toString() { + final var sb = new StringBuilder(20); + addType(sb.append(name), type); + return sb.toString(); + } + } + + /** + * Appends the given type name if non-null. + * + * @param sb where to append the type name. + * @param type the type name to add, or {@code null} if none. + */ + private static void addType(final StringBuilder sb, final TypeName type) { + if (type != null) { + sb.append(" : ").append(type); + } + } + + /** + * Tests whether the given object is equal to this function description. + * + * @param obj the object to test for equality. + * @return whether the given object describes the same function than this. + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof FunctionDescription) { + final var other = (FunctionDescription) obj; + return name.equals(other.name) + && result.equals(other.result) + && arguments.equals(other.arguments); + } + return false; + } + + /** + * {@return a hash-code value for this function description}. + */ + @Override + public int hashCode() { + return name.hashCode() + arguments.hashCode() + result.hashCode(); + } + + /** + * {@return a string representation of this function with its argument}. + * Should be used only for debugging purposes. + */ + @Override + public String toString() { + final var sb = new StringBuilder(40).append(name).append('('); + boolean isMore = false; + for (final Argument arg : getArguments()) { + if (isMore) sb.append(", "); + addType(sb.append(arg.getName()), arg.getValueType()); + isMore = true; + } + addType(sb.append(')'), getReturnType()); + return sb.toString(); } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java index f43e86f6e8..bc796d1350 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SQLMM.java @@ -842,7 +842,7 @@ public enum SQLMM { * Returns a description of this SQLMM function. * The Java types associated to arguments and return value depend on which geometry library is used. * - * @param library the geometry ilibrary implementation to use. + * @param library the geometry library implementation to use. * @return description of this SQLMM function. */ public final synchronized AvailableFunction description(final Geometries<?> library) { diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/CapabilitiesTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/CapabilitiesTest.java index 77b9e699d3..9d92781b14 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/filter/CapabilitiesTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/filter/CapabilitiesTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.*; import org.opengis.filter.ComparisonOperatorName; import org.opengis.filter.capability.IdCapabilities; import org.opengis.filter.capability.ScalarCapabilities; +import org.opengis.filter.capability.AvailableFunction; /** @@ -35,7 +36,7 @@ import org.opengis.filter.capability.ScalarCapabilities; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 1.1 */ public final class CapabilitiesTest extends TestCase { @@ -50,8 +51,9 @@ public final class CapabilitiesTest extends TestCase { */ @Test public void testResourceIdentifiers() { - assertTrue(Capabilities.INSTANCE.getConformance().implementsResourceld()); - final IdCapabilities idc = Capabilities.INSTANCE.getIdCapabilities().get(); + final var capabilities = DefaultFilterFactory.forFeatures().getCapabilities(); + assertTrue(capabilities.getConformance().implementsResourceld()); + final IdCapabilities idc = capabilities.getIdCapabilities().get(); final LocalName id = TestUtilities.getSingleton(idc.getResourceIdentifiers()); assertEquals("identifier", id.toString()); } @@ -61,10 +63,22 @@ public final class CapabilitiesTest extends TestCase { */ @Test public void testComparisonOperators() { - final ScalarCapabilities c = Capabilities.INSTANCE.getScalarCapabilities().get(); + final var capabilities = DefaultFilterFactory.forFeatures().getCapabilities(); + final ScalarCapabilities c = capabilities.getScalarCapabilities().get(); final Set<ComparisonOperatorName> op = c.getComparisonOperators(); assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO)); assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_LESS_THAN)); assertTrue(op.contains(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN)); } + + /** + * Tests {@link Capabilities#getFunctions()}. + */ + @Test + public void testFunctions() { + final var capabilities = DefaultFilterFactory.forFeatures().getCapabilities(); + AvailableFunction desc = capabilities.getFunctions().get("ST_Transform"); + assertEquals("SQLMM:ST_Transform", desc.getName().toFullyQualifiedName().toString()); + assertEquals("OGC:Geometry", desc.getReturnType().toFullyQualifiedName().toString()); + } }