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 c5bd9412dd Deprecate for removal the
`AbstractFeature.getValueOrFallback(…)` method because experience suggests that
it encourages bugs in user's codes that stay unnoticed. That method can be
replaced by the new `FeatureType.hasProperty(String)` method. However, that
latter method is actually not needed often.
c5bd9412dd is described below
commit c5bd9412dd452f924c3d5f56dbe0d901e4999074
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Oct 28 19:17:38 2025 +0100
Deprecate for removal the `AbstractFeature.getValueOrFallback(…)` method
because
experience suggests that it encourages bugs in user's codes that stay
unnoticed.
That method can be replaced by the new `FeatureType.hasProperty(String)`
method.
However, that latter method is actually not needed often.
The most important replacement for `getValueOrFallback(…)` is to do a
better handling
of `PropertyNotFoundException` as warnings. When that warning occurs, this
is usually
because we have not done a good job in eliminating dead expressions and
dead filters.
So instead of invoking `hasProperty(String)` everywhere, this commit
improves filter
optimizations so that a `PropertyNotFoundException` would be a real error.
---
.../org/apache/sis/feature/AbstractFeature.java | 63 ++----------
.../apache/sis/feature/DefaultAssociationRole.java | 5 +-
.../org/apache/sis/feature/DefaultFeatureType.java | 19 +++-
.../main/org/apache/sis/feature/DenseFeature.java | 3 +
.../org/apache/sis/feature/NamedFeatureType.java | 13 ++-
.../main/org/apache/sis/feature/SparseFeature.java | 3 +
.../internal/shared/AttributeConvention.java | 15 ---
.../internal/shared/FeatureProjectionBuilder.java | 11 +++
.../sis/feature/internal/shared/FeatureView.java | 8 +-
.../main/org/apache/sis/feature/package-info.java | 2 +-
.../org/apache/sis/filter/AssociationValue.java | 9 +-
.../org/apache/sis/filter/BinarySpatialFilter.java | 2 +
.../apache/sis/filter/DefaultFilterFactory.java | 6 +-
.../main/org/apache/sis/filter/DistanceFilter.java | 1 +
.../org/apache/sis/filter/IdentifierFilter.java | 16 +--
.../main/org/apache/sis/filter/LeafExpression.java | 64 ++++++++----
.../main/org/apache/sis/filter/Optimization.java | 8 +-
.../main/org/apache/sis/filter/PropertyValue.java | 28 ++++--
.../main/org/apache/sis/filter/internal/Node.java | 4 +-
.../sis/filter/internal/shared/WarningEvent.java | 14 ++-
.../main/org/apache/sis/filter/package-info.java | 2 +-
.../apache/sis/feature/AbstractFeatureTest.java | 8 --
.../apache/sis/filter/ArithmeticFunctionTest.java | 3 +-
.../apache/sis/filter/ComparisonFilterTest.java | 3 +-
.../apache/sis/filter/IdentifierFilterTest.java | 58 +++++++++--
.../org/apache/sis/filter/LeafExpressionTest.java | 3 +-
.../org/apache/sis/filter/LogicalFilterTest.java | 3 +-
.../org/apache/sis/filter/sqlmm/SQLMMTest.java | 2 +-
.../apache/sis/storage/sql/feature/Database.java | 42 +++++++-
.../sis/storage/sql/feature/FeatureStream.java | 23 +++--
.../apache/sis/storage/sql/feature/Resources.java | 4 +-
.../sis/storage/sql/feature/Resources.properties | 2 +-
.../storage/sql/feature/Resources_fr.properties | 2 +-
.../sis/storage/sql/feature/SelectionClause.java | 47 +--------
.../org/apache/sis/storage/sql/feature/Table.java | 3 +-
.../org/apache/sis/util/stream/StreamWrapper.java | 107 +++++++++++++--------
.../org/apache/sis/storage/sql/SQLStoreTest.java | 2 +-
.../sql/feature/SelectionClauseWriterTest.java | 2 +-
.../sis/storage/aggregate/JoinFeatureSet.java | 13 ++-
.../org/apache/sis/storage/FeatureQueryTest.java | 34 +++----
.../sis/storage/aggregate/JoinFeatureSetTest.java | 10 +-
geoapi/snapshot | 2 +-
.../test/org/apache/sis/map/SEPortrayerTest.java | 23 +++++
.../apache/sis/gui/dataset/ExpandedFeature.java | 8 --
44 files changed, 405 insertions(+), 295 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
index ae57b3522e..e8d683edcf 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/AbstractFeature.java
@@ -17,7 +17,6 @@
package org.apache.sis.feature;
import java.util.Objects;
-import java.util.Optional;
import java.util.Iterator;
import java.util.Collection;
import java.util.Collections;
@@ -87,7 +86,7 @@ import org.opengis.feature.Operation;
* @author Travis L. Pinney
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
*
* @see DefaultFeatureType#newInstance()
*
@@ -378,14 +377,10 @@ public abstract class AbstractFeature implements Feature,
Serializable {
/**
* Returns the value for the property of the given name if that property
exists, or a fallback value otherwise.
- * This method is equivalent to the following code, but potentially more
efficient when the property does not exist:
+ * This method is equivalent to the following code, but potentially more
efficient:
*
* {@snippet lang="java" :
- * try {
- * return getPropertyValue(name);
- * } catch (PropertyNotFoundException ignore) {
- * return missingPropertyFallback
- * }
+ * return type.hasProperty(name) ? getPropertyValue(name) :
missingPropertyFallback;
* }
*
* Note that if a property of the given name exists but has no value, then
this method returns the
@@ -399,9 +394,13 @@ public abstract class AbstractFeature implements Feature,
Serializable {
* if no attribute or association of that name exists. This value
may be {@code null}.
*
* @since 1.1
+ *
+ * @deprecated Experience suggests that this method encourage bugs in
user's code that stay unnoticed.
*/
- @Override
- public abstract Object getValueOrFallback(final String name, Object
missingPropertyFallback);
+ @Deprecated(since = "1.5", forRemoval = true)
+ public Object getValueOrFallback(final String name, Object
missingPropertyFallback) {
+ return type.hasProperty(name) ? getPropertyValue(name) :
missingPropertyFallback;
+ }
/**
* Executes the parameterless operation of the given name and returns the
value of its result.
@@ -475,50 +474,6 @@ public abstract class AbstractFeature implements Feature,
Serializable {
}
}
- /**
- * Returns the explicit or default value of a characteristic of a property.
- * This is a shortcut for the following chain of method invocations
- * (cast and null checks omitted for brevity),
- * except that the actual implementation is potentially more efficient:
- *
- * {@snippet lang="java" :
- * return Optional.ofNullable(
- * ((Attribute<?>) getProperty(property))
- * .characteristics()
- * .get(characteristic)
- * .getValue());
- * }
- *
- * If the attribute has no {@linkplain AbstractAttribute#characteristics()
characteristic} of the given name,
- * then this method fallbacks on the default value of the {@linkplain
DefaultAttributeType#characteristics()
- * characteristics of the attribute type}.
- *
- * @param property name of the property for which to get a
characteristic.
- * @param characteristic name of the characteristic of the property of
the given name.
- * @return value of the specified characteristic on the specified
property, or an empty value
- * if the property is not an attribute or the attribute has no
such characteristic.
- * @throws PropertyNotFoundException if the {@code property} argument is
not the name of a property of this feature.
- *
- * @since 1.5
- */
- public Optional<?> getCharacteristicValue(final String property, final
String characteristic)
- throws PropertyNotFoundException
- {
- Property p = getProperty(property);
- if (p instanceof Attribute<?>) {
- var attribute = (Attribute<?>) p;
- Attribute<?> ca = attribute.characteristics().get(characteristic);
- if (ca != null) {
- // If the characteristic is present, assume that an explicitly
null value is intentional.
- return Optional.ofNullable(ca.getValue());
- } else {
- return
Optional.ofNullable(attribute.getType().characteristics().get(characteristic))
- .map(AttributeType::getDefaultValue);
- }
- }
- return Optional.empty();
- }
-
/**
* Returns the value of the given attribute, as a singleton or as a
collection depending
* on the maximum number of occurrences.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java
index aba59be67e..e479c9bb4a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultAssociationRole.java
@@ -34,7 +34,6 @@ import org.opengis.feature.AttributeType;
import org.opengis.feature.FeatureType;
import org.opengis.feature.FeatureAssociation;
import org.opengis.feature.FeatureAssociationRole;
-import org.opengis.feature.PropertyNotFoundException;
/**
@@ -414,10 +413,8 @@ public class DefaultAssociationRole extends FieldType
implements FeatureAssociat
*/
private static String searchTitleProperty(final FeatureType ft) {
String fallback = null;
- try {
+ if (ft.hasProperty(AttributeConvention.IDENTIFIER)) {
return
ft.getProperty(AttributeConvention.IDENTIFIER).getName().toString();
- } catch (PropertyNotFoundException e) {
- // Ignore.
}
for (final PropertyType type : ft.getProperties(true)) {
if (type instanceof AttributeType<?>) {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java
index fb9a800298..35fe577163 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DefaultFeatureType.java
@@ -97,7 +97,7 @@ import org.opengis.feature.PropertyNotFoundException;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.6
*
* @see DefaultAttributeType
* @see DefaultAssociationRole
@@ -850,8 +850,25 @@ public class DefaultFeatureType extends
AbstractIdentifiedType implements Featur
return includeSuperTypes ? allProperties : properties;
}
+ /**
+ * Returns {@code true} if and only if an attribute, operation or
association role of the given name exists
+ * in this feature type or in one of its super-types. If this method
returns {@code true}, then calls to
+ * <code>{@linkplain #getProperty(String) getProperty}(name)</code> will
not throw
+ * {@link PropertyNotFoundException}.
+ *
+ * @param name the name of the property to search.
+ * @return whether an attribute, operation or association role exists for
the given name.
+ *
+ * @since 1.6
+ */
+ @Override
+ public boolean hasProperty(final String name) {
+ return byName.get(name) != null;
+ }
+
/**
* Returns the attribute, operation or association role for the given name.
+ * The method searches in this feature type and in all super-types.
*
* @param name the name of the property to search.
* @return the property for the given name, or {@code null} if none.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
index af8344ec28..38315596a9 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/DenseFeature.java
@@ -178,8 +178,11 @@ final class DenseFeature extends AbstractFeature
implements CloneAccess {
* @param name the property name.
* @param missingPropertyFallback the value to return if no attribute or
association of the given name exists.
* @return the value for the given property, or {@code null} if none.
+ *
+ * @deprecated Experience suggests that this method encourage bugs in
user's code that stay unnoticed.
*/
@Override
+ @Deprecated(since = "1.5", forRemoval = true)
public final Object getValueOrFallback(final String name, final Object
missingPropertyFallback) {
ArgumentChecks.ensureNonNull("name", name);
final Integer index = indices.get(name);
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java
index c1517e71f9..1b07cbd5a9 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/NamedFeatureType.java
@@ -101,11 +101,19 @@ final class NamedFeatureType implements FeatureType,
Serializable {
return false;
}
+ /**
+ * Always returns {@code false} since this feature type has no declared
property yet.
+ */
+ @Override
+ public boolean hasProperty(String name) {
+ return false;
+ }
+
/**
* Always throws {@link PropertyNotFoundException} since this feature type
has no declared property yet.
*/
@Override
- public PropertyType getProperty(final String name) throws
PropertyNotFoundException {
+ public PropertyType getProperty(String name) throws
PropertyNotFoundException {
throw new
PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2,
getName(), name));
}
@@ -113,7 +121,7 @@ final class NamedFeatureType implements FeatureType,
Serializable {
* Returns an empty set since this feature has no declared property yet.
*/
@Override
- public Collection<? extends PropertyType> getProperties(final boolean
includeSuperTypes) {
+ public Collection<? extends PropertyType> getProperties(boolean
includeSuperTypes) {
return Collections.emptySet();
}
@@ -139,6 +147,7 @@ final class NamedFeatureType implements FeatureType,
Serializable {
if (type == null) {
return false;
}
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final FeatureType resolved = this.resolved;
return (resolved != null) && resolved.isAssignableFrom(type);
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
index 6ddc2667dd..f49577495f 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/SparseFeature.java
@@ -225,8 +225,11 @@ final class SparseFeature extends AbstractFeature
implements CloneAccess {
* @param name the property name.
* @param missingPropertyFallback the value to return if no attribute or
association of the given name exists.
* @return the value for the given property, or {@code null} if none.
+ *
+ * @deprecated Experience suggests that this method encourage bugs in
user's code that stay unnoticed.
*/
@Override
+ @Deprecated(since = "1.5", forRemoval = true)
public final Object getValueOrFallback(final String name, final Object
missingPropertyFallback) {
ArgumentChecks.ensureNonNull("name", name);
final Integer index = indices.get(name);
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/AttributeConvention.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/AttributeConvention.java
index 70e3c6b2ef..395db909dc 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/AttributeConvention.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/AttributeConvention.java
@@ -249,21 +249,6 @@ public final class AttributeConvention {
return false;
}
- /**
- * Returns {@code true} if the given feature type is non-null and has a
{@value #IDENTIFIER} property.
- *
- * @param feature the feature type to test, or {@code null}.
- * @return whether the given feature type is non-null and has a {@value
#IDENTIFIER} property.
- */
- public static boolean hasIdentifier(final FeatureType feature) {
- if (feature != null) try {
- return feature.getProperty(IDENTIFIER) != null;
- } catch (PropertyNotFoundException e) {
- // Ignore
- }
- return false;
- }
-
/**
* Returns {@code true} if the given type is an {@link AttributeType} or
an {@link Operation} computing
* an attribute, and the attribute value is one of the geometry types
recognized by SIS.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjectionBuilder.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjectionBuilder.java
index ac9681ef89..c1e57566b3 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjectionBuilder.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureProjectionBuilder.java
@@ -36,6 +36,7 @@ import org.apache.sis.feature.builder.AttributeTypeBuilder;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.feature.builder.PropertyTypeBuilder;
import org.apache.sis.feature.internal.Resources;
+import org.apache.sis.filter.Optimization;
import org.apache.sis.util.ArgumentCheckByAssertion;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.internal.shared.Strings;
@@ -551,6 +552,13 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
return attributeValueGetter;
}
+ /**
+ * Optimizes the expression. This is invoked as the last step before
to build the final feature projection.
+ */
+ final void optimize(final Optimization optimizer) {
+ attributeValueGetter = optimizer.apply(attributeValueGetter);
+ }
+
/**
* Sets the coordinate reference system that characterizes the values
of this attribute.
*
@@ -766,6 +774,9 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
if (source.equals(typeRequested) &&
source.equals(typeWithDependencies)) {
return Optional.empty();
}
+ final var optimizer = new Optimization();
+ optimizer.setFeatureType(source);
+ requested.forEach((item) -> item.optimize(optimizer));
return Optional.of(new FeatureProjection(typeRequested,
typeWithDependencies, requested));
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureView.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureView.java
index 077d491398..931748153f 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureView.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/FeatureView.java
@@ -123,9 +123,15 @@ final class FeatureView extends AbstractFeature {
* @param name the property name.
* @param missingPropertyFallback the value to return if no attribute or
association of the given name exists.
* @return value or default value of the specified property, or {@code
missingPropertyFallback}.
+ *
+ * @deprecated Experience suggests that this method encourage bugs in
user's code that stay unnoticed.
*/
@Override
+ @Deprecated(since = "1.5", forRemoval = true)
public Object getValueOrFallback(final String name, final Object
missingPropertyFallback) {
- return source.getValueOrFallback(name, missingPropertyFallback);
+ if (source instanceof AbstractFeature) {
+ return ((AbstractFeature) source).getValueOrFallback(name,
missingPropertyFallback);
+ }
+ return super.getValueOrFallback(name, missingPropertyFallback);
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/package-info.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/package-info.java
index b7fe070c00..749cea6798 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/package-info.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/package-info.java
@@ -93,7 +93,7 @@
* @author Travis L. Pinney
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.5
*/
package org.apache.sis.feature;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
index 657a64b49f..2e0f126734 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
@@ -171,7 +171,7 @@ walk: if (specifiedType != null) try {
* Delegate the final property optimization to `accessor` which
may not only resolve
* links but also tune the `ObjectConverter`.
*/
- final PropertyValue<V> converted;
+ final Expression<Feature, V> converted;
optimization.setFeatureType(type);
try {
converted = accessor.optimize(optimization);
@@ -179,7 +179,12 @@ walk: if (specifiedType != null) try {
optimization.setFeatureType(specifiedType);
}
if (converted != accessor || direct != path) {
- return new AssociationValue<>(direct, converted);
+ if (converted instanceof PropertyValue<?>) {
+ return new AssociationValue<>(direct, (PropertyValue<V>)
converted);
+ } else {
+ // If not a `PropertyValue`, then it should be a `Literal`.
+ return converted;
+ }
}
} catch (PropertyNotFoundException e) {
warning(e, true);
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java
index 3e03ba7213..bda0bd58fe 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/BinarySpatialFilter.java
@@ -64,6 +64,7 @@ final class BinarySpatialFilter<R> extends
BinaryGeometryFilter<R> implements Bi
BinarySpatialFilter(final Geometries<?> library, final Expression<R,?>
geometry,
final Envelope bounds, final WraparoundMethod
wraparound)
{
+ // Checks for null value are done indirectly in the methods invoked
below.
super(library, geometry, new
LeafExpression.Transformed<>(library.toGeometry2D(bounds, wraparound),
new LeafExpression.Literal<>(bounds)), null);
operatorType = SpatialOperatorName.BBOX;
@@ -138,6 +139,7 @@ final class BinarySpatialFilter<R> extends
BinaryGeometryFilter<R> implements Bi
* @return {@code true} if the test(s) are passed for the provided object.
*/
@Override
+ @SuppressWarnings("UseSpecificCatch")
public boolean test(final R object) {
final GeometryWrapper left = expression1.apply(object);
if (left != null) {
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 73d2ed0d0d..22bab587df 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
@@ -189,8 +189,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);
+ static final Features<Object,Object> DEFAULT = new
Features<>(Object.class, Object.class, WraparoundMethod.SPLIT);
/**
* Creates a new factory operating on {@link Feature} instances.
@@ -284,6 +283,9 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
*/
@Override
public <V> Literal<R,V> literal(final V value) {
+ if (value == null) {
+ return LeafExpression.NULL();
+ }
return new LeafExpression.Literal<>(value);
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java
index 3fd8a10447..c71d0c641e 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DistanceFilter.java
@@ -155,6 +155,7 @@ final class DistanceFilter<R> extends
BinaryGeometryFilter<R> implements Distanc
* @return {@code true} if the test(s) are passed for the provided object.
*/
@Override
+ @SuppressWarnings("UseSpecificCatch")
public boolean test(final R object) {
final GeometryWrapper left = expression1.apply(object);
if (left != null) {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
index f2a7b4ef71..39d8bc7b3d 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
@@ -18,12 +18,13 @@ package org.apache.sis.filter;
import java.util.List;
import java.util.Collection;
-import org.apache.sis.util.ArgumentChecks;
+import java.util.Objects;
import org.apache.sis.filter.internal.Node;
import org.apache.sis.feature.internal.shared.AttributeConvention;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.feature.Feature;
+import org.opengis.feature.PropertyNotFoundException;
import org.opengis.filter.Expression;
import org.opengis.filter.ResourceId;
import org.opengis.filter.Filter;
@@ -51,8 +52,7 @@ final class IdentifierFilter extends Node implements
ResourceId<Feature>, Optimi
* Creates a new filter using the given identifier.
*/
IdentifierFilter(final String identifier) {
- ArgumentChecks.ensureNonEmpty("identifier", identifier);
- this.identifier = identifier;
+ this.identifier = Objects.requireNonNull(identifier);
}
/**
@@ -103,10 +103,12 @@ final class IdentifierFilter extends Node implements
ResourceId<Feature>, Optimi
*/
@Override
public boolean test(final Feature object) {
- if (object == null) {
- return false;
+ if (object != null) try {
+ Object id =
object.getPropertyValue(AttributeConvention.IDENTIFIER);
+ if (id != null) return identifier.equals(id.toString());
+ } catch (PropertyNotFoundException e) {
+ warning(e, false);
}
- final Object id =
object.getValueOrFallback(AttributeConvention.IDENTIFIER, null);
- return (id != null) && identifier.equals(id.toString());
+ return false;
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
index 77d3b85e97..8008b303e5 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/LeafExpression.java
@@ -77,6 +77,18 @@ abstract class LeafExpression<R,V> extends Node implements
FeatureExpression<R,V
return Set.of();
}
+ /**
+ * Returns a literal which always returns {@code null}.
+ *
+ * @param <R> ignored.
+ * @param <V> ignored.
+ * @return a literal for {@code null}.
+ */
+ @SuppressWarnings("unchecked")
+ protected static <R,V> Literal<R,V> NULL() {
+ return (Literal) Literal.NULL;
+ }
+
@@ -95,6 +107,9 @@ abstract class LeafExpression<R,V> extends Node implements
FeatureExpression<R,V
/** For cross-version compatibility. */
private static final long serialVersionUID = -8383113218490957822L;
+ /** A predefined literal which always returns {@code null}. */
+ private static Literal<?,?> NULL = new Literal<>(null);
+
/** The constant value to be returned by {@link #getValue()}. */
@SuppressWarnings("serial") // Not statically typed as
Serializable.
protected final V value;
@@ -138,16 +153,32 @@ abstract class LeafExpression<R,V> extends Node
implements FeatureExpression<R,V
*/
@Override
@SuppressWarnings("unchecked")
- public <N> Expression<R,N> toValueType(final Class<N> target) {
+ public final <N> Expression<R,N> toValueType(final Class<N> target) {
try {
final N c = ObjectConverters.convert(value, target);
- return (c != value) ? new Literal<>(c) : (Literal<R,N>) this;
+ if (c == null) return (Literal<R,N>) NULL;
+ if (c == value) return (Literal<R,N>) this;
+ return new Literal<>(c);
} catch (UnconvertibleObjectException e) {
- throw (ClassCastException) new
ClassCastException(Errors.format(
- Errors.Keys.CanNotConvertValue_2, getFunctionName(),
target)).initCause(e);
+ return unconvertibleValue(target, e);
}
}
+ /**
+ * Invoked when {@link #toValueType(Class)} failed to find a converter
to the given class.
+ * This method gives a chance to provide a fallback.
+ *
+ * @param <N> compile-time value of {@code target}.
+ * @param target the target class requested by the user.
+ * @param cause the exception that occurred.
+ * @return the fallback.
+ * @throws ClassCastException if there is no fallback.
+ */
+ protected <N> Expression<R,N> unconvertibleValue(final Class<N>
target, UnconvertibleObjectException cause) {
+ throw (ClassCastException) new ClassCastException(Errors.format(
+ Errors.Keys.CanNotConvertValue_2, getFunctionName(),
target)).initCause(cause);
+ }
+
/**
* Provides the type of values returned by {@link #apply(Object)}.
* The returned item wraps an {@link AttributeType} named "Literal".
@@ -227,26 +258,19 @@ abstract class LeafExpression<R,V> extends Node
implements FeatureExpression<R,V
}
/**
- * Converts the transformed value if possible, or the original value
as a fallback.
- *
- * @throws ClassCastException if values cannot be provided as
instances of the specified class.
+ * Invoked when {@link #toValueType(Class)} failed to find a converter
to the given class.
+ * This method tries to apply the same operation on the original value
as a fallback.
*/
@Override
- @SuppressWarnings("unchecked")
- public <N> Expression<R,N> toValueType(final Class<N> target) {
- // Same implementation as `super.toValueType(type)` except for
exception handling.
+ protected <N> Expression<R,N> unconvertibleValue(final Class<N>
target, UnconvertibleObjectException cause) {
try {
- final N c = ObjectConverters.convert(value, target);
- return (c != value) ? new Literal<>(c) : (Literal<R,N>) this;
- } catch (UnconvertibleObjectException e) {
+ return original.toValueType(target);
+ } catch (RuntimeException bis) {
try {
- return original.toValueType(target);
- } catch (RuntimeException bis) {
- final var c = new ClassCastException(Errors.format(
- Errors.Keys.CanNotConvertValue_2,
getFunctionName(), target));
- c.initCause(e);
- c.addSuppressed(bis);
- throw c;
+ return super.unconvertibleValue(target, cause); // For
creating the main exception.
+ } catch (RuntimeException e) {
+ e.addSuppressed(bis);
+ throw e;
}
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
index 71c84d6fe1..1c0cb4bc3b 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/Optimization.java
@@ -45,6 +45,7 @@ import org.opengis.feature.FeatureType;
* <ul>
* <li>Application of some logical identities such as {@code NOT(NOT(A)) ==
A}.</li>
* <li>Application of logical short circuits such as {@code A & FALSE ==
FALSE}.</li>
+ * <li>Replacement of value references to non-existent properties by null
literals.</li>
* <li>Immediate evaluation of expressions where all parameters are literal
values.</li>
* </ul>
*
@@ -72,13 +73,13 @@ import org.opengis.feature.FeatureType;
* <h2>Behavioral changes</h2>
* Optimized filters shall produce the same results as non-optimized filters.
* However side-effects may differ, in particular regarding exceptions that
may be thrown.
- * For example if a filter tests {@code A & B} and if {@code Optimization}
determines that the {@code B}
+ * For example, if a filter tests {@code A & B} and if {@code Optimization}
determines that the {@code B}
* condition will always evaluate to {@code false}, then the {@code A}
condition will never be tested.
* If that condition had side-effects or threw an exception,
* those effects will disappear in the optimized filter.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.6
* @since 1.1
*/
public class Optimization {
@@ -539,6 +540,9 @@ public class Optimization {
* @see DefaultFilterFactory#literal(Object)
*/
public static <R,V> Literal<R,V> literal(final V value) {
+ if (value == null) {
+ return LeafExpression.NULL();
+ }
return new LeafExpression.Literal<>(value);
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index 7b7d9c4d27..8df2b62ed6 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
@@ -33,6 +33,7 @@ import org.opengis.feature.FeatureType;
import org.opengis.feature.PropertyType;
import org.opengis.feature.AttributeType;
import org.opengis.feature.PropertyNotFoundException;
+import org.opengis.filter.Expression;
import org.opengis.filter.ValueReference;
@@ -205,7 +206,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
* using or not the database index.
*/
@Override
- public abstract PropertyValue<V> optimize(Optimization optimization);
+ public abstract Expression<Feature, V> optimize(Optimization optimization);
@@ -230,23 +231,29 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
*/
@Override
public Object apply(final Feature instance) {
- return (instance != null) ? instance.getValueOrFallback(name,
null) : null;
+ if (instance != null) try {
+ return instance.getPropertyValue(name);
+ } catch (PropertyNotFoundException e) {
+ warning(e, false);
+ }
+ return null;
}
/**
* If the evaluated property is a link, replaces this expression by a
more direct reference
* to the target property. This optimization is important for allowing
{@code SQLStore} to
- * put the column name in the SQL {@code WHERE} clause. It makes the
difference between
- * using or not the database index.
+ * put the column name in the <abbr>SQL</abbr> {@code WHERE} clause.
+ * It makes the difference between using or not the database index.
*/
@Override
- public PropertyValue<Object> optimize(final Optimization optimization)
{
+ public Expression<Feature, Object> optimize(final Optimization
optimization) {
final FeatureType type = optimization.getFeatureType();
if (type != null) try {
return Features.getLinkTarget(type.getProperty(name))
.map((rename) -> new AsObject(rename,
isVirtual)).orElse(this);
} catch (PropertyNotFoundException e) {
warning(e, true);
+ return NULL();
}
return this;
}
@@ -294,8 +301,8 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
@Override
public V apply(final Feature instance) {
if (instance != null) try {
- return
ObjectConverters.convert(instance.getValueOrFallback(name, null), type);
- } catch (UnconvertibleObjectException e) {
+ return
ObjectConverters.convert(instance.getPropertyValue(name), type);
+ } catch (PropertyNotFoundException | UnconvertibleObjectException
e) {
warning(e, false);
}
return null;
@@ -307,7 +314,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
* then a specialized expression is returned. Otherwise this method
returns {@code this}.
*/
@Override
- public final PropertyValue<V> optimize(final Optimization
optimization) {
+ public final Expression<Feature, V> optimize(final Optimization
optimization) {
final FeatureType featureType = optimization.getFeatureType();
if (featureType != null) try {
/*
@@ -344,6 +351,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
}
} catch (PropertyNotFoundException e) {
warning(e, true);
+ return NULL();
}
return this;
}
@@ -437,8 +445,8 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
@Override
public V apply(final Feature instance) {
if (instance != null) try {
- return
converter.apply(source.cast(instance.getValueOrFallback(name, null)));
- } catch (ClassCastException | UnconvertibleObjectException e) {
+ return
converter.apply(source.cast(instance.getPropertyValue(name)));
+ } catch (PropertyNotFoundException | ClassCastException |
UnconvertibleObjectException e) {
warning(e, false);
}
return null;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
index e3842427c0..37951b1e6f 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/Node.java
@@ -385,7 +385,7 @@ public abstract class Node implements Serializable {
*
* @param exception the exception that occurred.
* @param recoverable {@code true} if the caller has been able to
fallback on a default value,
- * or {@code false} if the caller has to return
{@code null}.
+ * or {@code false} if the caller has to return
{@code null} or {@code false}.
*
* @todo Consider defining a {@code Context} class providing, among other
information, listeners where to report warnings.
*
@@ -394,7 +394,7 @@ public abstract class Node implements Serializable {
protected final void warning(final Exception exception, final boolean
recoverable) {
final Consumer<WarningEvent> listener = WarningEvent.LISTENER.get();
if (listener != null) {
- listener.accept(new WarningEvent(this, exception));
+ listener.accept(new WarningEvent(this, exception, recoverable));
} else {
final String method = (this instanceof Predicate) ? "test" :
"apply";
if (recoverable) {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
index 72b098dc57..bea30abf46 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/shared/WarningEvent.java
@@ -52,15 +52,23 @@ public final class WarningEvent {
*/
public final Exception exception;
+ /**
+ * {@code true} if the caller has been able to fallback on a default value,
+ * or {@code false} if the caller has to return {@code null} or {@code
false}.
+ * If {@code true}, the warning should be logged at a finer level.
+ */
+ public final boolean recoverable;
+
/**
* Creates a new warning.
*
* @param source the filter or expression that produced this warning.
* @param exception the exception that occurred.
*/
- public WarningEvent(final Node source, final Exception exception) {
- this.source = source;
- this.exception = exception;
+ public WarningEvent(final Node source, final Exception exception, final
boolean recoverable) {
+ this.source = source;
+ this.exception = exception;
+ this.recoverable = recoverable;
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java
index d944af325a..52c562e1c3 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/package-info.java
@@ -57,7 +57,7 @@
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
*
* @since 1.1
*/
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java
index 637b6a10d9..3c319962a3 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/feature/AbstractFeatureTest.java
@@ -100,14 +100,6 @@ public final class AbstractFeatureTest extends
FeatureTestCase {
return values.get(name);
}
- /**
- * Synonymous of {@link #getPropertyValue(String)} for this test.
- */
- @Override
- public Object getValueOrFallback(final String name, final Object
missingPropertyFallback) {
- return getPropertyValue(name);
- }
-
/**
* Sets the value for the property of the given name. In order to
allow the tests to pass,
* we need to reproduce in this method some of the verifications
performed by the
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java
index 71ad083135..5d09d33270 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ArithmeticFunctionTest.java
@@ -32,11 +32,12 @@ import org.opengis.filter.FilterFactory;
*
* @author Johann Sorel (Geomatys)
*/
+@SuppressWarnings("exports")
public final class ArithmeticFunctionTest extends TestCase {
/**
* The factory to use for creating the objects to test.
*/
- private final FilterFactory<Feature,Object,?> factory;
+ private final FilterFactory<Feature, ?, ?> factory;
/**
* Creates a new test case.
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java
index 4385cb4629..fd3453ce0b 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java
@@ -39,11 +39,12 @@ import org.apache.sis.filter.internal.shared.FunctionNames;
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class ComparisonFilterTest extends TestCase {
/**
* The factory to use for creating the objects to test.
*/
- private final FilterFactory<Feature,Object,?> factory;
+ private final FilterFactory<Feature, ?, ?> factory;
/**
* Expressions used as constant for the tests.
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java
index 361e3cd26c..225e2373d8 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/IdentifierFilterTest.java
@@ -16,18 +16,24 @@
*/
package org.apache.sis.filter;
+import java.util.function.Consumer;
import org.apache.sis.feature.builder.AttributeRole;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.filter.internal.shared.WarningEvent;
// Test dependencies
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestCase;
+import static org.apache.sis.test.Assertions.assertMessageContains;
import static org.apache.sis.test.Assertions.assertSerializedEquals;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyNotFoundException;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
@@ -38,11 +44,17 @@ import org.opengis.filter.FilterFactory;
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
-public final class IdentifierFilterTest extends TestCase {
+@SuppressWarnings("exports")
+public final class IdentifierFilterTest extends TestCase implements
Consumer<WarningEvent> {
/**
* The factory to use for creating the objects to test.
*/
- private final FilterFactory<Feature,Object,?> factory;
+ private final FilterFactory<Feature, ?, ?> factory;
+
+ /**
+ * The warning that occurred while executing a filter or expression.
+ */
+ private WarningEvent warning;
/**
* Creates a new test case.
@@ -51,12 +63,40 @@ public final class IdentifierFilterTest extends TestCase {
factory = DefaultFilterFactory.forFeatures();
}
+ /**
+ * Setup a listener for warnings that may occur during expression or
filter execution.
+ */
+ @BeforeEach
+ public void registerWarningListener() {
+ WarningEvent.LISTENER.set(this);
+ }
+
+ /**
+ * Removes the listener.
+ */
+ @AfterEach
+ public void unregisterWarningListener() {
+ WarningEvent.LISTENER.remove();
+ }
+
+ /**
+ * Invoked when a warning occurred. We expect at most one warning per test.
+ *
+ * @param event the warning that occurred.
+ */
+ @Override
+ public void accept(final WarningEvent event) {
+ assertNull(warning);
+ warning = event;
+ }
+
/**
* Tests construction and serialization.
*/
@Test
public void testSerialize() {
assertSerializedEquals(factory.resourceId("abc"));
+ assertNull(warning);
}
/**
@@ -82,9 +122,11 @@ public final class IdentifierFilterTest extends TestCase {
final Filter<Feature> id = factory.resourceId("123");
assertEquals(Feature.class, id.getResourceClass());
- assertTrue (id.test(f1));
- assertTrue (id.test(f2));
- assertFalse(id.test(f3));
+ assertTrue (id.test(f1)); assertNull(warning);
+ assertTrue (id.test(f2)); assertNull(warning);
+ assertFalse(id.test(f3)); assertNotNull(warning);
+ var e = assertInstanceOf(PropertyNotFoundException.class,
warning.exception);
+ assertMessageContains(e, "sis:identifier", "Test 3");
}
/**
@@ -105,8 +147,8 @@ public final class IdentifierFilterTest extends TestCase {
factory.resourceId("123"));
assertEquals(Feature.class, id.getResourceClass());
- assertTrue (id.test(f1));
- assertTrue (id.test(f2));
- assertFalse(id.test(f3));
+ assertTrue (id.test(f1)); assertNull(warning);
+ assertTrue (id.test(f2)); assertNull(warning);
+ assertFalse(id.test(f3)); assertNull(warning);
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java
index cd285db672..9c63b284b5 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LeafExpressionTest.java
@@ -34,11 +34,12 @@ import org.opengis.filter.FilterFactory;
*
* @author Johann Sorel (Geomatys)
*/
+@SuppressWarnings("exports")
public final class LeafExpressionTest extends TestCase {
/**
* The factory to use for creating the objects to test.
*/
- private final FilterFactory<Feature,Object,?> factory;
+ private final FilterFactory<Feature, ?, ?> factory;
/**
* Creates a new test case.
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
index 2dcdb65554..39c3c62c35 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
@@ -43,11 +43,12 @@ import org.opengis.filter.LogicalOperator;
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class LogicalFilterTest extends TestCase {
/**
* The factory to use for creating the objects to test.
*/
- private final FilterFactory<Feature,Object,?> factory;
+ private final FilterFactory<Feature, ?, ?> factory;
/**
* Creates a new test case.
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java
index 7bd32b6664..74a758d6b3 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/sqlmm/SQLMMTest.java
@@ -48,7 +48,7 @@ public final class SQLMMTest extends TestCase {
/**
* The factory to use for creating the objects to test.
*/
- private final FilterFactory<Feature,Object,?> factory;
+ private final FilterFactory<Feature, ?, ?> factory;
/**
* Creates a new test case.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
index 3945e2e6c6..05c14066e5 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
@@ -28,6 +28,7 @@ import java.util.Optional;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.LogRecord;
+import java.util.function.Consumer;
import java.util.concurrent.locks.ReadWriteLock;
import java.sql.Array;
import java.sql.Connection;
@@ -50,13 +51,14 @@ import org.apache.sis.storage.FeatureSet;
import org.apache.sis.storage.FeatureNaming;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.IllegalNameException;
+import org.apache.sis.storage.sql.SQLStore;
+import org.apache.sis.storage.sql.ResourceDefinition;
import org.apache.sis.storage.base.MetadataBuilder;
+import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.filter.internal.shared.WarningEvent;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryType;
import org.apache.sis.system.Modules;
-import org.apache.sis.storage.sql.SQLStore;
-import org.apache.sis.storage.sql.ResourceDefinition;
-import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.util.Debug;
import org.apache.sis.util.Version;
import org.apache.sis.util.collection.TreeTable;
@@ -64,6 +66,8 @@ import org.apache.sis.util.collection.Cache;
import org.apache.sis.util.internal.shared.Strings;
import org.apache.sis.util.internal.shared.UnmodifiableArrayList;
import org.apache.sis.util.resources.Vocabulary;
+import org.opengis.filter.ValueReference;
+import org.opengis.util.CodeList;
/**
@@ -102,7 +106,7 @@ import org.apache.sis.util.resources.Vocabulary;
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
*/
-public class Database<G> extends Syntax {
+public class Database<G> extends Syntax {
/**
* The SQL wildcard for any characters. A string containing only this
wildcard
* means "any value" and can sometimes be replaced by {@code null}.
@@ -883,6 +887,13 @@ public class Database<G> extends Syntax {
return new InfoStatements(this, connection);
}
+ /**
+ * Returns the localized resources for warnings and error messages.
+ */
+ private Resources resources() {
+ return Resources.forLocale(listeners.getLocale());
+ }
+
/**
* Logs a warning with a localized message and an optional cause.
*
@@ -890,7 +901,7 @@ public class Database<G> extends Syntax {
* @param cause the cause, or {@code null} if none.
*/
final void warning(final short resourceKey, final Exception cause) {
- LogRecord record =
Resources.forLocale(listeners.getLocale()).createLogRecord(Level.WARNING,
resourceKey);
+ LogRecord record = resources().createLogRecord(Level.WARNING,
resourceKey);
record.setThrown(cause);
log(record);
}
@@ -908,6 +919,27 @@ public class Database<G> extends Syntax {
listeners.warning(record);
}
+ /**
+ * Creates a listener for warnings that occur during the execution of
filters or expressions.
+ * This method declares {@link FeatureSet#features(boolean)} as the public
source of the log.
+ *
+ * @return the warning listener.
+ */
+ final Consumer<WarningEvent> createFilterListener() {
+ return (event) -> {
+ final LogRecord record = resources().createLogRecord(
+ event.recoverable ? Level.FINE : Level.WARNING,
+ Resources.Keys.IncompatibleFunction_2,
+
event.getOperatorType().flatMap(CodeList::identifier).orElse("?"),
+
event.getParameter(ValueReference.class).map(ValueReference<?,?>::getXPath).orElse("?"));
+ record.setThrown(event.exception);
+ record.setSourceClassName(FeatureSet.class.getName());
+ record.setSourceMethodName("features");
+ record.setLoggerName(Modules.SQL);
+ listeners.warning(record);
+ };
+ }
+
/**
* Creates a tree representation of this database for debugging purpose.
*
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
index e4b9294b77..9605dd231a 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java
@@ -32,7 +32,6 @@ import java.sql.SQLException;
import java.sql.Statement;
import org.apache.sis.filter.Optimization;
import org.apache.sis.filter.internal.shared.SortByComparator;
-import org.apache.sis.filter.internal.shared.WarningEvent;
import org.apache.sis.metadata.sql.internal.shared.SQLBuilder;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.internal.shared.Strings;
@@ -136,6 +135,7 @@ final class FeatureStream extends DeferredStream<Feature> {
*/
FeatureStream(final Table table, final boolean parallel) {
super(FeatureIterator.CHARACTERISTICS, parallel);
+ listener = table.database.createFilterListener();
this.table = table;
}
@@ -191,9 +191,8 @@ final class FeatureStream extends DeferredStream<Feature> {
* if we have a "F₀ AND F₁ AND F₂" chain, it is possible to have some
Fₙ as SQL statements and
* other Fₙ executed in Java code.
*/
- Stream<Feature> stream = this;
- try {
- WarningEvent.LISTENER.set(selection);
+ return execute(() -> {
+ Stream<Feature> stream = this;
final var optimization = new Optimization();
optimization.setFeatureType(table.featureType);
for (final var filter : optimization.applyAndDecompose((Filter<?
super Feature>) predicate)) {
@@ -201,14 +200,16 @@ final class FeatureStream extends DeferredStream<Feature>
{
if (filter == Filter.exclude()) return empty();
if (!selection.tryAppend(filterToSQL, filter)) {
// Delegate to Java code all filters that we cannot
translate to SQL statement.
- stream = super.filter(filter);
+ if (stream == this) {
+ stream = super.filter(filter);
+ } else {
+ stream = stream.filter(filter);
+ }
hasPredicates = true;
}
}
- } finally {
- WarningEvent.LISTENER.remove();
- }
- return stream;
+ return stream;
+ });
}
/**
@@ -314,7 +315,9 @@ final class FeatureStream extends DeferredStream<Feature> {
projection = (FeatureProjection) mapper;
return (Stream) this;
}
- return new PaginedStream<>(super.map(mapper), this);
+ final var stream = new PaginedStream<R>(super.map(mapper), this);
+ stream.listener = listener;
+ return stream;
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
index 000c657f73..1edcfa8eac 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.java
@@ -99,9 +99,9 @@ public class Resources extends IndexedResourceBundle {
public static final short IllegalQualifiedName_1 = 3;
/**
- * The literal of function “{0}” is not compatible with the reference
system of property “{1}”.
+ * Function “{0}” does not accept the value of property “{1}”.
*/
- public static final short IncompatibleLiteralCRS_2 = 18;
+ public static final short IncompatibleFunction_2 = 18;
/**
* Unexpected error while analyzing the database schema.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
index 15eaa17134..6895b1ea61 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources.properties
@@ -28,7 +28,7 @@ DataSource = Provider of connections
to the database.
DuplicatedColumn_1 = Unexpected duplication of column named
\u201c{0}\u201d.
DuplicatedSRID_2 = Spatial Reference Identifier (SRID) {1}
has more than one entry in \u201c{0}\u201d table.
IllegalQualifiedName_1 = \u201c{0}\u201d is not a valid qualified
name for a table.
-IncompatibleLiteralCRS_2 = The literal of function \u201c{0}\u201d is
not compatible with the reference system of property \u201c{1}\u201d.
+IncompatibleFunction_2 = Function \u201c{0}\u201d does not accept
the value of property \u201c{1}\u201d.
InternalError = Unexpected error while analyzing the
database schema.
MalformedForeignerKey_2 = Unexpected column \u201c{1}\u201d in the
\u201c{0}\u201d foreigner key.
MappedSQLQueries = Resource names mapped to SQL queries.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
index 6f7724df8d..6673df2330 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Resources_fr.properties
@@ -33,7 +33,7 @@ DataSource = Fournisseur de connexions
\u00e0 la base de
DuplicatedColumn_1 = Doublon inattendu d\u2019une colonne
nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb.
DuplicatedSRID_2 = L\u2019identifiant de r\u00e9f\u00e9rence
spatiale (SRID) {1} a plusieurs entr\u00e9s dans la table
\u00ab\u202f{0}\u202f\u00bb.
IllegalQualifiedName_1 = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas
un nom qualifi\u00e9 de table valide.
-IncompatibleLiteralCRS_2 = Le litt\u00e9ral de la fonction
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas compatible avec le syst\u00e8me de
r\u00e9f\u00e9rence de la propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb.
+IncompatibleFunction_2 = La fonction \u00ab\u202f{0}\u202f\u00bb
n\u2019accepte pas la valeur de la propri\u00e9t\u00e9
\u00ab\u202f{1}\u202f\u00bb.
InternalError = Erreur inattendue pendant l\u2019analyse
du sch\u00e9ma de la base de donn\u00e9es.
MalformedForeignerKey_2 = Colonne \u00ab\u202f{1}\u202f\u00bb
inattendue dans la cl\u00e9 \u00e9trang\u00e8re \u00ab\u202f{0}\u202f\u00bb.
MappedSQLQueries = Noms de ressources associ\u00e9s \u00e0
des requ\u00eates SQL.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
index 714cd8aacb..f429a52b75 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClause.java
@@ -21,9 +21,6 @@ import java.util.AbstractMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.function.Consumer;
import java.sql.Connection;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.extent.GeographicBoundingBox;
@@ -33,26 +30,22 @@ import org.apache.sis.geometry.WraparoundMethod;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.geometry.wrapper.GeometryWrapper;
import org.apache.sis.metadata.sql.internal.shared.SQLBuilder;
-import org.apache.sis.filter.internal.shared.WarningEvent;
import org.apache.sis.referencing.CRS;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.system.Modules;
import org.apache.sis.util.Workaround;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.util.CodeList;
import org.opengis.feature.Feature;
import org.opengis.filter.Filter;
import org.opengis.filter.ValueReference;
/**
- * Builder for the SQL fragment on the right side of the {@code WHERE} keyword.
+ * Builder for the <abbr>SQL</abbr> fragment on the right side of the {@code
WHERE} keyword.
*
* @author Alexis Manin (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
-public final class SelectionClause extends SQLBuilder implements
Consumer<WarningEvent> {
+public final class SelectionClause extends SQLBuilder {
/**
* Whether the database rejects spatial functions that mix geometries with
and without <abbr>CRS</abbr>.
* We observed that PostGIS 3.4 produces an error not only when the
geometry operands have different CRS,
@@ -328,42 +321,6 @@ public final class SelectionClause extends SQLBuilder
implements Consumer<Warnin
isInvalid = true;
}
- /**
- * Returns the localized resources for warnings and error messages.
- */
- private Resources resources() {
- return Resources.forLocale(table.database.listeners.getLocale());
- }
-
- /**
- * Sets the logger, class and method names of the given record, then logs
it.
- * This method declares {@link FeatureSet#features(boolean)} as the public
source of the log.
- *
- * @param record the record to configure and log.
- */
- private void log(final LogRecord record) {
- record.setSourceClassName(FeatureSet.class.getName());
- record.setSourceMethodName("features");
- record.setLoggerName(Modules.SQL);
- table.database.listeners.warning(record);
- }
-
- /**
- * Invoked when a warning occurred during operations on filters or
expressions.
- *
- * @param event the warning.
- */
- @Override
- public void accept(final WarningEvent event) {
- final LogRecord record = resources().createLogRecord(
- Level.WARNING,
- Resources.Keys.IncompatibleLiteralCRS_2,
-
event.getOperatorType().flatMap(CodeList::identifier).orElse("?"),
-
event.getParameter(ValueReference.class).map(ValueReference<?,?>::getXPath).orElse("?"));
- record.setThrown(event.exception);
- log(record);
- }
-
/**
* Returns the <abbr>SQL</abbr> fragment built by this {@code
SelectionClause}.
* This method completes the information that we deferred until a
connection is established.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
index 793e8a9a2e..25c6ae8d5b 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Table.java
@@ -591,10 +591,9 @@ final class Table extends AbstractFeatureSet {
*
* @param parallel {@code true} for a parallel stream (if supported), or
{@code false} for a sequential stream.
* @return all features contained in this dataset.
- * @throws DataStoreException if an error occurred while creating the
stream.
*/
@Override
- public Stream<Feature> features(final boolean parallel) throws
DataStoreException {
+ public Stream<Feature> features(final boolean parallel) {
return new FeatureStream(this, parallel);
}
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/util/stream/StreamWrapper.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/util/stream/StreamWrapper.java
index 5c8981fe26..5225f44736 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/util/stream/StreamWrapper.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/util/stream/StreamWrapper.java
@@ -36,6 +36,7 @@ import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.DoubleStream;
import java.util.stream.Collector;
+import org.apache.sis.filter.internal.shared.WarningEvent;
/**
@@ -51,7 +52,6 @@ import java.util.stream.Collector;
*
* @param <T> the type of objects contained in the stream, as specified in
{@link Stream} interface.
*
- *
* @todo Add the methods that are new in JDK16.
*/
public abstract class StreamWrapper<T> extends BaseStreamWrapper<T, Stream<T>>
implements Stream<T> {
@@ -64,6 +64,12 @@ public abstract class StreamWrapper<T> extends
BaseStreamWrapper<T, Stream<T>> i
*/
Stream<T> source;
+ /**
+ * An optional listener to notify of warnings that occur during the
execution of filters or expressions.
+ * This is always {@code null} by default and must be set explicitly if
desired.
+ */
+ public Consumer<WarningEvent> listener;
+
/**
* Creates a new wrapper with initially no source.
* The {@link #source} field should be initialized by subclass constructor.
@@ -124,193 +130,216 @@ public abstract class StreamWrapper<T> extends
BaseStreamWrapper<T, Stream<T>> i
}
}
+ /**
+ * Executes the given action with a redirection of all warnings to the
{@linkplain #listener}.
+ *
+ * @todo Replace by {@code ScopedValue.call(…)} when allowed to use JDK25.
+ *
+ * @param <V> the return value type of the given action.
+ * @param action the action to execute.
+ * @return the return value of the given action.
+ */
+ protected final <V> V execute(final Supplier<V> action) {
+ if (listener == null) {
+ return action.get();
+ }
+ final ThreadLocal<Consumer<WarningEvent>> context =
WarningEvent.LISTENER;
+ final Consumer<WarningEvent> old = context.get();
+ try {
+ context.set(listener);
+ return action.get();
+ } finally {
+ context.set(old);
+ }
+ }
+
/** Returns an equivalent stream that is parallel. */
@Override public Stream<T> parallel() {
- return update(source().parallel());
+ return execute(() -> update(source().parallel()));
}
/** Returns an equivalent stream that is sequential. */
@Override public Stream<T> sequential() {
- return update(source().sequential());
+ return execute(() -> update(source().sequential()));
}
/** Returns an equivalent stream that is unordered. */
@Override public Stream<T> unordered() {
- return update(source().unordered());
+ return execute(() -> update(source().unordered()));
}
/** Returns a stream with elements of this stream that match the given
predicate. */
@Override public Stream<T> filter(Predicate<? super T> predicate) {
- return update(source().filter(predicate));
+ return execute(() -> update(source().filter(predicate)));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public <R> Stream<R> map(Function<? super T, ? extends R>
mapper) {
- return delegate().map(mapper);
+ return execute(() -> delegate().map(mapper));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public IntStream mapToInt(ToIntFunction<? super T> mapper) {
- return delegate().mapToInt(mapper);
+ return execute(() -> delegate().mapToInt(mapper));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public LongStream mapToLong(ToLongFunction<? super T> mapper) {
- return delegate().mapToLong(mapper);
+ return execute(() -> delegate().mapToLong(mapper));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public DoubleStream mapToDouble(ToDoubleFunction<? super T>
mapper) {
- return delegate().mapToDouble(mapper);
+ return execute(() -> delegate().mapToDouble(mapper));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public <R> Stream<R> flatMap(Function<? super T, ? extends
Stream<? extends R>> mapper) {
- return delegate().flatMap(mapper);
+ return execute(() -> delegate().flatMap(mapper));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public IntStream flatMapToInt(Function<? super T, ? extends
IntStream> mapper) {
- return delegate().flatMapToInt(mapper);
+ return execute(() -> delegate().flatMapToInt(mapper));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public LongStream flatMapToLong(Function<? super T, ? extends
LongStream> mapper) {
- return delegate().flatMapToLong(mapper);
+ return execute(() -> delegate().flatMapToLong(mapper));
}
/** Returns a stream with results of applying the given function to the
elements of this stream. */
@Override public DoubleStream flatMapToDouble(Function<? super T, ?
extends DoubleStream> mapper) {
- return delegate().flatMapToDouble(mapper);
+ return execute(() -> delegate().flatMapToDouble(mapper));
}
/** Returns a stream with distinct elements of this stream. */
@Override public Stream<T> distinct() {
- return update(source().distinct());
+ return execute(() -> update(source().distinct()));
}
/** Returns a stream with elements of this stream sorted in natural order.
*/
@Override public Stream<T> sorted() {
- return update(source().sorted());
+ return execute(() -> update(source().sorted()));
}
/** Returns a stream with elements of this stream sorted using the given
comparator. */
@Override public Stream<T> sorted(Comparator<? super T> comparator) {
- return update(source().sorted(comparator));
+ return execute(() -> update(source().sorted(comparator)));
}
/** Returns a stream performing the specified action on each element when
consumed. */
@Override public Stream<T> peek(Consumer<? super T> action) {
- return update(source().peek(action));
+ return execute(() -> update(source().peek(action)));
}
/** Returns a stream with truncated at the given number of elements. */
@Override public Stream<T> limit(long maxSize) {
- return update(source().limit(maxSize));
+ return execute(() -> update(source().limit(maxSize)));
}
/** Returns a stream discarding the specified number of elements. */
@Override public Stream<T> skip(long n) {
- return update(source().skip(n));
+ return execute(() -> update(source().skip(n)));
}
/** Performs an action for each element of this stream. */
@Override public void forEach(Consumer<? super T> action) {
- source().forEach(action);
+ execute(() -> {source().forEach(action); return null;});
}
/** Performs an action for each element of this stream in encounter order.
*/
@Override public void forEachOrdered(Consumer<? super T> action) {
- source().forEachOrdered(action);
+ execute(() -> {source().forEachOrdered(action); return null;});
}
/** Performs a reduction on the elements of this stream. */
@Override public T reduce(T identity, BinaryOperator<T> accumulator) {
- return source().reduce(identity, accumulator);
+ return execute(() -> source().reduce(identity, accumulator));
}
/** Performs a reduction on the elements of this stream. */
@Override public Optional<T> reduce(BinaryOperator<T> accumulator) {
- return source().reduce(accumulator);
+ return execute(() -> source().reduce(accumulator));
}
/** Performs a reduction on the elements of this stream. */
@Override public <U> U reduce(U identity, BiFunction<U, ? super T, U>
accumulator, BinaryOperator<U> combiner) {
- return source().reduce(identity, accumulator, combiner);
+ return execute(() -> source().reduce(identity, accumulator, combiner));
}
/** Performs a mutable reduction on the elements of this stream. */
@Override public <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super
T> accumulator, BiConsumer<R, R> combiner) {
- return source().collect(supplier, accumulator, combiner);
+ return execute(() -> source().collect(supplier, accumulator,
combiner));
}
/** Performs a mutable reduction on the elements of this stream. */
@Override public <R, A> R collect(Collector<? super T, A, R> collector) {
- return source().collect(collector);
+ return execute(() -> source().collect(collector));
}
/** Returns the minimum element of this stream according to the provided
comparator. */
@Override public Optional<T> min(Comparator<? super T> comparator) {
- return source().min(comparator);
+ return execute(() -> source().min(comparator));
}
/** Returns the maximum element of this stream according to the provided
comparator. */
@Override public Optional<T> max(Comparator<? super T> comparator) {
- return source().max(comparator);
+ return execute(() -> source().max(comparator));
}
/** Returns the number of elements in this stream. */
@Override public long count() {
- return source().count();
+ return execute(() -> source().count());
}
/** Returns whether at least one element of this stream matches the
provided predicate. */
@Override public boolean anyMatch(Predicate<? super T> predicate) {
- return source().anyMatch(predicate);
+ return execute(() -> source().anyMatch(predicate));
}
/** Returns whether all elements of this stream match the provided
predicate. */
@Override public boolean allMatch(Predicate<? super T> predicate) {
- return source().allMatch(predicate);
+ return execute(() -> source().allMatch(predicate));
}
/** Returns whether none element of this stream match the provided
predicate. */
@Override public boolean noneMatch(Predicate<? super T> predicate) {
- return source().noneMatch(predicate);
+ return execute(() -> source().noneMatch(predicate));
}
/** Returns the first element of this stream. */
@Override public Optional<T> findFirst() {
- return source().findFirst();
+ return execute(() -> source().findFirst());
}
/** Returns any element of this stream. */
@Override public Optional<T> findAny() {
- return source().findAny();
+ return execute(() -> source().findAny());
}
/** Returns an iterator for the elements of this stream. */
@Override public Iterator<T> iterator() {
- return source().iterator();
+ return execute(() -> source().iterator());
}
/** Returns a spliterator for the elements of this stream. */
@Override public Spliterator<T> spliterator() {
- return source().spliterator();
+ return execute(() -> source().spliterator());
}
/** Returns all elements in an array. */
@Override public Object[] toArray() {
- return source().toArray();
+ return execute(() -> source().toArray());
}
/** Returns all elements in an array. */
@Override public <A> A[] toArray(IntFunction<A[]> generator) {
- return source().toArray(generator);
+ return execute(() -> source().toArray(generator));
}
/** Returns an equivalent stream with an additional close handler. */
@Override public Stream<T> onClose(Runnable closeHandler) {
- return update(source().onClose(closeHandler));
+ return execute(() -> update(source().onClose(closeHandler)));
}
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
index cedd0166ec..f71123d66c 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -101,7 +101,7 @@ public final class SQLStoreTest extends TestOnAllDatabases {
/**
* Factory to use for creating filter objects.
*/
- private final FilterFactory<Feature,Object,Object> FF;
+ private final FilterFactory<Feature, ?, ?> FF;
/**
* Creates a new test.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java
index d4e304c81b..25fbc34507 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/SelectionClauseWriterTest.java
@@ -51,7 +51,7 @@ public final class SelectionClauseWriterTest extends TestCase
implements SchemaM
/**
* The factory to use for creating the filter objects.
*/
- private final FilterFactory<Feature,Object,Object> FF;
+ private final FilterFactory<Feature, Object, ?> FF;
/**
* A dummy table for testing purpose.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java
index 39180d69b6..df2843b599 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/JoinFeatureSet.java
@@ -205,6 +205,7 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
* @param featureInfo information about the {@link FeatureType} of this
feature set.
* @throws DataStoreException if an error occurred while creating the
feature set.
*/
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
public JoinFeatureSet(final Resource parent,
final FeatureSet left, String leftAlias,
final FeatureSet right, String rightAlias,
@@ -236,9 +237,7 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
new DefaultAssociationRole(properties(rightAlias), rightType,
joinType.minimumOccurs(true), 1)
};
final String identifierDelimiter = Containers.property(featureInfo,
"identifierDelimiter", String.class);
- if (identifierDelimiter != null &&
AttributeConvention.hasIdentifier(leftType)
- &&
AttributeConvention.hasIdentifier(rightType))
- {
+ if (identifierDelimiter != null && hasIdentifier(leftType) &&
hasIdentifier(rightType)) {
final Operation identifier = FeatureOperations.compound(
properties(AttributeConvention.IDENTIFIER_PROPERTY),
identifierDelimiter,
Containers.property(featureInfo, "identifierPrefix",
String.class),
@@ -252,6 +251,14 @@ public class JoinFeatureSet extends AggregatedFeatureSet {
type = new DefaultFeatureType(featureInfo, false, null, properties);
}
+ /**
+ * Returns whether the given feature type has a {@value
AttributeConvention#IDENTIFIER} property.
+ */
+ private static boolean hasIdentifier(final FeatureType feature) {
+ return feature.hasProperty(AttributeConvention.IDENTIFIER) &&
+ feature.getProperty(AttributeConvention.IDENTIFIER) != null;
+ }
+
/**
* Creates a minimal {@code properties} map for feature type or property
type constructors.
* This minimalist map contain only the mandatory entry, which is the name.
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 4642c8380d..1897df8c5e 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
@@ -273,12 +273,9 @@ public final class FeatureQueryTest extends TestCase {
final PropertyType pt1 = resultType.getProperty("value1");
final PropertyType pt2 = resultType.getProperty("renamed1");
final PropertyType pt3 = resultType.getProperty("computed");
- assertTrue(pt1 instanceof AttributeType);
- assertTrue(pt2 instanceof AttributeType);
- assertTrue(pt3 instanceof AttributeType);
- assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
- assertEquals(Integer.class, ((AttributeType) pt2).getValueClass());
- assertEquals(String.class, ((AttributeType) pt3).getValueClass());
+ assertEquals(Integer.class, assertInstanceOf(AttributeType.class,
pt1).getValueClass());
+ assertEquals(Integer.class, assertInstanceOf(AttributeType.class,
pt2).getValueClass());
+ assertEquals(String.class, assertInstanceOf(AttributeType.class,
pt3).getValueClass());
// Check feature instance.
assertEquals(3, instance.getPropertyValue("value1"));
@@ -349,14 +346,12 @@ public final class FeatureQueryTest extends TestCase {
assertEquals(2, resultType.getProperties(true).size());
final PropertyType pt1 = resultType.getProperty("value1");
final PropertyType pt2 = resultType.getProperty("unexpected");
- assertTrue(pt1 instanceof AttributeType<?>);
- assertTrue(pt2 instanceof AttributeType<?>);
- assertEquals(Integer.class, ((AttributeType<?>) pt1).getValueClass());
- assertEquals(Object.class, ((AttributeType<?>) pt2).getValueClass());
+ assertEquals(Integer.class, assertInstanceOf(AttributeType.class,
pt1).getValueClass());
+ assertEquals( Object.class, assertInstanceOf(AttributeType.class,
pt2).getValueClass());
// Check feature property values.
- assertEquals(3, instance.getPropertyValue("value1"));
- assertEquals(null, instance.getPropertyValue("unexpected"));
+ assertEquals(3, instance.getPropertyValue("value1"));
+ assertNull(instance.getPropertyValue("unexpected"));
}
/**
@@ -423,16 +418,11 @@ public final class FeatureQueryTest extends TestCase {
final PropertyType pt1 = resultType.getProperty("value1");
final PropertyType pt2 = resultType.getProperty("renamed1");
final PropertyType pt3 = resultType.getProperty("computed");
- assertTrue(pt1 instanceof AttributeType<?>);
- assertTrue(pt2 instanceof Operation);
- assertTrue(pt3 instanceof Operation);
- final IdentifiedType result2 = ((Operation) pt2).getResult();
- final IdentifiedType result3 = ((Operation) pt3).getResult();
- assertEquals(Integer.class, ((AttributeType<?>) pt1).getValueClass());
- assertTrue(result2 instanceof AttributeType<?>);
- assertTrue(result3 instanceof AttributeType<?>);
- assertEquals(Integer.class, ((AttributeType<?>)
result2).getValueClass());
- assertEquals(String.class, ((AttributeType<?>)
result3).getValueClass());
+ final IdentifiedType result2 = assertInstanceOf(Operation.class,
pt2).getResult();
+ final IdentifiedType result3 = assertInstanceOf(Operation.class,
pt3).getResult();
+ assertEquals(Integer.class, assertInstanceOf(AttributeType.class,
pt1).getValueClass());
+ assertEquals(Integer.class, assertInstanceOf(AttributeType.class,
result2).getValueClass());
+ assertEquals( String.class, assertInstanceOf(AttributeType.class,
result3).getValueClass());
// Check feature instance.
assertEquals(3, instance.getPropertyValue("value1"));
diff --git
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
index a284783097..84a431d15a 100644
---
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
+++
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/aggregate/JoinFeatureSetTest.java
@@ -17,7 +17,6 @@
package org.apache.sis.storage.aggregate;
import java.util.Map;
-import java.util.HashMap;
import java.util.List;
import java.util.Iterator;
import java.util.stream.Collectors;
@@ -48,6 +47,7 @@ import org.opengis.filter.MatchAction;
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
+@SuppressWarnings("exports")
public final class JoinFeatureSetTest extends TestCase {
/**
* The set of features to be joined together.
@@ -118,15 +118,13 @@ public final class JoinFeatureSetTest extends TestCase {
* Creates a new join feature set of the given type using the {@link
#featureSet1} and {@link #featureSet2}.
*/
private FeatureSet create(final JoinFeatureSet.Type type) throws
DataStoreException {
- final FilterFactory<Feature, Object, ?> factory =
DefaultFilterFactory.forFeatures();
+ final FilterFactory<Feature, ?, ?> factory =
DefaultFilterFactory.forFeatures();
final BinaryComparisonOperator<Feature> condition = factory.equal(
factory.property("att2", String.class),
factory.property("att3", String.class),
true, MatchAction.ANY);
- final Map<String,Object> properties = new HashMap<>(4);
- assertNull(properties.put("name", "JoinSet"));
- assertNull(properties.put("identifierDelimiter", " "));
- return new JoinFeatureSet(null, featureSet1, "s1", featureSet2, "s2",
type, condition, properties);
+ return new JoinFeatureSet(null, featureSet1, "s1", featureSet2, "s2",
type, condition,
+ Map.of("name", "JoinSet",
"identifierDelimiter", " "));
}
/**
diff --git a/geoapi/snapshot b/geoapi/snapshot
index 7eff7cfcbd..e8dfb3b92a 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit 7eff7cfcbd6ce7ac82b99a90b9ee465914e629c1
+Subproject commit e8dfb3b92a0141221d33991c197b3f74bb0831c9
diff --git
a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
index 06797449e3..425971bf5e 100644
---
a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
+++
b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/SEPortrayerTest.java
@@ -45,6 +45,7 @@ import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.feature.internal.shared.AttributeConvention;
+import org.apache.sis.filter.internal.shared.WarningEvent;
import org.apache.sis.storage.FeatureQuery;
import org.apache.sis.storage.Aggregate;
import org.apache.sis.storage.DataStoreException;
@@ -63,6 +64,8 @@ import org.apache.sis.util.iso.Names;
// Test dependencies
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
@@ -84,6 +87,26 @@ public class SEPortrayerTest {
private final FeatureSet fishes;
private final FeatureSet boats;
+ /**
+ * Shutdown the warnings that occur during the execution of expressions or
filters.
+ *
+ * @todo Investigate why we get those warnings.
+ */
+ @BeforeEach
+ public void disableFilterWarnings() {
+ WarningEvent.LISTENER.set((event) -> {});
+ }
+
+ /**
+ * Shutdown the warnings that occur during the execution of expressions or
filters.
+ *
+ * @todo Investigate why we get those warnings.
+ */
+ @AfterEach
+ public void resetFilterWarnings() {
+ WarningEvent.LISTENER.remove();
+ }
+
/**
* Creates a new test case.
*/
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java
index a67208b599..67ab103a13 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ExpandedFeature.java
@@ -169,14 +169,6 @@ final class ExpandedFeature implements Feature {
: List.of();
}
- /**
- * Synonymous of {@link #getPropertyValue(String)} since we do not check
property existence.
- */
- @Override
- public Object getValueOrFallback(final String name, final Object
missingPropertyFallback) {
- return getPropertyValue(name);
- }
-
/**
* Unsupported operation.
*/