blob: 9d5680443079cb708c38b7b068db84e785a7062e [file] [log] [blame]
/*
* 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.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.apache.sis.feature.privy.FeatureUtilities;
import org.apache.sis.filter.privy.FunctionNames;
import org.apache.sis.filter.privy.Visitor;
// Specific to the main branch:
import org.apache.sis.filter.Filter;
import org.apache.sis.filter.Expression;
import org.apache.sis.pending.geoapi.filter.LogicalOperator;
import org.apache.sis.pending.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)
*
* @param <V> class of values computed by the operation.
*/
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 AbstractFeature, ? extends V> expression;
/**
* The type of result of evaluating the expression.
*/
private final DefaultAttributeType<V> resultType;
/**
* 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 resultType type of values computed by the expression.
*/
static <V> AbstractOperation create(final Map<String,?> identification,
final Function<? super AbstractFeature, ? extends V> expression,
final DefaultAttributeType<? 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 AbstractFeature, ? extends V> expression,
final DefaultAttributeType<V> resultType)
{
super(identification);
this.expression = expression;
this.resultType = resultType;
if (expression instanceof Expression<?,?>) {
@SuppressWarnings("unchecked")
var c = (Expression<AbstractFeature,?>) expression; // Cast is okay because we will not pass or request any `Feature` instance.
dependencies = DependencyFinder.search(c);
} else {
dependencies = Set.of();
}
}
/**
* Returns a description of the input parameters.
*/
@Override
public ParameterDescriptorGroup getParameters() {
return PARAMETERS;
}
/**
* Returns the expected result type.
*/
@Override
public AbstractIdentifiedType getResult() {
return resultType;
}
/**
* 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 AbstractFeature feature, ParameterValueGroup parameters) {
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 AbstractFeature feature) {
super(resultType, feature);
}
/**
* Delegates the computation to the user supplied expression.
*/
@Override
public V getValue() {
return expression.apply(feature);
}
}
/**
* 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.
*/
private static final class DependencyFinder extends Visitor<AbstractFeature, 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<AbstractFeature,?> 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<AbstractFeature>) f;
for (Filter<AbstractFeature> child : filter.getOperands()) {
visit(child, dependencies);
}
});
setExpressionHandler(FunctionNames.ValueReference, (e, dependencies) -> {
final var expression = (ValueReference<AbstractFeature,?>) 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 Enum<?> type, final Filter<AbstractFeature> filter, final Collection<String> dependencies) {
for (final Expression<AbstractFeature,?> 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<AbstractFeature,?> expression, final Collection<String> dependencies) {
for (final Expression<AbstractFeature,?> p : expression.getParameters()) {
visit(p, dependencies);
}
}
}
}