/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2012, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.data.transform;
import org.geotools.filter.function.FilterFunction_Convert;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Add;
import org.opengis.filter.expression.BinaryExpression;
import org.opengis.filter.expression.Divide;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.ExpressionVisitor;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.Multiply;
import org.opengis.filter.expression.NilExpression;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.expression.Subtract;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Utility class that tries to figure out the resulting type of an expression against a given
* feature type by using static analysis.
*
* @author Andrea Aime - GeoSolutions
*/
class ExpressionTypeEvaluator implements ExpressionVisitor {
private SimpleFeatureType schema;
private CoordinateReferenceSystem crs;
public ExpressionTypeEvaluator(SimpleFeatureType schema) {
this.schema = schema;
}
/**
* Returns the coordinate reference system of the last encontered geometry property.
* Unless a filter function that reprojects geometries is used, that's also the crs of the eventual
* output, in case it's a Geometry, that is.
*
* @return
*/
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return this.crs;
}
@Override
public Object visit(NilExpression expression, Object extraData) {
return null;
}
@Override
public Object visit(Function f, Object extraData) {
FunctionName fn = f.getFunctionName();
if (fn != null && fn.getReturn() != null && fn.getReturn().getType() != Object.class) {
return fn.getReturn().getType();
} else if (f instanceof FilterFunction_Convert) {
// special case for the convert function, which has the return type as
// a parameter
return f.getParameters().get(1).evaluate(null, Class.class);
}
return null;
}
@Override
public Object visit(Literal expression, Object extraData) {
if (expression.getValue() == null) {
return null;
} else {
return expression.getValue().getClass();
}
}
@Override
public Object visit(PropertyName expression, Object extraData) {
AttributeDescriptor result = expression.evaluate(schema, AttributeDescriptor.class);
if (result == null) {
throw new IllegalArgumentException(
"Original feature type does not have a property named "
+ expression.getPropertyName());
}
if(result instanceof GeometryDescriptor) {
this.crs = ((GeometryDescriptor) result).getCoordinateReferenceSystem();
}
return result.getType().getBinding();
}
private Object visitMathExpression(BinaryExpression expression) {
Expression ex1 = expression.getExpression1();
Expression ex2 = expression.getExpression2();
Class c1 = getMathOperandType(ex1);
Class c2 = getMathOperandType(ex2);
if (c1 == Integer.class && c2 == Integer.class) {
return Integer.class;
} else if ((c1 == Integer.class || c1 == Long.class)
&& (c2 == Integer.class || c2 == Long.class)) {
return Long.class;
} else {
return Double.class;
}
}
private Class getMathOperandType(Expression expression) {
Class result = (Class) expression.accept(this, null);
// not a number, if a literal see if its contents can be cast to one
if (!(Number.class.isAssignableFrom(result)) && expression instanceof Literal) {
Double value = expression.evaluate(null, Double.class);
if (value != null) {
if (value.longValue() == value.doubleValue()) {
// integer type, keep it simple, int or long
if ((value < Integer.MAX_VALUE) && value > Integer.MIN_VALUE) {
result = Integer.class;
} else {
result = Long.class;
}
}
} else {
result = Double.class;
}
}
return result;
}
@Override
public Object visit(Multiply expression, Object extraData) {
return visitMathExpression(expression);
}
@Override
public Object visit(Add expression, Object extraData) {
return visitMathExpression(expression);
}
@Override
public Object visit(Divide expression, Object extraData) {
return visitMathExpression(expression);
}
@Override
public Object visit(Subtract expression, Object extraData) {
return visitMathExpression(expression);
}
}