You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2025/04/01 14:27:19 UTC
(sis) 01/03: Move `ListingPropertyVisitor` from Shapefile module to the main feature module. It will also be needed by the SQL data store, among others.
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 <ma...@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));
}