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 26bdb313f1 Move flexibility for configuring the builder wrapped by
`FeatureProjectionBuilder.Item`. More conservative check of whether the
FeatureType is different than the source.
26bdb313f1 is described below
commit 26bdb313f15987848a6422aa2fb21298f5b9d987
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun May 18 00:11:29 2025 +0200
Move flexibility for configuring the builder wrapped by
`FeatureProjectionBuilder.Item`.
More conservative check of whether the FeatureType is different than the
source.
---
.../sis/feature/privy/FeatureExpression.java | 4 +-
.../feature/privy/FeatureProjectionBuilder.java | 127 +++++++++++++++------
.../main/org/apache/sis/filter/PropertyValue.java | 2 +-
.../apache/sis/filter/sqlmm/SpatialFunction.java | 2 +-
4 files changed, 96 insertions(+), 39 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
index ae76ede67e..03dbf4913a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureExpression.java
@@ -22,7 +22,6 @@ import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.filter.Optimization;
import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.filter.internal.Node;
-import org.opengis.util.GenericName;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.feature.Feature;
@@ -88,8 +87,7 @@ public interface FeatureExpression<R,V> extends
Expression<R,V> {
* <ul>
* <li>{@link FeatureProjectionBuilder#source()} for the source of the
{@link PropertyType} in next point.</li>
* <li>{@link FeatureProjectionBuilder#addSourceProperty(PropertyType,
boolean)}</li>
- * <li>{@link FeatureProjectionBuilder#addOptionalAttribute(String,
Class)}</li>
- * <li>{@link FeatureProjectionBuilder#addComputedAttribute(GenericName,
Class)}</li>
+ * <li>{@link
FeatureProjectionBuilder#addComputedProperty(PropertyTypeBuilder, boolean)}</li>
* </ul>
*
* Inherited methods such as {@link
FeatureProjectionBuilder#addAttribute(Class)} can also be invoked,
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
index f8a5c7a7c3..2f9c7bd6df 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/privy/FeatureProjectionBuilder.java
@@ -30,10 +30,14 @@ import
org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.feature.Features;
import org.apache.sis.feature.AbstractOperation;
import org.apache.sis.feature.FeatureOperations;
+import org.apache.sis.feature.builder.AssociationRoleBuilder;
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.util.ArgumentCheckByAssertion;
+import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.privy.Strings;
+import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -108,15 +112,16 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
private final Map<GenericName, Item> reservedNames;
/**
- * Whether at least one item has a name different than the property in the
source feature type.
+ * Whether at least one item is modified compared to the original property
in the source feature type.
+ * A modified item may be an item with a name different than the property
in {@linkplain #source}.
* If {@code true}, then the projection cannot be an {@linkplain
#isIdentity() identity} operation.
- * Result of operations may also need to be fetched in advance, because
operations cannot be executed
- * anymore after the name of a dependency changed.
+ * Result of operations such as links may also need to be fetched in
advance,
+ * because operations cannot be executed anymore after the name of a
dependency changed.
*
* @see Item#setName(GenericName)
* @see #isIdentity()
*/
- private boolean hasRenamedItems;
+ private boolean hasModifiedProperties;
/**
* Sequential number for generating default names of unnamed properties.
This is used when the
@@ -237,6 +242,9 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
* @return handler for the given item, or {@code null} if the given
property cannot be resolved.
*/
public Item addSourceProperty(final PropertyType property, final boolean
named) {
+ if (property == null) {
+ return null;
+ }
final PropertyTypeBuilder builder;
List<String> deferred;
if (sourceIsDependency) {
@@ -272,32 +280,27 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
}
/**
- * Adds an optional attribute type for values of the specified class.
- * This method can be invoked as an alternative to {@link
#addSourceProperty(PropertyType, boolean)}
- * when the source property cannot be found and a fallback is added
instead.
+ * Adds a property created by the caller rather than extracted from the
source feature.
+ * The given builder should have been created by a method of the {@link
FeatureTypeBuilder} parent class.
+ * The name of the builder is usually not the name of a property in the
{@linkplain #source() source} feature.
*
- * @param name the name in the source feature.
- * @param type the class of attribute values.
- * @return handler for the given item.
- */
- public Item addOptionalAttribute(final String name, final Class<?> type) {
- final AttributeTypeBuilder<?> builder =
addAttribute(type).setMinimumOccurs(0);
- final var item = new Item(builder.setName(name).getName(), builder);
- requested.add(item);
- return item;
- }
-
- /**
- * Adds a mandatory attribute type for values of the specified class.
- * The attribute is assumed to be computed by a function of the given name.
- * This is usually not the name of a property in the {@linkplain #source}
feature.
+ * <h4>Assertions</h4>
+ * This method verifies that the given builder is a member of the
{@linkplain #properties() properties} collection.
+ * It also verifies that no {@link Item} have been created for that
builder yet.
+ * For performance reasons, those verifications are performed only if
assertions are enabled.
*
- * @param name name of the function computing the attribute value.
- * @param type the class of attribute values.
- * @return handler for the given item.
+ * @param builder builder for the computed property, or {@code null}.
+ * @param named whether the {@code builder} name can be used as a
default name.
+ * @return handler for the given item, or {@code null} if the given
builder was null.
*/
- public Item addComputedAttribute(final GenericName name, final Class<?>
type) {
- final var item = new Item(null, addAttribute(type).setName(name));
+ @ArgumentCheckByAssertion
+ public Item addComputedProperty(final PropertyTypeBuilder builder, final
boolean named) {
+ if (builder == null) {
+ return null;
+ }
+ assert properties().contains(builder) : builder;
+ assert requested.stream().noneMatch((item) ->item.builder() ==
builder) : builder;
+ final var item = new Item(named ? builder.getName() : null, builder);
requested.add(item);
return item;
}
@@ -375,6 +378,30 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
null, hasMissingDependency ? "hasMissingDependency" :
null);
}
+ /**
+ * Returns the property type builder wrapped by this item.
+ * The following operations are allowed on the returned builder:
+ *
+ * <ul>
+ * <li>Set the cardinality (minimum and maximum occurrences).</li>
+ * <li>Build the {@code PropertyType}.</li>
+ * </ul>
+ *
+ * The following operations should <em>not</em> be executed on the
returned builder.
+ * Use the dedicated methods in this class instead:
+ *
+ * <ul>
+ * <li>Set the name: use {@link #setName(GenericName)}.</li>
+ * <li>Set the value class: use {@link
#replaceValueClass(UnaryOperator)}.</li>
+ * </ul>
+ *
+ * @return the property type builder wrapped by this item.
+ */
+ public PropertyTypeBuilder builder() {
+ hasModifiedProperties = true; // Conservative because the
caller may do anything on the builder.
+ return builder;
+ }
+
/**
* Replaces this property by a stored attribute if at least one
dependency is not in the list of properties
* requested by the user. This method should be invoked only for
preparing the user requested feature type.
@@ -384,6 +411,7 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
private void replaceIfMissingDependency() {
if (hasMissingDependency) {
hasMissingDependency = false;
+ hasModifiedProperties = true;
final var old = builder;
builder = addPropertyResult(old.build(), null); //
`old.build()` returns the existing operation.
old.replaceBy(builder);
@@ -405,9 +433,35 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
final var ab = (AttributeTypeBuilder<?>) builder;
final Class<?> r = type.apply(ab.getValueClass());
if (r != null) {
- builder = ab.setValueClass(r);
+ if (builder != (builder = ab.setValueClass(r))) {
+ hasModifiedProperties = true;
+ }
return true;
}
+ } else if (builder instanceof AssociationRoleBuilder) {
+ // We do not yet have a special case for this one.
+ } else {
+ final var property = builder.build();
+ if (property instanceof Operation) {
+ /*
+ * Less common case where the caller wants to change the
type of an operation.
+ * We cannot change the type of an operation (unless we
replace the operation
+ * by a stored attribute). Therefore, we only check type
compatibility.
+ */
+ final var result = ((Operation) property).getResult();
+ if (result instanceof AttributeType<?>) {
+ final Class<?> c = ((AttributeType<?>)
result).getValueClass();
+ final Class<?> r = type.apply(c);
+ if (r != null) {
+ // We can be lenient for link operation, but must
be strict for other operations.
+ if (Features.getLinkTarget(property).isPresent() ?
r.isAssignableFrom(c) : r.equals(c)) {
+ return true;
+ }
+ throw new
UnconvertibleObjectException(Errors.forLocale(getLocale())
+
.getString(Errors.Keys.CanNotConvertFromType_2, c, r));
+ }
+ }
+ }
}
return false;
}
@@ -449,6 +503,7 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
final var identification =
Map.of(AbstractOperation.NAME_KEY, builder.getName());
builder =
addProperty(FeatureOperations.expression(identification, expression,
storedType));
atb.replaceBy(builder);
+ hasModifiedProperties = true;
}
} else {
// The property is an operation, usually a link. Leave it
as-is.
@@ -473,14 +528,16 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
public Item setCRS(final CoordinateReferenceSystem crs) {
if (builder instanceof AttributeTypeBuilder<?>) {
builder = ((AttributeTypeBuilder<?>) builder).setCRS(crs);
+ hasModifiedProperties = true;
}
return this;
}
/**
* Returns whether the property built by this item is equivalent to
the given property.
- * The current implementation bases its test only on the property name.
- * This is reliable only if {@link #hasRenamedItems} is {@code false}.
+ * The caller should have verified that {@link #hasModifiedProperties}
is {@code false}
+ * before to invoke this method, because the implementation performs a
filtering based
+ * on the property name only. This is that way for accepting
differences in metadata.
*
* @param property the property to compare.
* @return whether this item builds a property equivalent to the given
one.
@@ -521,7 +578,7 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
} else {
builder.setName(targetName);
reserve(targetName, this);
- hasRenamedItems = true;
+ hasModifiedProperties = true; // Because the name is
different.
isNamed = true;
}
}
@@ -606,7 +663,7 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
* @return whether the {@linkplain #source} feature type can be used
directly.
*/
private boolean isIdentity() {
- if (hasRenamedItems) {
+ if (hasModifiedProperties) {
return false;
}
final Iterator<Item> it = requested.iterator();
@@ -663,8 +720,10 @@ public final class FeatureProjectionBuilder extends
FeatureTypeBuilder {
do {
for (PropertyType property : deferred) {
final Item item = addSourceProperty(property, true);
- item.validateName();
-
item.setValueGetter(FeatureOperations.expressionOf(property), true);
+ if (item != null) {
+ item.validateName();
+
item.setValueGetter(FeatureOperations.expressionOf(property), true);
+ }
}
deferred.clear();
resolveDependencies(deferred);
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 473b98d4db..06410e94c4 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
@@ -176,7 +176,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
* @see #expectedType(FeatureProjectionBuilder)
*/
final FeatureProjectionBuilder.Item defaultType(final
FeatureProjectionBuilder addTo) {
- return addTo.addOptionalAttribute(name, getValueClass());
+ return
addTo.addComputedProperty(addTo.addAttribute(getValueClass()).setMinimumOccurs(0).setName(name),
true);
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
index 92e5900a57..b938087a8a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/SpatialFunction.java
@@ -200,6 +200,6 @@ abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Ob
}
throw new
InvalidFilterValueException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
}
- return addTo.addComputedAttribute(getFunctionName(), getValueClass());
+ return
addTo.addComputedProperty(addTo.addAttribute(getValueClass()).setName(getFunctionName()),
false);
}
}