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 <martin.desruisse...@geomatys.com>
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);
     }
 }

Reply via email to