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 <martin.desruisse...@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()); + } }