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 913e4ff28aa3b744c02a896e1873d4ce3500eb4e
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Mar 28 16:14:25 2025 +0100

    Move `ListingPropertyVisitor` from Shapefile module to the main feature 
module.
    It will also be needed by the SQL data store, among others.
---
 .../sis/filter/privy/ListingPropertyVisitor.java   | 137 +++++++++++++++++++++
 .../main/org/apache/sis/storage/FeatureQuery.java  |  24 ++++
 .../org/apache/sis/storage/FeatureQueryTest.java   |  24 ++++
 .../storage/shapefile/ListingPropertyVisitor.java  |  82 ------------
 .../sis/storage/shapefile/ShapefileStore.java      |   7 +-
 5 files changed, 188 insertions(+), 86 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/ListingPropertyVisitor.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/ListingPropertyVisitor.java
new file mode 100644
index 0000000000..4b9e664a31
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/ListingPropertyVisitor.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter.privy;
+
+import java.util.HashSet;
+import java.util.Set;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.util.CodeList;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.Filter;
+import org.opengis.filter.Expression;
+import org.opengis.filter.ValueReference;
+import org.opengis.filter.ComparisonOperatorName;
+import org.opengis.filter.LikeOperator;
+import org.opengis.filter.LogicalOperator;
+
+
+/**
+ * A collector of all attributes required by a filter or an expression.
+ * This visitor collects the XPaths of all {@link ValueReference} found.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public final class ListingPropertyVisitor extends Visitor<Object, Set<String>> 
{
+    /**
+     * The unique instance of this visitor.
+     */
+    private static final ListingPropertyVisitor INSTANCE = new 
ListingPropertyVisitor();
+
+    /**
+     * Creates the unique instance of this visitor.
+     */
+    private ListingPropertyVisitor() {
+        setLogicalHandlers((f, names) -> {
+            final var filter = (LogicalOperator<Object>) f;
+            for (Filter<Object> child : filter.getOperands()) {
+                visit(child, names);
+            }
+        });
+        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN),
 (f, names) -> {
+            final var filter = (BetweenComparisonOperator<Object>) f;
+            visit(filter.getExpression(),    names);
+            visit(filter.getLowerBoundary(), names);
+            visit(filter.getUpperBoundary(), names);
+        });
+        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE),
 (f, names) -> {
+            final var filter = (LikeOperator<Object>) f;
+            visit(filter.getExpressions().get(0), names);
+        });
+        setExpressionHandler(FunctionNames.ValueReference, (e, names) -> {
+            final var expression = (ValueReference<Object,?>) e;
+            names.add(expression.getXPath());
+        });
+    }
+
+    /**
+     * Visits all operands of the given filter for listing all value 
references.
+     *
+     * @param  type    the filter type (may be {@code null}).
+     * @param  filter  the filter (may be {@code null}).
+     * @param  xpaths  where to add the XPaths.
+     */
+    @Override
+    protected void typeNotFound(final CodeList<?> type, final Filter<Object> 
filter, final Set<String> xpaths) {
+        for (final var f : filter.getExpressions()) {
+            visit(f, xpaths);
+        }
+    }
+
+    /**
+     * Visits all parameters of the given expression for listing all value 
references.
+     *
+     * @param  type        the expression type (may be {@code null}).
+     * @param  expression  the expression (may be {@code null}).
+     * @param  xpaths      where to add the XPaths.
+     */
+    @Override
+    protected void typeNotFound(final String type, final Expression<Object, ?> 
expression, final Set<String> xpaths) {
+        for (final var p : expression.getParameters()) {
+            visit(p, xpaths);
+        }
+    }
+
+    /**
+     * Returns all XPaths used, directly or indirectly, by the given filter.
+     * The elements in the set are in no particular order.
+     *
+     * @param  filter  the filter for which to get the XPaths. May be {@code 
null}.
+     * @param  xpaths  a pre-allocated collection where to add the XPaths, or 
{@code null} if none.
+     * @return the given collection, or a new one if it was {@code null}, with 
XPaths added.
+     */
+    @SuppressWarnings("unchecked")
+    public static Set<String> xpaths(final Filter<?> filter, Set<String> 
xpaths) {
+        if (xpaths == null) {
+            xpaths = new HashSet<>();
+        }
+        if (filter != null) {
+            INSTANCE.visit((Filter) filter, xpaths);
+        }
+        return xpaths;
+    }
+
+    /**
+     * Returns all XPaths used, directly or indirectly, by the given 
expression.
+     * The elements in the set are in no particular order.
+     *
+     * @param  expression  the expression for which to get the XPaths. May be 
{@code null}.
+     * @param  xpaths  a pre-allocated collection where to add the XPaths, or 
{@code null} if none.
+     * @return the given collection, or a new one if it was {@code null}, with 
XPaths added.
+     */
+    @SuppressWarnings("unchecked")
+    public static Set<String> xpaths(final Expression<?,?> expression, 
Set<String> xpaths) {
+        if (xpaths == null) {
+            xpaths = new HashSet<>();
+        }
+        if (expression != null) {
+            INSTANCE.visit((Expression) expression, xpaths);
+        }
+        return xpaths;
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
index 03c96f1d7e..675104c8c7 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
@@ -36,6 +36,7 @@ import org.apache.sis.feature.privy.AttributeConvention;
 import org.apache.sis.feature.privy.FeatureExpression;
 import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.filter.Optimization;
+import org.apache.sis.filter.privy.ListingPropertyVisitor;
 import org.apache.sis.filter.privy.SortByComparator;
 import org.apache.sis.filter.privy.XPath;
 import org.apache.sis.storage.internal.Resources;
@@ -674,6 +675,29 @@ public class FeatureQuery extends Query implements 
Cloneable, Serializable {
         return CharSequences.shortSentence(text, 40).toString();
     }
 
+    /**
+     * Returns all XPaths used, directly or indirectly, by this query.
+     * The XPath values are extracted from all {@link ValueReference} 
expressions found in the
+     * {@linkplain #getSelection() selection} and in the {@linkplain 
#getProjection() projection}.
+     * The {@linkplain NamedExpression#alias aliases} are ignored.
+     *
+     * <p>The elements in the returned set are in no particular order.
+     * The set may be empty but never null.</p>
+     *
+     * @return all XPaths used, directly or indirectly, by this query.
+     *
+     * @since 1.5
+     */
+    public Set<String> getXPaths() {
+        Set<String> xpaths = ListingPropertyVisitor.xpaths(selection, null);
+        if (projection != null) {
+            for (NamedExpression e : projection) {
+                xpaths = ListingPropertyVisitor.xpaths(e.expression, xpaths);
+            }
+        }
+        return xpaths;
+    }
+
     /**
      * Applies this query on the given feature set.
      * This method is invoked by the default implementation of {@link 
FeatureSet#subset(Query)}.
diff --git 
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
 
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
index c57fb684fc..6bd071f8f3 100644
--- 
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
+++ 
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java
@@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
+import static org.apache.sis.test.Assertions.assertSetEquals;
 import static org.apache.sis.test.Assertions.assertMessageContains;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -57,6 +58,7 @@ import org.opengis.filter.SortProperty;
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  */
+@SuppressWarnings("exports")
 public final class FeatureQueryTest extends TestCase {
     /**
      * An arbitrary number of features, all of the same type.
@@ -170,6 +172,15 @@ public final class FeatureQueryTest extends TestCase {
         }
     }
 
+    /**
+     * Verifies that the XPath set contains all the given elements.
+     *
+     * @param expected the expected XPaths.
+     */
+    private void assertXPathsEqual(final String... expected) {
+        assertSetEquals(Arrays.asList(expected), query.getXPaths());
+    }
+
     /**
      * Verifies the effect of {@link FeatureQuery#setLimit(long)}.
      *
@@ -179,6 +190,7 @@ public final class FeatureQueryTest extends TestCase {
     public void testLimit() throws DataStoreException {
         createFeaturesWithAssociation();
         query.setLimit(2);
+        assertXPathsEqual();
         verifyQueryResult(0, 1);
     }
 
@@ -191,6 +203,7 @@ public final class FeatureQueryTest extends TestCase {
     public void testOffset() throws DataStoreException {
         createFeaturesWithAssociation();
         query.setOffset(2);
+        assertXPathsEqual();
         verifyQueryResult(2, 3, 4);
     }
 
@@ -205,6 +218,7 @@ public final class FeatureQueryTest extends TestCase {
         final FilterFactory<Feature,?,?> ff = 
DefaultFilterFactory.forFeatures();
         query.setSortBy(ff.sort(ff.property("value1", Integer.class), 
SortOrder.ASCENDING),
                         ff.sort(ff.property("value2", Integer.class), 
SortOrder.DESCENDING));
+        assertXPathsEqual();
         verifyQueryResult(3, 1, 2, 0, 4);
     }
 
@@ -219,6 +233,7 @@ public final class FeatureQueryTest extends TestCase {
         final FilterFactory<Feature,?,?> ff = 
DefaultFilterFactory.forFeatures();
         query.setSelection(ff.equal(ff.property("value1", Integer.class),
                                     ff.literal(2), true, MatchAction.ALL));
+        assertXPathsEqual("value1");
         verifyQueryResult(1, 2);
     }
 
@@ -233,6 +248,7 @@ public final class FeatureQueryTest extends TestCase {
         createFeaturesWithAssociation();
         final FilterFactory<Feature,?,?> ff = 
DefaultFilterFactory.forFeatures();
         query.setSelection(ff.equal(ff.property("dependency/value3"), 
ff.literal(18)));
+        assertXPathsEqual("dependency/value3");
         verifyQueryResult(3);
     }
 
@@ -248,6 +264,7 @@ public final class FeatureQueryTest extends TestCase {
         query.setProjection(new 
FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) 
null),
                             new 
FeatureQuery.NamedExpression(ff.property("value1", Integer.class), "renamed1"),
                             new FeatureQuery.NamedExpression(ff.literal("a 
literal"), "computed"));
+        assertXPathsEqual("value1");
 
         // Check result type.
         final Feature instance = executeAndGetFirst();
@@ -279,6 +296,7 @@ public final class FeatureQueryTest extends TestCase {
     public void testProjectionByNames() throws DataStoreException {
         createFeaturesWithAssociation();
         query.setProjection("value2");
+        assertXPathsEqual("value2");
         final Feature instance = executeAndGetFirst();
         final PropertyType p = 
TestUtilities.getSingleton(instance.getType().getProperties(true));
         assertEquals("value2", p.getName().toString());
@@ -298,6 +316,7 @@ public final class FeatureQueryTest extends TestCase {
         query.setProjection(
                 ff.add(ff.property("value1", Number.class), ff.literal(1)),
                 ff.add(ff.property("value2", Number.class), ff.literal(1)));
+        assertXPathsEqual("value1", "value2");
         final FeatureSet subset = featureSet.subset(query);
         final FeatureType type = subset.getType();
         final Iterator<? extends PropertyType> properties = 
type.getProperties(true).iterator();
@@ -322,6 +341,7 @@ public final class FeatureQueryTest extends TestCase {
         final FilterFactory<Feature,?,?> ff = 
DefaultFilterFactory.forFeatures();
         query.setProjection(new 
FeatureQuery.NamedExpression(ff.property("value1"),  (String) null),
                             new 
FeatureQuery.NamedExpression(ff.property("/*/unknown"), "unexpected"));
+        assertXPathsEqual("value1", "/*/unknown");
 
         // Check result type.
         final Feature instance = executeAndGetFirst();
@@ -352,6 +372,7 @@ public final class FeatureQueryTest extends TestCase {
         final FilterFactory<Feature,?,?> ff = 
DefaultFilterFactory.forFeatures();
         query.setProjection(new 
FeatureQuery.NamedExpression(ff.property("value1"),  (String) null),
                             new 
FeatureQuery.NamedExpression(ff.property("dependency/value3"), "value3"));
+        assertXPathsEqual("value1", "dependency/value3");
         query.setOffset(2);
         final Feature instance = executeAndGetFirst();
         assertEquals( 2, instance.getPropertyValue("value1"));
@@ -368,6 +389,7 @@ public final class FeatureQueryTest extends TestCase {
     public void testProjectionOfLink() throws DataStoreException {
         createFeatureWithIdentifier();
         query.setProjection(AttributeConvention.IDENTIFIER);
+        assertXPathsEqual(AttributeConvention.IDENTIFIER);
         final Feature instance = executeAndGetFirst();
         assertEquals("id-0", 
instance.getPropertyValue(AttributeConvention.IDENTIFIER));
     }
@@ -392,6 +414,7 @@ public final class FeatureQueryTest extends TestCase {
                 new FeatureQuery.NamedExpression(ff.property("value1", 
Integer.class), (String) null),
                 virtualProjection(ff.property("value1", Integer.class), 
"renamed1"),
                 virtualProjection(ff.literal("a literal"), "computed"));
+        assertXPathsEqual("value1");
 
         // Check result type.
         final Feature instance = executeAndGetFirst();
@@ -434,6 +457,7 @@ public final class FeatureQueryTest extends TestCase {
         final FilterFactory<Feature,?,?> ff = 
DefaultFilterFactory.forFeatures();
         query.setProjection(new 
FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) 
null),
                             virtualProjection(ff.property("valueMissing", 
Integer.class), "renamed1"));
+        assertXPathsEqual("value1", "valueMissing");
 
         var exception = assertThrows(DataStoreContentException.class, 
this::executeAndGetFirst);
         assertMessageContains(exception);
diff --git 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ListingPropertyVisitor.java
 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ListingPropertyVisitor.java
deleted file mode 100644
index bde71bcb45..0000000000
--- 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ListingPropertyVisitor.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.storage.shapefile;
-
-import java.util.Collection;
-import org.apache.sis.filter.privy.FunctionNames;
-import org.apache.sis.filter.privy.Visitor;
-
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.CodeList;
-import org.opengis.filter.BetweenComparisonOperator;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Expression;
-import org.opengis.filter.ValueReference;
-import org.opengis.filter.ComparisonOperatorName;
-import org.opengis.filter.LikeOperator;
-import org.opengis.filter.LogicalOperator;
-
-
-/**
- * Expression visitor that returns a list of all Feature attributs requiered 
by this expression.
- *
- * @author Johann Sorel (Geomatys)
- */
-final class ListingPropertyVisitor extends Visitor<Object,Collection<String>> {
-
-    public static final ListingPropertyVisitor VISITOR = new 
ListingPropertyVisitor();
-
-    protected ListingPropertyVisitor() {
-        setLogicalHandlers((f, names) -> {
-            final LogicalOperator<Object> filter = (LogicalOperator<Object>) f;
-            for (Filter<Object> child : filter.getOperands()) {
-                visit(child, names);
-            }
-        });
-        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN),
 (f, names) -> {
-            final BetweenComparisonOperator<Object> filter = 
(BetweenComparisonOperator<Object>) f;
-            visit(filter.getExpression(),    names);
-            visit(filter.getLowerBoundary(), names);
-            visit(filter.getUpperBoundary(), names);
-        });
-        
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE),
 (f, names) -> {
-            final LikeOperator<Object> filter = (LikeOperator<Object>) f;
-            visit(filter.getExpressions().get(0), names);
-        });
-        setExpressionHandler(FunctionNames.ValueReference, (e, names) -> {
-            final ValueReference<Object,?> expression = 
(ValueReference<Object,?>) e;
-            final String propName = expression.getXPath();
-            if (!propName.trim().isEmpty()) {
-                names.add(propName);
-            }
-        });
-    }
-
-    @Override
-    protected void typeNotFound(final CodeList<?> type, final Filter<Object> 
filter, final Collection<String> names) {
-        for (final Expression<? super Object, ?> f : filter.getExpressions()) {
-            visit(f, names);
-        }
-    }
-
-    @Override
-    protected void typeNotFound(final String type, final Expression<Object, ?> 
expression, final Collection<String> names) {
-        for (final Expression<? super Object, ?> p : 
expression.getParameters()) {
-            visit(p, names);
-        }
-    }
-}
diff --git 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
index 5015d89163..da6d5986a1 100644
--- 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
+++ 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/ShapefileStore.java
@@ -28,7 +28,6 @@ import java.time.LocalDate;
 import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map.Entry;
@@ -70,6 +69,7 @@ import org.apache.sis.feature.privy.AttributeConvention;
 import org.apache.sis.filter.DefaultFilterFactory;
 import org.apache.sis.filter.Optimization;
 import org.apache.sis.filter.privy.FunctionNames;
+import org.apache.sis.filter.privy.ListingPropertyVisitor;
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.io.stream.ChannelDataOutput;
@@ -559,10 +559,9 @@ public final class ShapefileStore extends DataStore 
implements WritableFeatureSe
                 boolean simpleSelection = true; //true if there are no alias 
and all expressions are ValueReference
                 Set<String> properties = null;
                 if (projection != null) {
-                    properties = new HashSet<>();
-                    if (selection!=null) 
ListingPropertyVisitor.VISITOR.visit((Filter) selection, properties);
+                    properties = ListingPropertyVisitor.xpaths(selection, 
properties);
                     for (FeatureQuery.NamedExpression ne : projection) {
-                        ListingPropertyVisitor.VISITOR.visit((Expression) 
ne.expression, properties);
+                        properties = 
ListingPropertyVisitor.xpaths(ne.expression, properties);
                         simpleSelection &= (ne.alias == null);
                         simpleSelection &= 
(ne.expression.getFunctionName().tip().toString().equals(FunctionNames.ValueReference));
                     }

Reply via email to