/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014 - 2015, 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.filter.visitor; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.FeatureType; 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.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; /** * Returns the output type of the visited expression, taking into account functions output types, * property types, and general promotion rules in arithmetic expressions * * @author Andrea Aime - GeoSolutions * */ public class ExpressionTypeVisitor implements ExpressionVisitor { static final Map<Class<?>, List<Class<?>>> PROMOTIONS = new HashMap<Class<?>, List<Class<?>>>() { { put(Byte.class, Arrays.asList((Class<?>) Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, BigInteger.class, BigDecimal.class)); put(Short.class, Arrays.asList((Class<?>) Short.class, Integer.class, Long.class, Float.class, Double.class, BigInteger.class, BigDecimal.class)); put(Integer.class, Arrays.asList((Class<?>) Integer.class, Long.class, Float.class, Double.class, BigInteger.class, BigDecimal.class)); put(Long.class, Arrays.asList((Class<?>) Long.class, Double.class, BigInteger.class, BigDecimal.class)); put(Float.class, Arrays.asList((Class<?>) Float.class, Double.class, BigDecimal.class)); put(Double.class, Arrays.asList((Class<?>) Double.class, BigDecimal.class)); put(BigInteger.class, Arrays.asList((Class<?>) BigInteger.class, BigDecimal.class)); } }; FeatureType featureType; public ExpressionTypeVisitor(FeatureType featureType) { this.featureType = featureType; } @Override public Object visit(NilExpression expression, Object extraData) { return Object.class; } @Override public Object visit(Add expression, Object extraData) { return visitBinaryExpression(expression); } @Override public Object visit(Divide expression, Object extraData) { return visitBinaryExpression(expression); } @Override public Object visit(Multiply expression, Object extraData) { return visitBinaryExpression(expression); } @Override public Object visit(Subtract expression, Object extraData) { return visitBinaryExpression(expression); } protected Class<?> visitBinaryExpression(BinaryExpression expression) { Class<?> type1 = (Class<?>) expression.getExpression1().accept(this, null); Class<?> type2 = (Class<?>) expression.getExpression2().accept(this, null); return largestType(type1, type2); } Class<?> largestType(Class<?> type1, Class<?> type2) { // start with the easy cases if (type1.isAssignableFrom(type2)) { return type1; } else if (type2.isAssignableFrom(type1)) { return type2; } // consider numbers and promotions if (Number.class.isAssignableFrom(type1) && Number.class.isAssignableFrom(type2)) { List<Class<?>> c1 = PROMOTIONS.get(type1); List<Class<?>> c2 = PROMOTIONS.get(type2); if (c1 == null || c2 == null) { return Object.class; } List<Class<?>> intersection = new ArrayList<>(c1); intersection.retainAll(c2); if (intersection.isEmpty()) { return Object.class; } else { // lowest shared type return intersection.get(0); } } // ok, let's get the most specific superclass then... return getCommonSuperclass(type1, type2); } /** * Traverses the super-classes trying to find a common superclass. Won't consider interfaces * (good enough for the moment, all basic types we handle have a common superclass) * * @param c1 * @param c2 * @return */ Class<?> getCommonSuperclass(Class<?> c1, Class<?> c2) { Class<?> curr = c1; while (!curr.isAssignableFrom(c2)) { curr = curr.getSuperclass(); } return curr; } @Override public Object visit(Function expression, Object extraData) { FunctionName name = expression.getFunctionName(); if (name != null && name.getReturn() != null) { return name.getReturn().getType(); } else { return Object.class; } } @Override public Object visit(Literal expression, Object extraData) { if (expression.getValue() != null) { return expression.getValue().getClass(); } else { return Object.class; } } @Override public Object visit(PropertyName expression, Object extraData) { AttributeDescriptor ad = expression.evaluate(featureType, AttributeDescriptor.class); if (ad != null) { return ad.getType().getBinding(); } else { return Object.class; } } }