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());
+ }
}