blob: 0133a032e9a5bbe5c51000c3874f7c1849c228ce [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;
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);
}
}
}