You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/04/29 16:30:04 UTC

[sis] 01/01: Merge branch 'geoapi-3.1'.

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git

commit ff2beab7ba1b2d4147cc8afb1d57fb87c64bcb6a
Merge: 62c8552cf8 8d1d6522c4
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 29 15:05:57 2023 +0200

    Merge branch 'geoapi-3.1'.

 .../{internal => }/coverage/CoverageCombiner.java  | 165 +++++++++++-----
 .../sis/coverage/grid/GridCoverageBuilder.java     |   5 +
 .../org/apache/sis/coverage/grid/GridExtent.java   |  82 +++++++-
 .../apache/sis/feature/ExpressionOperation.java    | 220 +++++++++++++++++++++
 .../org/apache/sis/feature/FeatureOperations.java  |  51 ++++-
 .../java/org/apache/sis/feature/LinkOperation.java |   2 +-
 .../sis/feature/builder/AttributeTypeBuilder.java  |   1 +
 .../java/org/apache/sis/image/ComputedImage.java   |  20 +-
 .../java/org/apache/sis/image/ImageCombiner.java   |  72 +++----
 .../java/org/apache/sis/image/ImageProcessor.java  |   8 +-
 .../java/org/apache/sis/image/Visualization.java   |   2 +-
 .../sis/internal/coverage/SampleDimensions.java    |  36 ++++
 .../sis/internal/coverage/j2d/ImageLayout.java     |  62 +++++-
 .../sis/internal/feature/FeatureExpression.java    |  14 ++
 .../apache/sis/coverage/CoverageCombinerTest.java  |  70 +++++++
 .../apache/sis/coverage/grid/GridExtentTest.java   |  22 ++-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   1 +
 .../org/apache/sis/portrayal/CanvasFollower.java   |   2 +-
 .../sis/referencing/operation/matrix/Matrices.java |   8 +-
 .../operation/transform/MathTransforms.java        | 209 ++++++++++----------
 .../operation/transform/UnitConversion.java        | 145 ++++++++++++++
 .../operation/transform/MathTransformsTest.java    |  71 +++----
 .../operation/transform/UnitConversionTest.java    |  59 ++++++
 .../sis/test/suite/ReferencingTestSuite.java       |   1 +
 .../org/apache/sis/measure/RangeFormatTest.java    |   4 +-
 .../java/org/apache/sis/measure/RangeTest.java     |   2 +-
 ide-project/NetBeans/nbproject/project.properties  |   2 +-
 pom.xml                                            |  16 +-
 .../apache/sis/internal/sql/feature/Column.java    |   2 +-
 .../internal/storage/WritableResourceSupport.java  |  11 +-
 .../java/org/apache/sis/storage/FeatureQuery.java  | 133 +++++++++++--
 .../org/apache/sis/storage/FeatureQueryTest.java   |  65 +++++-
 32 files changed, 1251 insertions(+), 312 deletions(-)

diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
index 0000000000,78ecc4b7ad..964a1c143b
mode 000000,100644..100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/ExpressionOperation.java
@@@ -1,0 -1,227 +1,220 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.feature;
+ 
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.HashSet;
+ import java.util.Collection;
+ import java.util.function.Function;
 -import org.opengis.util.CodeList;
+ import org.opengis.parameter.ParameterValueGroup;
+ import org.opengis.parameter.ParameterDescriptorGroup;
+ import org.apache.sis.internal.feature.FeatureUtilities;
+ import org.apache.sis.internal.filter.FunctionNames;
+ import org.apache.sis.internal.filter.Visitor;
+ 
+ // Branch-dependent imports
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.Property;
 -import org.opengis.feature.Attribute;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.IdentifiedType;
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.Expression;
 -import org.opengis.filter.LogicalOperator;
 -import org.opengis.filter.ValueReference;
++import org.apache.sis.filter.Filter;
++import org.apache.sis.filter.Expression;
++import org.apache.sis.internal.geoapi.filter.LogicalOperator;
++import org.apache.sis.internal.geoapi.filter.ValueReference;
+ 
+ 
+ /**
+  * A feature property which is an operation implemented by a filter expression.
+  * This operation computes expression results from given feature instances only,
+  * there is no parameters.
+  *
+  * @author  Johann Sorel (Geomatys)
+  * @version 1.4
+  * @since   1.4
+  */
+ final class ExpressionOperation<V> extends AbstractOperation {
+     /**
+      * For cross-version compatibility.
+      */
+     private static final long serialVersionUID = 5411697964136428848L;
+ 
+     /**
+      * The parameter descriptor for the "Expression" operation, which does not take any parameter.
+      */
+     private static final ParameterDescriptorGroup PARAMETERS = FeatureUtilities.parameters("Expression");
+ 
+     /**
+      * The expression on which to delegate the execution of this operation.
+      */
+     @SuppressWarnings("serial")                         // Not statically typed as serializable.
 -    private final Function<? super Feature, ? extends V> expression;
++    private final Function<? super AbstractFeature, ? extends V> expression;
+ 
+     /**
+      * The type of result of evaluating the expression.
+      */
 -    @SuppressWarnings("serial")                         // Apache SIS implementations are serializable.
 -    private final AttributeType<? super V> result;
++    private final DefaultAttributeType<? super V> result;
+ 
+     /**
+      * The name of all feature properties that are known to be read by the expression.
+      * This is determined by execution of {@link #VISITOR} on the {@linkplain #expression}.
+      * This set may be incomplete if some properties are read otherwise than by {@link ValueReference}.
+      */
+     @SuppressWarnings("serial")                         // Set.of(…) implementations are serializable.
+     private final Set<String> dependencies;
+ 
+     /**
+      * Creates a new operation which will delegate execution to the given expression.
+      *
+      * @param identification  the name of the operation, together with optional information.
+      * @param expression      the expression to evaluate on feature instances.
+      * @param result          type of values computed by the expression.
+      */
+     ExpressionOperation(final Map<String,?> identification,
 -                        final Function<? super Feature, ? extends V> expression,
 -                        final AttributeType<? super V> result)
++                        final Function<? super AbstractFeature, ? extends V> expression,
++                        final DefaultAttributeType<? super V> result)
+     {
+         super(identification);
+         this.expression = expression;
+         this.result     = result;
+         if (expression instanceof Expression<?,?>) {
+             dependencies = DependencyFinder.search((Expression<Object,?>) expression);
+         } else {
+             dependencies = Set.of();
+         }
+     }
+ 
+     /**
+      * Returns a description of the input parameters.
+      */
+     @Override
+     public ParameterDescriptorGroup getParameters() {
+         return PARAMETERS;
+     }
+ 
+     /**
+      * Returns the expected result type.
+      */
+     @Override
 -    public IdentifiedType getResult() {
++    public AbstractIdentifiedType getResult() {
+         return result;
+     }
+ 
+     /**
+      * Returns the names of feature properties that this operation needs for performing its task.
+      * This set may be incomplete if some properties are read otherwise than by {@link ValueReference}.
+      */
+     @Override
+     @SuppressWarnings("ReturnOfCollectionOrArrayField")     // Because the set is unmodifiable.
+     public Set<String> getDependencies() {
+         return dependencies;
+     }
+ 
+     /**
+      * Returns the value computed by the expression for the given feature instance.
+      *
+      * @param  feature     the feature to evaluate with the expression.
+      * @param  parameters  ignored (can be {@code null}).
+      * @return the computed property from the given feature.
+      */
+     @Override
 -    public Property apply(final Feature feature, ParameterValueGroup parameters) {
 -        final Attribute<? super V> instance = result.newInstance();
++    public Property apply(final AbstractFeature feature, ParameterValueGroup parameters) {
++        final AbstractAttribute<? super V> instance = result.newInstance();
+         instance.setValue(expression.apply(feature));
+         return instance;
+     }
+ 
+     /**
+      * Computes a hash-code value for this operation.
+      */
+     @Override
+     public int hashCode() {
+         return super.hashCode() + expression.hashCode();
+     }
+ 
+     /**
+      * Compares this operation with the given object for equality.
+      */
+     @Override
+     public boolean equals(final Object obj) {
+         /*
+          * `this.result` is compared (indirectly) by the super class.
+          * `this.dependencies` does not need to be compared because it is derived from `expression`.
+          */
+         return super.equals(obj) && expression.equals(((ExpressionOperation) obj).expression);
+     }
+ 
+     /**
+      * An expression visitor for finding all dependencies of a given expression.
+      * The dependencies are feature properties read by {@link ValueReference} nodes.
+      *
+      * @todo The first parameterized type should be {@code Feature} instead of {@code Object}.
+      */
+     private static final class DependencyFinder extends Visitor<Object, Collection<String>> {
+         /**
+          * The unique instance.
+          */
+         private static final DependencyFinder VISITOR = new DependencyFinder();
+ 
+         /**
+          * Returns all dependencies read by a {@link ValueReference} node.
+          *
+          * @param  expression  the expression for which to get dependencies.
+          * @return all dependencies recognized by this method.
+          */
+         static Set<String> search(final Expression<Object,?> expression) {
+             final Set<String> dependencies = new HashSet<>();
+             VISITOR.visit(expression, dependencies);
+             return Set.copyOf(dependencies);
+         }
+ 
+         /**
+          * Constructor for the unique instance.
+          */
+         private DependencyFinder() {
+             setLogicalHandlers((f, dependencies) -> {
+                 final var filter = (LogicalOperator<Object>) f;
+                 for (Filter<Object> child : filter.getOperands()) {
+                     visit(child, dependencies);
+                 }
+             });
+             setExpressionHandler(FunctionNames.ValueReference, (e, dependencies) -> {
+                 final var expression = (ValueReference<Object,?>) e;
+                 final String propName = expression.getXPath();
+                 if (!propName.trim().isEmpty()) {
+                     dependencies.add(propName);
+                 }
+             });
+         }
+ 
+         /**
+          * Fallback for all filters not explicitly handled by the setting applied in the constructor.
+          */
+         @Override
 -        protected void typeNotFound(final CodeList<?> type, final Filter<Object> filter, final Collection<String> dependencies) {
++        protected void typeNotFound(final Enum<?> type, final Filter<Object> filter, final Collection<String> dependencies) {
+             for (final Expression<Object,?> f : filter.getExpressions()) {
+                 visit(f, dependencies);
+             }
+         }
+ 
+         /**
+          * Fallback for all expressions not explicitly handled by the setting applied in the constructor.
+          */
+         @Override
+         protected void typeNotFound(final String type, final Expression<Object,?> expression, final Collection<String> dependencies) {
+             for (final Expression<Object,?> p : expression.getParameters()) {
+                 visit(p, dependencies);
+             }
+         }
+     }
+ }
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
index 7f052ee11b,ad4c132716..3ddd0ff779
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
@@@ -27,6 -28,14 +28,9 @@@ import org.apache.sis.util.Unconvertibl
  import org.apache.sis.util.collection.WeakHashSet;
  import org.apache.sis.util.resources.Errors;
  
+ // Branch-dependent imports
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.Operation;
 -import org.opengis.feature.PropertyType;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.FeatureAssociationRole;
 -import org.opengis.filter.Expression;
++import org.apache.sis.filter.Expression;
+ 
  
  /**
   * A set of predefined operations expecting a {@code Feature} as input and producing an {@code Attribute} as output.
@@@ -267,4 -264,49 +271,49 @@@ public final class FeatureOperations ex
          ArgumentChecks.ensureNonNull("geometryAttributes", geometryAttributes);
          return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes));
      }
+ 
+     /**
+      * Creates an operation which delegates the computation to a given expression.
+      * The {@code expression} argument should generally be an instance of
+      * {@link org.opengis.filter.Expression},
+      * but more generic functions are accepted as well.
+      *
+      * @param  <V>             the type of values computed by the expression and assigned to the feature property.
+      * @param  identification  the name of the operation, together with optional information.
+      * @param  expression      the expression to evaluate on feature instances.
+      * @param  result          type of values computed by the expression and assigned to the feature property.
+      * @return a feature operation which computes its values using the given expression.
+      *
+      * @since 1.4
+      */
 -    public static <V> Operation expression(final Map<String,?> identification,
 -                                           final Function<? super Feature, ? extends V> expression,
 -                                           final AttributeType<? super V> result)
++    public static <V> AbstractOperation expression(final Map<String,?> identification,
++                                           final Function<? super AbstractFeature, ? extends V> expression,
++                                           final DefaultAttributeType<? super V> result)
+     {
+         ArgumentChecks.ensureNonNull("expression", expression);
+         return POOL.unique(new ExpressionOperation<>(identification, expression, result));
+     }
+ 
+     /**
+      * Creates an operation which delegates the computation to a given expression producing values of unknown type.
+      * This method can be used as an alternative to {@link #expression expression(…)} when the constraint on the
+      * parameterized type {@code <V>} between {@code expression} and {@code result} can not be enforced at compile time.
+      * This method casts or converts the expression to the expected type by a call to
+      * {@link Expression#toValueType(Class)}.
+      *
+      * @param  <V>             the type of values computed by the expression and assigned to the feature property.
+      * @param  identification  the name of the operation, together with optional information.
+      * @param  expression      the expression to evaluate on feature instances.
+      * @param  result          type of values computed by the expression and assigned to the feature property.
+      * @return a feature operation which computes its values using the given expression.
+      * @throws ClassCastException if the result type is not a target type supported by the expression.
+      *
+      * @since 1.4
+      */
 -    public static <V> Operation expressionToResult(final Map<String,?> identification,
 -                                                   final Expression<? super Feature, ?> expression,
 -                                                   final AttributeType<V> result)
++    public static <V> AbstractOperation expressionToResult(final Map<String,?> identification,
++                                                   final Expression<? super AbstractFeature, ?> expression,
++                                                   final DefaultAttributeType<V> result)
+     {
+         return expression(identification, expression.toValueType(result.getValueClass()), result);
+     }
  }
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
index 402192c939,86b379a201..bc345fff42
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
@@@ -21,9 -25,7 +21,10 @@@ import org.apache.sis.filter.Optimizati
  import org.apache.sis.filter.DefaultFilterFactory;
  import org.apache.sis.feature.builder.FeatureTypeBuilder;
  import org.apache.sis.feature.builder.PropertyTypeBuilder;
+ import org.apache.sis.feature.builder.AttributeTypeBuilder;
 +import org.apache.sis.feature.DefaultFeatureType;
 +import org.apache.sis.internal.geoapi.filter.Literal;
 +import org.apache.sis.internal.geoapi.filter.ValueReference;
  
  
  /**
diff --cc pom.xml
index 44e695b47c,4d4f351707..7fa6eac0a1
--- a/pom.xml
+++ b/pom.xml
@@@ -546,8 -546,8 +546,8 @@@
      <maven.compiler.target>11</maven.compiler.target>
      <sis.plugin.version>${project.version}</sis.plugin.version>
      <sis.non-free.version>1.3</sis.non-free.version>                <!-- Used only if "non-free" profile is activated. -->
-     <javafx.version>19</javafx.version>                             <!-- Used only if "javafx" profile is activated. -->
+     <javafx.version>20.0.1</javafx.version>                         <!-- Used only if "javafx" profile is activated. -->
 -    <geoapi.version>3.1-SNAPSHOT</geoapi.version>
 +    <geoapi.version>3.0.2</geoapi.version>
    </properties>
  
    <profiles>
diff --cc storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
index 9edc48afa9,3fdbb0720a..30be91a062
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureQuery.java
@@@ -43,14 -47,19 +47,15 @@@ import org.apache.sis.util.iso.Names
  import org.apache.sis.util.resources.Vocabulary;
  
  // Branch-dependent imports
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureType;
 -import org.opengis.feature.Attribute;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.Operation;
 -import org.opengis.filter.FilterFactory;
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.Expression;
 -import org.opengis.filter.Literal;
 -import org.opengis.filter.ValueReference;
 -import org.opengis.filter.SortBy;
 -import org.opengis.filter.SortProperty;
 -import org.opengis.filter.InvalidFilterValueException;
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.feature.DefaultFeatureType;
++import org.apache.sis.feature.DefaultAttributeType;
 +import org.apache.sis.filter.Filter;
 +import org.apache.sis.filter.Expression;
 +import org.apache.sis.internal.geoapi.filter.Literal;
 +import org.apache.sis.internal.geoapi.filter.ValueReference;
 +import org.apache.sis.internal.geoapi.filter.SortBy;
 +import org.apache.sis.internal.geoapi.filter.SortProperty;
  
  
  /**
@@@ -436,10 -506,8 +508,8 @@@ public class FeatureQuery extends Quer
           *
           * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
           */
 -        public NamedExpression(final Expression<? super Feature, ?> expression) {
 +        public NamedExpression(final Expression<? super AbstractFeature, ?> expression) {
-             ArgumentChecks.ensureNonNull("expression", expression);
-             this.expression = expression;
-             this.alias = null;
+             this(expression, (GenericName) null);
          }
  
          /**
@@@ -448,10 -516,8 +518,8 @@@
           * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
           * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
           */
 -        public NamedExpression(final Expression<? super Feature, ?> expression, final GenericName alias) {
 +        public NamedExpression(final Expression<? super AbstractFeature, ?> expression, final GenericName alias) {
-             ArgumentChecks.ensureNonNull("expression", expression);
-             this.expression = expression;
-             this.alias = alias;
+             this(expression, alias, ProjectionType.STORED);
          }
  
          /**
@@@ -465,6 -531,24 +533,24 @@@
              ArgumentChecks.ensureNonNull("expression", expression);
              this.expression = expression;
              this.alias = (alias != null) ? Names.createLocalName(null, null, alias) : null;
+             this.type = ProjectionType.STORED;
+         }
+ 
+         /**
+          * Creates a new column with the given expression, the given name and the given projection type.
+          *
+          * @param expression  the literal, value reference or expression to be retrieved by a {@code Query}.
+          * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
+          * @param type        whether to create a feature {@link Attribute} or a feature {@link Operation}.
+          *
+          * @since 1.4
+          */
 -        public NamedExpression(final Expression<? super Feature, ?> expression, final GenericName alias, ProjectionType type) {
++        public NamedExpression(final Expression<? super AbstractFeature, ?> expression, final GenericName alias, ProjectionType type) {
+             ArgumentChecks.ensureNonNull("expression", expression);
+             ArgumentChecks.ensureNonNull("type", type);
+             this.expression = expression;
+             this.alias = alias;
+             this.type  = type;
          }
  
          /**
@@@ -602,14 -687,14 +689,14 @@@
               * For each property, get the expected type (mandatory) and its name (optional).
               * A default name will be computed if no alias were explicitly given by user.
               */
-             GenericName name = projection[column].alias;
-             final Expression<?,?> expression = projection[column].expression;
 -            final Expression<? super Feature,?> expression = item.expression;
++            final Expression<? super AbstractFeature,?> expression = item.expression;
              final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(expression);
              final PropertyTypeBuilder resultType;
              if (fex == null || (resultType = fex.expectedType(valueType, ftb)) == null) {
 -                throw new InvalidFilterValueException(Resources.format(Resources.Keys.InvalidExpression_2,
 +                throw new IllegalArgumentException(Resources.format(Resources.Keys.InvalidExpression_2,
                              expression.getFunctionName().toInternationalString(), column));
              }
+             GenericName name = item.alias;
              if (name == null) {
                  /*
                   * Build a list of aliases declared by the user, for making sure that we do not collide with them.
@@@ -653,6 -738,18 +740,18 @@@
                  name = Names.createLocalName(null, null, text);
              }
              resultType.setName(name);
+             /*
+              * If the attribute that we just added should be virtual,
+              * replace the attribute by an operation.
+              */
+             if (item.type == ProjectionType.VIRTUAL && resultType instanceof AttributeTypeBuilder<?>) {
+                 final var ab = (AttributeTypeBuilder<?>) resultType;
 -                final AttributeType<?> storedType = ab.build();
++                final DefaultAttributeType<?> storedType = ab.build();
+                 if (ftb.properties().remove(resultType)) {
+                     final var properties = Map.of(AbstractOperation.NAME_KEY, name);
+                     ftb.addProperty(FeatureOperations.expressionToResult(properties, expression, storedType));
+                 }
+             }
          }
          return ftb.build();
      }
diff --cc storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
index f90b95c094,ca2dfb0321..91886d5b03
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/FeatureQueryTest.java
@@@ -30,10 -31,17 +31,12 @@@ import org.junit.Test
  import static org.junit.Assert.*;
  
  // Branch-dependent imports
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureType;
 -import org.opengis.feature.PropertyType;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.Operation;
 -import org.opengis.filter.Expression;
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.FilterFactory;
 -import org.opengis.filter.MatchAction;
 -import org.opengis.filter.SortOrder;
 -import org.opengis.filter.SortProperty;
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.feature.DefaultFeatureType;
- import org.apache.sis.feature.AbstractIdentifiedType;
 +import org.apache.sis.feature.DefaultAttributeType;
++import org.apache.sis.feature.AbstractIdentifiedType;
++import org.apache.sis.feature.AbstractOperation;
++import org.apache.sis.filter.Expression;
  
  
  /**
@@@ -301,4 -322,62 +304,62 @@@ public final class FeatureQueryTest ext
          assertEquals("value1",  2, instance.getPropertyValue("value1"));
          assertEquals("value3", 25, instance.getPropertyValue("value3"));
      }
+ 
+     /**
+      * Shortcut for creating expression for a projection computed on-the-fly.
+      */
 -    private static FeatureQuery.NamedExpression virtualProjection(final Expression<? super Feature, ?> expression, final String alias) {
++    private static FeatureQuery.NamedExpression virtualProjection(final Expression<? super AbstractFeature, ?> expression, final String alias) {
+         return new FeatureQuery.NamedExpression(expression, Names.createLocalName(null, null, alias), FeatureQuery.ProjectionType.VIRTUAL);
+     }
+ 
+     /**
+      * Verifies the effect of virtual projections.
+      *
+      * @throws DataStoreException if an error occurred while executing the query.
+      */
+     @Test
+     public void testVirtualProjection() throws DataStoreException {
 -        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
++        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
+         query.setProjection(
+                 new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) null),
+                 virtualProjection(ff.property("value1", Integer.class), "renamed1"),
+                 virtualProjection(ff.literal("a literal"), "computed"));
+ 
+         // Check result type.
 -        final Feature instance = executeAndGetFirst();
 -        final FeatureType resultType = instance.getType();
++        final AbstractFeature instance = executeAndGetFirst();
++        final DefaultFeatureType resultType = instance.getType();
+         assertEquals("Test", resultType.getName().toString());
+         assertEquals(3, resultType.getProperties(true).size());
 -        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);
 -        assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
 -        assertTrue(((Operation) pt2).getResult() instanceof AttributeType);
 -        assertTrue(((Operation) pt3).getResult() instanceof AttributeType);
 -        assertEquals(Integer.class, ((AttributeType)((Operation) pt2).getResult()).getValueClass());
 -        assertEquals(String.class,  ((AttributeType)((Operation) pt3).getResult()).getValueClass());
++        final AbstractIdentifiedType pt1 = resultType.getProperty("value1");
++        final AbstractIdentifiedType pt2 = resultType.getProperty("renamed1");
++        final AbstractIdentifiedType pt3 = resultType.getProperty("computed");
++        assertTrue(pt1 instanceof DefaultAttributeType);
++        assertTrue(pt2 instanceof AbstractOperation);
++        assertTrue(pt3 instanceof AbstractOperation);
++        assertEquals(Integer.class, ((DefaultAttributeType) pt1).getValueClass());
++        assertTrue(((AbstractOperation) pt2).getResult() instanceof DefaultAttributeType);
++        assertTrue(((AbstractOperation) pt3).getResult() instanceof DefaultAttributeType);
++        assertEquals(Integer.class, ((DefaultAttributeType)((AbstractOperation) pt2).getResult()).getValueClass());
++        assertEquals(String.class,  ((DefaultAttributeType)((AbstractOperation) pt3).getResult()).getValueClass());
+ 
+         // Check feature instance.
+         assertEquals(3, instance.getPropertyValue("value1"));
+         assertEquals(3, instance.getPropertyValue("renamed1"));
+         assertEquals("a literal", instance.getPropertyValue("computed"));
+     }
+ 
+     /**
+      * Verifies that a virtual projection on a missing field causes an exception.
+      *
+      * @throws DataStoreException if an error occurred while executing the query.
+      */
+     @Test
+     public void testIncorrectVirtualProjection() throws DataStoreException {
 -        final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures();
++        final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures();
+         query.setProjection(new FeatureQuery.NamedExpression(ff.property("value1", Integer.class), (String) null),
+                             virtualProjection(ff.property("valueMissing", Integer.class), "renamed1"));
+ 
+         DataStoreContentException ex = assertThrows(DataStoreContentException.class, this::executeAndGetFirst);
+         assertNotNull(ex.getMessage());
+     }
  }