blob: 40728be85aaa7d27bb0f9a3fb879d5b95c97ad9b [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.Objects;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.InvalidObjectException;
import org.opengis.util.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
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.privy.Constants;
import org.apache.sis.util.resources.Errors;
// Specific to the main branch:
import org.apache.sis.feature.DefaultFeatureType;
import org.apache.sis.filter.Expression;
import org.apache.sis.pending.geoapi.filter.Literal;
/**
* A function where the last argument is the identifier of a Coordinate Reference System.
* The first argument may be a geometry or data (WKT, WKT, GML…) for creating a geometry.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
*
* @param <R> the type of resources (e.g. {@code Feature}) used as inputs.
*/
abstract class FunctionWithSRID<R> extends SpatialFunction<R> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -6870024245928121613L;
/**
* The expression giving the spatial reference system identifier, or {@code null} if none.
*/
@SuppressWarnings("serial") // Most SIS implementations are serializable.
final Expression<R,?> srid;
/**
* Identifier of the coordinate reference system in which to represent the geometry.
* This identifier is specified by the second expression and is stored in order to
* avoid computing {@link #targetCRS} many times when the SRID does not change.
*/
private transient Object lastSRID;
/**
* The coordinate reference system in which to represent the geometry, or {@code null}
* if not yet determined. This field is recomputed when the {@link #lastSRID} changed.
* If {@link #literalCRS} is {@code true}, then {@code targetCRS} shall be effectively final.
*/
private transient CoordinateReferenceSystem targetCRS;
/**
* Whether the {@link #getTargetCRS(Object)} value is defined by a literal.
*/
final boolean literalCRS;
/**
* Whether the SRID is present, absent, or may be present or absent depending on the value.
* If {@code ABSENT} then the {@link #srid} field will be null. In all other cases that field
* will be non-null.
*/
static final int PRESENT = 1, ABSENT = 0, MAYBE = 2;
/**
* Creates a new function for a geometry represented by the given parameter.
*
* @param operation identification of the SQLMM operation.
* @param parameters sub-expressions that will be evaluated to provide the parameters to the function.
* @param hasSRID whether the SRID is expected as one of {@link #PRESENT}, {@link #ABSENT} or {@link #MAYBE}.
*
* @todo The {@code MAYBE} flag could be removed if we know the type of value evaluated by the expression.
* For now it exists mostly because the last parameter given to {@code ST_Point} can be of various types.
*/
FunctionWithSRID(final SQLMM operation, final Expression<R,?>[] parameters, int hasSRID) {
super(operation, parameters);
if (hasSRID == MAYBE && parameters.length < operation.maxParamCount) {
hasSRID = ABSENT;
}
if (hasSRID == ABSENT) {
literalCRS = true;
srid = null;
return;
}
srid = parameters[operation.maxParamCount - 1];
if (srid instanceof Literal<?,?>) {
final Object value = ((Literal<?,?>) srid).getValue();
if (value == null) {
literalCRS = true;
} else {
literalCRS = (hasSRID == PRESENT || isCRS(value.getClass()));
if (literalCRS) try {
setTargetCRS(value);
} catch (FactoryException e) {
throw new IllegalArgumentException(e);
}
}
} else {
literalCRS = false;
}
}
/**
* Invoked on deserialization for restoring the {@link #targetCRS} field.
*
* @param in the input stream from which to deserialize an attribute.
* @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
* @throws ClassNotFoundException if the class serialized on the stream is not on the module path.
*/
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
if (literalCRS && srid != null) try {
setTargetCRS(((Literal<?,?>) srid).getValue());
} catch (FactoryException e) {
throw (IOException) new InvalidObjectException(e.getLocalizedMessage()).initCause(e);
}
}
/**
* Returns whether the given type is a CRS or may be a SRID.
*/
private static boolean isCRS(final Class<?> type) {
return type == Integer.class || type == String.class || CoordinateReferenceSystem.class.isAssignableFrom(type);
}
/**
* Sets {@link #targetCRS} to a coordinate reference system inferred from the given value.
* The CRS argument shall be the result of {@code parameters.get(1).apply(object)}.
*
* @param crs the object evaluated by the {@link #srid} expression.
* @throws FactoryException if no CRS can be created from the given object.
*/
private void setTargetCRS(final Object crs) throws FactoryException {
search: if (crs instanceof CoordinateReferenceSystem) {
targetCRS = (CoordinateReferenceSystem) crs;
} else {
final String code;
if (crs instanceof String) {
code = (String) crs;
} else if (crs instanceof Integer) {
if (((Integer) crs) == 0) {
targetCRS = null;
break search;
}
// TODO: should be a reference in the "spatial_ref_sys" table instead.
code = Constants.EPSG + ':' + crs;
} else {
throw new InvalidGeodeticParameterException(crs == null
? Errors.format(Errors.Keys.UnspecifiedCRS)
: Errors.format(Errors.Keys.IllegalCRSType_1, crs.getClass()));
}
targetCRS = CRS.forCode(code);
}
lastSRID = crs;
}
/**
* Gets the coordinate reference system for the {@code input} resources.
* The {@code input} argument is used only if the SRID is not a literal (which is rare).
* If the {@link #srid} parameter is optional, then it is caller's responsibility to verify that it is non-null.
*
* @param input the resource for which to get the CRS. This is often ignored.
* @return the CRS for the given resource.
* @throws FactoryException if the CRS can be created.
*/
final CoordinateReferenceSystem getTargetCRS(final R input) throws FactoryException {
if (literalCRS) {
return targetCRS; // No need to synchronize because effectively final.
} else {
final Object value = srid.apply(input);
if (value == null) {
return null;
}
synchronized (this) {
if (!Objects.equals(value, lastSRID)) {
setTargetCRS(value);
}
return targetCRS; // Must be inside synchronized block.
}
}
}
/**
* Returns the class of resources expected by this expression.
* Subclasses should override this method.
*/
@Override
public Class<? super R> getResourceClass() {
return (srid != null) ? srid.getResourceClass() : Object.class;
}
/**
* Provides the type of values produced by this expression when a feature of the given type is evaluated.
*
* @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.
*/
@Override
public PropertyTypeBuilder expectedType(final DefaultFeatureType valueType, final FeatureTypeBuilder addTo) {
final PropertyTypeBuilder pt = super.expectedType(valueType, addTo);
if (pt instanceof AttributeTypeBuilder<?>) {
// We must unconditionally override the CRS set by parent class.
((AttributeTypeBuilder<?>) pt).setCRS(literalCRS ? targetCRS : null);
}
return pt;
}
}