| /* |
| * 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; |
| |
| import java.util.Map; |
| import java.util.Collection; |
| import java.util.Collections; |
| import org.apache.sis.util.Classes; |
| import org.apache.sis.util.iso.Names; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.ObjectConverters; |
| import org.apache.sis.util.UnconvertibleObjectException; |
| import org.apache.sis.util.collection.WeakValueHashMap; |
| import org.apache.sis.internal.feature.FeatureExpression; |
| import org.apache.sis.feature.builder.FeatureTypeBuilder; |
| import org.apache.sis.feature.builder.PropertyTypeBuilder; |
| |
| // Branch-dependent imports |
| import org.opengis.feature.Feature; |
| import org.opengis.feature.FeatureType; |
| import org.opengis.feature.PropertyType; |
| import org.opengis.feature.AttributeType; |
| import org.opengis.feature.IdentifiedType; |
| import org.opengis.feature.Operation; |
| import org.opengis.feature.PropertyNotFoundException; |
| import org.opengis.filter.expression.Expression; |
| import org.opengis.filter.expression.ExpressionVisitor; |
| |
| |
| /** |
| * Expressions that do not depend on any other expression. |
| * Those expression may read value from a feature property, or return a constant value. |
| * |
| * @author Johann Sorel (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.1 |
| * @since 1.1 |
| * @module |
| */ |
| abstract class LeafExpression extends Node implements Expression, FeatureExpression { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 4262341851590811918L; |
| |
| /** |
| * Creates a new property reader. |
| */ |
| LeafExpression() { |
| } |
| |
| /** |
| * Evaluates the expression for producing a result of the given type. |
| * If this method can not produce a value of the given type, then it returns {@code null}. |
| * This implementation evaluates the expression {@linkplain #evaluate(Object) in the default way}, |
| * then tries to convert the result to the target type. |
| * |
| * @param feature to feature to evaluate with this expression. |
| * @param target the desired type for the expression result. |
| * @return the result, or {@code null} if it can not be of the specified type. |
| */ |
| @Override |
| public final <T> T evaluate(final Object feature, final Class<T> target) { |
| ArgumentChecks.ensureNonNull("target", target); |
| final Object value = evaluate(feature); |
| try { |
| return ObjectConverters.convert(value, target); |
| } catch (UnconvertibleObjectException e) { |
| warning(e); |
| return null; // As per method contract. |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Expression whose value is computed by retrieving the value indicated by the provided name. |
| * A property name does not store any value; it acts as an indirection to a property value of |
| * the evaluated feature. |
| */ |
| static final class Property extends LeafExpression implements org.opengis.filter.expression.PropertyName { |
| /** For cross-version compatibility. */ |
| private static final long serialVersionUID = 3417789380239058201L; |
| |
| /** Name of the property from which to retrieve the value. */ |
| private final String name; |
| |
| /** Creates a new expression retrieving values from a property of the given name. */ |
| Property(final String name) { |
| ArgumentChecks.ensureNonNull("name", name); |
| this.name = name; |
| } |
| |
| /** Identification of this expression. */ |
| @Override protected String getName() { |
| return "PropertyName"; |
| } |
| |
| /** For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations. */ |
| @Override protected Collection<?> getChildren() { |
| return Collections.singleton(name); |
| } |
| |
| /** Returns the name of the property whose value will be returned by the {@link #evaluate(Object)} method. */ |
| @Override public String getPropertyName() { |
| return name; |
| } |
| |
| /** |
| * Returns the value of the property of the given name. |
| * The {@code candidate} object can be any of the following type: |
| * |
| * <ul> |
| * <li>A {@link Feature}, in which case {@link Feature#getPropertyValue(String)} will be invoked.</li> |
| * <li>A {@link Map}, in which case {@link Map#get(Object)} will be invoked.</li> |
| * </ul> |
| * |
| * If no value is found for the given feature, then this method returns {@code null}. |
| */ |
| @Override |
| public Object evaluate(final Object candidate) { |
| if (candidate instanceof Feature) try { |
| return ((Feature) candidate).getPropertyValue(name); |
| } catch (PropertyNotFoundException ex) { |
| warning(ex); |
| // Null will be returned below. |
| } else if (candidate instanceof Map<?,?>) { |
| return ((Map<?,?>) candidate).get(name); |
| } |
| return null; |
| } |
| |
| /** |
| * Provides the expected type of values produced by this expression when a feature of the given type is evaluated. |
| * |
| * @param valueType the type of features to be evaluated by the given expression. |
| * @param addTo where to add the type of properties evaluated by the given expression. |
| * @return builder of the added property, or {@code null} if this method can not add a property. |
| * @throws IllegalArgumentException if this method can not determine the property type for the given feature type. |
| */ |
| @Override |
| public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) { |
| PropertyType type = valueType.getProperty(name); // May throw IllegalArgumentException. |
| while (type instanceof Operation) { |
| final IdentifiedType result = ((Operation) type).getResult(); |
| if (result != type && result instanceof PropertyType) { |
| type = (PropertyType) result; |
| } else if (result instanceof FeatureType) { |
| return addTo.addAssociation((FeatureType) result).setName(name); |
| } else { |
| return null; |
| } |
| } |
| return addTo.addProperty(type); |
| } |
| |
| /** Implementation of the visitor pattern. */ |
| @Override public Object accept(final ExpressionVisitor visitor, final Object extraData) { |
| return visitor.visit(this, extraData); |
| } |
| } |
| |
| |
| |
| |
| /** |
| * A constant, literal value that can be used in expressions. |
| * The {@link #evaluate(Object)} method ignores the argument and always returns {@link #getValue()}. |
| */ |
| static final class Literal extends LeafExpression implements org.opengis.filter.expression.Literal { |
| /** For cross-version compatibility. */ |
| private static final long serialVersionUID = -8383113218490957822L; |
| |
| /** The constant value to be returned by {@link #getValue()}. */ |
| private final Object value; |
| |
| /** Creates a new literal holding the given constant value. */ |
| Literal(final Object value) { |
| ArgumentChecks.ensureNonNull("value", value); |
| this.value = value; |
| } |
| |
| /** Identification of this expression. */ |
| @Override protected String getName() { |
| return "Literal"; |
| } |
| |
| /** For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations. */ |
| @Override protected Collection<?> getChildren() { |
| return Collections.singleton(value); |
| } |
| |
| /** Returns the constant value held by this object. */ |
| @Override public Object getValue() { |
| return value; |
| } |
| |
| /** Expression evaluation, which just returns the constant value. */ |
| @Override public Object evaluate(Object ignored) { |
| return value; |
| } |
| |
| /** |
| * Provides the type of values returned by {@link #evaluate(Object)} |
| * wrapped in an {@link AttributeType} named "Literal". |
| * |
| * @param addTo where to add the type of properties evaluated by the given expression. |
| * @return builder of the added property. |
| */ |
| @Override |
| public PropertyTypeBuilder expectedType(FeatureType ignored, final FeatureTypeBuilder addTo) { |
| final Class<?> valueType = value.getClass(); |
| AttributeType<?> propertyType = TYPES.get(valueType); |
| if (propertyType == null) { |
| final Class<?> standardType = Classes.getStandardType(valueType); |
| propertyType = TYPES.computeIfAbsent(standardType, Literal::newType); |
| if (valueType != standardType) { |
| TYPES.put(valueType, propertyType); |
| } |
| } |
| return addTo.addProperty(propertyType); |
| } |
| |
| /** |
| * A cache of {@link AttributeType} instances for literal classes. Used for avoiding to create |
| * duplicated instances when the literal is a common type like {@link String} or {@link Integer}. |
| */ |
| @SuppressWarnings("unchecked") |
| private static final WeakValueHashMap<Class<?>, AttributeType<?>> TYPES = new WeakValueHashMap<>((Class) Class.class); |
| |
| /** |
| * Invoked when a new attribute type need to be created for the given standard type. |
| * The given standard type should be a GeoAPI interface, not the implementation class. |
| */ |
| private static <T> AttributeType<T> newType(final Class<T> standardType) { |
| return createType(standardType, Names.createLocalName(null, null, "Literal")); |
| } |
| |
| /** Implementation of the visitor pattern. */ |
| @Override public Object accept(final ExpressionVisitor visitor, final Object extraData) { |
| return visitor.visit(this, extraData); |
| } |
| } |
| } |