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 d8bbaf214a Clarify which `Feature` instance is used for the different
projection types. Other opportunistic clarifications as we tried to verify the
behavior. Replace `ExpressionOperation` by `LinkOperation` when applicable.
d8bbaf214a is described below
commit d8bbaf214a1c60b20a7928bd11457b13b7061594
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue May 2 19:29:16 2023 +0200
Clarify which `Feature` instance is used for the different projection types.
Other opportunistic clarifications as we tried to verify the behavior.
Replace `ExpressionOperation` by `LinkOperation` when applicable.
---
.../org/apache/sis/feature/AbstractFeature.java | 12 ++-
.../java/org/apache/sis/feature/DenseFeature.java | 1 +
.../org/apache/sis/feature/EnvelopeOperation.java | 21 +----
.../apache/sis/feature/ExpressionOperation.java | 65 +++++++++++++---
.../org/apache/sis/feature/FeatureOperations.java | 25 +++---
.../org/apache/sis/feature/OperationResult.java | 71 +++++++++++++++++
.../java/org/apache/sis/feature/SparseFeature.java | 1 +
.../apache/sis/feature/StringJoinOperation.java | 13 +---
.../java/org/apache/sis/filter/PropertyValue.java | 1 +
.../apache/sis/{ => internal}/filter/XPath.java | 8 +-
.../test/java/org/apache/sis/filter/XPathTest.java | 1 +
.../org/apache/sis/internal/map/SEPortrayer.java | 5 +-
.../java/org/apache/sis/storage/FeatureQuery.java | 90 +++++++++++++++++-----
.../java/org/apache/sis/storage/FeatureSubset.java | 2 +-
.../org/apache/sis/storage/FeatureQueryTest.java | 8 +-
15 files changed, 246 insertions(+), 78 deletions(-)
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
index 3d823e76e7..becfe57dfb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
@@ -67,6 +67,13 @@ import org.opengis.feature.Operation;
* <li>the cardinality of all attributes is constrained to [1 … 1].</li>
* </ul>
*
+ * <h2>Operations</h2>
+ * Properties that are instances of {@link Operation} are usually not stored
in {@code Feature} instances.
+ * Instead, the {@link Operation#apply Operation.apply(…)} method is invoked
every times that the property
+ * value is requested. {@code AbstractFeature} does not cache operation
results.
+ * Those results are usually read-only, but may be writable under the
conditions documented in
+ * {@link #setOperationValue(String, Object)}.
+ *
* <h2>Limitations</h2>
* <ul>
* <li><b>Multi-threading:</b> {@code AbstractFeature} instances are
<strong>not</strong> thread-safe.
@@ -439,7 +446,10 @@ public abstract class AbstractFeature implements Feature,
Serializable {
* Executes the parameterless operation of the given name and sets the
value of its result.
* This method is the complement of {@link #getOperationValue(String)} for
subclasses where
* some properties may be operations. Not all operations accept
assignments,
- * but the {@linkplain FeatureOperations#link link} operation for instance
does.
+ * but the {@linkplain FeatureOperations#link link} and
+ * {@linkplain FeatureOperations#compound compound} operations (for
instances) do.
+ * Whether an operation is writable or not depends on whether the computed
{@link Property}
+ * supports {@link Attribute#setValue(Object)} or {@link
FeatureAssociation#setValue(Feature)}.
*
* @param name the name of the operation to execute. The caller is
responsible to ensure that the
* property type for that name is an instance of {@link
Operation}.
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
index 319a81f9d1..54193162b0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
@@ -60,6 +60,7 @@ final class DenseFeature extends AbstractFeature implements
Cloneable {
/**
* The properties (attributes or feature associations) in this feature.
+ * This array does not include operation results, which are always
computed on the fly.
*
* Conceptually, values in this array are {@link Property} instances.
However, at first we will store only
* the property <em>values</em>, and convert to an array of type {@code
Property[]} only when at least one
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
index 6018a951d6..88594a2fa2 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
@@ -272,24 +272,17 @@ final class EnvelopeOperation extends AbstractOperation {
* The attributes that contains the result of union of all envelope
extracted from other attributes.
* Value is calculated each time it is accessed.
*/
- private final class Result extends AbstractAttribute<Envelope> {
+ private final class Result extends OperationResult<Envelope> {
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = 926172863066901618L;
-
- /**
- * The feature specified to the {@link
StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
- */
- @SuppressWarnings("serial") // Most SIS implementations are
serializable.
- private final Feature feature;
+ private static final long serialVersionUID = 4900962888075807964L;
/**
* Creates a new attribute for the given feature.
*/
Result(final Feature feature) {
- super(resultType);
- this.feature = feature;
+ super(resultType, feature);
}
/**
@@ -406,14 +399,6 @@ final class EnvelopeOperation extends AbstractOperation {
throw new
FeatureOperationException(Errors.formatInternational(Errors.Keys.CanNotTransformEnvelope),
e);
}
}
-
- /**
- * Unconditionally throws an {@link UnsupportedOperationException}.
- */
- @Override
- public void setValue(Envelope value) {
- throw new
UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1,
Attribute.class));
- }
}
/**
diff --git
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
index c5990072e8..7b897b979a 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
@@ -31,7 +31,6 @@ 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;
@@ -70,7 +69,7 @@ final class ExpressionOperation<V> extends AbstractOperation {
* The type of result of evaluating the expression.
*/
@SuppressWarnings("serial") // Apache SIS
implementations are serializable.
- private final AttributeType<? super V> result;
+ private final AttributeType<V> resultType;
/**
* The name of all feature properties that are known to be read by the
expression.
@@ -85,17 +84,35 @@ final class ExpressionOperation<V> extends
AbstractOperation {
*
* @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.
+ * @param resultType type of values computed by the expression.
*/
- ExpressionOperation(final Map<String,?> identification,
- final Function<? super Feature, ? extends V>
expression,
- final AttributeType<? super V> result)
+ static <V> AbstractOperation create(final Map<String,?> identification,
+ final Function<? super Feature, ?
extends V> expression,
+ final AttributeType<? super V>
resultType)
+ {
+ if (expression instanceof ValueReference<?,?>) {
+ final String xpath = ((ValueReference<?,?>) expression).getXPath();
+ if (xpath.equals(resultType.getName().toString())) {
+ return new LinkOperation(identification, resultType);
+ }
+ }
+ return new ExpressionOperation<>(identification, expression,
resultType);
+ }
+
+ /**
+ * Creates a generic operation when no optimized case has been identifier.
+ */
+ private ExpressionOperation(final Map<String,?> identification,
+ final Function<? super Feature, ? extends V>
expression,
+ final AttributeType<V> resultType)
{
super(identification);
this.expression = expression;
- this.result = result;
+ this.resultType = resultType;
if (expression instanceof Expression<?,?>) {
- dependencies = DependencyFinder.search((Expression<Feature,?>)
expression);
+ @SuppressWarnings("unchecked")
+ var c = (Expression<Feature,?>) expression; // Cast is okay
because we will not pass or request any `Feature` instance.
+ dependencies = DependencyFinder.search(c);
} else {
dependencies = Set.of();
}
@@ -114,7 +131,7 @@ final class ExpressionOperation<V> extends
AbstractOperation {
*/
@Override
public IdentifiedType getResult() {
- return result;
+ return resultType;
}
/**
@@ -136,9 +153,33 @@ final class ExpressionOperation<V> extends
AbstractOperation {
*/
@Override
public Property apply(final Feature feature, ParameterValueGroup
parameters) {
- final Attribute<? super V> instance = result.newInstance();
- instance.setValue(expression.apply(feature));
- return instance;
+ return new Result(feature);
+ }
+
+ /**
+ * The attributes that delegates computation to the expression.
+ * Value is calculated each time it is accessed.
+ */
+ private final class Result extends OperationResult<V> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -19004252522001532L;
+
+ /**
+ * Creates a new attribute for the given feature.
+ */
+ Result(final Feature feature) {
+ super(resultType, feature);
+ }
+
+ /**
+ * Delegates the computation to the user-supplied expression.
+ */
+ @Override
+ public V getValue() {
+ return expression.apply(feature);
+ }
}
/**
diff --git
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
index 15c36b01cb..db15ddcb83 100644
---
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
@@ -130,7 +130,7 @@ public final class FeatureOperations extends Static {
* Creates an operation which is only an alias for another property.
*
* <h4>Example</h4>
- * features often have a property that can be used as identifier or
primary key.
+ * Features often have a property that can be used as identifier or
primary key.
* But the name of that property may vary between features of different
types.
* For example, features of type <b>Country</b> may have identifiers named
“ISO country code”
* while features of type <b>Car</b> may have identifiers named “license
plate number”.
@@ -274,22 +274,23 @@ public final class FeatureOperations extends Static {
* @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.
+ * @param resultType 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> Operation function(final Map<String,?> identification,
+ final Function<? super Feature, ?
extends V> expression,
+ final AttributeType<? super V>
resultType)
{
ArgumentChecks.ensureNonNull("expression", expression);
- return POOL.unique(new ExpressionOperation<>(identification,
expression, result));
+ ArgumentChecks.ensureNonNull("result", resultType);
+ return POOL.unique(ExpressionOperation.create(identification,
expression, resultType));
}
/**
* 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
+ * This method can be used as an alternative to {@link #function
function(…)} when the constraint on the
* parameterized type {@code <V>} between {@code expression} and {@code
result} cannot be enforced at compile time.
* This method casts or converts the expression to the expected type by a
call to
* {@link Expression#toValueType(Class)}.
@@ -297,16 +298,16 @@ public final class FeatureOperations extends Static {
* @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.
+ * @param resultType 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> Operation expression(final Map<String,?> identification,
+ final Expression<? super Feature,
?> expression,
+ final AttributeType<V> resultType)
{
- return expression(identification,
expression.toValueType(result.getValueClass()), result);
+ return function(identification,
expression.toValueType(resultType.getValueClass()), resultType);
}
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/feature/OperationResult.java
b/core/sis-feature/src/main/java/org/apache/sis/feature/OperationResult.java
new file mode 100644
index 0000000000..b05f884c54
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/OperationResult.java
@@ -0,0 +1,71 @@
+/*
+ * 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 org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+
+
+/**
+ * Base class of attributes that are the result of a feature operation.
+ * This base class is defined for making easier to identify where computations
are done.
+ *
+ * @todo A future version may provide caching services, methods for taking a
snapshot, <i>etc.</i>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ *
+ * @param <V> the type of attribute values.
+ *
+ * @since 1.4
+ */
+abstract class OperationResult<V> extends AbstractAttribute<V> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 1418917854672134381L;
+
+ /**
+ * The feature instance to use as a source for computing the result.
+ */
+ @SuppressWarnings("serial") // Most SIS implementations are
serializable.
+ protected final Feature feature;
+
+ /**
+ * Creates a new operation for a result of the given type.
+ *
+ * @param type information about the attribute (base Java class, domain
of values, <i>etc.</i>).
+ */
+ protected OperationResult(final AttributeType<V> type, final Feature
feature) {
+ super(type);
+ this.feature = feature;
+ }
+
+ /**
+ * Retro-propagate an operation result to the properties in the source
feature instance.
+ * This is an optional operation.
+ * The default implementation unconditionally throws an {@link
UnsupportedOperationException}.
+ */
+ @Override
+ public void setValue(V value) {
+ throw new
UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1,
Attribute.class));
+ }
+}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
index 55af5252ed..7b3ddce242 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
@@ -82,6 +82,7 @@ final class SparseFeature extends AbstractFeature implements
Cloneable {
/**
* The properties (attributes or feature associations) in this feature.
+ * This map does not include operation results, which are always computed
on the fly.
*
* Conceptually, values in this map are {@link Property} instances.
However, at first we will store
* only the property <em>values</em>, and build the full {@code Property}
objects only if they are
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
index a618de04a4..3bc72ab684 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
@@ -317,24 +317,17 @@ final class StringJoinOperation extends AbstractOperation
{
* The attributes that contains the result of concatenating the string
representation of other attributes.
* Value is calculated each time it is accessed.
*/
- private final class Result extends AbstractAttribute<String> {
+ private final class Result extends OperationResult<String> {
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = -8435975199763452547L;
-
- /**
- * The feature specified to the {@link
StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
- */
- @SuppressWarnings("serial") // Most SIS implementations are
serializable.
- private final Feature feature;
+ private static final long serialVersionUID = -555025854115540108L;
/**
* Creates a new attribute for the given feature.
*/
Result(final Feature feature) {
- super(resultType);
- this.feature = feature;
+ super(resultType, feature);
}
/**
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
index cae63556d3..4fb0bcbe1a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java
@@ -26,6 +26,7 @@ import org.apache.sis.util.UnconvertibleObjectException;
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.internal.filter.XPath;
import org.apache.sis.util.resources.Errors;
// Branch-dependent imports
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/XPath.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/XPath.java
similarity index 90%
rename from core/sis-feature/src/main/java/org/apache/sis/filter/XPath.java
rename to
core/sis-feature/src/main/java/org/apache/sis/internal/filter/XPath.java
index c1ca1cccb6..aa40ecceba 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/XPath.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/XPath.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter;
+package org.apache.sis.internal.filter;
import java.util.List;
import java.util.ArrayList;
@@ -32,9 +32,11 @@ import static org.apache.sis.util.CharSequences.*;
* @version 1.3
* @since 0.4
*/
-final class XPath extends Static {
+public final class XPath extends Static {
/**
* The separator between path components.
+ * Should not be used for URL or Unix name separator, even if the
character is the same.
+ * We use this constant for identifying locations in the code where there
is some X-Path parsing.
*/
public static final char SEPARATOR = '/';
@@ -57,7 +59,7 @@ final class XPath extends Static {
* if there is no separator. If non-null, the list always contains
at least one element.
* @throws IllegalArgumentException if the XPath contains at least one
empty component.
*/
- static List<String> split(final String xpath) {
+ public static List<String> split(final String xpath) {
int next = xpath.indexOf(SEPARATOR);
if (next < 0) {
return null;
diff --git
a/core/sis-feature/src/test/java/org/apache/sis/filter/XPathTest.java
b/core/sis-feature/src/test/java/org/apache/sis/filter/XPathTest.java
index 2d7e4e599c..a8f11aa454 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/XPathTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/XPathTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.filter;
+import org.apache.sis.internal.filter.XPath;
import org.apache.sis.test.TestCase;
import org.junit.Test;
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
index 61da8fd80e..6eff0d15a4 100644
---
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
+++
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
@@ -42,6 +42,7 @@ import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.internal.feature.AttributeConvention;
+import org.apache.sis.internal.filter.XPath;
import org.apache.sis.storage.FeatureQuery;
import org.apache.sis.portrayal.MapItem;
import org.apache.sis.portrayal.MapLayer;
@@ -710,7 +711,7 @@ public final class SEPortrayer {
* Removes any xpath elements, keep only the root property name.
*/
private static String stripXpath(String attName) {
- int index = attName.indexOf('/');
+ int index = attName.indexOf(XPath.SEPARATOR);
if (index == 0) {
attName = attName.substring(1); // Remove first slash
final Pattern pattern =
Pattern.compile("(\\{[^\\{\\}]*\\})|(\\[[^\\[\\]]*\\])|/{1}");
@@ -722,7 +723,7 @@ matches: while (matcher.find()) {
sb.append(attName.substring(position, matcher.start()));
position = matcher.end();
switch (match.charAt(0)) {
- case '/': {
+ case XPath.SEPARATOR: {
// We do not query precisely sub elements.
position = attName.length();
break matches;
diff --git
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
index 0f4bb9e3e7..46947ce71e 100644
--- 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
@@ -37,10 +37,12 @@ import org.apache.sis.feature.builder.AttributeTypeBuilder;
import org.apache.sis.internal.feature.AttributeConvention;
import org.apache.sis.internal.feature.FeatureExpression;
import org.apache.sis.internal.filter.SortByComparator;
+import org.apache.sis.internal.filter.XPath;
import org.apache.sis.internal.storage.Resources;
import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.filter.Optimization;
import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.iso.Names;
@@ -254,6 +256,25 @@ public class FeatureQuery extends Query implements
Cloneable, Serializable {
return (projection != null) ? projection.clone() : null;
}
+ /**
+ * Returns the properties to be stored in the target feature.
+ */
+ final NamedExpression[] getStoredProjection() {
+ final NamedExpression[] stored = getProjection();
+ if (stored != null) {
+ int count = 0;
+ for (final NamedExpression p : stored) {
+ if (p.type == ProjectionType.STORED) {
+ stored[count++] = p;
+ }
+ }
+ if (count != 0) {
+ return ArraysExt.resize(stored, count);
+ }
+ }
+ return null;
+ }
+
/**
* Sets the approximate area of feature instances to include in the subset.
* This convenience method creates a filter that checks if the bounding box
@@ -430,9 +451,33 @@ public class FeatureQuery extends Query implements
Cloneable, Serializable {
* and the result is stored as a feature attribute.
* The feature property type will be {@link Attribute} and its value
will be modifiable.
* This is the default projection type.
+ *
+ * <h4>Feature instances in expression evaluation</h4>
+ * The features given in calls to {@link Expression#apply(Object)} are
instances from the
+ * <em>source</em> {@link FeatureSet}, before filtering.
*/
STORED,
+ /*
+ * The expression is evaluated every times that the property value is
requested.
+ * This projection type is similar to {@link #COMPLETING}, except that
the features
+ * given in calls to {@link Expression#apply(Object)} are the same
instances than
+ * the ones used by {@link #STORED}.
+ *
+ * <div class="note"><b>Note on naming:</b>
+ * the {@code STORED} and {@code VIRTUAL} enumeration values are named
according usage in SQL
+ * {@code GENERATE ALWAYS} statement. Those two keywords work on
columns in the source tables.
+ * </div>
+ *
+ * <h4>Feature instances in expression evaluation</h4>
+ * The combination of deferred calculation (like {@link #COMPLETING})
and usage of feature instances
+ * from the <em>source</em> {@link FeatureSet} (like {@link #STORED})
may cause this projection type
+ * to retain the source feature instances for a longer time than other
types.
+ *
+ * @todo Waiting to see if there is a need for this type before to
implement it.
+ */
+ // VIRTUAL,
+
/**
* The expression is evaluated every times that the property value is
requested.
* The feature property type will be {@link Operation}.
@@ -445,14 +490,19 @@ public class FeatureQuery extends Query implements
Cloneable, Serializable {
* <li>Computation result should not be stored in order to reduce
memory usage.</li>
* </ul>
*
+ * <h4>Feature instances in expression evaluation</h4>
+ * The features given in calls to {@link Expression#apply(Object)} are
instances from the <em>target</em>
+ * {@link FeatureSet}, after filtering. The instances from the source
{@code FeatureSet} are no longer
+ * available when the expression is executed. Consequently, all fields
that are necessary for computing
+ * a {@code COMPLETING} field shall have been first copied in {@link
#STORED} fields.
+ *
+ * <div class="note"><b>Note on naming:</b>
+ * verb tense <i>-ing</i> instead of <i>-ed</i> is for emphasizing
that the data used for computation
+ * are current (filtered) data instead of past (original) data.</div>
+ *
* @see FeatureOperations#expression(Map, Function, AttributeType)
*/
- VIRTUAL
-
- /*
- * Examples of other enumeration values that we may add in the future:
- * GENERATED for meaning "STORED but read-only", CACHED for lazy
computation.
- */
+ COMPLETING
}
/**
@@ -586,10 +636,9 @@ public class FeatureQuery extends Query implements
Cloneable, Serializable {
*/
@Override
public String toString() {
- final StringBuilder buffer = new StringBuilder();
- buffer.append(getClass().getSimpleName()).append('['); //
Class name without enclosing class.
+ final StringBuilder buffer = new StringBuilder("SELECT ");
appendTo(buffer);
- return buffer.append(']').toString();
+ return buffer.toString();
}
/**
@@ -597,11 +646,14 @@ public class FeatureQuery extends Query implements
Cloneable, Serializable {
*/
final void appendTo(final StringBuilder buffer) {
if (expression instanceof Literal<?,?>) {
- buffer.append('“').append(((Literal<?,?>)
expression).getValue()).append('”');
+ buffer.append('‘').append(((Literal<?,?>)
expression).getValue()).append('’');
} else if (expression instanceof ValueReference<?,?>) {
- buffer.append(((ValueReference<?,?>) expression).getXPath());
+ buffer.append('“').append(((ValueReference<?,?>)
expression).getXPath()).append('”');
} else {
- buffer.append(expression.getFunctionName());
+
buffer.append("=“").append(expression.getFunctionName()).append("”()");
+ }
+ if (type != ProjectionType.STORED) {
+ buffer.append(' ').append(type);
}
if (alias != null) {
buffer.append(" AS “").append(alias).append('”');
@@ -721,7 +773,7 @@ public class FeatureQuery extends Query implements
Cloneable, Serializable {
continue;
}
String xpath = ((ValueReference<?,?>)
expression).getXPath().trim();
- xpath = xpath.substring(xpath.lastIndexOf('/') + 1); //
Works also if '/' is not found.
+ xpath = xpath.substring(xpath.lastIndexOf(XPath.SEPARATOR)
+ 1); // Works also if '/' is not found.
if (!(xpath.isEmpty() || names.contains(xpath))) {
text = xpath;
}
@@ -737,18 +789,20 @@ public class FeatureQuery extends Query implements
Cloneable, Serializable {
} while (!names.add(text.toString()));
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 the attribute that we just added should be virtual, replace
the attribute by an operation.
+ * We need to keep the property name computed by
`fex.expectedType(…)` for the operation result,
+ * because that name is the name of the link to create if the
operation is `ValueReference`.
*/
- if (item.type == ProjectionType.VIRTUAL && resultType instanceof
AttributeTypeBuilder<?>) {
+ if (item.type == ProjectionType.COMPLETING && resultType
instanceof AttributeTypeBuilder<?>) {
final var ab = (AttributeTypeBuilder<?>) resultType;
final AttributeType<?> storedType = ab.build();
if (ftb.properties().remove(resultType)) {
final var properties = Map.of(AbstractOperation.NAME_KEY,
name);
-
ftb.addProperty(FeatureOperations.expressionToResult(properties, expression,
storedType));
+ ftb.addProperty(FeatureOperations.expression(properties,
expression, storedType));
}
+ } else {
+ resultType.setName(name);
}
}
return ftb.build();
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
index dc4a9f6992..5b2e67c900 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSubset.java
@@ -139,7 +139,7 @@ final class FeatureSubset extends AbstractFeatureSet {
* Transform feature instances.
* Note: "projection" here is in relational database sense, not map
projection.
*/
- final FeatureQuery.NamedExpression[] projection =
query.getProjection();
+ final FeatureQuery.NamedExpression[] projection =
query.getStoredProjection();
if (projection != null) {
@SuppressWarnings({"unchecked", "rawtypes"})
final Expression<? super Feature,?>[] expressions = new
Expression[projection.length];
diff --git
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
index b4aa6f3786..fb70453eb6 100644
---
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
@@ -20,6 +20,7 @@ import java.util.List;
import java.util.Arrays;
import java.util.Iterator;
import java.util.stream.Collectors;
+import org.apache.sis.feature.Features;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.internal.storage.MemoryFeatureSet;
import org.apache.sis.filter.DefaultFilterFactory;
@@ -328,7 +329,7 @@ public final class FeatureQueryTest extends TestCase {
* Shortcut for creating expression for a projection computed on-the-fly.
*/
private static FeatureQuery.NamedExpression virtualProjection(final
Expression<Feature, ?> expression, final String alias) {
- return new FeatureQuery.NamedExpression(expression,
Names.createLocalName(null, null, alias), FeatureQuery.ProjectionType.VIRTUAL);
+ return new FeatureQuery.NamedExpression(expression,
Names.createLocalName(null, null, alias),
FeatureQuery.ProjectionType.COMPLETING);
}
/**
@@ -367,6 +368,11 @@ public final class FeatureQueryTest extends TestCase {
assertEquals(3, instance.getPropertyValue("value1"));
assertEquals(3, instance.getPropertyValue("renamed1"));
assertEquals("a literal", instance.getPropertyValue("computed"));
+
+ // The `ValueReference` operation should have been optimized as a link.
+ assertEquals("value1", Features.getLinkTarget(pt2).get());
+ assertTrue(Features.getLinkTarget(pt1).isEmpty());
+ assertTrue(Features.getLinkTarget(pt3).isEmpty());
}
/**