blob: f4f27cc967969ad25fe9c8bc83e580d020dd17eb [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.filter.sqlmm;
import java.util.EnumMap;
import java.util.Collection;
import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
import org.apache.sis.filter.Optimization;
import org.apache.sis.filter.internal.Node;
import org.apache.sis.feature.internal.Resources;
import org.apache.sis.feature.privy.FeatureExpression;
import org.apache.sis.geometry.wrapper.Geometries;
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.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.iso.Names;
// Specific to the main branch:
import org.apache.sis.filter.Expression;
import org.apache.sis.feature.DefaultFeatureType;
/**
* Base class of SQLMM spatial functions.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*
* @param <R> the type of resources (e.g. {@code Feature}) used as inputs.
*/
abstract class SpatialFunction<R> extends Node implements FeatureExpression<R,Object>, Optimization.OnExpression<R,Object> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 6933519274722660893L;
/**
* Scope of all names defined by SQLMM standard.
*
* @see #createName(SQLMM)
*/
private static final LocalName SCOPE = Names.createLocalName("ISO", null, "sqlmm");;
/**
* Identification of the SQLMM operation.
*/
final SQLMM operation;
/**
* All operation names as {@link ScopedName} instances.
* Values are {@linkplain #createName(SQLMM) created} when first needed.
*/
private static final EnumMap<SQLMM, ScopedName> NAMES = new EnumMap<>(SQLMM.class);
/**
* Creates a new function. This constructor verifies that the number of parameters
* is between {@link SQLMM#minParamCount} and {@link SQLMM#maxParamCount} inclusive,
* but does not store the parameters. Parameters shall be stored by subclasses.
*
* @param operation identification of the SQLMM operation.
* @param parameters sub-expressions that will be evaluated to provide the parameters to the function.
* @throws IllegalArgumentException if the number of parameters is not in the expected range.
*/
SpatialFunction(final SQLMM operation, final Expression<R,?>[] parameters) {
this.operation = operation;
ArgumentChecks.ensureCountBetween("parameters", true,
operation.minParamCount, operation.maxParamCount, parameters.length);
}
/**
* Returns a handler for the library of geometric objects used by this expression.
* This is typically implemented by a call to {@code getGeometryLibrary(geometry)}
* where {@code geometry} as the first expression returning a geometry object.
*
* @return the geometry library (never {@code null}).
*
* @see #getGeometryLibrary(Expression)
*/
abstract Geometries<?> getGeometryLibrary();
/**
* Returns the name of the function to be called. This method returns
* a scoped name with the {@link SQLMM} function name in the local part.
*/
@Override
public final ScopedName getFunctionName() {
synchronized (NAMES) {
return NAMES.computeIfAbsent(operation, SpatialFunction::createName);
}
}
/**
* Invoked by {@link Expression#getFunctionName()} implementations
* when a name needs to be created.
*/
private static ScopedName createName(final SQLMM operation) {
return Names.createScopedName(SCOPE, null, operation.name());
}
/**
* Returns the children of this node, which are the {@linkplain #getParameters() parameters list}.
* This is used for information purpose only, for example in order to build a string representation.
*
* @return the children of this node.
*/
@Override
protected final Collection<?> getChildren() {
return getParameters();
}
/**
* Returns a Backus-Naur Form (BNF) of this function.
*
* @todo Fetch parameter names from {@code FilterCapabilities}.
* Or maybe use annotations, which would also be used for capabilities implementation.
*/
public String getSyntax() {
final int minParamCount = operation.minParamCount;
final int maxParamCount = operation.maxParamCount;
final StringBuilder sb = new StringBuilder();
sb.append(getFunctionName().tip()).append('(');
for (int i = 0; i < maxParamCount; i++) {
if (i == minParamCount) sb.append('[');
if (i != 0) sb.append(", ");
sb.append("param").append(i + 1);
}
if (maxParamCount > minParamCount) {
sb.append(']');
}
return sb.append(')').toString();
}
/**
* Returns the kind of objects evaluated by this expression.
*/
@Override
public final Class<?> getValueClass() {
return operation.getReturnType(getGeometryLibrary());
}
/**
* Returns {@code this} if this expression provides values of the specified type,
* or throws an exception otherwise.
*/
@Override
@SuppressWarnings("unchecked")
public final <N> Expression<R,N> toValueType(final Class<N> target) {
if (target.isAssignableFrom(getValueClass())) {
return (Expression<R,N>) this;
} else {
throw new ClassCastException(Errors.format(Errors.Keys.CanNotConvertValue_2, getFunctionName(), target));
}
}
/**
* Provides the type of values produced by this expression when a feature of the given type is evaluated.
* There are two cases:
*
* <ul class="verbose">
* <li>If the operation expects at least one geometric parameter and returns a geometry,
* then the characteristics of the first parameter (in particular the CRS) are copied.
* The first parameter is used as a template for compliance with SQLMM specification.</li>
* <li>Otherwise an attribute is created with the return value specified by the operation.</li>
* </ul>
*
* @param valueType the type of features on which to apply this expression.
* @param addTo where to add the type of properties evaluated by this expression.
* @return builder of type resulting from expression evaluation (never null).
* @throws IllegalArgumentException if the given feature type does not contain the expected properties,
* or if this method cannot determine the result type of the expression.
* It may be because that expression is backed by an unsupported implementation.
*/
@Override
public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
AttributeTypeBuilder<?> att;
cases: if (operation.isGeometryInOut()) {
final FeatureExpression<?,?> fex = FeatureExpression.castOrCopy(getParameters().get(0));
if (fex != null) {
final PropertyTypeBuilder type = fex.expectedType(valueType, addTo);
if (type instanceof AttributeTypeBuilder<?>) {
att = (AttributeTypeBuilder<?>) type;
final Geometries<?> library = Geometries.factory(att.getValueClass());
if (library != null) {
att = att.setValueClass(operation.getReturnType(library));
break cases;
}
}
}
throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
} else {
att = addTo.addAttribute(getValueClass());
}
return att.setName(getFunctionName());
}
}