kinow commented on a change in pull request #23:
URL: https://github.com/apache/sis/pull/23#discussion_r656801677
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
##########
@@ -511,19 +540,29 @@ private static Number number(final boolean result) {
/**
- * The {@value #NAME} {@literal (<)} filter.
+ * The {@code "PropertyIsLessThan"} {@literal (<)} filter.
*/
- static final class LessThan extends ComparisonFunction implements
org.opengis.filter.PropertyIsLessThan {
+ static final class LessThan<R> extends ComparisonFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 6126039112844823196L;
- /** Creates a new filter for the {@value #NAME} operation. */
- LessThan(Expression expression1, Expression expression2, boolean
isMatchingCase, MatchAction matchAction) {
+ /** Creates a new filter. */
+ LessThan(final Expression<? super R, ?> expression1,
+ final Expression<? super R, ?> expression2,
+ boolean isMatchingCase, MatchAction matchAction)
+ {
super(expression1, expression2, isMatchingCase, matchAction);
}
- /** Identification of this operation. */
- @Override public String getName() {return NAME;}
+ /** Creates a new filter of the same type but different parameters. */
+ @Override public Filter<R> recreate(final Expression<? super R, ?>[]
effective) {
+ return new LessThan<>(effective[0], effective[1], isMatchingCase,
matchAction);
+ }
+
+ /** Identification of the this operation. */
Review comment:
I think either the or this, not both?
##########
File path:
storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
##########
@@ -16,506 +16,330 @@
*/
package org.apache.sis.internal.sql.feature;
-import java.util.Arrays;
import java.util.List;
-import java.util.Optional;
-import java.util.function.BooleanSupplier;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+import org.opengis.util.LocalName;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameSpace;
+import org.opengis.util.NameFactory;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
+import org.opengis.metadata.extent.GeographicBoundingBox;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.WraparoundMethod;
import org.apache.sis.internal.feature.Geometries;
import org.apache.sis.internal.feature.GeometryWrapper;
+import org.apache.sis.internal.filter.FunctionNames;
+import org.apache.sis.internal.filter.Visitor;
+import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.setup.GeometryLibrary;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.iso.Names;
-import org.opengis.filter.*;
-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.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.BinarySpatialOperator;
-import org.opengis.filter.spatial.Contains;
-import org.opengis.filter.spatial.Crosses;
-import org.opengis.filter.spatial.DWithin;
-import org.opengis.filter.spatial.Disjoint;
-import org.opengis.filter.spatial.Equals;
-import org.opengis.filter.spatial.Intersects;
-import org.opengis.filter.spatial.Overlaps;
-import org.opengis.filter.spatial.Touches;
-import org.opengis.filter.spatial.Within;
-import org.opengis.filter.temporal.*;
-import org.opengis.geometry.Envelope;
-import org.opengis.geometry.Geometry;
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.util.GenericName;
-import org.opengis.util.LocalName;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+import org.opengis.filter.ValueReference;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.ComparisonOperator;
+import org.opengis.filter.ComparisonOperatorName;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.SpatialOperator;
+import org.opengis.filter.SpatialOperatorName;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.LikeOperator;
+
/**
- * Port of Geotk FilterToSQL for an ANSI compliant query builder.
+ * Writes SQL statement for a filter or an expression.
+ * This base class is restricted to ANSI compliant SQL.
*
* @implNote For now, we over-use parenthesis to ensure consistent operator
priority. In the future, we could evolve
* this component to provide more elegant transcription of filter groups.
*
* No case insensitive support of binary comparison is done.
*
- * TODO: define a set of accepter property names (even better: link to {@link
FeatureAdapter}), so any {@link PropertyName}
+ * TODO: define a set of accepter property names (even better: link to {@link
FeatureAdapter}), so any {@link ValueReference}
* filter refering to non pure SQL property (like relations) will cause a
failure.
*
- * @author Alexis Manin (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @author Alexis Manin (Geomatys)
+ * @version 1.1
+ * @since 1.1
* @module
*/
-public class ANSIInterpreter implements FilterVisitor, ExpressionVisitor {
+class ANSIInterpreter extends Visitor<Feature,StringBuilder> {
/**
* TODO
*/
private static final GeometryLibrary LIBRARY = null;
- private final java.util.function.Function<Literal, CharSequence>
valueFormatter;
+ private final NameFactory nameFactory;
- private final java.util.function.Function<PropertyName, CharSequence>
nameFormatter;
+ private final NameSpace scope;
public ANSIInterpreter() {
- this(ANSIInterpreter::format, ANSIInterpreter::format);
- }
-
- public ANSIInterpreter(
- java.util.function.Function<Literal, CharSequence> valueFormatter,
- java.util.function.Function<PropertyName, CharSequence>
nameFormatter)
- {
- ArgumentChecks.ensureNonNull("valueFormatter", valueFormatter);
- ArgumentChecks.ensureNonNull("nameFormatter", nameFormatter);
- this.valueFormatter = valueFormatter;
- this.nameFormatter = nameFormatter;
- }
-
- @Override
- public CharSequence visitNullFilter(Object extraData) {
- throw new UnsupportedOperationException("Null filter is not
supported.");
- }
-
- @Override
- public Object visit(ExcludeFilter filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(IncludeFilter filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public CharSequence visit(And filter, Object extraData) {
- return join(filter, " AND ", extraData);
- }
-
- @Override
- public Object visit(Id filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(Not filter, Object extraData) {
- final CharSequence innerFilter = evaluateMandatory(filter.getFilter(),
extraData);
- return "NOT (" + innerFilter + ")";
- }
-
- @Override
- public Object visit(Or filter, Object extraData) {
- return join(filter, " OR ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsBetween filter, Object extraData) {
- final CharSequence propertyExp =
evaluateMandatory(filter.getExpression(), extraData);
- final CharSequence lowerExp =
evaluateMandatory(filter.getLowerBoundary(), extraData);
- final CharSequence upperExp =
evaluateMandatory(filter.getUpperBoundary(), extraData);
-
- return new StringBuilder(propertyExp)
- .append(" BETWEEN ")
- .append(lowerExp)
- .append(" AND ")
- .append(upperExp);
- }
-
- @Override
- public Object visit(PropertyIsEqualTo filter, Object extraData) {
- return joinMatchCase(filter, " = ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsNotEqualTo filter, Object extraData) {
- return joinMatchCase(filter, " <> ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsGreaterThan filter, Object extraData) {
- return joinMatchCase(filter, " > ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object
extraData) {
- return joinMatchCase(filter, " >= ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsLessThan filter, Object extraData) {
- return joinMatchCase(filter, " < ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) {
- return joinMatchCase(filter, " <= ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsLike filter, Object extraData) {
- // TODO: PostgreSQL extension : ilike
- ensureMatchCase(filter::isMatchingCase);
- // TODO: port Geotk
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(PropertyIsNull filter, Object extraData) {
- return evaluateMandatory(filter.getExpression(), extraData) + " IS
NULL";
- }
-
- @Override
- public Object visit(PropertyIsNil filter, Object extraData) {
- return evaluateMandatory(filter.getExpression(), extraData) + " IS
NULL";
+ nameFactory = DefaultFactories.forBuildin(NameFactory.class);
+ scope = nameFactory.createNameSpace(nameFactory.createLocalName(null,
"xpath"),
+
Collections.singletonMap("separator", "/"));
+
+ setFilterHandler(LogicalOperatorName.AND, new BinaryLogicJoin(" AND
"));
+ setFilterHandler(LogicalOperatorName.OR, new BinaryLogicJoin(" OR "));
+ setFilterHandler(LogicalOperatorName.NOT, (f,sb) -> {
+ final LogicalOperator<Feature> filter = (LogicalOperator<Feature>)
f;
+ evaluateMandatory(sb.append("NOT ("), filter.getOperands().get(0));
+ sb.append(')');
+ });
+
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN),
(f,sb) -> {
+ final BetweenComparisonOperator<Feature> filter =
(BetweenComparisonOperator<Feature>) f;
+ evaluateMandatory(sb, filter.getExpression());
+ evaluateMandatory(sb.append(" BETWEEN "),
filter.getLowerBoundary());
+ evaluateMandatory(sb.append(" AND "),
filter.getUpperBoundary());
+ });
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO,
new JoinMatchCase(" = "));
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO,
new JoinMatchCase(" <> "));
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN,
new JoinMatchCase(" > "));
+
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO,
new JoinMatchCase(" >= "));
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,
new JoinMatchCase(" < "));
+
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,
new JoinMatchCase(" <= "));
+
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE),
(f,sb) -> {
+ final LikeOperator<Feature> filter = (LikeOperator<Feature>) f;
+ ensureMatchCase(filter.isMatchingCase());
+ // TODO: port Geotk
+ throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ });
+ setNullAndNilHandlers((f,sb) -> {
+ final ComparisonOperator<Feature> filter =
(ComparisonOperator<Feature>) f;
+ evaluateMandatory(sb, filter.getExpressions().get(0));
+ sb.append(" IS NULL");
+ });
+ /*
+ * SPATIAL FILTERS
+ */
+ setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
+ final SpatialOperator<Feature> filter = (SpatialOperator<Feature>)
f;
+ // TODO: This is a wrong interpretation, but sqlmm has no
equivalent of filter encoding bbox, so we'll
+ // fallback on a standard intersection. However, PostGIS, H2, etc.
have their own versions of such filters.
+ for (final Expression<? super Feature, ?> e :
filter.getExpressions()) {
+ if (e == null) {
+ throw new UnsupportedOperationException("Not supported
yet: bbox over all geometric properties");
+ }
+ }
+ bbox(sb, filter);
+ });
+ setFilterHandler(SpatialOperatorName.CONTAINS, new
Function(FunctionNames.ST_Contains));
+ setFilterHandler(SpatialOperatorName.CROSSES, new
Function(FunctionNames.ST_Crosses));
+ setFilterHandler(SpatialOperatorName.DISJOINT, new
Function(FunctionNames.ST_Disjoint));
+ setFilterHandler(SpatialOperatorName.EQUALS, new
Function(FunctionNames.ST_Equals));
+ setFilterHandler(SpatialOperatorName.INTERSECTS, new
Function(FunctionNames.ST_Intersects));
+ setFilterHandler(SpatialOperatorName.OVERLAPS, new
Function(FunctionNames.ST_Overlaps));
+ setFilterHandler(SpatialOperatorName.TOUCHES, new
Function(FunctionNames.ST_Touches));
+ setFilterHandler(SpatialOperatorName.WITHIN, new
Function(FunctionNames.ST_Within));
+ /*
+ * Expression visitor
+ */
+ setExpressionHandler(FunctionNames.Add, new Join(" + "));
+ setExpressionHandler(FunctionNames.Subtract, new Join(" - "));
+ setExpressionHandler(FunctionNames.Divide, new Join(" / "));
+ setExpressionHandler(FunctionNames.Multiply, new Join(" * "));
+ setExpressionHandler(FunctionNames.Literal, (e,sb) -> writeLiteral(sb,
(Literal<Feature,?>) e));
+ setExpressionHandler(FunctionNames.ValueReference, (e,sb) ->
writeColumnName(sb, (ValueReference<Feature,?>) e));
}
- /*
- * SPATIAL FILTERS
+ /**
+ * Returns the SQL fragment to use for {@link SpatialOperatorName#BBOX}
type of filter.
*/
-
- @Override
- public Object visit(BBOX filter, Object extraData) {
- // TODO: This is a wrong interpretation, but sqlmm has no equivalent
of filter encoding bbox, so we'll
- // fallback on a standard intersection. However, PostGIS, H2, etc.
have their own versions of such filters.
- if (filter.getExpression1() == null || filter.getExpression2() == null)
- throw new UnsupportedOperationException("Not supported yet : bbox
over all geometric properties");
- return function("ST_Intersects", filter, extraData);
- }
-
- @Override
- public Object visit(Beyond filter, Object extraData) {
- // TODO: ISO SQL specifies that unit of distance could be specified.
However, PostGIS documentation does not
- // talk about it. For now, we'll fallback on Java implementation until
we're sure how to perform native
- // operation properly.
- throw new UnsupportedOperationException("Not yet: unit management
ambiguous");
- }
-
- @Override
- public Object visit(Contains filter, Object extraData) {
- return function("ST_Contains", filter, extraData);
- }
-
- @Override
- public Object visit(Crosses filter, Object extraData) {
- return function("ST_Crosses", filter, extraData);
- }
-
- @Override
- public Object visit(Disjoint filter, Object extraData) {
- return function("ST_Disjoint", filter, extraData);
- }
-
- @Override
- public Object visit(DWithin filter, Object extraData) {
- // TODO: as for beyond filter above, unit determination is a bit
complicated.
- throw new UnsupportedOperationException("Not yet: unit management to
handle properly");
- }
-
- @Override
- public Object visit(Equals filter, Object extraData) {
- return function("ST_Equals", filter, extraData);
- }
-
- @Override
- public Object visit(Intersects filter, Object extraData) {
- return function("ST_Intersects", filter, extraData);
- }
-
- @Override
- public Object visit(Overlaps filter, Object extraData) {
- return function("ST_Overlaps", filter, extraData);
- }
-
- @Override
- public Object visit(Touches filter, Object extraData) {
- return function("ST_Touches", filter, extraData);
- }
-
- @Override
- public Object visit(Within filter, Object extraData) {
- return function("ST_Within", filter, extraData);
+ void bbox(final StringBuilder sb, final SpatialOperator<Feature> filter) {
+ function(sb, "ST_Intersects", filter);
+ }
+
+ private static void writeLiteral(final StringBuilder sb, final
Literal<Feature,?> literal) {
+ Object value;
+ if (literal == null || (value = literal.getValue()) == null) {
+ sb.append("NULL");
+ } else if (value instanceof CharSequence) {
+ String text = value.toString();
+ text = text.replace("'", "''");
+ sb.append('\'').append(text).append('\'');
+ } else if (value instanceof Number || value instanceof Boolean) {
+ sb.append(value);
+ } else {
+ // Geometric special cases
+ if (value instanceof GeographicBoundingBox) {
+ value = new GeneralEnvelope((GeographicBoundingBox) value);
+ }
+ if (value instanceof Envelope) {
+ value = asGeometry((Envelope) value);
+ }
+ if (value instanceof Geometry) {
+ format(sb, (Geometry) value);
+ } else {
+ throw new UnsupportedOperationException("Not supported yet:
Literal value of type " + value.getClass());
+ }
+ }
}
- /*
- * TEMPORAL OPERATORS
+ /**
+ * Beware ! This implementation is a naïve one, expecting given property
name to match exactly SQL database names.
+ * In the future, it would be appreciable to be able to configure a mapper
between feature and SQL names.
+ *
+ * @param candidate name of property to insert in SQL statement.
*/
-
- @Override
- public Object visit(After filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(AnyInteracts filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(Before filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(Begins filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(BegunBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(During filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(EndedBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ private void writeColumnName(final StringBuilder sb, final
ValueReference<Feature,?> candidate) {
+ final GenericName name = nameFactory.parseGenericName(scope,
candidate.getXPath());
+ final List<? extends LocalName> components = name.getParsedNames();
+ final int n = components.size();
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append('.');
+ sb.append('"').append(components.get(i)).append('"');
+ }
}
- @Override
- public Object visit(Ends filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
+ private final class BinaryLogicJoin implements BiConsumer<Filter<Feature>,
StringBuilder> {
+ private final String operator;
- @Override
- public Object visit(Meets filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(MetBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(OverlappedBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
+ BinaryLogicJoin(final String operator) {
+ this.operator = operator;
+ }
- @Override
- public Object visit(TContains filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ @Override
+ public void accept(final Filter<Feature> f, final StringBuilder sb) {
+ final LogicalOperator<Feature> filter = (LogicalOperator<Feature>)
f;
+ final List<Filter<? super Feature>> subFilters =
filter.getOperands();
+ final int n = subFilters.size();
+ if (n != 0) {
+ sb.append('(');
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append(operator);
+ evaluateMandatory(sb, subFilters.get(i));
+ }
+ sb.append(')');
+ }
+ }
}
- @Override
- public Object visit(TEquals filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ private void join(final StringBuilder sb,
+ final List<Expression<? super Feature, ?>> expressions,
+ final int maxCount, final String operator)
+ {
+ sb.append('(');
+ final int n = Math.min(expressions.size(), maxCount);
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append(operator);
+ evaluateMandatory(sb, expressions.get(i));
+ }
+ sb.append(')');
}
- @Override
- public Object visit(TOverlaps filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
+ private final class JoinMatchCase implements BiConsumer<Filter<Feature>,
StringBuilder> {
+ private final String operator;
- /*
- * Expression visitor
- */
+ JoinMatchCase(final String operator) {
+ this.operator = operator;
+ }
- @Override
- public Object visit(NilExpression expression, Object extraData) {
- return "NULL";
+ @Override
+ public void accept(final Filter<Feature> f, final StringBuilder sb) {
+ final BinaryComparisonOperator<Feature> filter =
(BinaryComparisonOperator<Feature>) f;
+ ensureMatchCase(filter.isMatchingCase());
+ join(sb, filter.getExpressions(), 2, operator);
+ }
}
- @Override
- public Object visit(Add expression, Object extraData) {
- return join(expression, " + ", extraData);
+ protected final void join(final StringBuilder sb, final
SpatialOperator<Feature> op, final String operator) {
+ join(sb, op.getExpressions(), 2, operator);
}
- @Override
- public Object visit(Divide expression, Object extraData) {
- return join(expression, " / ", extraData);
- }
+ private final class Join implements BiConsumer<Expression<Feature,?>,
StringBuilder> {
+ private final String operator;
- @Override
- public Object visit(Function expression, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 01/10/2019
- }
+ Join(final String operator) {
+ this.operator = operator;
+ }
- @Override
- public Object visit(Literal expression, Object extraData) {
- return valueFormatter.apply(expression);
+ @Override
+ public void accept(final Expression<Feature,?> expression, final
StringBuilder sb) {
+ join(sb, expression.getParameters(), 2, operator);
+ }
}
- @Override
- public Object visit(Multiply expression, Object extraData) {
- return join(expression, " * ", extraData);
- }
+ private final class Function implements BiConsumer<Filter<Feature>,
StringBuilder> {
+ private final String name;
+ Function(final String name) {
+ this.name = name;
+ }
- @Override
- public Object visit(PropertyName expression, Object extraData) {
- return nameFormatter.apply(expression);
+ @Override
+ public void accept(final Filter<Feature> f, final StringBuilder sb) {
+ function(sb, name, (SpatialOperator<Feature>) f);
+ }
}
- @Override
- public Object visit(Subtract expression, Object extraData) {
- return join(expression, " - ", extraData);
+ private void function(final StringBuilder sb, final String name, final
SpatialOperator<Feature> filter) {
+ join(sb.append(name), filter.getExpressions(), Integer.MAX_VALUE, ",
");
}
- /*
- * UTILITIES
+ /**
+ * Executes the registered action for the given filter.
+ * Throws an exception if the filter did not wrote anything in the buffer.
+ *
+ * <h4>Note on type safety</h4>
+ * This method signature uses {@code <? super R>} for caller's convenience
because this is the type that
+ * we get from {@link LogicalOperator#getOperands()}. But the {@link
BiConsumer} uses exactly {@code <R>}
+ * type because doing otherwise causes complications with types that can
not be expressed in Java (kinds
+ * of {@code <? super ? super R>}). The cast in this method is okay if we
do not invoke any {@code filter}
+ * method with a return value (directly or indirectly as list elements) of
exactly {@code <R>} type.
+ * Such methods do not exist in the GeoAPI interfaces, so we are safe if
the {@link BiConsumer}
+ * does not invoke implementation-specific methods.
+ *
+ * @param sb where to write the result of all actions.
+ * @param filter the filter for which to execute an action based on its
type.
+ * @throws UnsupportedOperationException if there is no action registered
for the given filter.
*/
-
- protected static CharSequence format(Literal candidate) {
- Object value = candidate == null ? null : candidate.getValue();
- if (value == null) return "NULL";
- else if (value instanceof CharSequence) {
- final String asStr = value.toString();
- asStr.replace("'", "''");
- return "'"+asStr+"'";
- } else if (value instanceof Number || value instanceof Boolean) {
- return value.toString();
- }
-
- // geometric special cases
- if (value instanceof GeographicBoundingBox) {
- value = new GeneralEnvelope((GeographicBoundingBox) value);
- }
- if (value instanceof Envelope) {
- value = asGeometry((Envelope) value);
+ @SuppressWarnings("unchecked")
+ private void evaluateMandatory(final StringBuilder sb, final Filter<?
super Feature> filter) {
+ final int pos = sb.length();
+ visit((Filter<Feature>) filter, sb);
+ if (sb.length() <= pos) {
+ throw new IllegalArgumentException("Filter evaluate to an empty
text: " + filter);
}
- if (value instanceof Geometry) {
- return format((Geometry) value);
- }
- throw new UnsupportedOperationException("Not supported yet: Literal
value of type "+value.getClass());
}
/**
- * Beware ! This implementation is a naïve one, expecting given property
name to match exactly SQL database names.
- * In the future, it would be appreciable to be able to configure a mapper
between feature and SQL names.
- * @param candidate The property name to parse.
- * @return The SQL representation of the given name.
+ * Executes the registered action for the given expression.
+ * Throws an exception if the expression did not wrote anything in the
buffer.
Review comment:
s/did not wrote/did not write
##########
File path: core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
##########
@@ -43,21 +49,23 @@
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.And;
+import org.opengis.util.FactoryException;
+import org.opengis.feature.Feature;
+import org.opengis.filter.Expression;
import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.Or;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.PropertyName;
+import org.opengis.filter.FilterFactory;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.ValueReference;
import static org.apache.sis.internal.cql.CQLParser.*;
/**
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
Review comment:
Isn't the `@since` tag showing when the file was introduced? Shouldn't
it be left as `1.0`? Then any method added can receive a `@since 1.1`.
We used to have `@version` tags in Apache Commons too, first with SVN
commits (done automatically by subsversion) but during the move to Git we
decided to drop it, since it was hard to define the version when you had
multiple changes in a short time, in-between releases. So we ended up leaving
users to decide the version of the file based on git. Probably doesn't apply to
Sis.
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
##########
@@ -572,28 +616,33 @@ private static Number number(final boolean result) {
@Override protected boolean compare (ChronoLocalDate left,
ChronoLocalDate right) {return !left.isAfter(right);}
@Override protected boolean compare (ChronoLocalDateTime<?> left,
ChronoLocalDateTime<?> right) {return !left.isAfter(right);}
@Override protected boolean compare (ChronoZonedDateTime<?> left,
ChronoZonedDateTime<?> right) {return !left.isAfter(right);}
-
- /** Implementation of the visitor pattern (not used by Apache SIS). */
- @Override public Object accept(FilterVisitor visitor, Object
extraData) {
- return visitor.visit(this, extraData);
- }
}
/**
- * The {@value #NAME} {@literal (>)} filter.
+ * The {@code "PropertyIsGreaterThan"} {@literal (>)} filter.
*/
- static final class GreaterThan extends ComparisonFunction implements
org.opengis.filter.PropertyIsGreaterThan {
+ static final class GreaterThan<R> extends ComparisonFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 8605517892232632586L;
- /** Creates a new filter for the {@value #NAME} operation. */
- GreaterThan(Expression expression1, Expression expression2, boolean
isMatchingCase, MatchAction matchAction) {
+ /** Creates a new filter. */
+ GreaterThan(final Expression<? super R, ?> expression1,
+ final Expression<? super R, ?> expression2,
+ boolean isMatchingCase, MatchAction matchAction)
+ {
super(expression1, expression2, isMatchingCase, matchAction);
}
- /** Identification of this operation. */
- @Override public String getName() {return NAME;}
+ /** Creates a new filter of the same type but different parameters. */
+ @Override public Filter<R> recreate(final Expression<? super R, ?>[]
effective) {
+ return new GreaterThan<>(effective[0], effective[1],
isMatchingCase, matchAction);
+ }
+
+ /** Identification of the this operation. */
Review comment:
Ditto here, the or this?
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
##########
@@ -95,13 +103,34 @@
* @param isMatchingCase specifies whether comparisons are case
sensitive.
* @param matchAction specifies how the comparisons shall be
evaluated for a collection of values.
*/
- ComparisonFunction(final Expression expression1, final Expression
expression2, final boolean isMatchingCase, final MatchAction matchAction) {
+ ComparisonFunction(final Expression<? super R, ?> expression1,
+ final Expression<? super R, ?> expression2,
+ final boolean isMatchingCase, final MatchAction
matchAction)
+ {
super(expression1, expression2);
this.isMatchingCase = isMatchingCase;
this.matchAction = matchAction;
ArgumentChecks.ensureNonNull("matchAction", matchAction);
}
+ /**
+ * Returns the element on the left side of the comparison expression.
+ * This is the element at index 0 in the {@linkplain #getExpressions()
list of expressions}.
+ */
+ @Override
+ public final Expression<? super R, ?> getOperand1() {
+ return expression1;
+ }
+
+ /**
+ * Returns the element on the left side of the comparison expression.
+ * This is the element at index 0 in the {@linkplain #getExpressions()
list of expressions}.
Review comment:
Should it be the element on the right side, and at index 1? Just asking
because the description looks the same as the one from the method above this
one.
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
##########
@@ -16,788 +16,976 @@
*/
package org.apache.sis.filter;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
import java.util.Map;
+import java.util.HashMap;
+import java.util.Collection;
import java.util.ServiceLoader;
-import java.util.Set;
-import org.opengis.filter.*;
-import org.opengis.filter.capability.*;
-import org.opengis.filter.expression.Add;
-import org.opengis.filter.expression.Divide;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.opengis.filter.identity.FeatureId;
-import org.opengis.filter.identity.GmlObjectId;
-import org.opengis.filter.identity.Identifier;
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.sort.SortOrder;
-import org.opengis.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.Contains;
-import org.opengis.filter.spatial.Crosses;
-import org.opengis.filter.spatial.DWithin;
-import org.opengis.filter.spatial.Disjoint;
-import org.opengis.filter.spatial.Equals;
-import org.opengis.filter.spatial.Intersects;
-import org.opengis.filter.spatial.Overlaps;
-import org.opengis.filter.spatial.Touches;
-import org.opengis.filter.spatial.Within;
-import org.opengis.filter.temporal.*;
+import java.time.Instant;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.Geometry;
-import org.opengis.referencing.NoSuchAuthorityCodeException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.util.FactoryException;
-import org.opengis.util.GenericName;
-import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.system.SystemListener;
-import org.apache.sis.internal.feature.FunctionRegister;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.internal.feature.Geometries;
import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.internal.filter.sqlmm.SQLMM;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.internal.filter.sqlmm.Registry;
+import org.apache.sis.internal.filter.FunctionRegister;
+import org.apache.sis.geometry.WraparoundMethod;
+import org.apache.sis.util.iso.AbstractFactory;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.*;
+import org.opengis.feature.Feature;
+import org.opengis.filter.capability.FilterCapabilities;
/**
- * Default implementation of GeoAPI filter factory for creation of {@link
Filter} and {@link Expression} instances.
- *
- * <div class="warning"><b>Warning:</b> most methods in this class are still
unimplemented.
- * This is a very early draft subject to changes.
- * <b>TODO: the API of this class needs severe revision! DO NOT RELEASE.</b>
- * See <a href="https://github.com/opengeospatial/geoapi/issues/32">GeoAPI
issue #32</a>.</div>
+ * A factory of default {@link Filter} and {@link Expression} implementations.
+ * This base class operates on resources of arbitrary type {@code <R>}.
+ * Concrete subclass operates on resources of specific type such as {@link
org.opengis.feature.Feature}.
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.1
- * @since 1.1
+ *
+ * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) to use as inputs.
+ * @param <G> base class of geometry objects. The implementation-neutral
type is GeoAPI {@link Geometry},
+ * but this factory allows the use of other implementations such
as JTS
+ * {@link org.locationtech.jts.geom.Geometry} or ESRI {@link
com.esri.core.geometry.Geometry}.
+ * @param <T> base class of temporal objects.
+ *
+ * @since 1.1
* @module
*/
-public class DefaultFilterFactory implements FilterFactory2 {
+public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory
implements FilterFactory<R,G,T> {
/**
- * All functions identified by a name like {@code "cos"}, {@code "hypot"},
<i>etc</i>.
- * The actual function creations is delegated to an external factory such
as {@link SQLMM}.
- * The factories are fetched by {@link #function(String, Expression...)}
when first needed.
- * This factory is cleared if classpath changes, for allowing dynamic
reloading.
- *
- * @see #function(String, Expression...)
+ * The geometry library used by this factory.
*/
- private static final Map<String,FunctionRegister> FUNCTION_REGISTERS = new
HashMap<>();
- static {
- SystemListener.add(new SystemListener(Modules.FEATURE) {
- @Override protected void classpathChanged() {
- synchronized (FUNCTION_REGISTERS) {
- FUNCTION_REGISTERS.clear();
- }
- }
- });
- }
+ private final Geometries<G> library;
/**
- * According to OGC Filter encoding v2.0, comparison operators should
default to case sensitive comparison.
- * We use this constant to model it, so it will be easier to change
default value if the standard evolves.
- * Documentation reference: OGC 09-026r1 and ISO 19143:2010(E), section
7.7.3.2.
+ * The strategy to use for representing a region crossing the
anti-meridian.
*/
- private static final boolean DEFAULT_MATCH_CASE = true;
+ private final WraparoundMethod wraparound;
/**
- * Creates a new factory.
+ * All functions identified by a name like {@code "cos"}, {@code "hypot"},
<i>etc</i>.
+ * The actual function creations is delegated to an external factory such
as SQLMM registry.
Review comment:
s/creations/creation
##########
File path:
storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
##########
@@ -16,506 +16,330 @@
*/
package org.apache.sis.internal.sql.feature;
-import java.util.Arrays;
import java.util.List;
-import java.util.Optional;
-import java.util.function.BooleanSupplier;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import java.util.Collections;
+import java.util.function.BiConsumer;
+import org.opengis.util.LocalName;
+import org.opengis.util.GenericName;
+import org.opengis.util.NameSpace;
+import org.opengis.util.NameFactory;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
+import org.opengis.metadata.extent.GeographicBoundingBox;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.WraparoundMethod;
import org.apache.sis.internal.feature.Geometries;
import org.apache.sis.internal.feature.GeometryWrapper;
+import org.apache.sis.internal.filter.FunctionNames;
+import org.apache.sis.internal.filter.Visitor;
+import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.setup.GeometryLibrary;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.iso.Names;
-import org.opengis.filter.*;
-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.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.BinarySpatialOperator;
-import org.opengis.filter.spatial.Contains;
-import org.opengis.filter.spatial.Crosses;
-import org.opengis.filter.spatial.DWithin;
-import org.opengis.filter.spatial.Disjoint;
-import org.opengis.filter.spatial.Equals;
-import org.opengis.filter.spatial.Intersects;
-import org.opengis.filter.spatial.Overlaps;
-import org.opengis.filter.spatial.Touches;
-import org.opengis.filter.spatial.Within;
-import org.opengis.filter.temporal.*;
-import org.opengis.geometry.Envelope;
-import org.opengis.geometry.Geometry;
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.util.GenericName;
-import org.opengis.util.LocalName;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+import org.opengis.filter.ValueReference;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.ComparisonOperator;
+import org.opengis.filter.ComparisonOperatorName;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.SpatialOperator;
+import org.opengis.filter.SpatialOperatorName;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.LikeOperator;
+
/**
- * Port of Geotk FilterToSQL for an ANSI compliant query builder.
+ * Writes SQL statement for a filter or an expression.
+ * This base class is restricted to ANSI compliant SQL.
*
* @implNote For now, we over-use parenthesis to ensure consistent operator
priority. In the future, we could evolve
* this component to provide more elegant transcription of filter groups.
*
* No case insensitive support of binary comparison is done.
*
- * TODO: define a set of accepter property names (even better: link to {@link
FeatureAdapter}), so any {@link PropertyName}
+ * TODO: define a set of accepter property names (even better: link to {@link
FeatureAdapter}), so any {@link ValueReference}
* filter refering to non pure SQL property (like relations) will cause a
failure.
*
- * @author Alexis Manin (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @author Alexis Manin (Geomatys)
+ * @version 1.1
+ * @since 1.1
* @module
*/
-public class ANSIInterpreter implements FilterVisitor, ExpressionVisitor {
+class ANSIInterpreter extends Visitor<Feature,StringBuilder> {
/**
* TODO
*/
private static final GeometryLibrary LIBRARY = null;
- private final java.util.function.Function<Literal, CharSequence>
valueFormatter;
+ private final NameFactory nameFactory;
- private final java.util.function.Function<PropertyName, CharSequence>
nameFormatter;
+ private final NameSpace scope;
public ANSIInterpreter() {
- this(ANSIInterpreter::format, ANSIInterpreter::format);
- }
-
- public ANSIInterpreter(
- java.util.function.Function<Literal, CharSequence> valueFormatter,
- java.util.function.Function<PropertyName, CharSequence>
nameFormatter)
- {
- ArgumentChecks.ensureNonNull("valueFormatter", valueFormatter);
- ArgumentChecks.ensureNonNull("nameFormatter", nameFormatter);
- this.valueFormatter = valueFormatter;
- this.nameFormatter = nameFormatter;
- }
-
- @Override
- public CharSequence visitNullFilter(Object extraData) {
- throw new UnsupportedOperationException("Null filter is not
supported.");
- }
-
- @Override
- public Object visit(ExcludeFilter filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(IncludeFilter filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public CharSequence visit(And filter, Object extraData) {
- return join(filter, " AND ", extraData);
- }
-
- @Override
- public Object visit(Id filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(Not filter, Object extraData) {
- final CharSequence innerFilter = evaluateMandatory(filter.getFilter(),
extraData);
- return "NOT (" + innerFilter + ")";
- }
-
- @Override
- public Object visit(Or filter, Object extraData) {
- return join(filter, " OR ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsBetween filter, Object extraData) {
- final CharSequence propertyExp =
evaluateMandatory(filter.getExpression(), extraData);
- final CharSequence lowerExp =
evaluateMandatory(filter.getLowerBoundary(), extraData);
- final CharSequence upperExp =
evaluateMandatory(filter.getUpperBoundary(), extraData);
-
- return new StringBuilder(propertyExp)
- .append(" BETWEEN ")
- .append(lowerExp)
- .append(" AND ")
- .append(upperExp);
- }
-
- @Override
- public Object visit(PropertyIsEqualTo filter, Object extraData) {
- return joinMatchCase(filter, " = ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsNotEqualTo filter, Object extraData) {
- return joinMatchCase(filter, " <> ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsGreaterThan filter, Object extraData) {
- return joinMatchCase(filter, " > ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object
extraData) {
- return joinMatchCase(filter, " >= ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsLessThan filter, Object extraData) {
- return joinMatchCase(filter, " < ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) {
- return joinMatchCase(filter, " <= ", extraData);
- }
-
- @Override
- public Object visit(PropertyIsLike filter, Object extraData) {
- // TODO: PostgreSQL extension : ilike
- ensureMatchCase(filter::isMatchingCase);
- // TODO: port Geotk
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(PropertyIsNull filter, Object extraData) {
- return evaluateMandatory(filter.getExpression(), extraData) + " IS
NULL";
- }
-
- @Override
- public Object visit(PropertyIsNil filter, Object extraData) {
- return evaluateMandatory(filter.getExpression(), extraData) + " IS
NULL";
+ nameFactory = DefaultFactories.forBuildin(NameFactory.class);
+ scope = nameFactory.createNameSpace(nameFactory.createLocalName(null,
"xpath"),
+
Collections.singletonMap("separator", "/"));
+
+ setFilterHandler(LogicalOperatorName.AND, new BinaryLogicJoin(" AND
"));
+ setFilterHandler(LogicalOperatorName.OR, new BinaryLogicJoin(" OR "));
+ setFilterHandler(LogicalOperatorName.NOT, (f,sb) -> {
+ final LogicalOperator<Feature> filter = (LogicalOperator<Feature>)
f;
+ evaluateMandatory(sb.append("NOT ("), filter.getOperands().get(0));
+ sb.append(')');
+ });
+
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN),
(f,sb) -> {
+ final BetweenComparisonOperator<Feature> filter =
(BetweenComparisonOperator<Feature>) f;
+ evaluateMandatory(sb, filter.getExpression());
+ evaluateMandatory(sb.append(" BETWEEN "),
filter.getLowerBoundary());
+ evaluateMandatory(sb.append(" AND "),
filter.getUpperBoundary());
+ });
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO,
new JoinMatchCase(" = "));
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO,
new JoinMatchCase(" <> "));
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN,
new JoinMatchCase(" > "));
+
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO,
new JoinMatchCase(" >= "));
+ setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN,
new JoinMatchCase(" < "));
+
setFilterHandler(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO,
new JoinMatchCase(" <= "));
+
setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE),
(f,sb) -> {
+ final LikeOperator<Feature> filter = (LikeOperator<Feature>) f;
+ ensureMatchCase(filter.isMatchingCase());
+ // TODO: port Geotk
+ throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ });
+ setNullAndNilHandlers((f,sb) -> {
+ final ComparisonOperator<Feature> filter =
(ComparisonOperator<Feature>) f;
+ evaluateMandatory(sb, filter.getExpressions().get(0));
+ sb.append(" IS NULL");
+ });
+ /*
+ * SPATIAL FILTERS
+ */
+ setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
+ final SpatialOperator<Feature> filter = (SpatialOperator<Feature>)
f;
+ // TODO: This is a wrong interpretation, but sqlmm has no
equivalent of filter encoding bbox, so we'll
+ // fallback on a standard intersection. However, PostGIS, H2, etc.
have their own versions of such filters.
+ for (final Expression<? super Feature, ?> e :
filter.getExpressions()) {
+ if (e == null) {
+ throw new UnsupportedOperationException("Not supported
yet: bbox over all geometric properties");
+ }
+ }
+ bbox(sb, filter);
+ });
+ setFilterHandler(SpatialOperatorName.CONTAINS, new
Function(FunctionNames.ST_Contains));
+ setFilterHandler(SpatialOperatorName.CROSSES, new
Function(FunctionNames.ST_Crosses));
+ setFilterHandler(SpatialOperatorName.DISJOINT, new
Function(FunctionNames.ST_Disjoint));
+ setFilterHandler(SpatialOperatorName.EQUALS, new
Function(FunctionNames.ST_Equals));
+ setFilterHandler(SpatialOperatorName.INTERSECTS, new
Function(FunctionNames.ST_Intersects));
+ setFilterHandler(SpatialOperatorName.OVERLAPS, new
Function(FunctionNames.ST_Overlaps));
+ setFilterHandler(SpatialOperatorName.TOUCHES, new
Function(FunctionNames.ST_Touches));
+ setFilterHandler(SpatialOperatorName.WITHIN, new
Function(FunctionNames.ST_Within));
+ /*
+ * Expression visitor
+ */
+ setExpressionHandler(FunctionNames.Add, new Join(" + "));
+ setExpressionHandler(FunctionNames.Subtract, new Join(" - "));
+ setExpressionHandler(FunctionNames.Divide, new Join(" / "));
+ setExpressionHandler(FunctionNames.Multiply, new Join(" * "));
+ setExpressionHandler(FunctionNames.Literal, (e,sb) -> writeLiteral(sb,
(Literal<Feature,?>) e));
+ setExpressionHandler(FunctionNames.ValueReference, (e,sb) ->
writeColumnName(sb, (ValueReference<Feature,?>) e));
}
- /*
- * SPATIAL FILTERS
+ /**
+ * Returns the SQL fragment to use for {@link SpatialOperatorName#BBOX}
type of filter.
*/
-
- @Override
- public Object visit(BBOX filter, Object extraData) {
- // TODO: This is a wrong interpretation, but sqlmm has no equivalent
of filter encoding bbox, so we'll
- // fallback on a standard intersection. However, PostGIS, H2, etc.
have their own versions of such filters.
- if (filter.getExpression1() == null || filter.getExpression2() == null)
- throw new UnsupportedOperationException("Not supported yet : bbox
over all geometric properties");
- return function("ST_Intersects", filter, extraData);
- }
-
- @Override
- public Object visit(Beyond filter, Object extraData) {
- // TODO: ISO SQL specifies that unit of distance could be specified.
However, PostGIS documentation does not
- // talk about it. For now, we'll fallback on Java implementation until
we're sure how to perform native
- // operation properly.
- throw new UnsupportedOperationException("Not yet: unit management
ambiguous");
- }
-
- @Override
- public Object visit(Contains filter, Object extraData) {
- return function("ST_Contains", filter, extraData);
- }
-
- @Override
- public Object visit(Crosses filter, Object extraData) {
- return function("ST_Crosses", filter, extraData);
- }
-
- @Override
- public Object visit(Disjoint filter, Object extraData) {
- return function("ST_Disjoint", filter, extraData);
- }
-
- @Override
- public Object visit(DWithin filter, Object extraData) {
- // TODO: as for beyond filter above, unit determination is a bit
complicated.
- throw new UnsupportedOperationException("Not yet: unit management to
handle properly");
- }
-
- @Override
- public Object visit(Equals filter, Object extraData) {
- return function("ST_Equals", filter, extraData);
- }
-
- @Override
- public Object visit(Intersects filter, Object extraData) {
- return function("ST_Intersects", filter, extraData);
- }
-
- @Override
- public Object visit(Overlaps filter, Object extraData) {
- return function("ST_Overlaps", filter, extraData);
- }
-
- @Override
- public Object visit(Touches filter, Object extraData) {
- return function("ST_Touches", filter, extraData);
- }
-
- @Override
- public Object visit(Within filter, Object extraData) {
- return function("ST_Within", filter, extraData);
+ void bbox(final StringBuilder sb, final SpatialOperator<Feature> filter) {
+ function(sb, "ST_Intersects", filter);
+ }
+
+ private static void writeLiteral(final StringBuilder sb, final
Literal<Feature,?> literal) {
+ Object value;
+ if (literal == null || (value = literal.getValue()) == null) {
+ sb.append("NULL");
+ } else if (value instanceof CharSequence) {
+ String text = value.toString();
+ text = text.replace("'", "''");
+ sb.append('\'').append(text).append('\'');
+ } else if (value instanceof Number || value instanceof Boolean) {
+ sb.append(value);
+ } else {
+ // Geometric special cases
+ if (value instanceof GeographicBoundingBox) {
+ value = new GeneralEnvelope((GeographicBoundingBox) value);
+ }
+ if (value instanceof Envelope) {
+ value = asGeometry((Envelope) value);
+ }
+ if (value instanceof Geometry) {
+ format(sb, (Geometry) value);
+ } else {
+ throw new UnsupportedOperationException("Not supported yet:
Literal value of type " + value.getClass());
+ }
+ }
}
- /*
- * TEMPORAL OPERATORS
+ /**
+ * Beware ! This implementation is a naïve one, expecting given property
name to match exactly SQL database names.
+ * In the future, it would be appreciable to be able to configure a mapper
between feature and SQL names.
+ *
+ * @param candidate name of property to insert in SQL statement.
*/
-
- @Override
- public Object visit(After filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(AnyInteracts filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(Before filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(Begins filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(BegunBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(During filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(EndedBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ private void writeColumnName(final StringBuilder sb, final
ValueReference<Feature,?> candidate) {
+ final GenericName name = nameFactory.parseGenericName(scope,
candidate.getXPath());
+ final List<? extends LocalName> components = name.getParsedNames();
+ final int n = components.size();
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append('.');
+ sb.append('"').append(components.get(i)).append('"');
+ }
}
- @Override
- public Object visit(Ends filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
+ private final class BinaryLogicJoin implements BiConsumer<Filter<Feature>,
StringBuilder> {
+ private final String operator;
- @Override
- public Object visit(Meets filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(MetBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
-
- @Override
- public Object visit(OverlappedBy filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
+ BinaryLogicJoin(final String operator) {
+ this.operator = operator;
+ }
- @Override
- public Object visit(TContains filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ @Override
+ public void accept(final Filter<Feature> f, final StringBuilder sb) {
+ final LogicalOperator<Feature> filter = (LogicalOperator<Feature>)
f;
+ final List<Filter<? super Feature>> subFilters =
filter.getOperands();
+ final int n = subFilters.size();
+ if (n != 0) {
+ sb.append('(');
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append(operator);
+ evaluateMandatory(sb, subFilters.get(i));
+ }
+ sb.append(')');
+ }
+ }
}
- @Override
- public Object visit(TEquals filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
+ private void join(final StringBuilder sb,
+ final List<Expression<? super Feature, ?>> expressions,
+ final int maxCount, final String operator)
+ {
+ sb.append('(');
+ final int n = Math.min(expressions.size(), maxCount);
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append(operator);
+ evaluateMandatory(sb, expressions.get(i));
+ }
+ sb.append(')');
}
- @Override
- public Object visit(TOverlaps filter, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 30/09/2019
- }
+ private final class JoinMatchCase implements BiConsumer<Filter<Feature>,
StringBuilder> {
+ private final String operator;
- /*
- * Expression visitor
- */
+ JoinMatchCase(final String operator) {
+ this.operator = operator;
+ }
- @Override
- public Object visit(NilExpression expression, Object extraData) {
- return "NULL";
+ @Override
+ public void accept(final Filter<Feature> f, final StringBuilder sb) {
+ final BinaryComparisonOperator<Feature> filter =
(BinaryComparisonOperator<Feature>) f;
+ ensureMatchCase(filter.isMatchingCase());
+ join(sb, filter.getExpressions(), 2, operator);
+ }
}
- @Override
- public Object visit(Add expression, Object extraData) {
- return join(expression, " + ", extraData);
+ protected final void join(final StringBuilder sb, final
SpatialOperator<Feature> op, final String operator) {
+ join(sb, op.getExpressions(), 2, operator);
}
- @Override
- public Object visit(Divide expression, Object extraData) {
- return join(expression, " / ", extraData);
- }
+ private final class Join implements BiConsumer<Expression<Feature,?>,
StringBuilder> {
+ private final String operator;
- @Override
- public Object visit(Function expression, Object extraData) {
- throw new UnsupportedOperationException("Not supported yet"); //
"Alexis Manin (Geomatys)" on 01/10/2019
- }
+ Join(final String operator) {
+ this.operator = operator;
+ }
- @Override
- public Object visit(Literal expression, Object extraData) {
- return valueFormatter.apply(expression);
+ @Override
+ public void accept(final Expression<Feature,?> expression, final
StringBuilder sb) {
+ join(sb, expression.getParameters(), 2, operator);
+ }
}
- @Override
- public Object visit(Multiply expression, Object extraData) {
- return join(expression, " * ", extraData);
- }
+ private final class Function implements BiConsumer<Filter<Feature>,
StringBuilder> {
+ private final String name;
+ Function(final String name) {
+ this.name = name;
+ }
- @Override
- public Object visit(PropertyName expression, Object extraData) {
- return nameFormatter.apply(expression);
+ @Override
+ public void accept(final Filter<Feature> f, final StringBuilder sb) {
+ function(sb, name, (SpatialOperator<Feature>) f);
+ }
}
- @Override
- public Object visit(Subtract expression, Object extraData) {
- return join(expression, " - ", extraData);
+ private void function(final StringBuilder sb, final String name, final
SpatialOperator<Feature> filter) {
+ join(sb.append(name), filter.getExpressions(), Integer.MAX_VALUE, ",
");
}
- /*
- * UTILITIES
+ /**
+ * Executes the registered action for the given filter.
+ * Throws an exception if the filter did not wrote anything in the buffer.
Review comment:
s/did not wrote/did not write
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ * <li>Application of some logical identities such as {@code NOT(NOT(A)) ==
A}.</li>
+ * <li>Application of logical short circuits such as {@code A & FALSE ==
FALSE}.</li>
+ * <li>Immediate evaluation of expressions where all parameters are literal
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ * Filter<R> filter = ...;
+ * filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization}
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A}
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public class Optimization {
+ /**
+ * An arbitrary object meaning that a filter or expression optimization in
under progress.
Review comment:
/in under progress/is under progress
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ * <li>Application of some logical identities such as {@code NOT(NOT(A)) ==
A}.</li>
+ * <li>Application of logical short circuits such as {@code A & FALSE ==
FALSE}.</li>
+ * <li>Immediate evaluation of expressions where all parameters are literal
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ * Filter<R> filter = ...;
+ * filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization}
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A}
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public class Optimization {
+ /**
+ * An arbitrary object meaning that a filter or expression optimization in
under progress.
+ */
+ private static final Object COMPUTING = Void.TYPE;
+
+ /**
+ * Filters and expressions already optimized. Also used for avoiding
never-ending loops.
+ * The map is created when first needed.
+ *
+ * <div class="note"><b>Note:</b> the same map is used for filters and
expressions.
+ * It is not a problem if keys do not implement the two interfaces in same
time.
+ * If it happens anyway, it should still be okay because the method
signatures are
+ * the same in both interfaces (only the return type changes), so the same
methods
+ * would be invoked no matter if we consider the keys as a filter or an
expression.</div>
+ */
+ private Map<Object,Object> done;
+
+ /**
+ * Creates a new instance.
+ */
+ public Optimization() {
+ }
+
+ /**
+ * Optimizes or simplifies the given filter. If the given instance
implements the {@link OnFilter} interface,
+ * then its {@code optimize(this)} method is invoked. Otherwise this
method returns the given filter as-is.
+ *
+ * @param <R> the type of resources (e.g. {@code Feature}) used as
inputs.
+ * @param filter the filter to optimize, or {@code null}.
+ * @return the optimized filter, or {@code null} if the given filter was
null.
+ * May be {@code filter} if no optimization or simplification has
been applied.
+ * @throws IllegalArgumentException if the given filter is already in
process of being optimized
+ * (i.e. there is a recursive call to {@code apply(…)} for the
same filter).
+ */
+ public <R> Filter<? super R> apply(final Filter<R> filter) {
+ if (!(filter instanceof OnFilter<?>)) {
+ return filter;
+ }
+ final boolean isFirstCall = (done == null);
+ if (isFirstCall) {
+ done = new IdentityHashMap<>();
+ }
+ try {
+ final Object previous = done.putIfAbsent(filter, COMPUTING);
+ if (previous == COMPUTING) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.RecursiveCreateCallForKey_1,
filter.getOperatorType()));
+ }
+ @SuppressWarnings("unchecked")
+ Filter<? super R> result = (Filter<? super R>) previous;
+ if (result == null) {
+ result = ((OnFilter<R>) filter).optimize(this);
+ if (done.put(filter, result) != COMPUTING) {
+ // Should not happen unless this `Optimization` is used
concurrently in many threads.
+ throw new ConcurrentModificationException();
+ }
+ }
+ return result;
+ } finally {
+ if (isFirstCall) {
+ done = null;
+ }
+ }
+ }
+
+ /**
+ * Filter than can be optimized. Each filter implementation knows which
rules can be applied.
+ * For that reason, the optimization algorithms are keep in each
implementation class.
Review comment:
a/are keep/are kept
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ * <li>Application of some logical identities such as {@code NOT(NOT(A)) ==
A}.</li>
+ * <li>Application of logical short circuits such as {@code A & FALSE ==
FALSE}.</li>
+ * <li>Immediate evaluation of expressions where all parameters are literal
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ * Filter<R> filter = ...;
+ * filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization}
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A}
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public class Optimization {
+ /**
+ * An arbitrary object meaning that a filter or expression optimization in
under progress.
+ */
+ private static final Object COMPUTING = Void.TYPE;
+
+ /**
+ * Filters and expressions already optimized. Also used for avoiding
never-ending loops.
+ * The map is created when first needed.
+ *
+ * <div class="note"><b>Note:</b> the same map is used for filters and
expressions.
+ * It is not a problem if keys do not implement the two interfaces in same
time.
Review comment:
s/in same time/at the same time
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
##########
@@ -0,0 +1,183 @@
+/*
+ * 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.List;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
+import org.opengis.geometry.Geometry;
+import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.internal.feature.GeometryWrapper;
+import org.apache.sis.internal.feature.SpatialOperationContext;
+import org.apache.sis.util.ArgumentChecks;
+
+// Branch-dependent imports
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+import org.opengis.filter.DistanceOperator;
+import org.opengis.filter.DistanceOperatorName;
+
+
+/**
+ * Spatial operations between two geometries and using a distance.
+ * The nature of the operation depends on the subclass.
+ *
+ * <div class="note"><b>Note:</b>
+ * this class has 3 parameters, but the third one is not an expression.
+ * It still a "binary" operator if we count only the expressions.</div>
Review comment:
s/It still/It is still
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.List;
+import java.util.IdentityHashMap;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+
+
+/**
+ * Description of optimizations or simplifications to attempt on filters and
expressions.
+ * Optimizations can include the following changes:
+ *
+ * <ul>
+ * <li>Application of some logical identities such as {@code NOT(NOT(A)) ==
A}.</li>
+ * <li>Application of logical short circuits such as {@code A & FALSE ==
FALSE}.</li>
+ * <li>Immediate evaluation of expressions where all parameters are literal
values.</li>
+ * </ul>
+ *
+ * Current version does not yet provide configuration options.
+ * But this class is the place where such options may be added in the future.
+ *
+ * <p>This class is <strong>not</strong> thread-safe. A new instance shall be
created
+ * for each thread applying optimizations. Example:</p>
+ *
+ * {@preformat java
+ * Filter<R> filter = ...;
+ * filter = new Optimization().apply(filter);
+ * }
+ *
+ * <h2>How optimizations are applied</h2>
+ * Optimizations are specific to each expression and filter type.
+ * For optimizations to happen, classes must implement the {@link
OnExpression} or {@link OnFilter} interface.
+ * The {@link #apply(Filter)} and {@link #apply(Expression)} methods in this
{@code Optimization} class merely
+ * delegate to the methods defined in above-cited interfaces, with safety
guards against infinite recursivity.
+ *
+ * <h2>Behavioral changes</h2>
+ * Optimized filters shall produce the same results than non-optimized filters.
+ * However side-effects may differ, in particular regarding exceptions that
may be thrown.
+ * For example if a filter tests {@code A & B} and if {@code Optimization}
determines that the {@code B}
+ * condition will always evaluate to {@code false}, then the {@code A}
condition will never be tested.
+ * If that condition had side-effects or threw an exception,
+ * those effects will disappear in the optimized filter.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public class Optimization {
+ /**
+ * An arbitrary object meaning that a filter or expression optimization in
under progress.
+ */
+ private static final Object COMPUTING = Void.TYPE;
+
+ /**
+ * Filters and expressions already optimized. Also used for avoiding
never-ending loops.
+ * The map is created when first needed.
+ *
+ * <div class="note"><b>Note:</b> the same map is used for filters and
expressions.
+ * It is not a problem if keys do not implement the two interfaces in same
time.
+ * If it happens anyway, it should still be okay because the method
signatures are
+ * the same in both interfaces (only the return type changes), so the same
methods
+ * would be invoked no matter if we consider the keys as a filter or an
expression.</div>
+ */
+ private Map<Object,Object> done;
+
+ /**
+ * Creates a new instance.
+ */
+ public Optimization() {
+ }
+
+ /**
+ * Optimizes or simplifies the given filter. If the given instance
implements the {@link OnFilter} interface,
+ * then its {@code optimize(this)} method is invoked. Otherwise this
method returns the given filter as-is.
+ *
+ * @param <R> the type of resources (e.g. {@code Feature}) used as
inputs.
+ * @param filter the filter to optimize, or {@code null}.
+ * @return the optimized filter, or {@code null} if the given filter was
null.
+ * May be {@code filter} if no optimization or simplification has
been applied.
+ * @throws IllegalArgumentException if the given filter is already in
process of being optimized
+ * (i.e. there is a recursive call to {@code apply(…)} for the
same filter).
+ */
+ public <R> Filter<? super R> apply(final Filter<R> filter) {
+ if (!(filter instanceof OnFilter<?>)) {
+ return filter;
+ }
+ final boolean isFirstCall = (done == null);
+ if (isFirstCall) {
+ done = new IdentityHashMap<>();
+ }
+ try {
+ final Object previous = done.putIfAbsent(filter, COMPUTING);
+ if (previous == COMPUTING) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.RecursiveCreateCallForKey_1,
filter.getOperatorType()));
+ }
+ @SuppressWarnings("unchecked")
+ Filter<? super R> result = (Filter<? super R>) previous;
+ if (result == null) {
+ result = ((OnFilter<R>) filter).optimize(this);
+ if (done.put(filter, result) != COMPUTING) {
+ // Should not happen unless this `Optimization` is used
concurrently in many threads.
+ throw new ConcurrentModificationException();
+ }
+ }
+ return result;
+ } finally {
+ if (isFirstCall) {
+ done = null;
+ }
+ }
+ }
+
+ /**
+ * Filter than can be optimized. Each filter implementation knows which
rules can be applied.
Review comment:
s/than can be/that can be
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
##########
@@ -248,13 +159,38 @@ public PropertyTypeBuilder expectedType(FeatureType
ignored, final FeatureTypeBu
* 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) {
+ private static <R> AttributeType<R> newType(final Class<R>
standardType) {
return createType(standardType, Names.createLocalName(null, null,
"Literal"));
}
+ }
+
+
+
+
Review comment:
Unnecessary extra lines? No harm in leaving them though :man_shrugging:
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
##########
@@ -0,0 +1,228 @@
+/*
+ * 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.internal.filter.sqlmm;
+
+import java.util.EnumMap;
+import java.util.Collection;
+import org.opengis.util.LocalName;
+import org.opengis.util.ScopedName;
+import org.apache.sis.filter.Optimization;
+import org.apache.sis.internal.filter.Node;
+import org.apache.sis.internal.feature.Resources;
+import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.internal.feature.FeatureExpression;
+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.resources.Errors;
+import org.apache.sis.util.iso.Names;
+
+// Branch-dependent imports
+import org.opengis.feature.FeatureType;
+import org.opengis.filter.Expression;
+import org.opengis.filter.InvalidFilterValueException;
+
+
+/**
+ * Base class of SQLMM spatial functions.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
+ *
+ * @since 1.1
+ * @module
+ */
+abstract class SpatialFunction<R> extends Node implements
FeatureExpression<R,Object>, Optimization.OnExpression<R,Object> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 6933519274722660893L;
+
+ /**
+ * Scope of all names defined by SQLMM standard.
+ *
+ * @see #createName(SQLMM)
+ */
+ private static final LocalName SCOPE = Names.createLocalName("ISO", null,
"sqlmm");;
+
+ /**
+ * Identification of the SQLMM operation.
+ */
+ final SQLMM operation;
+
+ /**
+ * All operation names as {@link ScopedName} instances.
+ * Values are {@linkplain #createName(SQLMM) created} when first needed.
+ */
+ private static final EnumMap<SQLMM, ScopedName> NAMES = new
EnumMap<>(SQLMM.class);
+
+ /**
+ * Creates a new function. This constructor verifies that the number of
parameters
+ * is between {@link SQLMM#minParamCount} and {@link SQLMM#maxParamCount}
inclusive,
+ * but does not store the parameters. Parameters shall be stored by
subclasses.
+ *
+ * @param operation identification of the SQLMM operation.
+ * @param parameters sub-expressions that will be evaluated to provide
the parameters to the function.
+ * @throws IllegalArgumentException if the number of parameters is not in
the expected range.
+ */
+ SpatialFunction(final SQLMM operation, final Expression<? super R, ?>[]
parameters) {
+ this.operation = operation;
+ final int n = parameters.length;
+ final int minParamCount = operation.minParamCount;
+ final String message;
+ if (n < minParamCount) {
+ if (n == 0) {
+ message = Errors.format(Errors.Keys.EmptyArgument_1,
"parameters");
+ } else {
+ message = Errors.format(Errors.Keys.TooFewArguments_2,
minParamCount, n);
+ }
+ } else {
+ final int maxParamCount = operation.maxParamCount;
+ if (n > maxParamCount) {
+ message = Errors.format(Errors.Keys.TooManyArguments_2,
maxParamCount, n);
+ } else {
+ return;
+ }
+ }
+ throw new IllegalArgumentException(message);
+ }
+
+ /**
+ * Returns a handler for the library of geometric objects used by this
expression.
+ * This is typically implemented by a call to {@code
getGeometryLibrary(geometry)}
+ * where {@code geometry} as the first expression returning a geometry
object.
+ *
+ * @return the geometry library (never {@code null}).
+ *
+ * @see #getGeometryLibrary(Expression)
+ */
+ abstract Geometries<?> getGeometryLibrary();
+
+ /**
+ * Returns the name of the function to be called. This method returns
+ * a scoped name with the {@link SQLMM} function name in the local part.
+ */
+ @Override
+ public final ScopedName getFunctionName() {
+ synchronized (NAMES) {
+ return NAMES.computeIfAbsent(operation,
SpatialFunction::createName);
+ }
+ }
+
+ /**
+ * Invoked by {@link #getFunctionName()} when a name needs to be created.
+ */
+ private static ScopedName createName(final SQLMM operation) {
+ return Names.createScopedName(SCOPE, null, operation.name());
+ }
+
+ /**
+ * Returns the children of this node, which are the {@linkplain
#getParameters() parameters list}.
+ * This is used for information purpose only, for example in order to
build a string representation.
+ *
+ * @return the children of this node.
+ */
+ @Override
+ protected final Collection<?> getChildren() {
+ return getParameters();
+ }
+
+ /**
+ * Returns a Backus-Naur Form (BNF) of this function.
+ *
+ * @todo Fetch parameter names from {@code FilterCapabilities}.
+ * Or maybe use annotations, which would also be used for
capabilities implementation.
+ */
+ public String getSyntax() {
+ final int minParamCount = operation.minParamCount;
+ final int maxParamCount = operation.maxParamCount;
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getFunctionName().tip()).append('(');
+ for (int i = 0; i < maxParamCount; i++) {
+ if (i == minParamCount) sb.append('[');
+ if (i != 0) sb.append(", ");
+ sb.append("param").append(i + 1);
+ }
+ if (maxParamCount > minParamCount) {
+ sb.append(']');
+ }
+ return sb.append(')').toString();
+ }
+
+ /**
+ * Returns the kind of objects evaluated by this expression.
+ */
+ @Override
+ public final Class<?> getValueClass() {
+ return operation.getReturnType(getGeometryLibrary());
+ }
+
+ /**
+ * Returns {@code this} if this expression provides values of the
specified type,
+ * or throws an exception otherwise.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public final <N> Expression<R,N> toValueType(final Class<N> type) {
+ if (type.isAssignableFrom(getValueClass())) {
+ return (Expression<R,N>) this;
+ } else {
+ throw new
ClassCastException(Errors.format(Errors.Keys.CanNotConvertValue_2,
getFunctionName(), type));
+ }
+ }
+
+ /**
+ * Provides the type of values produced by this expression when a feature
of the given type is evaluated.
+ * There is two cases:
Review comment:
s/There is two cases/There are two cases
##########
File path:
core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
##########
@@ -16,788 +16,976 @@
*/
package org.apache.sis.filter;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
import java.util.Map;
+import java.util.HashMap;
+import java.util.Collection;
import java.util.ServiceLoader;
-import java.util.Set;
-import org.opengis.filter.*;
-import org.opengis.filter.capability.*;
-import org.opengis.filter.expression.Add;
-import org.opengis.filter.expression.Divide;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.opengis.filter.identity.FeatureId;
-import org.opengis.filter.identity.GmlObjectId;
-import org.opengis.filter.identity.Identifier;
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.sort.SortOrder;
-import org.opengis.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.Contains;
-import org.opengis.filter.spatial.Crosses;
-import org.opengis.filter.spatial.DWithin;
-import org.opengis.filter.spatial.Disjoint;
-import org.opengis.filter.spatial.Equals;
-import org.opengis.filter.spatial.Intersects;
-import org.opengis.filter.spatial.Overlaps;
-import org.opengis.filter.spatial.Touches;
-import org.opengis.filter.spatial.Within;
-import org.opengis.filter.temporal.*;
+import java.time.Instant;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.Geometry;
-import org.opengis.referencing.NoSuchAuthorityCodeException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.util.FactoryException;
-import org.opengis.util.GenericName;
-import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.system.SystemListener;
-import org.apache.sis.internal.feature.FunctionRegister;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.internal.feature.Geometries;
import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.internal.filter.sqlmm.SQLMM;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.internal.filter.sqlmm.Registry;
+import org.apache.sis.internal.filter.FunctionRegister;
+import org.apache.sis.geometry.WraparoundMethod;
+import org.apache.sis.util.iso.AbstractFactory;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.*;
+import org.opengis.feature.Feature;
+import org.opengis.filter.capability.FilterCapabilities;
/**
- * Default implementation of GeoAPI filter factory for creation of {@link
Filter} and {@link Expression} instances.
- *
- * <div class="warning"><b>Warning:</b> most methods in this class are still
unimplemented.
- * This is a very early draft subject to changes.
- * <b>TODO: the API of this class needs severe revision! DO NOT RELEASE.</b>
- * See <a href="https://github.com/opengeospatial/geoapi/issues/32">GeoAPI
issue #32</a>.</div>
+ * A factory of default {@link Filter} and {@link Expression} implementations.
+ * This base class operates on resources of arbitrary type {@code <R>}.
+ * Concrete subclass operates on resources of specific type such as {@link
org.opengis.feature.Feature}.
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.1
- * @since 1.1
+ *
+ * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) to use as inputs.
+ * @param <G> base class of geometry objects. The implementation-neutral
type is GeoAPI {@link Geometry},
+ * but this factory allows the use of other implementations such
as JTS
+ * {@link org.locationtech.jts.geom.Geometry} or ESRI {@link
com.esri.core.geometry.Geometry}.
+ * @param <T> base class of temporal objects.
+ *
+ * @since 1.1
* @module
*/
-public class DefaultFilterFactory implements FilterFactory2 {
+public abstract class DefaultFilterFactory<R,G,T> extends AbstractFactory
implements FilterFactory<R,G,T> {
/**
- * All functions identified by a name like {@code "cos"}, {@code "hypot"},
<i>etc</i>.
- * The actual function creations is delegated to an external factory such
as {@link SQLMM}.
- * The factories are fetched by {@link #function(String, Expression...)}
when first needed.
- * This factory is cleared if classpath changes, for allowing dynamic
reloading.
- *
- * @see #function(String, Expression...)
+ * The geometry library used by this factory.
*/
- private static final Map<String,FunctionRegister> FUNCTION_REGISTERS = new
HashMap<>();
- static {
- SystemListener.add(new SystemListener(Modules.FEATURE) {
- @Override protected void classpathChanged() {
- synchronized (FUNCTION_REGISTERS) {
- FUNCTION_REGISTERS.clear();
- }
- }
- });
- }
+ private final Geometries<G> library;
/**
- * According to OGC Filter encoding v2.0, comparison operators should
default to case sensitive comparison.
- * We use this constant to model it, so it will be easier to change
default value if the standard evolves.
- * Documentation reference: OGC 09-026r1 and ISO 19143:2010(E), section
7.7.3.2.
+ * The strategy to use for representing a region crossing the
anti-meridian.
*/
- private static final boolean DEFAULT_MATCH_CASE = true;
+ private final WraparoundMethod wraparound;
/**
- * Creates a new factory.
+ * All functions identified by a name like {@code "cos"}, {@code "hypot"},
<i>etc</i>.
+ * The actual function creations is delegated to an external factory such
as SQLMM registry.
+ * The factories are fetched by {@link #function(String, Expression...)}
when first needed.
+ *
+ * @see #function(String, Expression...)
*/
- public DefaultFilterFactory() {
- }
-
- // SPATIAL FILTERS
/////////////////////////////////////////////////////////
+ private final Map<String,FunctionRegister> availableFunctions;
/**
- * Creates an operator that evaluates to {@code true} when the bounding
box of the feature's geometry overlaps
- * the given bounding box.
+ * Creates a new factory for geometries of temporal objects of the given
types.
+ * The {@code spatial} argument can be one of the following classes:
*
- * @param propertyName name of geometry property (for a {@link
PropertyName} to access a feature's Geometry)
- * @param minx minimum "x" value (for a literal envelope).
- * @param miny minimum "y" value (for a literal envelope).
- * @param maxx maximum "x" value (for a literal envelope).
- * @param maxy maximum "y" value (for a literal envelope).
- * @param srs identifier of the Coordinate Reference System to
use for a literal envelope.
- * @return operator that evaluates to {@code true} when the bounding box
of the feature's geometry overlaps
- * the bounding box provided in arguments to this method.
+ * <table class="sis">
+ * <caption>Authorized spatial class argument values</caption>
+ * <tr><th>Library</th> <th>Spatial class</th></tr>
+ * <tr><td>ESRI</td> <td>{@code
com.esri.core.geometry.Geometry}</td></tr>
+ * <tr><td>JTS</td> <td>{@code
org.locationtech.jts.geom.Geometry}</td></tr>
+ * <tr><td>Java2D</td> <td>{@code java.awt.Shape}</td></tr>
+ * <tr><td>Default</td> <td>{@code java.lang.Object}</td></tr>
+ * </table>
*
- * @see #bbox(Expression, Envelope)
- */
- @Override
- public BBOX bbox(final String propertyName, final double minx,
- final double miny, final double maxx, final double maxy, final
String srs)
- {
- return bbox(property(propertyName), minx, miny, maxx, maxy, srs);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public BBOX bbox(final Expression e,
- final double minx, final double miny,
- final double maxx, final double maxy, final String srs)
- {
- final CoordinateReferenceSystem crs = decodeCRS(srs);
- return bbox(e, new ImmutableEnvelope(new double[] {minx, miny},
- new double[] {maxx, maxy}, crs));
- }
-
- /**
- * Tries to decode a full {@link CoordinateReferenceSystem} from given
text.
- * First, we try to interpret it as a code, and if it fails, we try to
read it as a WKT.
+ * <table class="sis">
+ * <caption>Authorized temporal class argument values</caption>
+ * <tr><th>Library</th> <th>Temporal class</th></tr>
+ * <tr><td>Default</td> <td>{@code java.lang.Object}</td></tr>
+ * </table>
*
- * @param srs the text describing the reference system. If null or
blank, a null value is returned.
- * @return possible null value if input text is empty or blank.
- * @throws BackingStoreException if an error occurs while decoding the
text.
- */
- private static CoordinateReferenceSystem decodeCRS(String srs) {
- if (srs == null || (srs = srs.trim()).isEmpty()) {
- return null;
- }
- try {
- return CRS.forCode(srs);
- } catch (NoSuchAuthorityCodeException e) {
- try {
- return CRS.fromWKT(srs);
- } catch (FactoryException bis) {
- e.addSuppressed(bis);
+ * @param spatial type of spatial objects, or {@code Object.class}
for default.
+ * @param temporal type of temporal objects, or {@code Object.class}
for default.
+ * @param wraparound the strategy to use for representing a region
crossing the anti-meridian.
+ */
+ @SuppressWarnings("unchecked")
+ protected DefaultFilterFactory(final Class<G> spatial, final Class<T>
temporal, final WraparoundMethod wraparound) {
+ ArgumentChecks.ensureNonNull("spatial", spatial);
+ ArgumentChecks.ensureNonNull("temporal", temporal);
+ ArgumentChecks.ensureNonNull("wraparound", wraparound);
+ if (spatial == Object.class) {
+ library = (Geometries<G>)
Geometries.implementation((GeometryLibrary) null);
+ } else {
+ library = (Geometries<G>) Geometries.implementation(spatial);
+ if (library == null || library.rootClass != spatial) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"spatial", spatial));
}
- throw new BackingStoreException(e);
- } catch (FactoryException e) {
- throw new BackingStoreException(e);
}
+ if (temporal != Object.class) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"temporal", temporal));
+ }
+ this.wraparound = wraparound;
+ availableFunctions = new HashMap<>();
}
/**
- * {@inheritDoc}
- */
- @Override
- public BBOX bbox(final Expression e, final Envelope bounds) {
- return new DefaultBBOX(e, literal(bounds));
- }
-
- /**
- * Creates an operator that checks if all of a feature's geometry is more
distant than the given distance
- * from the given geometry.
+ * Returns a factory operating on {@link Feature} instances.
+ * The {@linkplain GeometryLibrary geometry library} will be the system
default.
*
- * @param propertyName name of geometry property (for a {@link
PropertyName} to access a feature's Geometry).
- * @param geometry the geometry from which to evaluate the distance.
- * @param distance minimal distance for evaluating the expression as
{@code true}.
- * @param units units of the given {@code distance}.
- * @return operator that evaluates to {@code true} when all of a feature's
geometry is more distant than
- * the given distance from the given geometry.
- */
- @Override
- public Beyond beyond(final String propertyName, final Geometry geometry,
- final double distance, final String units)
- {
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return beyond(name, geom, distance, units);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Beyond beyond(final Expression left, final Expression right,
- final double distance, final String units)
- {
- return new SpatialFunction.Beyond(left, right, distance, units);
- }
-
- /**
- * {@inheritDoc}
+ * @return factory operating on {@link Feature} instances.
+ *
+ * @todo The type of temporal object is not yet determined.
*/
- @Override
- public Contains contains(final String propertyName, final Geometry
geometry) {
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return contains(name, geom);
+ public static synchronized FilterFactory<Feature, Object, Object>
forFeatures() {
+ return Features.DEFAULT;
}
/**
- * {@inheritDoc}
+ * Describes the abilities of this factory. The description includes
restrictions on
+ * the available spatial operations, scalar operations, lists of supported
functions,
+ * and description of which geometry literals are understood.
+ *
+ * @return description of the abilities of this factory.
*/
@Override
- public Contains contains(final Expression left, final Expression right) {
- return new SpatialFunction.Contains(left, right);
+ public FilterCapabilities getCapabilities() {
+ return Capabilities.INSTANCE;
}
/**
- * {@inheritDoc}
- */
- @Override
- public Crosses crosses(final String propertyName, final Geometry geometry)
{
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return crosses(name, geom);
- }
+ * A filter factory operating on {@link Feature} instances.
+ *
+ * @param <G> base class of geometry objects. The implementation-neutral
type is GeoAPI {@link Geometry},
+ * but this factory allows the use of other implementations
such as JTS
+ * {@link org.locationtech.jts.geom.Geometry} or ESRI {@link
com.esri.core.geometry.Geometry}.
+ * @param <T> base class of temporal objects.
+ */
+ public static class Features<G,T> extends DefaultFilterFactory<Feature, G,
T> {
+ /**
+ * The instance using system default.
+ *
+ * @see #forFeatures()
+ */
+ static final FilterFactory<Feature,Object,Object> DEFAULT =
+ new Features<>(Object.class, Object.class,
WraparoundMethod.SPLIT);;
+
+ /**
+ * Creates a new factory operating on {@link Feature} instances.
+ * See the {@linkplain
DefaultFilterFactory#DefaultFilterFactory(Class, Class, WraparoundMethod)}
+ * super-class constructor} for a list of valid class arguments.
+ *
+ * @param spatial type of spatial objects, or {@code
Object.class} for default.
+ * @param temporal type of temporal objects, or {@code
Object.class} for default.
+ * @param wraparound the strategy to use for representing a region
crossing the anti-meridian.
+ *
+ * @see DefaultFilterFactory#forFeatures()
+ */
+ public Features(final Class<G> spatial, final Class<T> temporal, final
WraparoundMethod wraparound) {
+ super(spatial, temporal, wraparound);
+ }
- /**
- * {@inheritDoc}
- */
- @Override
- public Crosses crosses(final Expression left, final Expression right) {
- return new SpatialFunction.Crosses(left, right);
- }
+ /**
+ * Creates a new predicate to identify an identifiable resource within
a filter expression.
+ * If {@code startTime} and {@code endTime} are non-null, the filter
will select all versions
+ * of a resource between the specified dates.
+ *
+ * @param identifier identifier of the resource that shall be
selected by the predicate.
+ * @param version version of the resource shall be selected, or
{@code null} for any version.
+ * @param startTime start time of the resource to select, or {@code
null} if none.
+ * @param endTime end time of the resource to select, or {@code
null} if none.
+ * @return the predicate.
+ *
+ * @todo Current implementation ignores the version, start time and
end time.
+ * This limitation may be resolved in a future version.
+ */
+ @Override
+ public ResourceId<Feature> resourceId(final String identifier, final
Version version,
+ final Instant startTime, final
Instant endTime)
+ {
+ return new FilterByIdentifier<>(identifier);
+ }
- /**
- * {@inheritDoc}
- */
- @Override
- public Disjoint disjoint(final String propertyName, final Geometry
geometry) {
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return disjoint(name, geom);
+ /**
+ * Creates an expression whose value is computed by retrieving the
value indicated by a path in a resource.
+ * If all characters in the path are {@linkplain
Character#isUnicodeIdentifierPart(int) Unicode identifier parts},
+ * then the XPath expression is simply a property name.
+ *
+ * <p>The desired type of property values can be specified. For
example if the property values should be numbers,
+ * then {@code type} can be <code>{@linkplain Number}.class</code>. If
property values can be of any type with no
+ * conversion desired, then {@code type} should be {@code
Object.class}.</p>
+ *
+ * @param <V> the type of the values to be fetched (compile-time
value of {@code type}).
+ * @param xpath the path to the property whose value will be
returned by the {@code apply(R)} method.
+ * @param type the type of the values to be fetched (run-time value
of {@code <V>}).
+ * @return an expression evaluating the referenced property value.
+ */
+ @Override
+ public <V> ValueReference<Feature,V> property(final String xpath,
final Class<V> type) {
+ return PropertyValue.create(xpath, type);
+ }
}
/**
- * {@inheritDoc}
+ * Creates a constant, literal value that can be used in expressions.
+ * The given value should be data objects such as strings, numbers, dates
or geometries.
+ *
+ * @param <V> the type of the value of the literal.
+ * @param value the literal value. May be {@code null}.
+ * @return a literal for the given value.
*/
@Override
- public Disjoint disjoint(final Expression left, final Expression right) {
- return new SpatialFunction.Disjoint(left, right);
+ public <V> Literal<R,V> literal(final V value) {
+ return new LeafExpression.Literal<>(value);
}
/**
- * {@inheritDoc}
+ * Filter operator that compares that two sub-expressions are equal to
each other.
+ *
+ * @param expression1 the first of the two expressions to be used by
this comparator.
+ * @param expression2 the second of the two expressions to be used by
this comparator.
+ * @param isMatchingCase specifies whether comparisons are case
sensitive.
+ * @param matchAction specifies how the comparisons shall be
evaluated for a collection of values.
+ * @return a filter evaluating {@code expression1} = {@code expression2}.
+ *
+ * @see ComparisonOperatorName#PROPERTY_IS_EQUAL_TO
+ * @todo Revisit if we can be more specific on the second parameterized
type in expressions.
*/
@Override
- public DWithin dwithin(final String propertyName, final Geometry geometry,
- final double distance, final String units)
+ public BinaryComparisonOperator<R> equal(final Expression<? super R, ?>
expression1,
+ final Expression<? super R, ?>
expression2,
+ boolean isMatchingCase,
MatchAction matchAction)
{
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return dwithin(name, geom, distance, units);
+ return new ComparisonFunction.EqualTo<>(expression1, expression2,
isMatchingCase, matchAction);
}
/**
- * {@inheritDoc}
+ * Filter operator that compares that its two sub-expressions are not
equal to each other.
+ *
+ * @param expression1 the first of the two expressions to be used by
this comparator.
+ * @param expression2 the second of the two expressions to be used by
this comparator.
+ * @param isMatchingCase specifies whether comparisons are case
sensitive.
+ * @param matchAction specifies how the comparisons shall be
evaluated for a collection of values.
+ * @return a filter evaluating {@code expression1} ≠ {@code expression2}.
+ *
+ * @see ComparisonOperatorName#PROPERTY_IS_NOT_EQUAL_TO
+ * @todo Revisit if we can be more specific on the second parameterized
type in expressions.
*/
@Override
- public DWithin dwithin(final Expression left, final Expression right,
- final double distance, final String units)
+ public BinaryComparisonOperator<R> notEqual(final Expression<? super R, ?>
expression1,
+ final Expression<? super R, ?>
expression2,
+ boolean isMatchingCase,
MatchAction matchAction)
{
- return new SpatialFunction.DWithin(left, right, distance, units);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Equals equals(final String propertyName, final Geometry geometry) {
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return equal(name, geom);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Equals equal(final Expression left, final Expression right) {
- return new SpatialFunction.Equals(left, right);
+ return new ComparisonFunction.NotEqualTo<>(expression1, expression2,
isMatchingCase, matchAction);
}
/**
- * {@inheritDoc}
- */
- @Override
- public Intersects intersects(final String propertyName, final Geometry
geometry) {
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return intersects(name, geom);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Intersects intersects(final Expression left, final Expression
right) {
- return new SpatialFunction.Intersects(left, right);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Overlaps overlaps(final String propertyName, final Geometry
geometry) {
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return overlaps(name, geom);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Overlaps overlaps(final Expression left, final Expression right) {
- return new SpatialFunction.Overlaps(left, right);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Touches touches(final String propertyName, final Geometry geometry)
{
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return touches(name, geom);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Touches touches(final Expression left, final Expression right) {
- return new SpatialFunction.Touches(left, right);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Within within(final String propertyName, final Geometry geometry) {
- final PropertyName name = property(propertyName);
- final Literal geom = literal(geometry);
- return within(name, geom);
- }
-
- /**
- * {@inheritDoc}
+ * Filter operator that checks that its first sub-expression is less than
its second sub-expression.
+ *
+ * @param expression1 the first of the two expressions to be used by
this comparator.
+ * @param expression2 the second of the two expressions to be used by
this comparator.
+ * @param isMatchingCase specifies whether comparisons are case
sensitive.
+ * @param matchAction specifies how the comparisons shall be
evaluated for a collection of values.
+ * @return a filter evaluating {@code expression1} < {@code
expression2}.
+ *
+ * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN
+ * @todo Revisit if we can be more specific on the second parameterized
type in expressions.
*/
@Override
- public Within within(final Expression left, final Expression right) {
- return new SpatialFunction.Within(left, right);
+ public BinaryComparisonOperator<R> less(final Expression<? super R, ?>
expression1,
+ final Expression<? super R, ?>
expression2,
+ boolean isMatchingCase,
MatchAction matchAction)
+ {
+ return new ComparisonFunction.LessThan<>(expression1, expression2,
isMatchingCase, matchAction);
}
- // IDENTIFIERS
/////////////////////////////////////////////////////////////
-
/**
- * {@inheritDoc}
+ * Filter operator that checks that its first sub-expression is greater
than its second sub-expression.
+ *
+ * @param expression1 the first of the two expressions to be used by
this comparator.
+ * @param expression2 the second of the two expressions to be used by
this comparator.
+ * @param isMatchingCase specifies whether comparisons are case
sensitive.
+ * @param matchAction specifies how the comparisons shall be
evaluated for a collection of values.
+ * @return a filter evaluating {@code expression1} > {@code
expression2}.
+ *
+ * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN
+ * @todo Revisit if we can be more specific on the second parameterized
type in expressions.
*/
@Override
- public FeatureId featureId(final String id) {
- return new DefaultObjectId(id);
+ public BinaryComparisonOperator<R> greater(final Expression<? super R, ?>
expression1,
+ final Expression<? super R, ?>
expression2,
+ boolean isMatchingCase,
MatchAction matchAction)
+ {
+ return new ComparisonFunction.GreaterThan<>(expression1, expression2,
isMatchingCase, matchAction);
}
/**
- * {@inheritDoc}
- */
- @Override
- public GmlObjectId gmlObjectId(final String id) {
- return new DefaultObjectId(id);
- }
-
- // FILTERS
/////////////////////////////////////////////////////////////////
-
- /**
- * {@inheritDoc}
+ * Filter operator that checks that its first sub-expression is less than
or equal to its second sub-expression.
+ *
+ * @param expression1 the first of the two expressions to be used by
this comparator.
+ * @param expression2 the second of the two expressions to be used by
this comparator.
+ * @param isMatchingCase specifies whether comparisons are case
sensitive.
+ * @param matchAction specifies how the comparisons shall be
evaluated for a collection of values.
+ * @return a filter evaluating {@code expression1} ≤ {@code expression2}.
+ *
+ * @see ComparisonOperatorName#PROPERTY_IS_LESS_THAN_OR_EQUAL_TO
+ * @todo Revisit if we can be more specific on the second parameterized
type in expressions.
*/
@Override
- public And and(final Filter filter1, final Filter filter2) {
- return and(Arrays.asList(filter1, filter2));
+ public BinaryComparisonOperator<R> lessOrEqual(final Expression<? super R,
?> expression1,
+ final Expression<? super R,
?> expression2,
+ boolean isMatchingCase,
MatchAction matchAction)
+ {
+ return new ComparisonFunction.LessThanOrEqualTo<>(expression1,
expression2, isMatchingCase, matchAction);
}
/**
- * {@inheritDoc}
+ * Filter operator that checks that its first sub-expression is greater
than its second sub-expression.
+ *
+ * @param expression1 the first of the two expressions to be used by
this comparator.
+ * @param expression2 the second of the two expressions to be used by
this comparator.
+ * @param isMatchingCase specifies whether comparisons are case
sensitive.
+ * @param matchAction specifies how the comparisons shall be
evaluated for a collection of values.
+ * @return a filter evaluating {@code expression1} ≥ {@code expression2}.
+ *
+ * @see ComparisonOperatorName#PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO
+ * @todo Revisit if we can be more specific on the second parameterized
type in expressions.
*/
@Override
- public And and(final List<Filter> filters) {
- return new LogicalFunction.And(filters);
+ public BinaryComparisonOperator<R> greaterOrEqual(final Expression<? super
R, ?> expression1,
+ final Expression<? super
R, ?> expression2,
+ boolean isMatchingCase,
MatchAction matchAction)
+ {
+ return new ComparisonFunction.GreaterThanOrEqualTo<>(expression1,
expression2, isMatchingCase, matchAction);
}
/**
- * {@inheritDoc}
+ * Filter operation for a range check.
+ * The lower and upper boundary values are inclusive.
+ *
+ * @param expression the expression to be compared by this comparator.
+ * @param lowerBoundary the lower bound (inclusive) as an expression.
+ * @param upperBoundary the upper bound (inclusive) as an expression.
+ * @return a filter evaluating ({@code expression} ≥ {@code lowerBoundary})
+ * & ({@code expression} ≤ {@code
upperBoundary}).
*/
@Override
- public Or or(final Filter filter1, final Filter filter2) {
- return or(Arrays.asList(filter1, filter2));
+ public BetweenComparisonOperator<R> between(final Expression<? super R, ?>
expression,
+ final Expression<? super R, ?>
lowerBoundary,
+ final Expression<? super R, ?>
upperBoundary)
+ {
+ return new ComparisonFunction.Between<>(expression, lowerBoundary,
upperBoundary);
}
/**
- * {@inheritDoc}
+ * Character string comparison operator with pattern matching and
specified wildcards.
+ *
+ * @param expression source of values to compare against the pattern.
+ * @param pattern pattern to match against expression values.
+ * @param wildcard pattern character for matching any sequence of
characters.
+ * @param singleChar pattern character for matching exactly one
character.
+ * @param escape pattern character for indicating that the next
character should be matched literally.
+ * @param isMatchingCase specifies how a filter expression processor
should perform string comparisons.
+ * @return a character string comparison operator with pattern matching.
*/
@Override
- public Or or(final List<Filter> filters) {
- return new LogicalFunction.Or(filters);
+ public LikeOperator<R> like(final Expression<? super R, ?> expression,
final String pattern,
+ final char wildcard, final char singleChar, final char escape,
final boolean isMatchingCase)
+ {
+ return new DefaultLike<>(expression, pattern, wildcard, singleChar,
escape, isMatchingCase);
}
/**
- * {@inheritDoc}
+ * An operator that tests if an expression's value is {@code null}.
+ * This corresponds to checking whether the property exists in the
real-world.
+ *
+ * @param expression source of values to compare against {@code null}.
+ * @return a filter that checks if an expression's value is {@code null}.
*/
@Override
- public Not not(final Filter filter) {
- return new UnaryFunction.Not(filter);
+ public NullOperator<R> isNull(final Expression<? super R, ?> expression) {
+ return new UnaryFunction.IsNull<>(expression);
}
/**
- * {@inheritDoc}
+ * An operator that tests if an expression's value is nil.
+ * The difference with {@link NullOperator} is that a value should exist
+ * but can not be provided for the reason given by {@code nilReason}.
+ * Possible reasons are:
+ *
+ * <ul>
+ * <li><b>inapplicable</b> — there is no value.</li>
+ * <li><b>template</b> — the value will be available later.</li>
+ * <li><b>missing</b> — the correct value is not readily available
to the sender of this data.
+ * Furthermore, a correct value may not
exist.</li>
+ * <li><b>unknown</b> — the correct value is not known to, and not
computable by, the sender of this data.
+ * However, a correct value probably
exists..</li>
+ * <li><b>withheld</b> — the value is not divulged.</li>
+ * <li>Other strings at implementation choice.</li>
+ * </ul>
+ *
+ * @param expression source of values to compare against nil values.
+ * @param nilReason the reason why the value is nil, or {@code null}
for accepting any reason.
+ * @return a filter that checks if an expression's value is nil for the
specified reason.
+ *
+ * @see org.apache.sis.xml.NilObject
+ * @see org.apache.sis.xml.NilReason
*/
@Override
- public Id id(final Set<? extends Identifier> ids) {
- return new FilterByIdentifier(ids);
+ public NilOperator<R> isNil(final Expression<? super R, ?> expression,
final String nilReason) {
+ return new UnaryFunction.IsNil<>(expression, nilReason);
}
/**
- * {@inheritDoc}
+ * Creates a {@code AND} filter between two or more filters.
+ *
+ * @param operands a collection of at least 2 operands.
+ * @return a filter evaluating {@code operand1 AND operand2 AND operand3}…
+ * @throws IllegalArgumentException if the given collection contains less
than 2 elements.
+ *
+ * @see LogicalOperatorName#AND
*/
@Override
- public PropertyName property(final GenericName name) {
- return property(name.toString());
+ public LogicalOperator<R> and(final Collection<? extends Filter<? super
R>> operands) {
+ return new LogicalFunction.And<>(operands);
}
/**
- * Creates a new expression retrieving values from a property of the given
name.
+ * Creates a {@code OR} filter between two or more filters.
*
- * @param name name of the property (usually a feature attribute).
+ * @param operands a collection of at least 2 operands.
+ * @return a filter evaluating {@code operand1 OR operand2 OR operand3}…
+ * @throws IllegalArgumentException if the given collection contains less
than 2 elements.
+ *
+ * @see LogicalOperatorName#OR
*/
@Override
- public PropertyName property(final String name) {
- return new LeafExpression.Property(name);
+ public LogicalOperator<R> or(final Collection<? extends Filter<? super R>>
operands) {
+ return new LogicalFunction.Or<>(operands);
}
/**
- * {@inheritDoc}
+ * Creates a {@code NOT} filter for the given filter.
+ *
+ * @param operand the operand of the NOT operation.
+ * @return a filter evaluating {@code NOT operand}.
+ *
+ * @see LogicalOperatorName#NOT
*/
@Override
- public PropertyIsBetween between(final Expression expression, final
Expression lower, final Expression upper) {
- return new ComparisonFunction.Between(expression, lower, upper);
+ public LogicalOperator<R> not(final Filter<? super R> operand) {
+ return new LogicalFunction.Not<>(operand);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if the bounding box of the feature's
geometry interacts
+ * with the bounding box provided in the filter properties.
+ *
+ * @param geometry expression fetching the geometry to check for
interaction with bounds.
+ * @param bounds the bounds to check geometry against.
+ * @return a filter checking for any interactions between the bounding
boxes.
+ *
+ * @see SpatialOperatorName#BBOX
+ *
+ * @todo Maybe the expression parameterized type should extend {@link
Geometry}.
*/
@Override
- public PropertyIsEqualTo equals(final Expression expression1, final
Expression expression2) {
- return equal(expression1, expression2, DEFAULT_MATCH_CASE,
MatchAction.ANY);
+ public BinarySpatialOperator<R> bbox(final Expression<? super R, ? extends
G> geometry, final Envelope bounds) {
+ return new SpatialFunction<>(library, geometry, bounds, wraparound);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if the geometry of the two operands are
equal.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Equals" operation between the two geometries.
+ *
+ * @see SpatialOperatorName#EQUALS
+ *
+ * @todo Rename {@code equal}.
*/
@Override
- public PropertyIsEqualTo equal(final Expression expression1, final
Expression expression2,
- final boolean isMatchingCase, final
MatchAction matchAction)
+ public BinarySpatialOperator<R> equals(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return new ComparisonFunction.EqualTo(expression1, expression2,
isMatchingCase, matchAction);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public PropertyIsNotEqualTo notEqual(final Expression expression1, final
Expression expression2) {
- return notEqual(expression1, expression2, DEFAULT_MATCH_CASE,
MatchAction.ANY);
+ return new SpatialFunction<>(SpatialOperatorName.EQUALS, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if the first operand is disjoint from
the second.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Disjoint" operation between the two
geometries.
+ *
+ * @see SpatialOperatorName#DISJOINT
*/
@Override
- public PropertyIsNotEqualTo notEqual(final Expression expression1, final
Expression expression2,
- final boolean isMatchingCase, final
MatchAction matchAction)
+ public BinarySpatialOperator<R> disjoint(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return new ComparisonFunction.NotEqualTo(expression1, expression2,
isMatchingCase, matchAction);
+ return new SpatialFunction<>(SpatialOperatorName.DISJOINT, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
- */
- @Override
- public PropertyIsGreaterThan greater(final Expression expression1, final
Expression expression2) {
- return greater(expression1, expression2, DEFAULT_MATCH_CASE,
MatchAction.ANY);
- }
-
- /**
- * {@inheritDoc}
+ * Creates an operator that checks if the two geometric operands intersect.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Intersects" operation between the two
geometries.
+ *
+ * @see SpatialOperatorName#INTERSECTS
*/
@Override
- public PropertyIsGreaterThan greater(final Expression expression1, final
Expression expression2,
- final boolean isMatchingCase, final
MatchAction matchAction)
+ public BinarySpatialOperator<R> intersects(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return new ComparisonFunction.GreaterThan(expression1, expression2,
isMatchingCase, matchAction);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public PropertyIsGreaterThanOrEqualTo greaterOrEqual(final Expression
expression1, final Expression expression2) {
- return greaterOrEqual(expression1, expression2, DEFAULT_MATCH_CASE,
MatchAction.ANY);
+ return new SpatialFunction<>(SpatialOperatorName.INTERSECTS, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if the two geometric operands touch
each other, but do not overlap.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Touches" operation between the two geometries.
+ *
+ * @see SpatialOperatorName#TOUCHES
*/
@Override
- public PropertyIsGreaterThanOrEqualTo greaterOrEqual(final Expression
expression1, final Expression expression2,
- final boolean
isMatchingCase, final MatchAction matchAction)
+ public BinarySpatialOperator<R> touches(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return new ComparisonFunction.GreaterThanOrEqualTo(expression1,
expression2, isMatchingCase, matchAction);
+ return new SpatialFunction<>(SpatialOperatorName.TOUCHES, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
- */
- @Override
- public PropertyIsLessThan less(final Expression expression1, final
Expression expression2) {
- return less(expression1, expression2, DEFAULT_MATCH_CASE,
MatchAction.ANY);
- }
-
- /**
- * {@inheritDoc}
+ * Creates an operator that checks if the first geometric operand crosses
the second.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Crosses" operation between the two geometries.
+ *
+ * @see SpatialOperatorName#CROSSES
*/
@Override
- public PropertyIsLessThan less(final Expression expression1, final
Expression expression2,
- final boolean isMatchingCase, MatchAction
matchAction)
+ public BinarySpatialOperator<R> crosses(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return new ComparisonFunction.LessThan(expression1, expression2,
isMatchingCase, matchAction);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public PropertyIsLessThanOrEqualTo lessOrEqual(final Expression
expression1, final Expression expression2) {
- return lessOrEqual(expression1, expression2, DEFAULT_MATCH_CASE,
MatchAction.ANY);
+ return new SpatialFunction<>(SpatialOperatorName.CROSSES, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if the first geometric operand is
completely
+ * contained by the constant geometric operand.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Within" operation between the two geometries.
+ *
+ * @see SpatialOperatorName#WITHIN
*/
@Override
- public PropertyIsLessThanOrEqualTo lessOrEqual(final Expression
expression1, final Expression expression2,
- final boolean
isMatchingCase, final MatchAction matchAction)
+ public BinarySpatialOperator<R> within(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return new ComparisonFunction.LessThanOrEqualTo(expression1,
expression2, isMatchingCase, matchAction);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public PropertyIsLike like(final Expression expression, final String
pattern) {
- return like(expression, pattern, "*", "?", "\\");
+ return new SpatialFunction<>(SpatialOperatorName.WITHIN, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if the first geometric operand contains
the second.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Contains" operation between the two
geometries.
+ *
+ * @see SpatialOperatorName#CONTAINS
*/
@Override
- public PropertyIsLike like(final Expression expression, final String
pattern,
- final String wildcard, final String singleChar, final String
escape)
+ public BinarySpatialOperator<R> contains(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return like(expression, pattern, wildcard, singleChar, escape,
DEFAULT_MATCH_CASE);
+ return new SpatialFunction<>(SpatialOperatorName.CONTAINS, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if the interior of the first geometric
operand
+ * somewhere overlaps the interior of the second geometric operand.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary
operator.
+ * @param geometry2 expression fetching the second geometry of the
binary operator.
+ * @return a filter for the "Overlaps" operation between the two
geometries.
+ *
+ * @see SpatialOperatorName#OVERLAPS
*/
@Override
- public PropertyIsLike like(final Expression expression, final String
pattern,
- final String wildcard, final String singleChar,
- final String escape, final boolean isMatchingCase)
+ public BinarySpatialOperator<R> overlaps(final Expression<? super R, ?
extends G> geometry1,
+ final Expression<? super R, ?
extends G> geometry2)
{
- return new DefaultLike(expression, pattern, wildcard, singleChar,
escape, isMatchingCase);
+ return new SpatialFunction<>(SpatialOperatorName.OVERLAPS, library,
geometry1, geometry2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if all of a feature's geometry is more
distant
Review comment:
s/is more/are more ?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]